@trpc/openapi 11.15.1-alpha → 11.15.2-alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export { generateOpenAPIDocument } from './generate';
2
- export type { GenerateOptions, JsonSchema, OpenAPIDocument } from './generate';
2
+ export type { GenerateOptions } from './generate';
3
+ export * as OpenAPIV3_1 from './types';
@@ -13,7 +13,7 @@ import type {
13
13
  $ZodTypeDef,
14
14
  GlobalMeta,
15
15
  } from 'zod/v4/core';
16
- import type { JsonSchema } from './generate';
16
+ import type { SchemaObject } from './types';
17
17
 
18
18
  /** Description strings extracted from Zod `.describe()` calls, keyed by dot-delimited property path. */
19
19
  export interface DescriptionMap {
@@ -81,7 +81,6 @@ const wrapperDefTypes: ReadonlySet<$ZodTypeDef['type']> = new Set([
81
81
  'pipe',
82
82
  'transform',
83
83
  'promise',
84
- 'lazy',
85
84
  ]);
86
85
 
87
86
  /**
@@ -129,7 +128,7 @@ export function extractZodDescriptions(schema: unknown): DescriptionMap | null {
129
128
  }
130
129
 
131
130
  // Walk object shape
132
- walkZodShape(schema, '', { registry, map });
131
+ walkZodShape(schema, '', { registry, map, seenLazy: new Set() });
133
132
  if (map.properties.size > 0) hasAny = true;
134
133
 
135
134
  return hasAny ? map : null;
@@ -138,9 +137,26 @@ export function extractZodDescriptions(schema: unknown): DescriptionMap | null {
138
137
  function walkZodShape(
139
138
  schema: $ZodType,
140
139
  prefix: string,
141
- ctx: { registry: $ZodRegistry<GlobalMeta>; map: DescriptionMap },
140
+ ctx: {
141
+ registry: $ZodRegistry<GlobalMeta>;
142
+ map: DescriptionMap;
143
+ seenLazy: Set<$ZodType>;
144
+ },
142
145
  ): void {
143
146
  const unwrapped = unwrapZodSchema(schema);
147
+ const def = unwrapped._zod.def;
148
+
149
+ if (def.type === 'lazy' && 'getter' in def) {
150
+ if (ctx.seenLazy.has(unwrapped)) {
151
+ return;
152
+ }
153
+ ctx.seenLazy.add(unwrapped);
154
+ const inner = (def as { getter: () => unknown }).getter();
155
+ if (isZodSchema(inner)) {
156
+ walkZodShape(inner, prefix, ctx);
157
+ }
158
+ return;
159
+ }
144
160
 
145
161
  // If this is an array, check for a description on the element schema itself
146
162
  // (stored as `[]` in the path) and recurse into the element's shape.
@@ -325,23 +341,75 @@ function isProcedure(
325
341
  * JSON schema produced by the TypeScript type checker. Mutates in place.
326
342
  */
327
343
  export function applyDescriptions(
328
- schema: JsonSchema,
344
+ schema: SchemaObject,
329
345
  descs: DescriptionMap,
346
+ schemas?: Record<string, SchemaObject>,
330
347
  ): void {
331
348
  if (descs.self) {
332
349
  schema.description = descs.self;
333
350
  }
334
351
 
335
352
  for (const [propPath, description] of descs.properties) {
336
- setNestedDescription(schema, propPath.split('.'), description);
353
+ setNestedDescription({
354
+ schema,
355
+ pathParts: propPath.split('.'),
356
+ description,
357
+ schemas,
358
+ });
337
359
  }
338
360
  }
339
361
 
340
- function setNestedDescription(
341
- schema: JsonSchema,
342
- pathParts: string[],
343
- description: string,
344
- ): void {
362
+ function resolveSchemaRef(
363
+ schema: SchemaObject,
364
+ schemas?: Record<string, SchemaObject>,
365
+ ): SchemaObject | null {
366
+ const ref = schema.$ref;
367
+ if (!ref) {
368
+ return schema;
369
+ }
370
+ if (!schemas || !ref.startsWith('#/components/schemas/')) {
371
+ return null;
372
+ }
373
+
374
+ const refName = ref.slice('#/components/schemas/'.length);
375
+ return refName ? (schemas[refName] ?? null) : null;
376
+ }
377
+
378
+ function getArrayItemsSchema(schema: SchemaObject): SchemaObject | null {
379
+ const items = schema.items;
380
+ if (schema.type !== 'array' || items == null || items === false) {
381
+ return null;
382
+ }
383
+ return items;
384
+ }
385
+
386
+ function getPropertySchema(
387
+ schema: SchemaObject,
388
+ propertyName: string,
389
+ ): SchemaObject | null {
390
+ return schema.properties?.[propertyName] ?? null;
391
+ }
392
+
393
+ function setLeafDescription(schema: SchemaObject, description: string): void {
394
+ if (schema.$ref) {
395
+ const ref = schema.$ref;
396
+ delete schema.$ref;
397
+ schema.allOf = [{ $ref: ref }, ...(schema.allOf ?? [])];
398
+ }
399
+ schema.description = description;
400
+ }
401
+
402
+ function setNestedDescription({
403
+ schema,
404
+ pathParts,
405
+ description,
406
+ schemas,
407
+ }: {
408
+ schema: SchemaObject;
409
+ pathParts: string[];
410
+ description: string;
411
+ schemas?: Record<string, SchemaObject>;
412
+ }): void {
345
413
  if (pathParts.length === 0) return;
346
414
 
347
415
  const [head, ...rest] = pathParts;
@@ -349,35 +417,37 @@ function setNestedDescription(
349
417
 
350
418
  // `[]` means "array items" — navigate to the `items` sub-schema
351
419
  if (head === '[]') {
352
- const items =
353
- schema.type === 'array' &&
354
- schema.items &&
355
- typeof schema.items === 'object'
356
- ? schema.items
357
- : null;
420
+ const items = getArrayItemsSchema(schema);
358
421
  if (!items) return;
359
422
  if (rest.length === 0) {
360
- items.description = description;
423
+ setLeafDescription(items, description);
361
424
  } else {
362
- setNestedDescription(items, rest, description);
425
+ const target = resolveSchemaRef(items, schemas) ?? items;
426
+ setNestedDescription({
427
+ schema: target,
428
+ pathParts: rest,
429
+ description,
430
+ schemas,
431
+ });
363
432
  }
364
433
  return;
365
434
  }
366
435
 
367
- const propSchema = schema.properties?.[head];
368
- if (!propSchema || typeof propSchema !== 'object') return;
436
+ const propSchema = getPropertySchema(schema, head);
437
+ if (!propSchema) return;
369
438
 
370
439
  if (rest.length === 0) {
371
440
  // Leaf — Zod .describe() takes priority over JSDoc
372
- propSchema.description = description;
441
+ setLeafDescription(propSchema, description);
373
442
  } else {
374
443
  // For arrays, step through `items` transparently
375
- const target =
376
- propSchema.type === 'array' &&
377
- propSchema.items &&
378
- typeof propSchema.items === 'object'
379
- ? propSchema.items
380
- : propSchema;
381
- setNestedDescription(target, rest, description);
444
+ const target = getArrayItemsSchema(propSchema) ?? propSchema;
445
+ const resolvedTarget = resolveSchemaRef(target, schemas) ?? target;
446
+ setNestedDescription({
447
+ schema: resolvedTarget,
448
+ pathParts: rest,
449
+ description,
450
+ schemas,
451
+ });
382
452
  }
383
453
  }
package/src/types.ts ADDED
@@ -0,0 +1,151 @@
1
+ import type { OpenAPIV3_1 as BaseOpenAPIV3_1 } from 'openapi-types';
2
+
3
+ export type Replace<TTarget, TReplaceWith> = Omit<TTarget, keyof TReplaceWith> &
4
+ TReplaceWith;
5
+
6
+ export type SchemaType =
7
+ | 'array'
8
+ | 'boolean'
9
+ | 'integer'
10
+ | 'null'
11
+ | 'number'
12
+ | 'object'
13
+ | 'string';
14
+
15
+ export type PrimitiveSchemaType = SchemaType;
16
+
17
+ export type HttpMethods = BaseOpenAPIV3_1.HttpMethods;
18
+ export type ReferenceObject = BaseOpenAPIV3_1.ReferenceObject;
19
+ export type ExampleObject = BaseOpenAPIV3_1.ExampleObject;
20
+ export type DiscriminatorObject = BaseOpenAPIV3_1.DiscriminatorObject;
21
+ export type ExternalDocumentationObject =
22
+ BaseOpenAPIV3_1.ExternalDocumentationObject;
23
+ export type XMLObject = BaseOpenAPIV3_1.XMLObject;
24
+ export type LinkObject = BaseOpenAPIV3_1.LinkObject;
25
+ export type SecuritySchemeObject = BaseOpenAPIV3_1.SecuritySchemeObject;
26
+
27
+ export type SchemaObject = Replace<
28
+ BaseOpenAPIV3_1.BaseSchemaObject,
29
+ {
30
+ $ref?: string;
31
+ $defs?: Record<string, SchemaObject>;
32
+ $schema?: string;
33
+ type?: string | string[];
34
+ properties?: Record<string, SchemaObject>;
35
+ required?: string[];
36
+ items?: SchemaObject | false;
37
+ prefixItems?: SchemaObject[];
38
+ const?: string | number | boolean | null;
39
+ enum?: (string | number | boolean | null)[];
40
+ oneOf?: SchemaObject[];
41
+ anyOf?: SchemaObject[];
42
+ allOf?: SchemaObject[];
43
+ not?: SchemaObject;
44
+ additionalProperties?: boolean | SchemaObject;
45
+ discriminator?: DiscriminatorObject;
46
+ externalDocs?: ExternalDocumentationObject;
47
+ xml?: XMLObject;
48
+ contentMediaType?: string;
49
+ exclusiveMinimum?: boolean | number;
50
+ exclusiveMaximum?: boolean | number;
51
+ }
52
+ >;
53
+
54
+ export type SchemaLike = SchemaObject;
55
+
56
+ export interface ArraySchemaObject extends SchemaObject {
57
+ type: 'array';
58
+ items: SchemaObject | false;
59
+ }
60
+
61
+ export type MediaTypeObject = Replace<
62
+ BaseOpenAPIV3_1.MediaTypeObject,
63
+ {
64
+ schema?: SchemaObject | ReferenceObject;
65
+ examples?: Record<string, ReferenceObject | ExampleObject>;
66
+ }
67
+ >;
68
+
69
+ export interface ParameterBaseObject
70
+ extends Replace<
71
+ BaseOpenAPIV3_1.ParameterBaseObject,
72
+ {
73
+ schema?: SchemaObject | ReferenceObject;
74
+ examples?: Record<string, ReferenceObject | ExampleObject>;
75
+ content?: Record<string, MediaTypeObject>;
76
+ }
77
+ > {}
78
+
79
+ export interface ParameterObject extends ParameterBaseObject {
80
+ name: string;
81
+ in: string;
82
+ }
83
+
84
+ export type HeaderObject = ParameterBaseObject;
85
+
86
+ export type RequestBodyObject = Replace<
87
+ BaseOpenAPIV3_1.RequestBodyObject,
88
+ {
89
+ content: Record<string, MediaTypeObject>;
90
+ }
91
+ >;
92
+
93
+ export type ResponseObject = Replace<
94
+ BaseOpenAPIV3_1.ResponseObject,
95
+ {
96
+ headers?: Record<string, ReferenceObject | HeaderObject>;
97
+ content?: Record<string, MediaTypeObject>;
98
+ links?: Record<string, ReferenceObject | LinkObject>;
99
+ }
100
+ >;
101
+
102
+ export type ResponsesObject = Record<string, ReferenceObject | ResponseObject>;
103
+
104
+ export type OperationObject<T extends {} = {}> = Replace<
105
+ BaseOpenAPIV3_1.OperationObject<T>,
106
+ {
107
+ parameters?: (ReferenceObject | ParameterObject)[];
108
+ requestBody?: ReferenceObject | RequestBodyObject;
109
+ responses?: ResponsesObject;
110
+ callbacks?: Record<string, ReferenceObject | CallbackObject>;
111
+ }
112
+ > &
113
+ T;
114
+
115
+ export type PathItemObject<T extends {} = {}> = Replace<
116
+ BaseOpenAPIV3_1.PathItemObject<T>,
117
+ {
118
+ parameters?: (ReferenceObject | ParameterObject)[];
119
+ }
120
+ > & {
121
+ [method in HttpMethods]?: OperationObject<T>;
122
+ };
123
+
124
+ export type PathsObject<T extends {} = {}, TPath extends {} = {}> = Record<
125
+ string,
126
+ (PathItemObject<T> & TPath) | undefined
127
+ >;
128
+
129
+ export type CallbackObject = Record<string, PathItemObject | ReferenceObject>;
130
+
131
+ export type ComponentsObject = Replace<
132
+ BaseOpenAPIV3_1.ComponentsObject,
133
+ {
134
+ schemas?: Record<string, SchemaObject>;
135
+ responses?: Record<string, ReferenceObject | ResponseObject>;
136
+ parameters?: Record<string, ReferenceObject | ParameterObject>;
137
+ requestBodies?: Record<string, ReferenceObject | RequestBodyObject>;
138
+ headers?: Record<string, ReferenceObject | HeaderObject>;
139
+ links?: Record<string, ReferenceObject | LinkObject>;
140
+ callbacks?: Record<string, ReferenceObject | CallbackObject>;
141
+ pathItems?: Record<string, ReferenceObject | PathItemObject>;
142
+ }
143
+ >;
144
+
145
+ export type Document<T extends {} = {}> = Replace<
146
+ BaseOpenAPIV3_1.Document<T>,
147
+ {
148
+ paths?: PathsObject<T>;
149
+ components?: ComponentsObject;
150
+ }
151
+ >;