@orpc/openapi 0.0.0-next.b6b0cc3 → 0.0.0-next.b6b8746

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.
Files changed (33) hide show
  1. package/README.md +127 -20
  2. package/dist/adapters/aws-lambda/index.d.mts +20 -0
  3. package/dist/adapters/aws-lambda/index.d.ts +20 -0
  4. package/dist/adapters/aws-lambda/index.mjs +18 -0
  5. package/dist/adapters/fastify/index.d.mts +23 -0
  6. package/dist/adapters/fastify/index.d.ts +23 -0
  7. package/dist/adapters/fastify/index.mjs +18 -0
  8. package/dist/adapters/fetch/index.d.mts +11 -5
  9. package/dist/adapters/fetch/index.d.ts +11 -5
  10. package/dist/adapters/fetch/index.mjs +1 -1
  11. package/dist/adapters/node/index.d.mts +11 -5
  12. package/dist/adapters/node/index.d.ts +11 -5
  13. package/dist/adapters/node/index.mjs +1 -1
  14. package/dist/adapters/standard/index.d.mts +8 -23
  15. package/dist/adapters/standard/index.d.ts +8 -23
  16. package/dist/adapters/standard/index.mjs +1 -1
  17. package/dist/index.d.mts +25 -13
  18. package/dist/index.d.ts +25 -13
  19. package/dist/index.mjs +3 -3
  20. package/dist/plugins/index.d.mts +21 -5
  21. package/dist/plugins/index.d.ts +21 -5
  22. package/dist/plugins/index.mjs +69 -20
  23. package/dist/shared/{openapi.C_UtQ8Us.mjs → openapi.BB-W-NKv.mjs} +33 -8
  24. package/dist/shared/openapi.BGy4N6eR.d.mts +120 -0
  25. package/dist/shared/openapi.BGy4N6eR.d.ts +120 -0
  26. package/dist/shared/{openapi.PDTdnRIU.mjs → openapi.BwdtJjDu.mjs} +366 -83
  27. package/dist/shared/openapi.DwaweYRb.d.mts +54 -0
  28. package/dist/shared/openapi.DwaweYRb.d.ts +54 -0
  29. package/package.json +24 -14
  30. package/dist/shared/openapi.CwdCLgSU.d.mts +0 -53
  31. package/dist/shared/openapi.CwdCLgSU.d.ts +0 -53
  32. package/dist/shared/openapi.D3j94c9n.d.mts +0 -12
  33. package/dist/shared/openapi.D3j94c9n.d.ts +0 -12
@@ -3,8 +3,8 @@ import { toHttpPath } from '@orpc/client/standard';
3
3
  import { fallbackContractConfig, getEventIteratorSchemaDetails } from '@orpc/contract';
4
4
  import { standardizeHTTPPath, StandardOpenAPIJsonSerializer, getDynamicParams } from '@orpc/openapi-client/standard';
5
5
  import { isProcedure, resolveContractProcedures } from '@orpc/server';
6
- import { isObject, findDeepMatches, toArray, clone, stringifyJSON } from '@orpc/shared';
7
- import 'json-schema-typed/draft-2020-12';
6
+ import { isObject, stringifyJSON, findDeepMatches, toArray, clone, value } from '@orpc/shared';
7
+ import { TypeName } from 'json-schema-typed/draft-2020-12';
8
8
 
9
9
  const OPERATION_EXTENDER_SYMBOL = Symbol("ORPC_OPERATION_EXTENDER");
10
10
  function customOpenAPIOperation(o, extend) {
@@ -108,22 +108,47 @@ function isAnySchema(schema) {
108
108
  if (schema === true) {
109
109
  return true;
110
110
  }
111
- if (Object.keys(schema).every((k) => !LOGIC_KEYWORDS.includes(k))) {
111
+ if (Object.keys(schema).filter((v) => schema[v] !== void 0).every((k) => !LOGIC_KEYWORDS.includes(k))) {
112
112
  return true;
113
113
  }
114
114
  return false;
115
115
  }
116
+ function isNeverSchema(schema) {
117
+ if (schema === false) {
118
+ return true;
119
+ }
120
+ if (typeof schema === "object" && schema.not !== void 0) {
121
+ if (schema.not === true) {
122
+ return true;
123
+ }
124
+ if (typeof schema.not === "object" && Object.keys(schema.not).length === 0) {
125
+ return true;
126
+ }
127
+ }
128
+ return false;
129
+ }
116
130
  function separateObjectSchema(schema, separatedProperties) {
117
- if (Object.keys(schema).some((k) => k !== "type" && k !== "properties" && k !== "required" && LOGIC_KEYWORDS.includes(k))) {
131
+ if (Object.keys(schema).some(
132
+ (k) => !["type", "properties", "required", "additionalProperties"].includes(k) && LOGIC_KEYWORDS.includes(k) && schema[k] !== void 0
133
+ )) {
118
134
  return [{ type: "object" }, schema];
119
135
  }
120
136
  const matched = { ...schema };
121
137
  const rest = { ...schema };
122
- matched.properties = schema.properties && Object.entries(schema.properties).filter(([key]) => separatedProperties.includes(key)).reduce((acc, [key, value]) => {
123
- acc[key] = value;
138
+ matched.properties = separatedProperties.reduce((acc, key) => {
139
+ const keySchema = schema.properties?.[key] ?? schema.additionalProperties;
140
+ if (keySchema !== void 0) {
141
+ acc[key] = keySchema;
142
+ }
124
143
  return acc;
125
144
  }, {});
145
+ if (Object.keys(matched.properties).length === 0) {
146
+ matched.properties = void 0;
147
+ }
126
148
  matched.required = schema.required?.filter((key) => separatedProperties.includes(key));
149
+ if (matched.required?.length === 0) {
150
+ matched.required = void 0;
151
+ }
127
152
  matched.examples = schema.examples?.map((example) => {
128
153
  if (!isObject(example)) {
129
154
  return example;
@@ -135,11 +160,14 @@ function separateObjectSchema(schema, separatedProperties) {
135
160
  return acc;
136
161
  }, {});
137
162
  });
138
- rest.properties = schema.properties && Object.entries(schema.properties).filter(([key]) => !separatedProperties.includes(key)).reduce((acc, [key, value]) => {
163
+ rest.properties = schema.properties && Object.entries(schema.properties).filter(([key]) => !separatedProperties.includes(key)).reduce((acc = {}, [key, value]) => {
139
164
  acc[key] = value;
140
165
  return acc;
141
- }, {});
166
+ }, void 0);
142
167
  rest.required = schema.required?.filter((key) => !separatedProperties.includes(key));
168
+ if (rest.required?.length === 0) {
169
+ rest.required = void 0;
170
+ }
143
171
  rest.examples = schema.examples?.map((example) => {
144
172
  if (!isObject(example)) {
145
173
  return example;
@@ -196,6 +224,45 @@ function expandUnionSchema(schema) {
196
224
  }
197
225
  return [schema];
198
226
  }
227
+ function expandArrayableSchema(schema) {
228
+ const schemas = expandUnionSchema(schema);
229
+ if (schemas.length !== 2) {
230
+ return void 0;
231
+ }
232
+ const arraySchema = schemas.find(
233
+ (s) => typeof s === "object" && s.type === "array" && Object.keys(s).filter((k) => LOGIC_KEYWORDS.includes(k)).every((k) => k === "type" || k === "items")
234
+ );
235
+ if (arraySchema === void 0) {
236
+ return void 0;
237
+ }
238
+ const items1 = arraySchema.items;
239
+ const items2 = schemas.find((s) => s !== arraySchema);
240
+ if (stringifyJSON(items1) !== stringifyJSON(items2)) {
241
+ return void 0;
242
+ }
243
+ return [items2, arraySchema];
244
+ }
245
+ const PRIMITIVE_SCHEMA_TYPES = /* @__PURE__ */ new Set([
246
+ TypeName.String,
247
+ TypeName.Number,
248
+ TypeName.Integer,
249
+ TypeName.Boolean,
250
+ TypeName.Null
251
+ ]);
252
+ function isPrimitiveSchema(schema) {
253
+ return expandUnionSchema(schema).every((s) => {
254
+ if (typeof s === "boolean") {
255
+ return false;
256
+ }
257
+ if (typeof s.type === "string" && PRIMITIVE_SCHEMA_TYPES.has(s.type)) {
258
+ return true;
259
+ }
260
+ if (s.const !== void 0) {
261
+ return true;
262
+ }
263
+ return false;
264
+ });
265
+ }
199
266
 
200
267
  function toOpenAPIPath(path) {
201
268
  return standardizeHTTPPath(path).replace(/\/\{\+([^}]+)\}/g, "/{$1}");
@@ -211,7 +278,7 @@ function toOpenAPIContent(schema) {
211
278
  schema: toOpenAPISchema(file)
212
279
  };
213
280
  }
214
- if (restSchema !== void 0) {
281
+ if (restSchema !== void 0 && !isAnySchema(restSchema) && !isNeverSchema(restSchema)) {
215
282
  content["application/json"] = {
216
283
  schema: toOpenAPISchema(restSchema)
217
284
  };
@@ -268,13 +335,26 @@ function toOpenAPIParameters(schema, parameterIn) {
268
335
  const parameters = [];
269
336
  for (const key in schema.properties) {
270
337
  const keySchema = schema.properties[key];
338
+ let isDeepObjectStyle = true;
339
+ if (parameterIn !== "query") {
340
+ isDeepObjectStyle = false;
341
+ } else if (isPrimitiveSchema(keySchema)) {
342
+ isDeepObjectStyle = false;
343
+ } else {
344
+ const [item] = expandArrayableSchema(keySchema) ?? [];
345
+ if (item !== void 0 && isPrimitiveSchema(item)) {
346
+ isDeepObjectStyle = false;
347
+ }
348
+ }
271
349
  parameters.push({
272
350
  name: key,
273
351
  in: parameterIn,
274
352
  required: schema.required?.includes(key),
275
- style: parameterIn === "query" ? "deepObject" : void 0,
276
- explode: parameterIn === "query" ? true : void 0,
277
- schema: toOpenAPISchema(keySchema)
353
+ schema: toOpenAPISchema(keySchema),
354
+ style: isDeepObjectStyle ? "deepObject" : void 0,
355
+ explode: isDeepObjectStyle ? true : void 0,
356
+ allowEmptyValue: parameterIn === "query" ? true : void 0,
357
+ allowReserved: parameterIn === "query" ? true : void 0
278
358
  });
279
359
  }
280
360
  return parameters;
@@ -293,6 +373,116 @@ function checkParamsSchema(schema, params) {
293
373
  function toOpenAPISchema(schema) {
294
374
  return schema === true ? {} : schema === false ? { not: {} } : schema;
295
375
  }
376
+ const OPENAPI_JSON_SCHEMA_REF_PREFIX = "#/components/schemas/";
377
+ function resolveOpenAPIJsonSchemaRef(doc, schema) {
378
+ if (typeof schema !== "object" || !schema.$ref?.startsWith(OPENAPI_JSON_SCHEMA_REF_PREFIX)) {
379
+ return schema;
380
+ }
381
+ const name = schema.$ref.slice(OPENAPI_JSON_SCHEMA_REF_PREFIX.length);
382
+ const resolved = doc.components?.schemas?.[name];
383
+ return resolved ?? schema;
384
+ }
385
+ function simplifyComposedObjectJsonSchemasAndRefs(schema, doc) {
386
+ if (doc) {
387
+ schema = resolveOpenAPIJsonSchemaRef(doc, schema);
388
+ }
389
+ if (typeof schema !== "object" || !schema.anyOf && !schema.oneOf && !schema.allOf) {
390
+ return schema;
391
+ }
392
+ const unionSchemas = [
393
+ ...toArray(schema.anyOf?.map((s) => simplifyComposedObjectJsonSchemasAndRefs(s, doc))),
394
+ ...toArray(schema.oneOf?.map((s) => simplifyComposedObjectJsonSchemasAndRefs(s, doc)))
395
+ ];
396
+ const objectUnionSchemas = [];
397
+ for (const u of unionSchemas) {
398
+ if (!isObjectSchema(u)) {
399
+ return schema;
400
+ }
401
+ objectUnionSchemas.push(u);
402
+ }
403
+ const mergedUnionPropertyMap = /* @__PURE__ */ new Map();
404
+ for (const u of objectUnionSchemas) {
405
+ if (u.properties) {
406
+ for (const [key, value] of Object.entries(u.properties)) {
407
+ let entry = mergedUnionPropertyMap.get(key);
408
+ if (!entry) {
409
+ const required = objectUnionSchemas.every((s) => s.required?.includes(key));
410
+ entry = { required, schemas: [] };
411
+ mergedUnionPropertyMap.set(key, entry);
412
+ }
413
+ entry.schemas.push(value);
414
+ }
415
+ }
416
+ }
417
+ const intersectionSchemas = toArray(schema.allOf?.map((s) => simplifyComposedObjectJsonSchemasAndRefs(s, doc)));
418
+ const objectIntersectionSchemas = [];
419
+ for (const u of intersectionSchemas) {
420
+ if (!isObjectSchema(u)) {
421
+ return schema;
422
+ }
423
+ objectIntersectionSchemas.push(u);
424
+ }
425
+ if (isObjectSchema(schema)) {
426
+ objectIntersectionSchemas.push(schema);
427
+ }
428
+ const mergedInteractionPropertyMap = /* @__PURE__ */ new Map();
429
+ for (const u of objectIntersectionSchemas) {
430
+ if (u.properties) {
431
+ for (const [key, value] of Object.entries(u.properties)) {
432
+ let entry = mergedInteractionPropertyMap.get(key);
433
+ if (!entry) {
434
+ const required = objectIntersectionSchemas.some((s) => s.required?.includes(key));
435
+ entry = { required, schemas: [] };
436
+ mergedInteractionPropertyMap.set(key, entry);
437
+ }
438
+ entry.schemas.push(value);
439
+ }
440
+ }
441
+ }
442
+ const resultObjectSchema = { type: "object", properties: {}, required: [] };
443
+ const keys = /* @__PURE__ */ new Set([
444
+ ...mergedUnionPropertyMap.keys(),
445
+ ...mergedInteractionPropertyMap.keys()
446
+ ]);
447
+ if (keys.size === 0) {
448
+ return schema;
449
+ }
450
+ const deduplicateSchemas = (schemas) => {
451
+ const seen = /* @__PURE__ */ new Set();
452
+ const result = [];
453
+ for (const schema2 of schemas) {
454
+ const key = stringifyJSON(schema2);
455
+ if (!seen.has(key)) {
456
+ seen.add(key);
457
+ result.push(schema2);
458
+ }
459
+ }
460
+ return result;
461
+ };
462
+ for (const key of keys) {
463
+ const unionEntry = mergedUnionPropertyMap.get(key);
464
+ const intersectionEntry = mergedInteractionPropertyMap.get(key);
465
+ resultObjectSchema.properties[key] = (() => {
466
+ const dedupedUnionSchemas = unionEntry ? deduplicateSchemas(unionEntry.schemas) : [];
467
+ const dedupedIntersectionSchemas = intersectionEntry ? deduplicateSchemas(intersectionEntry.schemas) : [];
468
+ if (!dedupedUnionSchemas.length) {
469
+ return dedupedIntersectionSchemas.length === 1 ? dedupedIntersectionSchemas[0] : { allOf: dedupedIntersectionSchemas };
470
+ }
471
+ if (!dedupedIntersectionSchemas.length) {
472
+ return dedupedUnionSchemas.length === 1 ? dedupedUnionSchemas[0] : { anyOf: dedupedUnionSchemas };
473
+ }
474
+ const allOf = deduplicateSchemas([
475
+ ...dedupedIntersectionSchemas,
476
+ dedupedUnionSchemas.length === 1 ? dedupedUnionSchemas[0] : { anyOf: dedupedUnionSchemas }
477
+ ]);
478
+ return allOf.length === 1 ? allOf[0] : { allOf };
479
+ })();
480
+ if (unionEntry?.required || intersectionEntry?.required) {
481
+ resultObjectSchema.required.push(key);
482
+ }
483
+ }
484
+ return resultObjectSchema;
485
+ }
296
486
 
297
487
  class CompositeSchemaConverter {
298
488
  converters;
@@ -321,39 +511,50 @@ class OpenAPIGenerator {
321
511
  /**
322
512
  * Generates OpenAPI specifications from oRPC routers/contracts.
323
513
  *
324
- * @see {@link https://orpc.unnoq.com/docs/openapi/openapi-specification OpenAPI Specification Docs}
514
+ * @see {@link https://orpc.dev/docs/openapi/openapi-specification OpenAPI Specification Docs}
325
515
  */
326
- async generate(router, options = {}) {
327
- const exclude = options.exclude ?? (() => false);
516
+ async generate(router, { customErrorResponseBodySchema, commonSchemas, filter: baseFilter, exclude, ...baseDoc } = {}) {
517
+ const filter = baseFilter ?? (({ contract, path }) => {
518
+ return !(exclude?.(contract, path) ?? false);
519
+ });
328
520
  const doc = {
329
- ...clone(options),
330
- info: options.info ?? { title: "API Reference", version: "0.0.0" },
331
- openapi: "3.1.1",
332
- exclude: void 0
521
+ ...clone(baseDoc),
522
+ info: baseDoc.info ?? { title: "API Reference", version: "0.0.0" },
523
+ openapi: "3.1.1"
333
524
  };
525
+ const { baseSchemaConvertOptions, undefinedErrorJsonSchema } = await this.#resolveCommonSchemas(doc, commonSchemas);
334
526
  const contracts = [];
335
- await resolveContractProcedures({ path: [], router }, ({ contract, path }) => {
336
- if (!exclude(contract, path)) {
337
- contracts.push({ contract, path });
527
+ await resolveContractProcedures({ path: [], router }, (traverseOptions) => {
528
+ if (!value(filter, traverseOptions)) {
529
+ return;
338
530
  }
531
+ contracts.push(traverseOptions);
339
532
  });
340
533
  const errors = [];
341
534
  for (const { contract, path } of contracts) {
342
- const operationId = path.join(".");
535
+ const stringPath = path.join(".");
343
536
  try {
344
537
  const def = contract["~orpc"];
345
538
  const method = toOpenAPIMethod(fallbackContractConfig("defaultMethod", def.route.method));
346
539
  const httpPath = toOpenAPIPath(def.route.path ?? toHttpPath(path));
347
- const operationObjectRef = {
348
- operationId,
349
- summary: def.route.summary,
350
- description: def.route.description,
351
- deprecated: def.route.deprecated,
352
- tags: def.route.tags?.map((tag) => tag)
353
- };
354
- await this.#request(operationObjectRef, def);
355
- await this.#successResponse(operationObjectRef, def);
356
- await this.#errorResponse(operationObjectRef, def);
540
+ let operationObjectRef;
541
+ if (def.route.spec !== void 0 && typeof def.route.spec !== "function") {
542
+ operationObjectRef = def.route.spec;
543
+ } else {
544
+ operationObjectRef = {
545
+ operationId: def.route.operationId ?? stringPath,
546
+ summary: def.route.summary,
547
+ description: def.route.description,
548
+ deprecated: def.route.deprecated,
549
+ tags: def.route.tags?.map((tag) => tag)
550
+ };
551
+ await this.#request(doc, operationObjectRef, def, baseSchemaConvertOptions);
552
+ await this.#successResponse(doc, operationObjectRef, def, baseSchemaConvertOptions);
553
+ await this.#errorResponse(operationObjectRef, def, baseSchemaConvertOptions, undefinedErrorJsonSchema, customErrorResponseBodySchema);
554
+ }
555
+ if (typeof def.route.spec === "function") {
556
+ operationObjectRef = def.route.spec(operationObjectRef);
557
+ }
357
558
  doc.paths ??= {};
358
559
  doc.paths[httpPath] ??= {};
359
560
  doc.paths[httpPath][method] = applyCustomOpenAPIOperation(operationObjectRef, contract);
@@ -362,7 +563,7 @@ class OpenAPIGenerator {
362
563
  throw e;
363
564
  }
364
565
  errors.push(
365
- `[OpenAPIGenerator] Error occurred while generating OpenAPI for procedure at path: ${operationId}
566
+ `[OpenAPIGenerator] Error occurred while generating OpenAPI for procedure at path: ${stringPath}
366
567
  ${e.message}`
367
568
  );
368
569
  }
@@ -376,25 +577,102 @@ ${errors.join("\n\n")}`
376
577
  }
377
578
  return this.serializer.serialize(doc)[0];
378
579
  }
379
- async #request(ref, def) {
580
+ async #resolveCommonSchemas(doc, commonSchemas) {
581
+ let undefinedErrorJsonSchema = {
582
+ type: "object",
583
+ properties: {
584
+ defined: { const: false },
585
+ code: { type: "string" },
586
+ status: { type: "number" },
587
+ message: { type: "string" },
588
+ data: {}
589
+ },
590
+ required: ["defined", "code", "status", "message"]
591
+ };
592
+ const baseSchemaConvertOptions = {};
593
+ if (commonSchemas) {
594
+ baseSchemaConvertOptions.components = [];
595
+ for (const key in commonSchemas) {
596
+ const options = commonSchemas[key];
597
+ if (options.schema === void 0) {
598
+ continue;
599
+ }
600
+ const { schema, strategy = "input" } = options;
601
+ const [required, json] = await this.converter.convert(schema, { strategy });
602
+ const allowedStrategies = [strategy];
603
+ if (strategy === "input") {
604
+ const [outputRequired, outputJson] = await this.converter.convert(schema, { strategy: "output" });
605
+ if (outputRequired === required && stringifyJSON(outputJson) === stringifyJSON(json)) {
606
+ allowedStrategies.push("output");
607
+ }
608
+ } else if (strategy === "output") {
609
+ const [inputRequired, inputJson] = await this.converter.convert(schema, { strategy: "input" });
610
+ if (inputRequired === required && stringifyJSON(inputJson) === stringifyJSON(json)) {
611
+ allowedStrategies.push("input");
612
+ }
613
+ }
614
+ baseSchemaConvertOptions.components.push({
615
+ schema,
616
+ required,
617
+ ref: `#/components/schemas/${key}`,
618
+ allowedStrategies
619
+ });
620
+ }
621
+ doc.components ??= {};
622
+ doc.components.schemas ??= {};
623
+ for (const key in commonSchemas) {
624
+ const options = commonSchemas[key];
625
+ if (options.schema === void 0) {
626
+ if (options.error === "UndefinedError") {
627
+ doc.components.schemas[key] = toOpenAPISchema(undefinedErrorJsonSchema);
628
+ undefinedErrorJsonSchema = { $ref: `#/components/schemas/${key}` };
629
+ }
630
+ continue;
631
+ }
632
+ const { schema, strategy = "input" } = options;
633
+ const [, json] = await this.converter.convert(
634
+ schema,
635
+ {
636
+ ...baseSchemaConvertOptions,
637
+ strategy,
638
+ minStructureDepthForRef: 1
639
+ // not allow use $ref for root schemas
640
+ }
641
+ );
642
+ doc.components.schemas[key] = toOpenAPISchema(json);
643
+ }
644
+ }
645
+ return { baseSchemaConvertOptions, undefinedErrorJsonSchema };
646
+ }
647
+ async #request(doc, ref, def, baseSchemaConvertOptions) {
380
648
  const method = fallbackContractConfig("defaultMethod", def.route.method);
381
649
  const details = getEventIteratorSchemaDetails(def.inputSchema);
382
650
  if (details) {
383
651
  ref.requestBody = {
384
652
  required: true,
385
653
  content: toOpenAPIEventIteratorContent(
386
- await this.converter.convert(details.yields, { strategy: "input" }),
387
- await this.converter.convert(details.returns, { strategy: "input" })
654
+ await this.converter.convert(details.yields, { ...baseSchemaConvertOptions, strategy: "input" }),
655
+ await this.converter.convert(details.returns, { ...baseSchemaConvertOptions, strategy: "input" })
388
656
  )
389
657
  };
390
658
  return;
391
659
  }
392
660
  const dynamicParams = getDynamicParams(def.route.path)?.map((v) => v.name);
393
661
  const inputStructure = fallbackContractConfig("defaultInputStructure", def.route.inputStructure);
394
- let [required, schema] = await this.converter.convert(def.inputSchema, { strategy: "input" });
662
+ let [required, schema] = await this.converter.convert(
663
+ def.inputSchema,
664
+ {
665
+ ...baseSchemaConvertOptions,
666
+ strategy: "input"
667
+ }
668
+ );
669
+ let omitResponseBody = false;
395
670
  if (isAnySchema(schema) && !dynamicParams?.length) {
396
671
  return;
397
672
  }
673
+ if (inputStructure === "detailed" || inputStructure === "compact" && (dynamicParams?.length || method === "GET")) {
674
+ schema = simplifyComposedObjectJsonSchemasAndRefs(schema, doc);
675
+ }
398
676
  if (inputStructure === "compact") {
399
677
  if (dynamicParams?.length) {
400
678
  const error2 = new OpenAPIGeneratorError(
@@ -406,6 +684,7 @@ ${errors.join("\n\n")}`
406
684
  const [paramsSchema, rest] = separateObjectSchema(schema, dynamicParams);
407
685
  schema = rest;
408
686
  required = rest.required ? rest.required.length !== 0 : false;
687
+ omitResponseBody = !required && !rest.properties;
409
688
  if (!checkParamsSchema(paramsSchema, dynamicParams)) {
410
689
  throw error2;
411
690
  }
@@ -420,7 +699,7 @@ ${errors.join("\n\n")}`
420
699
  }
421
700
  ref.parameters ??= [];
422
701
  ref.parameters.push(...toOpenAPIParameters(schema, "query"));
423
- } else {
702
+ } else if (!omitResponseBody) {
424
703
  ref.requestBody = {
425
704
  required,
426
705
  content: toOpenAPIContent(schema)
@@ -434,7 +713,8 @@ ${errors.join("\n\n")}`
434
713
  if (!isObjectSchema(schema)) {
435
714
  throw error;
436
715
  }
437
- if (dynamicParams?.length && (schema.properties?.params === void 0 || !isObjectSchema(schema.properties.params) || !checkParamsSchema(schema.properties.params, dynamicParams))) {
716
+ const resolvedParamSchema = schema.properties?.params !== void 0 ? simplifyComposedObjectJsonSchemasAndRefs(schema.properties.params, doc) : void 0;
717
+ if (dynamicParams?.length && (resolvedParamSchema === void 0 || !isObjectSchema(resolvedParamSchema) || !checkParamsSchema(resolvedParamSchema, dynamicParams))) {
438
718
  throw new OpenAPIGeneratorError(
439
719
  'When input structure is "detailed" and path has dynamic params, the "params" schema must be an object with all dynamic params as required.'
440
720
  );
@@ -442,12 +722,13 @@ ${errors.join("\n\n")}`
442
722
  for (const from of ["params", "query", "headers"]) {
443
723
  const fromSchema = schema.properties?.[from];
444
724
  if (fromSchema !== void 0) {
445
- if (!isObjectSchema(fromSchema)) {
725
+ const resolvedSchema = simplifyComposedObjectJsonSchemasAndRefs(fromSchema, doc);
726
+ if (!isObjectSchema(resolvedSchema)) {
446
727
  throw error;
447
728
  }
448
729
  const parameterIn = from === "params" ? "path" : from === "headers" ? "header" : "query";
449
730
  ref.parameters ??= [];
450
- ref.parameters.push(...toOpenAPIParameters(fromSchema, parameterIn));
731
+ ref.parameters.push(...toOpenAPIParameters(resolvedSchema, parameterIn));
451
732
  }
452
733
  }
453
734
  if (schema.properties?.body !== void 0) {
@@ -457,7 +738,7 @@ ${errors.join("\n\n")}`
457
738
  };
458
739
  }
459
740
  }
460
- async #successResponse(ref, def) {
741
+ async #successResponse(doc, ref, def, baseSchemaConvertOptions) {
461
742
  const outputSchema = def.outputSchema;
462
743
  const status = fallbackContractConfig("defaultSuccessStatus", def.route.successStatus);
463
744
  const description = fallbackContractConfig("defaultSuccessDescription", def.route?.successDescription);
@@ -468,13 +749,20 @@ ${errors.join("\n\n")}`
468
749
  ref.responses[status] = {
469
750
  description,
470
751
  content: toOpenAPIEventIteratorContent(
471
- await this.converter.convert(eventIteratorSchemaDetails.yields, { strategy: "output" }),
472
- await this.converter.convert(eventIteratorSchemaDetails.returns, { strategy: "output" })
752
+ await this.converter.convert(eventIteratorSchemaDetails.yields, { ...baseSchemaConvertOptions, strategy: "output" }),
753
+ await this.converter.convert(eventIteratorSchemaDetails.returns, { ...baseSchemaConvertOptions, strategy: "output" })
473
754
  )
474
755
  };
475
756
  return;
476
757
  }
477
- const [required, json] = await this.converter.convert(outputSchema, { strategy: "output" });
758
+ const [required, json] = await this.converter.convert(
759
+ outputSchema,
760
+ {
761
+ ...baseSchemaConvertOptions,
762
+ strategy: "output",
763
+ minStructureDepthForRef: outputStructure === "detailed" ? 1 : 0
764
+ }
765
+ );
478
766
  if (outputStructure === "compact") {
479
767
  ref.responses ??= {};
480
768
  ref.responses[status] = {
@@ -495,17 +783,19 @@ ${errors.join("\n\n")}`
495
783
 
496
784
  But got: ${stringifyJSON(item)}
497
785
  `);
498
- if (!isObjectSchema(item)) {
786
+ const simplifiedItem = simplifyComposedObjectJsonSchemasAndRefs(item, doc);
787
+ if (!isObjectSchema(simplifiedItem)) {
499
788
  throw error;
500
789
  }
501
790
  let schemaStatus;
502
791
  let schemaDescription;
503
- if (item.properties?.status !== void 0) {
504
- if (typeof item.properties.status !== "object" || item.properties.status.const === void 0 || typeof item.properties.status.const !== "number" || !Number.isInteger(item.properties.status.const) || isORPCErrorStatus(item.properties.status.const)) {
792
+ if (simplifiedItem.properties?.status !== void 0) {
793
+ const statusSchema = resolveOpenAPIJsonSchemaRef(doc, simplifiedItem.properties.status);
794
+ if (typeof statusSchema !== "object" || statusSchema.const === void 0 || typeof statusSchema.const !== "number" || !Number.isInteger(statusSchema.const) || isORPCErrorStatus(statusSchema.const)) {
505
795
  throw error;
506
796
  }
507
- schemaStatus = item.properties.status.const;
508
- schemaDescription = item.properties.status.description;
797
+ schemaStatus = statusSchema.const;
798
+ schemaDescription = statusSchema.description;
509
799
  }
510
800
  const itemStatus = schemaStatus ?? status;
511
801
  const itemDescription = schemaDescription ?? description;
@@ -520,71 +810,64 @@ ${errors.join("\n\n")}`
520
810
  ref.responses[itemStatus] = {
521
811
  description: itemDescription
522
812
  };
523
- if (item.properties?.headers !== void 0) {
524
- if (!isObjectSchema(item.properties.headers)) {
813
+ if (simplifiedItem.properties?.headers !== void 0) {
814
+ const headersSchema = simplifyComposedObjectJsonSchemasAndRefs(simplifiedItem.properties.headers, doc);
815
+ if (!isObjectSchema(headersSchema)) {
525
816
  throw error;
526
817
  }
527
- for (const key in item.properties.headers.properties) {
528
- const headerSchema = item.properties.headers.properties[key];
818
+ for (const key in headersSchema.properties) {
819
+ const headerSchema = headersSchema.properties[key];
529
820
  if (headerSchema !== void 0) {
530
821
  ref.responses[itemStatus].headers ??= {};
531
822
  ref.responses[itemStatus].headers[key] = {
532
823
  schema: toOpenAPISchema(headerSchema),
533
- required: item.properties.headers.required?.includes(key)
824
+ required: simplifiedItem.required?.includes("headers") && headersSchema.required?.includes(key)
534
825
  };
535
826
  }
536
827
  }
537
828
  }
538
- if (item.properties?.body !== void 0) {
829
+ if (simplifiedItem.properties?.body !== void 0) {
539
830
  ref.responses[itemStatus].content = toOpenAPIContent(
540
- applySchemaOptionality(item.required?.includes("body") ?? false, item.properties.body)
831
+ applySchemaOptionality(simplifiedItem.required?.includes("body") ?? false, simplifiedItem.properties.body)
541
832
  );
542
833
  }
543
834
  }
544
835
  }
545
- async #errorResponse(ref, def) {
836
+ async #errorResponse(ref, def, baseSchemaConvertOptions, undefinedErrorSchema, customErrorResponseBodySchema) {
546
837
  const errorMap = def.errorMap;
547
- const errors = {};
838
+ const errorResponsesByStatus = {};
548
839
  for (const code in errorMap) {
549
840
  const config = errorMap[code];
550
841
  if (!config) {
551
842
  continue;
552
843
  }
553
844
  const status = fallbackORPCErrorStatus(code, config.status);
554
- const message = fallbackORPCErrorMessage(code, config.message);
555
- const [dataRequired, dataSchema] = await this.converter.convert(config.data, { strategy: "output" });
556
- errors[status] ??= [];
557
- errors[status].push({
845
+ const defaultMessage = fallbackORPCErrorMessage(code, config.message);
846
+ errorResponsesByStatus[status] ??= { status, definedErrorDefinitions: [], errorSchemaVariants: [] };
847
+ const [dataRequired, dataSchema] = await this.converter.convert(config.data, { ...baseSchemaConvertOptions, strategy: "output" });
848
+ errorResponsesByStatus[status].definedErrorDefinitions.push([code, defaultMessage, dataRequired, dataSchema]);
849
+ errorResponsesByStatus[status].errorSchemaVariants.push({
558
850
  type: "object",
559
851
  properties: {
560
852
  defined: { const: true },
561
853
  code: { const: code },
562
854
  status: { const: status },
563
- message: { type: "string", default: message },
855
+ message: { type: "string", default: defaultMessage },
564
856
  data: dataSchema
565
857
  },
566
858
  required: dataRequired ? ["defined", "code", "status", "message", "data"] : ["defined", "code", "status", "message"]
567
859
  });
568
860
  }
569
861
  ref.responses ??= {};
570
- for (const status in errors) {
571
- const schemas = errors[status];
572
- ref.responses[status] = {
573
- description: status,
574
- content: toOpenAPIContent({
862
+ for (const statusString in errorResponsesByStatus) {
863
+ const errorResponse = errorResponsesByStatus[statusString];
864
+ const customBodySchema = value(customErrorResponseBodySchema, errorResponse.definedErrorDefinitions, errorResponse.status);
865
+ ref.responses[statusString] = {
866
+ description: statusString,
867
+ content: toOpenAPIContent(customBodySchema ?? {
575
868
  oneOf: [
576
- ...schemas,
577
- {
578
- type: "object",
579
- properties: {
580
- defined: { const: false },
581
- code: { type: "string" },
582
- status: { type: "number" },
583
- message: { type: "string" },
584
- data: {}
585
- },
586
- required: ["defined", "code", "status", "message"]
587
- }
869
+ ...errorResponse.errorSchemaVariants,
870
+ undefinedErrorSchema
588
871
  ]
589
872
  })
590
873
  };
@@ -592,4 +875,4 @@ ${errors.join("\n\n")}`
592
875
  }
593
876
  }
594
877
 
595
- export { CompositeSchemaConverter as C, LOGIC_KEYWORDS as L, OpenAPIGenerator as O, applyCustomOpenAPIOperation as a, toOpenAPIMethod as b, customOpenAPIOperation as c, toOpenAPIContent as d, toOpenAPIEventIteratorContent as e, toOpenAPIParameters as f, getCustomOpenAPIOperation as g, checkParamsSchema as h, toOpenAPISchema as i, isFileSchema as j, isObjectSchema as k, isAnySchema as l, filterSchemaBranches as m, applySchemaOptionality as n, expandUnionSchema as o, separateObjectSchema as s, toOpenAPIPath as t };
878
+ export { CompositeSchemaConverter as C, LOGIC_KEYWORDS as L, OpenAPIGenerator as O, applyCustomOpenAPIOperation as a, toOpenAPIMethod as b, customOpenAPIOperation as c, toOpenAPIContent as d, toOpenAPIEventIteratorContent as e, toOpenAPIParameters as f, getCustomOpenAPIOperation as g, checkParamsSchema as h, toOpenAPISchema as i, isFileSchema as j, isObjectSchema as k, isAnySchema as l, isNeverSchema as m, separateObjectSchema as n, filterSchemaBranches as o, applySchemaOptionality as p, expandUnionSchema as q, resolveOpenAPIJsonSchemaRef as r, simplifyComposedObjectJsonSchemasAndRefs as s, toOpenAPIPath as t, expandArrayableSchema as u, isPrimitiveSchema as v };