Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Build & Development Commands

```bash
# Install dependencies
pnpm install

# Build all packages
pnpm build

# Build all packages (dev mode - faster, no optimizations)
pnpm build:dev

# Lint all packages (auto-fix enabled)
pnpm lint

# Clean build artifacts
pnpm clean

# Update dependencies interactively
pnpm deps
```

### Per-Package Commands

Navigate to any package directory (e.g., `cd pgpm/cli`) and run:

```bash
pnpm build # Build the package
pnpm lint # Lint with auto-fix
pnpm test # Run tests
pnpm test:watch # Run tests in watch mode
pnpm dev # Run in development mode (where available)
```

### Running a Single Test File

```bash
cd packages/cli
pnpm test -- path/to/test.test.ts
# or with pattern matching:
pnpm test -- --testNamePattern="test name pattern"
```

## Project Architecture

This is a **pnpm monorepo** using Lerna for versioning/publishing. The workspace is organized into domain-specific directories:

### Core Package Groups

| Directory | Purpose |
|-----------|---------|
| `pgpm/` | PostgreSQL Package Manager - CLI, core engine, types |
| `graphql/` | GraphQL layer - server, codegen, React hooks, testing |
| `graphile/` | PostGraphile plugins - filters, i18n, meta-schema, PostGIS |
| `postgres/` | PostgreSQL utilities - introspection, testing, seeding, AST |
| `packages/` | Shared utilities - CLI, ORM, query builder |
| `uploads/` | File streaming - S3, ETags, content-type detection |
| `jobs/` | Job scheduling and worker infrastructure |

### Key Packages

**pgpm (PostgreSQL Package Manager)**
- `pgpm/cli` - Main CLI tool (`pgpm` command)
- `pgpm/core` - Migration engine, dependency resolution, deployment

**GraphQL Stack**
- `graphql/server` - Express + PostGraphile API server
- `graphql/codegen` - SDK generator (React Query hooks or Prisma-like ORM)
- `graphql/query` - Fluent GraphQL query builder

**Testing Infrastructure**
- `postgres/pgsql-test` - Isolated PostgreSQL test environments with transaction rollback
- `graphile/graphile-test` - GraphQL testing utilities

### Testing Pattern

Tests use `pgsql-test` for database testing with per-test transaction rollback:

```typescript
import { getConnections } from 'pgsql-test';

let db, teardown;

beforeAll(async () => {
({ db, teardown } = await getConnections());
});

beforeEach(() => db.beforeEach());
afterEach(() => db.afterEach());
afterAll(() => teardown());

test('example', async () => {
db.setContext({ role: 'authenticated', 'jwt.claims.user_id': '123' });
const result = await db.query('SELECT current_user_id()');
expect(result.rows[0].current_user_id).toBe('123');
});
```

### Database Configuration

Tests require PostgreSQL. Standard PG environment variables:
- `PGHOST` (default: localhost)
- `PGPORT` (default: 5432)
- `PGUSER` (default: postgres)
- `PGPASSWORD` (default: password)

For S3/MinIO tests: `MINIO_ENDPOINT`, `AWS_ACCESS_KEY`, `AWS_SECRET_KEY`, `AWS_REGION`

### Build System

- Uses `makage` for TypeScript compilation (handles both CJS and ESM output)
- Jest with ts-jest for testing
- ESLint with TypeScript support
- Each package has its own `tsconfig.json` extending root config

### Code Conventions

- TypeScript with `strict: true` (but `strictNullChecks: false`)
- Target: ES2022, Module: CommonJS
- Packages publish to npm from `dist/` directory
- Workspace dependencies use `workspace:^` protocol
149 changes: 147 additions & 2 deletions graphql/codegen/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -837,12 +837,12 @@ import { useCreateCarMutation, useCarsQuery } from './generated/hooks';

function CreateCarWithInvalidation() {
const queryClient = useQueryClient();

const createCar = useCreateCarMutation({
onSuccess: () => {
// Invalidate all car queries to refetch
queryClient.invalidateQueries({ queryKey: ['cars'] });

// Or invalidate specific queries
queryClient.invalidateQueries({ queryKey: ['cars', { first: 10 }] });
},
Expand All @@ -852,6 +852,151 @@ function CreateCarWithInvalidation() {
}
```

### Centralized Query Keys

The codegen generates a centralized query key factory following the [lukemorales query-key-factory](https://tanstack.com/query/docs/framework/react/community/lukemorales-query-key-factory) pattern. This provides type-safe cache management with autocomplete support.

#### Generated Files

| File | Purpose |
|------|---------|
| `query-keys.ts` | Query key factories for all entities |
| `mutation-keys.ts` | Mutation key factories for tracking in-flight mutations |
| `invalidation.ts` | Type-safe cache invalidation helpers |

#### Using Query Keys

```tsx
import { userKeys, invalidate } from './generated/hooks';
import { useQueryClient } from '@tanstack/react-query';

// Query key structure
userKeys.all // ['user']
userKeys.lists() // ['user', 'list']
userKeys.list({ first: 10 }) // ['user', 'list', { first: 10 }]
userKeys.details() // ['user', 'detail']
userKeys.detail('user-123') // ['user', 'detail', 'user-123']

// Granular cache invalidation
const queryClient = useQueryClient();

// Invalidate ALL user queries
queryClient.invalidateQueries({ queryKey: userKeys.all });

// Invalidate only list queries
queryClient.invalidateQueries({ queryKey: userKeys.lists() });

// Invalidate a specific user
queryClient.invalidateQueries({ queryKey: userKeys.detail(userId) });
```

#### Invalidation Helpers

Type-safe invalidation utilities:

```tsx
import { invalidate, remove } from './generated/hooks';

// Invalidate queries (triggers refetch)
invalidate.user.all(queryClient);
invalidate.user.lists(queryClient);
invalidate.user.detail(queryClient, userId);

// Remove from cache (for delete operations)
remove.user(queryClient, userId);
```

#### Mutation Key Tracking

Track in-flight mutations with `useIsMutating`:

```tsx
import { useIsMutating } from '@tanstack/react-query';
import { userMutationKeys } from './generated/hooks';

function UserList() {
// Check if any user mutations are in progress
const isMutating = useIsMutating({ mutationKey: userMutationKeys.all });

// Check if a specific user is being deleted
const isDeleting = useIsMutating({
mutationKey: userMutationKeys.delete(userId)
});

return (
<div>
{isMutating > 0 && <Spinner />}
<button disabled={isDeleting > 0}>Delete</button>
</div>
);
}
```

#### Optimistic Updates with Query Keys

```tsx
import { useCreateUserMutation, userKeys } from './generated/hooks';

const createUser = useCreateUserMutation({
onMutate: async (newUser) => {
// Cancel outgoing refetches
await queryClient.cancelQueries({ queryKey: userKeys.lists() });

// Snapshot previous value
const previous = queryClient.getQueryData(userKeys.list());

// Optimistically update cache
queryClient.setQueryData(userKeys.list(), (old) => ({
...old,
users: {
...old.users,
nodes: [...old.users.nodes, { id: 'temp', ...newUser.input.user }]
},
}));

return { previous };
},
onError: (err, variables, context) => {
// Rollback on error
queryClient.setQueryData(userKeys.list(), context.previous);
},
onSettled: () => {
// Refetch after mutation
queryClient.invalidateQueries({ queryKey: userKeys.lists() });
},
});
```

#### Configuration

Query key generation is enabled by default. Configure in your config file:

```typescript
// graphql-sdk.config.ts
export default defineConfig({
endpoint: 'https://api.example.com/graphql',

queryKeys: {
// Generate scope-aware keys (default: true)
generateScopedKeys: true,

// Generate mutation keys (default: true)
generateMutationKeys: true,

// Generate invalidation helpers (default: true)
generateCascadeHelpers: true,

// Define entity relationships for cascade invalidation
relationships: {
table: { parent: 'database', foreignKey: 'databaseId' },
field: { parent: 'table', foreignKey: 'tableId' },
},
},
});
```

For detailed documentation on query key factory design and implementation, see [docs/QUERY-KEY-FACTORY.md](./docs/QUERY-KEY-FACTORY.md).

### Prefetching

```tsx
Expand Down
Loading