Skip to content

feat: full OpenAPI 3.1 type coverage — const, discriminated unions, readOnly, and more #1846

@jefflembeck

Description

@jefflembeck

Part of #1844
Depends on #1845

Summary

With the SpecGenerator3x base in place, add support for TypeScript type constructs that can be represented in OpenAPI 3.1 but aren't today.

Features

1. const for single literal types

  • type Role = 'admin'{ const: 'admin' } in 3.1 (stays { enum: ['admin'] } in 3.0)
  • Implemented via buildSingleLiteralType() hook
  • Schema change: add const?: unknown to Schema31
  • Implementation
  • Tests: assert const in 3.1, enum in 3.0 for same fixture

2. Discriminated union auto-detection + @tsoaDiscriminator override

  • Auto-detect: when all union members share a common property with distinct literal values,
    emit oneOf + discriminator instead of anyOf
  • For $ref members, resolve via existing metadata property lists (no recursive AST walking)
  • Ambiguous cases (multiple candidate properties): fall back to anyOf
  • @tsoaDiscriminator JSDoc tag overrides auto-detection and supports custom mapping
  • Works in both 3.0 and 3.1 (shared SpecGenerator3x logic)
  • Metadata change: add optional discriminator to Tsoa.UnionType
  • Auto-detection implementation
  • JSDoc tag extraction in typeResolver.ts
  • Tests: auto-detected, decorator override, custom mapping, error on nonexistent property

3. readOnly from TypeScript readonly + @WriteOnly decorator

  • Map readonly property modifier → readOnly: true in schema
  • New @WriteOnly decorator → writeOnly: true
  • Metadata change: add readonly?: boolean and writeOnly?: boolean to Tsoa.Property
  • Schema change: add writeOnly?: boolean to BaseSchema
  • Error if both readonly and @WriteOnly on same property
  • Metadata extraction for readonly modifier
  • @WriteOnly decorator
  • buildProperties() integration
  • Tests: readonly, writeOnly, mutual exclusion error

4. patternProperties for template literal record keys (3.1 only)

  • Record<foo_${string}, number>patternProperties: { "^foo_.*$": { type: "number" } }
  • Falls back to additionalProperties in 3.0
  • Metadata change: add patternProperties field to NestedObjectLiteralType
  • Regex conversion: ${string}.*, ${number}[0-9]+
  • Template literal detection in typeResolver.ts
  • Spec generation
  • Tests: simple prefix, suffix, multi-interpolation, 3.0 fallback

5. propertyNames for index signature key constraints (3.1 only)

  • { [key: 'a' | 'b']: number }propertyNames: { enum: ['a', 'b'] }
  • Falls back to plain additionalProperties in 3.0
  • Metadata change: add indexSignatureKeyType?: Tsoa.Type to NestedObjectLiteralType
  • Implementation
  • Tests

6. examples array on schemas (3.1 only)

  • Multiple @Example decorators → examples: [...] in 3.1, example (first) in 3.0
  • Implemented via applyExamples() hook
  • Implementation
  • Tests

7. contains for array assertions (3.1 only, deferred)

  • @Contains(SomeType) decorator → contains: { $ref: '...' }
  • Lowest priority — no TypeScript native equivalent
  • Design decorator API
  • Implementation
  • Tests

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions