Added readme and other stuff for cursor

This commit is contained in:
Nate Kelley 2025-07-17 12:56:42 -06:00
parent ea02041ea5
commit 16045ced49
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
3 changed files with 372 additions and 27 deletions

View File

@ -1,48 +1,161 @@
---
globs: src/*
alwaysApply: false
description: Global rules for server-shared folder
description: Global rules for server-shared folder - Shared TypeScript types and schemas
---
## Overview of the `src` Folder Structure
The `src` folder is organized to reflect the structure of our API endpoints and general concepts such as organization, user, team, etc. Each of these concepts is encapsulated within its own folder.
The `src` folder is organized to reflect the structure of our API endpoints and general concepts such as organization, user, team, etc. Each of these concepts is encapsulated within its own folder following strict patterns to ensure consistency across frontend and backend services.
### Folder Structure
### Core Architecture Principles
- **Endpoint and Concept Folders**: Each folder corresponds to a specific endpoint or concept. Within these folders, you will find:
- `request.ts`: Defines the request types using Zod schemas.
- `response.ts`: Defines the response types using Zod schemas.
- `index.ts`: Exports the types defined in `request.ts` and `response.ts`.
#### 1. Zod-First Approach (MANDATORY)
All types MUST be defined using Zod schemas first, then TypeScript types are inferred from them:
- **Additional Files**: Some folders may contain additional files to assist in defining types specific to that folder's context.
```typescript
// ✅ CORRECT: Define schema first
export const UserSchema = z.object({
id: z.string(),
email: z.string().email(),
name: z.string(),
});
### Type Definition
export type User = z.infer<typeof UserSchema>;
- **Zod Schemas**: All types are defined using Zod schemas to ensure type safety and validation.
// ❌ WRONG: Defining types without schemas
export interface User {
id: string;
email: string;
name: string;
}
```
### Special Folder: `type-utilities`
#### 2. Database Type Imports (CRITICAL)
When referencing database types from `@buster/database`, **ALWAYS** import them as types to avoid compilation errors:
- This folder is dedicated to generic types, such as pagination or query array processing. It provides utility functions and types that can be reused across different folders.
```typescript
// ✅ CORRECT: Import as type
import type { organizations, userOrganizationRoleEnum } from '@buster/database';
// ❌ WRONG: Import as value (will cause build failures)
import { organizations } from '@buster/database';
```
### Folder Structure Requirements
Each module folder MUST contain:
- **`index.ts`**: Barrel exports for all public types in the module
- **`requests.ts`**: Request schemas and types for API endpoints
- **`responses.ts`**: Response schemas and types for API endpoints
- **Additional type files**: As needed for the module's domain
Example structure:
```
organization/
├── index.ts # export * from './organization.types'; etc.
├── organization.types.ts
├── requests.ts # UpdateOrganizationRequestSchema, etc.
├── responses.ts # GetOrganizationResponseSchema, etc.
├── roles.types.ts # Additional domain types
└── user.types.ts
```
### Enum Pattern for Database Parity
When creating enums that mirror database enums, use frozen objects to maintain type safety:
```typescript
import type { userOrganizationRoleEnum } from '@buster/database';
type OrganizationRoleBase = (typeof userOrganizationRoleEnum.enumValues)[number];
// Create a frozen object that mirrors the database enum
export const OrganizationRoleEnum: Record<OrganizationRoleBase, OrganizationRoleBase> =
Object.freeze({
viewer: 'viewer',
workspace_admin: 'workspace_admin',
data_admin: 'data_admin',
querier: 'querier',
restricted_querier: 'restricted_querier',
});
// Create Zod schema from the enum
export const OrganizationRoleSchema = z.enum(
Object.values(OrganizationRoleEnum) as [OrganizationRoleBase, ...OrganizationRoleBase[]]
);
export type OrganizationRole = z.infer<typeof OrganizationRoleSchema>;
```
### Database Type Parity
When types are direct copies of database models, use the `Expect` and `Equal` utilities:
```typescript
import type { organizations } from '@buster/database';
import type { Equal, Expect } from '../type-utilities';
export type Organization = z.infer<typeof OrganizationSchema>;
// This will cause a TypeScript error if the types don't match
type _OrganizationEqualityCheck = Expect<Equal<Organization, typeof organizations.$inferSelect>>;
```
### Special Folders
#### `type-utilities`
Contains generic utility types that can be reused across modules:
- Pagination types and utilities
- Type equality checking utilities (`Equal`, `Expect`)
- Query array preprocessing utilities
- Other cross-cutting type utilities
### Exporting Types
- **Package.json**: Any new folder should ensure its types are exported via the `package.json` to facilitate proper building and exporting of types.
1. **Module Exports**: Each module must have proper barrel exports in its `index.ts`
2. **Package.json**: New modules MUST be added to the package.json exports:
```json
"./your-module": {
"types": "./dist/your-module/index.d.ts",
"default": "./dist/your-module/index.js"
}
```
This structure ensures a consistent and organized approach to managing types across the application, leveraging Zod for robust type definitions and validation.
### Naming Conventions
- Use either `requests.ts`/`responses.ts` OR `requests.types.ts`/`responses.types.ts` consistently within a module
- Type files should use `.types.ts` suffix (e.g., `organization.types.ts`)
- Test files should use `.test.ts` suffix and be colocated with source files
### Pagination
### Common Patterns
If we need to use pagination for a type we can take advantage of the generic types found in the type-utilities/pagination file
## Database partity
There are cirumstances where we need to ensure that a schema in server-shared and a database type are the same. We should use a pattern like this:
```
type _OrganizationEqualityCheck = Expect<Equal<Organization, typeof organizations.$inferSelect>>;
#### Hex Color Validation
```typescript
const HexColorSchema = z
.string()
.regex(
/^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/,
'Must be a valid 3 or 6 digit hex color code'
);
```
Where organizations is imported as a "type" from @buster/database. This ensure that we are maintaining type parity between the two packages.
#### Pagination
```typescript
import { PaginationParams } from '../type-utilities';
export const ListUsersRequestSchema = z.object({
...PaginationParams.shape,
filter: z.string().optional(),
});
```
### Critical Rules Summary
1. **NEVER** import database constants directly - only import as types
2. **ALWAYS** define Zod schemas before TypeScript types
3. **ALWAYS** use `Expect<Equal<>>` for database model type parity
4. **ALWAYS** export modules through package.json
5. **ALWAYS** include requests.ts and responses.ts in each module
6. **NEVER** use interfaces when you can use Zod inference
7. **ALWAYS** use barrel exports through index.ts files

View File

@ -0,0 +1,234 @@
# @buster/server-shared
> Shared TypeScript types and schemas for the Buster ecosystem
## Overview
The `@buster/server-shared` package provides a centralized location for all shared TypeScript types, Zod schemas, and type utilities used across the Buster monorepo. This package ensures type safety and consistency between frontend and backend services.
## Architecture Principles
### 1. Zod-First Approach
All types are defined using Zod schemas first, then TypeScript types are inferred from them:
```typescript
// ✅ Good: Define schema first
export const UserSchema = z.object({
id: z.string(),
email: z.string().email(),
name: z.string(),
});
export type User = z.infer<typeof UserSchema>;
// ❌ Bad: Defining types without schemas
export interface User {
id: string;
email: string;
name: string;
}
```
### 2. Database Type Imports
When referencing database types from `@buster/database`, **ALWAYS** import them as types to avoid compilation errors:
```typescript
// ✅ Good: Import as type
import type { organizations, userOrganizationRoleEnum } from '@buster/database';
// ❌ Bad: Import as value
import { organizations } from '@buster/database';
```
### 3. Enum Pattern for Database Parity
When creating enums that mirror database enums, use frozen objects to maintain type safety:
```typescript
import type { userOrganizationRoleEnum } from '@buster/database';
type OrganizationRoleBase = (typeof userOrganizationRoleEnum.enumValues)[number];
// Create a frozen object that mirrors the database enum
export const OrganizationRoleEnum: Record<OrganizationRoleBase, OrganizationRoleBase> =
Object.freeze({
viewer: 'viewer',
workspace_admin: 'workspace_admin',
data_admin: 'data_admin',
querier: 'querier',
restricted_querier: 'restricted_querier',
});
// Create Zod schema from the enum
export const OrganizationRoleSchema = z.enum(
Object.values(OrganizationRoleEnum) as [OrganizationRoleBase, ...OrganizationRoleBase[]]
);
export type OrganizationRole = z.infer<typeof OrganizationRoleSchema>;
```
### 4. Type Parity Checks
When types are direct copies of database models, use the `Expect` and `Equal` utilities to ensure parity:
```typescript
import type { organizations } from '@buster/database';
import type { Equal, Expect } from '../type-utilities';
export type Organization = z.infer<typeof OrganizationSchema>;
// This will cause a TypeScript error if the types don't match
type _OrganizationEqualityCheck = Expect<Equal<Organization, typeof organizations.$inferSelect>>;
```
## Directory Structure
```
src/
├── index.ts # Main barrel export (currently incomplete)
├── chats/ # Chat-related types
│ ├── index.ts # Barrel exports for chats
│ ├── chat.types.ts # Core chat types
│ ├── chat-message.types.ts
│ └── ...
├── organization/ # Organization types
│ ├── index.ts # Barrel exports
│ ├── organization.types.ts
│ ├── requests.ts # Request schemas/types
│ ├── responses.ts # Response schemas/types
│ └── ...
├── metrics/ # Metrics types
│ ├── index.ts
│ ├── requests.types.ts # Note: Some use .types.ts suffix
│ ├── responses.types.ts
│ └── ...
├── type-utilities/ # Generic utility types
│ ├── index.ts
│ ├── pagination.ts # Pagination utilities
│ ├── isEqual.ts # Type equality checks
│ └── ...
└── ... (other modules)
```
## Module Structure Guidelines
Each module should follow this structure:
### 1. Barrel Export (index.ts)
Export all public types from the module:
```typescript
export * from './organization.types';
export * from './roles.types';
export * from './requests';
export * from './responses';
```
### 2. Request Types (requests.ts)
Define all request schemas for the module:
```typescript
import { z } from 'zod';
export const CreateUserRequestSchema = z.object({
email: z.string().email(),
name: z.string(),
});
export type CreateUserRequest = z.infer<typeof CreateUserRequestSchema>;
```
### 3. Response Types (responses.ts)
Define all response schemas for the module:
```typescript
import { z } from 'zod';
import { UserSchema } from './user.types';
export const GetUserResponseSchema = UserSchema;
export type GetUserResponse = z.infer<typeof GetUserResponseSchema>;
export const ListUsersResponseSchema = z.object({
users: z.array(UserSchema),
total: z.number(),
});
export type ListUsersResponse = z.infer<typeof ListUsersResponseSchema>;
```
## Usage Examples
### Importing Types
```typescript
// Import from specific modules
import { Organization, UpdateOrganizationRequest } from '@buster/server-shared/organization';
import { Chat, ChatMessage } from '@buster/server-shared/chats';
import { PaginationParams } from '@buster/server-shared/type-utilities';
```
### Using Schemas for Validation
```typescript
import { UpdateOrganizationRequestSchema } from '@buster/server-shared/organization';
// Validate incoming data
const validatedData = UpdateOrganizationRequestSchema.parse(requestBody);
// Or safe parse with error handling
const result = UpdateOrganizationRequestSchema.safeParse(requestBody);
if (!result.success) {
console.error(result.error);
}
```
## Adding New Modules
1. Create a new directory under `src/`
2. Add the following files:
- `index.ts` - Barrel exports
- `requests.ts` - Request schemas/types
- `responses.ts` - Response schemas/types
- Additional type files as needed
3. Update `package.json` exports:
```json
"./your-module": {
"types": "./dist/your-module/index.d.ts",
"default": "./dist/your-module/index.js"
}
```
## Common Patterns
### Hex Color Validation
```typescript
const HexColorSchema = z
.string()
.regex(
/^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/,
'Must be a valid 3 or 6 digit hex color code'
);
```
### Pagination
Use the generic pagination types from `type-utilities`:
```typescript
import { PaginationParams } from '../type-utilities';
export const ListUsersRequestSchema = z.object({
...PaginationParams.shape,
filter: z.string().optional(),
});
```
## Important Notes
1. **Never import database constants directly** - This will cause build failures in frontend packages
2. **Always define schemas before types** - Types should be inferred from schemas
3. **Maintain type parity** - Use `Expect<Equal<>>` pattern for database model copies
4. **Export through package.json** - Each module needs its own export entry
5. **Follow naming conventions** - Use either `requests.ts/responses.ts` or `requests.types.ts/responses.types.ts` consistently within a module
## Contributing
When adding new types or modifying existing ones:
1. Follow the Zod-first approach
2. Ensure proper barrel exports
3. Add request/response types where applicable
4. Update package.json exports if adding new modules
5. Run type checking before committing

View File

@ -1,2 +0,0 @@
// Re-export all modules
export * from './chats/index';