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
54 changes: 54 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# CLAUDE.md

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

## Commands

```bash
npm run dev # Start dev server with nodemon + tsx (interactive wizard)
npm run build # Compile TypeScript to dist/
npm start # Run compiled server (dist/index.js)
npm test # Run all tests with Jest
npm run test:watch # Watch mode
npm run test:coverage # Coverage report
npm run clean # Remove dist/
```

Run a single test file:
```bash
npm test -- constraintExtractor.test.ts
npm test -- --testPathPattern=pluralize
```

TypeScript is strict (`noUnusedLocals`, `noUnusedParameters`, `noUncheckedIndexedAccess`, etc.). Tests live in `tests/` and are excluded from `tsconfig.json`.

## Architecture

**Entry point**: `src/index.ts` — parses CLI args via Commander; if no args, runs the interactive `src/cli/wizard.ts`. Both paths call `startServer(config)`.

**Request lifecycle** (`src/server.ts` → `src/core/router.ts`):
1. Express middleware chain: CORS → JSON → logger → `statusOverride` → optional latency
2. All non-system routes hit `dynamicRouteHandler` (catch-all `app.all('*')`)
3. Router calls `findTypeForUrl(url, typesDir)` to resolve a TypeScript interface name from the URL
4. Calls `generateMockFromInterface` or `generateMockArray` from `src/core/parser.ts`
5. Results cached in `schemaCache` (single objects only, not arrays)

**URL → Interface resolution** (`src/utils/typeMapping.ts` + `src/utils/pluralize.ts`):
- Scans `typesDir` recursively for `.ts` files
- Only interfaces with `// @endpoint` (or in a JSDoc block containing `@endpoint`) are exposed
- URL last segment is converted to PascalCase; if a matching interface exists directly (e.g. `Users` for `/users`), it wins as a non-array route
- Otherwise, the segment is singularized via the `pluralize` library (`users` → `User`) and `isArray: true` is set

**Mock generation** (`src/core/parser.ts`):
- Uses `intermock` with `isFixedMode: false` for random data
- After generation, `extractConstraints` parses the TypeScript AST (via `typescript` compiler API) for JSDoc annotations (`@min`, `@max`, `@minLength`, `@maxLength`, `@pattern`, `@enum`)
- `applyConstraintsToMock` in `src/core/constrainedGenerator.ts` then regenerates non-conforming fields using Faker

**Special headers**:
- `x-mock-status: <code>` — forces the response HTTP status code (handled by `src/middlewares/statusOverride.ts`)

**System routes** (not matched by dynamic handler):
- `GET /health` — server status + cache stats
- `GET /api-docs` — Swagger UI (spec auto-regenerated on hot-reload file changes)

**Key types** (`src/types/config.ts`): `ServerConfig`, `RouteTypeMapping`, `InterfaceMetadata`, `ParsedSchema`, `MockGenerationOptions`
82 changes: 77 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,9 @@ Options:
curl http://localhost:8080/user
# → {"id": 482, "name": "John Doe", "email": "john.d@gmail.com", "role": "admin"}

# Array of objects (plural)
# Array of objects (plural) — with pagination metadata
curl http://localhost:8080/users
# → [{"id": 1, ...}, {"id": 2, ...}, ...]
# → {"data": [...], "meta": {"total": 100, "page": 1, "pageSize": 20, "totalPages": 5}}
```

**Step 4: View API Documentation**
Expand All @@ -113,7 +113,78 @@ All endpoints are documented with examples and you can test them directly from y

---

## Field Constraints with JSDoc Annotations
## Pagination, Filtering & Sorting

All list endpoints (plural routes that return an array) support pagination, filtering, and sorting via query parameters. Responses always use the envelope format:

```json
{
"data": [...],
"meta": {
"total": 100,
"page": 2,
"pageSize": 20,
"totalPages": 5
}
}
```

The server generates a pool of 100 mock items and applies your filters/sort/pagination to that pool, so `total` and `totalPages` reflect realistic numbers.

### Pagination

| Param | Default | Max | Description |
|---|---|---|---|
| `page` | `1` | — | Page number (1-based) |
| `pageSize` | `20` | `100` | Items per page |

```bash
GET /users?page=2&pageSize=50
```

### Filtering

| Convention | Example | Description |
|---|---|---|
| `field=value` | `status=active` | Exact match (case-insensitive for strings) |
| `field_like=value` | `email_like=@example.com` | Substring match (case-insensitive) |
| `field_from=date` | `createdAt_from=2024-01-01` | Date range — start (inclusive) |
| `field_to=date` | `createdAt_to=2024-12-31` | Date range — end (inclusive) |

Multiple filters are combined with AND logic. Unknown fields are silently ignored.

```bash
GET /users?status=active&email_like=@example.com&createdAt_from=2024-01-01
```

### Sorting

Use `sort=field:dir` with comma-separated entries for multi-field sort. Direction must be `asc` or `desc`.

```bash
GET /users?sort=createdAt:desc,lastName:asc
```

Sorting by a field that does not exist in the interface returns `400`.

### Combined Example

```bash
GET /users?page=2&pageSize=50&status=active&email_like=@example.com&sort=createdAt:desc
```

### Error Responses

Invalid query parameters return `400` with a descriptive message:

```json
{ "error": "Invalid query parameters", "message": "\"pageSize\" must not exceed 100" }
{ "error": "Invalid sort parameter", "message": "Cannot sort by unknown field \"foo\". Allowed fields: email, id, name" }
```

---

## 🎯 Field Constraints with JSDoc Annotations

Add validation constraints to your interfaces using JSDoc annotations. This ensures generated mock data follows your API rules.

Expand Down Expand Up @@ -194,9 +265,10 @@ export interface Product {
## Available Commands

- `npm run dev` - Start development server
- `npm run build` - Compile TypeScript
- `npm run build` - Compile TypeScript
- `npm start` - Start production server
- `npm test` - Run all tests
- `npm test -- queryProcessor.test.ts` - Test pagination/filtering/sorting
- `npm test -- constraintExtractor.test.ts` - Test constraint JSDoc extraction
- `npm test -- constraintValidator.test.ts` - Test constraint validation
- `npm test -- constrainedGenerator.test.ts` - Test constrained data generation
Expand All @@ -208,7 +280,7 @@ export interface Product {
The server maps URL paths to TypeScript interfaces by converting the route to PascalCase and singularizing it. For example:

- `/user` → looks for `User` interface → returns single object
- `/users` → looks for `User` interface → returns array of 3-10 objects
- `/users` → looks for `User` interface → generates 100-item pool, applies query params, returns paginated envelope

Only interfaces marked with `// @endpoint` are exposed. The server uses Intermock to parse TypeScript AST and Faker to generate realistic test data.

Expand Down
Loading