zmdbzero-maintenance data layer
Docs Benchmarks Anti-patterns OpenAPI
Docs / Validation and contracts

Schema DecoratorsSupported

ToDo / feature gap. There are no documentation-only decorators such as @ApiOperation, @ApiProperty, @ApiResponse, @ApiTags, @ApiQuery, or @ApiBearerAuth.

What replaces them#

The shared HTTP contract covers executable meaning:

The contract compiler derives schema projections from the same TypeScript types used by runtime validation. There is no field-level @ApiProperty() declaration to duplicate:

createPost: httpOperation<CreatePostOperation>({
  controller: PostsController,
  handler: 'create',
  method: 'POST',
  path: '/posts',
  parameters: [],
  requestBody: {
    kind: 'json',
    mediaType: 'application/json',
    required: true,
  },
  responses: {
    201: {
      description: 'Created',
      body: { kind: 'json', mediaType: 'application/json' },
    },
  },
  security: [{ bearerAuth: ['posts:write'] }],
  version: { kind: 'none' },
  deprecated: false,
}),

toOpenApi(compiled.ir) copies that method-specific contract. Adding or changing a typed field changes the same precomputed schema used by validators and clients.

Prose metadata#

Summaries, descriptions, tags, examples, and external links are not inferred. Add them in a deterministic post-processing pass keyed by the explicit operation ID:

const document = toOpenApi(compiled.ir, {
  info: { title: 'Blog API', version: '1.0.0' },
});

const DOCS = {
  listPosts: {
    summary: 'List published posts',
    tags: ['Posts'],
  },
  getPost: {
    summary: 'Fetch one post',
    tags: ['Posts'],
  },
  createPost: {
    summary: 'Create a post',
    tags: ['Posts'],
  },
} as const;

for (const item of Object.values(document.paths)) {
  for (const operation of Object.values(item)) {
    Object.assign(operation, DOCS[operation.operationId]);
  }
}

Keying by operationId avoids a second controller or route walk. A test can require one metadata entry per contract operation:

it('documents every operation', () => {
  for (const operation of compiled.ir.operations) {
    expect(DOCS[operation.operationId]).toBeDefined();
  }
});

A missing summary now fails CI. With optional decorators, a missing annotation is otherwise invisible.

Query parameters and responses#

Query, header, cookie, and path parameters are already emitted from HttpOperationIR.parameters; exact responses come from HttpOperationIR.responses. Do not add those in a prose pass, because that would create a second executable contract.

Examples#

Examples are documentation rather than validation. Attach synthetic values to the rendered schema or operation:

const operation = document.paths['/posts']?.post;
if (operation === undefined) throw new Error('createPost is missing');

Object.assign(operation, {
  requestBody: {
    ...operation.requestBody,
    description: 'A new post',
  },
});

Do not use real customer data in examples. Generated specifications are commonly published.

What a future decorator may do#

A future @ApiDoc({ summary, tags }) could record prose metadata, but the OpenAPI renderer will remain a pure HttpContractIR backend. A build-time collector would have to copy that prose into the contract or a separate operation-ID-keyed documentation map before rendering. Schema-bearing decorators remain out of scope because they duplicate the typed contract.

---

See also: OpenAPI Operations · OpenAPI schemas · Anti-Patterns