buster/packages/server-shared/.cursor/global.mdc

161 lines
5.0 KiB
Plaintext

---
globs: src/*
alwaysApply: false
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 following strict patterns to ensure consistency across frontend and backend services.
### Core Architecture Principles
#### 1. Zod-First Approach (MANDATORY)
All types MUST be defined using Zod schemas first, then TypeScript types are inferred from them:
```typescript
// ✅ CORRECT: 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>;
// ❌ WRONG: Defining types without schemas
export interface User {
id: string;
email: string;
name: string;
}
```
#### 2. Database Type Imports (CRITICAL)
When referencing database types from `@buster/database`, **ALWAYS** import them as types to avoid compilation errors:
```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
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"
}
```
### 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
### 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
```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