@orpc/openapi 0.0.0-next.2147e3f → 0.0.0-next.21703c0
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/README.md +137 -14
- package/dist/adapters/aws-lambda/index.d.mts +8 -5
- package/dist/adapters/aws-lambda/index.d.ts +8 -5
- package/dist/adapters/aws-lambda/index.mjs +5 -5
- package/dist/adapters/fastify/index.d.mts +23 -0
- package/dist/adapters/fastify/index.d.ts +23 -0
- package/dist/adapters/fastify/index.mjs +18 -0
- package/dist/adapters/fetch/index.d.mts +11 -5
- package/dist/adapters/fetch/index.d.ts +11 -5
- package/dist/adapters/fetch/index.mjs +3 -3
- package/dist/adapters/node/index.d.mts +11 -5
- package/dist/adapters/node/index.d.ts +11 -5
- package/dist/adapters/node/index.mjs +3 -3
- package/dist/adapters/standard/index.d.mts +8 -23
- package/dist/adapters/standard/index.d.ts +8 -23
- package/dist/adapters/standard/index.mjs +1 -1
- package/dist/index.d.mts +11 -3
- package/dist/index.d.ts +11 -3
- package/dist/index.mjs +2 -2
- package/dist/plugins/index.d.mts +20 -3
- package/dist/plugins/index.d.ts +20 -3
- package/dist/plugins/index.mjs +69 -20
- package/dist/shared/{openapi.C_UtQ8Us.mjs → openapi.BB-W-NKv.mjs} +33 -8
- package/dist/shared/openapi.BGy4N6eR.d.mts +120 -0
- package/dist/shared/openapi.BGy4N6eR.d.ts +120 -0
- package/dist/shared/{openapi.DaYgbD_w.mjs → openapi.BwdtJjDu.mjs} +300 -74
- package/dist/shared/openapi.DwaweYRb.d.mts +54 -0
- package/dist/shared/openapi.DwaweYRb.d.ts +54 -0
- package/package.json +20 -13
- package/dist/shared/openapi.D3j94c9n.d.mts +0 -12
- package/dist/shared/openapi.D3j94c9n.d.ts +0 -12
- package/dist/shared/openapi.qZLdpE0a.d.mts +0 -52
- package/dist/shared/openapi.qZLdpE0a.d.ts +0 -52
|
@@ -3,7 +3,7 @@ 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, stringifyJSON, findDeepMatches, toArray, clone } from '@orpc/shared';
|
|
6
|
+
import { isObject, stringifyJSON, findDeepMatches, toArray, clone, value } from '@orpc/shared';
|
|
7
7
|
import { TypeName } from 'json-schema-typed/draft-2020-12';
|
|
8
8
|
|
|
9
9
|
const OPERATION_EXTENDER_SYMBOL = Symbol("ORPC_OPERATION_EXTENDER");
|
|
@@ -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(
|
|
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 =
|
|
123
|
-
|
|
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;
|
|
@@ -250,7 +278,7 @@ function toOpenAPIContent(schema) {
|
|
|
250
278
|
schema: toOpenAPISchema(file)
|
|
251
279
|
};
|
|
252
280
|
}
|
|
253
|
-
if (restSchema !== void 0) {
|
|
281
|
+
if (restSchema !== void 0 && !isAnySchema(restSchema) && !isNeverSchema(restSchema)) {
|
|
254
282
|
content["application/json"] = {
|
|
255
283
|
schema: toOpenAPISchema(restSchema)
|
|
256
284
|
};
|
|
@@ -345,6 +373,116 @@ function checkParamsSchema(schema, params) {
|
|
|
345
373
|
function toOpenAPISchema(schema) {
|
|
346
374
|
return schema === true ? {} : schema === false ? { not: {} } : schema;
|
|
347
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
|
+
}
|
|
348
486
|
|
|
349
487
|
class CompositeSchemaConverter {
|
|
350
488
|
converters;
|
|
@@ -373,43 +511,49 @@ class OpenAPIGenerator {
|
|
|
373
511
|
/**
|
|
374
512
|
* Generates OpenAPI specifications from oRPC routers/contracts.
|
|
375
513
|
*
|
|
376
|
-
* @see {@link https://orpc.
|
|
514
|
+
* @see {@link https://orpc.dev/docs/openapi/openapi-specification OpenAPI Specification Docs}
|
|
377
515
|
*/
|
|
378
|
-
async generate(router,
|
|
379
|
-
const
|
|
516
|
+
async generate(router, { customErrorResponseBodySchema, commonSchemas, filter: baseFilter, exclude, ...baseDoc } = {}) {
|
|
517
|
+
const filter = baseFilter ?? (({ contract, path }) => {
|
|
518
|
+
return !(exclude?.(contract, path) ?? false);
|
|
519
|
+
});
|
|
380
520
|
const doc = {
|
|
381
|
-
...clone(
|
|
382
|
-
info:
|
|
383
|
-
openapi: "3.1.1"
|
|
384
|
-
exclude: void 0
|
|
521
|
+
...clone(baseDoc),
|
|
522
|
+
info: baseDoc.info ?? { title: "API Reference", version: "0.0.0" },
|
|
523
|
+
openapi: "3.1.1"
|
|
385
524
|
};
|
|
525
|
+
const { baseSchemaConvertOptions, undefinedErrorJsonSchema } = await this.#resolveCommonSchemas(doc, commonSchemas);
|
|
386
526
|
const contracts = [];
|
|
387
|
-
await resolveContractProcedures({ path: [], router }, (
|
|
388
|
-
if (!
|
|
389
|
-
|
|
527
|
+
await resolveContractProcedures({ path: [], router }, (traverseOptions) => {
|
|
528
|
+
if (!value(filter, traverseOptions)) {
|
|
529
|
+
return;
|
|
390
530
|
}
|
|
531
|
+
contracts.push(traverseOptions);
|
|
391
532
|
});
|
|
392
533
|
const errors = [];
|
|
393
534
|
for (const { contract, path } of contracts) {
|
|
394
|
-
const
|
|
535
|
+
const stringPath = path.join(".");
|
|
395
536
|
try {
|
|
396
537
|
const def = contract["~orpc"];
|
|
397
538
|
const method = toOpenAPIMethod(fallbackContractConfig("defaultMethod", def.route.method));
|
|
398
539
|
const httpPath = toOpenAPIPath(def.route.path ?? toHttpPath(path));
|
|
399
540
|
let operationObjectRef;
|
|
400
|
-
if (def.route.spec !== void 0) {
|
|
541
|
+
if (def.route.spec !== void 0 && typeof def.route.spec !== "function") {
|
|
401
542
|
operationObjectRef = def.route.spec;
|
|
402
543
|
} else {
|
|
403
544
|
operationObjectRef = {
|
|
404
|
-
operationId,
|
|
545
|
+
operationId: def.route.operationId ?? stringPath,
|
|
405
546
|
summary: def.route.summary,
|
|
406
547
|
description: def.route.description,
|
|
407
548
|
deprecated: def.route.deprecated,
|
|
408
549
|
tags: def.route.tags?.map((tag) => tag)
|
|
409
550
|
};
|
|
410
|
-
await this.#request(operationObjectRef, def);
|
|
411
|
-
await this.#successResponse(operationObjectRef, def);
|
|
412
|
-
await this.#errorResponse(operationObjectRef, def);
|
|
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);
|
|
413
557
|
}
|
|
414
558
|
doc.paths ??= {};
|
|
415
559
|
doc.paths[httpPath] ??= {};
|
|
@@ -419,7 +563,7 @@ class OpenAPIGenerator {
|
|
|
419
563
|
throw e;
|
|
420
564
|
}
|
|
421
565
|
errors.push(
|
|
422
|
-
`[OpenAPIGenerator] Error occurred while generating OpenAPI for procedure at path: ${
|
|
566
|
+
`[OpenAPIGenerator] Error occurred while generating OpenAPI for procedure at path: ${stringPath}
|
|
423
567
|
${e.message}`
|
|
424
568
|
);
|
|
425
569
|
}
|
|
@@ -433,25 +577,102 @@ ${errors.join("\n\n")}`
|
|
|
433
577
|
}
|
|
434
578
|
return this.serializer.serialize(doc)[0];
|
|
435
579
|
}
|
|
436
|
-
async #
|
|
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) {
|
|
437
648
|
const method = fallbackContractConfig("defaultMethod", def.route.method);
|
|
438
649
|
const details = getEventIteratorSchemaDetails(def.inputSchema);
|
|
439
650
|
if (details) {
|
|
440
651
|
ref.requestBody = {
|
|
441
652
|
required: true,
|
|
442
653
|
content: toOpenAPIEventIteratorContent(
|
|
443
|
-
await this.converter.convert(details.yields, { strategy: "input" }),
|
|
444
|
-
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" })
|
|
445
656
|
)
|
|
446
657
|
};
|
|
447
658
|
return;
|
|
448
659
|
}
|
|
449
660
|
const dynamicParams = getDynamicParams(def.route.path)?.map((v) => v.name);
|
|
450
661
|
const inputStructure = fallbackContractConfig("defaultInputStructure", def.route.inputStructure);
|
|
451
|
-
let [required, schema] = await this.converter.convert(
|
|
662
|
+
let [required, schema] = await this.converter.convert(
|
|
663
|
+
def.inputSchema,
|
|
664
|
+
{
|
|
665
|
+
...baseSchemaConvertOptions,
|
|
666
|
+
strategy: "input"
|
|
667
|
+
}
|
|
668
|
+
);
|
|
669
|
+
let omitResponseBody = false;
|
|
452
670
|
if (isAnySchema(schema) && !dynamicParams?.length) {
|
|
453
671
|
return;
|
|
454
672
|
}
|
|
673
|
+
if (inputStructure === "detailed" || inputStructure === "compact" && (dynamicParams?.length || method === "GET")) {
|
|
674
|
+
schema = simplifyComposedObjectJsonSchemasAndRefs(schema, doc);
|
|
675
|
+
}
|
|
455
676
|
if (inputStructure === "compact") {
|
|
456
677
|
if (dynamicParams?.length) {
|
|
457
678
|
const error2 = new OpenAPIGeneratorError(
|
|
@@ -463,6 +684,7 @@ ${errors.join("\n\n")}`
|
|
|
463
684
|
const [paramsSchema, rest] = separateObjectSchema(schema, dynamicParams);
|
|
464
685
|
schema = rest;
|
|
465
686
|
required = rest.required ? rest.required.length !== 0 : false;
|
|
687
|
+
omitResponseBody = !required && !rest.properties;
|
|
466
688
|
if (!checkParamsSchema(paramsSchema, dynamicParams)) {
|
|
467
689
|
throw error2;
|
|
468
690
|
}
|
|
@@ -477,7 +699,7 @@ ${errors.join("\n\n")}`
|
|
|
477
699
|
}
|
|
478
700
|
ref.parameters ??= [];
|
|
479
701
|
ref.parameters.push(...toOpenAPIParameters(schema, "query"));
|
|
480
|
-
} else {
|
|
702
|
+
} else if (!omitResponseBody) {
|
|
481
703
|
ref.requestBody = {
|
|
482
704
|
required,
|
|
483
705
|
content: toOpenAPIContent(schema)
|
|
@@ -491,7 +713,8 @@ ${errors.join("\n\n")}`
|
|
|
491
713
|
if (!isObjectSchema(schema)) {
|
|
492
714
|
throw error;
|
|
493
715
|
}
|
|
494
|
-
|
|
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))) {
|
|
495
718
|
throw new OpenAPIGeneratorError(
|
|
496
719
|
'When input structure is "detailed" and path has dynamic params, the "params" schema must be an object with all dynamic params as required.'
|
|
497
720
|
);
|
|
@@ -499,12 +722,13 @@ ${errors.join("\n\n")}`
|
|
|
499
722
|
for (const from of ["params", "query", "headers"]) {
|
|
500
723
|
const fromSchema = schema.properties?.[from];
|
|
501
724
|
if (fromSchema !== void 0) {
|
|
502
|
-
|
|
725
|
+
const resolvedSchema = simplifyComposedObjectJsonSchemasAndRefs(fromSchema, doc);
|
|
726
|
+
if (!isObjectSchema(resolvedSchema)) {
|
|
503
727
|
throw error;
|
|
504
728
|
}
|
|
505
729
|
const parameterIn = from === "params" ? "path" : from === "headers" ? "header" : "query";
|
|
506
730
|
ref.parameters ??= [];
|
|
507
|
-
ref.parameters.push(...toOpenAPIParameters(
|
|
731
|
+
ref.parameters.push(...toOpenAPIParameters(resolvedSchema, parameterIn));
|
|
508
732
|
}
|
|
509
733
|
}
|
|
510
734
|
if (schema.properties?.body !== void 0) {
|
|
@@ -514,7 +738,7 @@ ${errors.join("\n\n")}`
|
|
|
514
738
|
};
|
|
515
739
|
}
|
|
516
740
|
}
|
|
517
|
-
async #successResponse(ref, def) {
|
|
741
|
+
async #successResponse(doc, ref, def, baseSchemaConvertOptions) {
|
|
518
742
|
const outputSchema = def.outputSchema;
|
|
519
743
|
const status = fallbackContractConfig("defaultSuccessStatus", def.route.successStatus);
|
|
520
744
|
const description = fallbackContractConfig("defaultSuccessDescription", def.route?.successDescription);
|
|
@@ -525,13 +749,20 @@ ${errors.join("\n\n")}`
|
|
|
525
749
|
ref.responses[status] = {
|
|
526
750
|
description,
|
|
527
751
|
content: toOpenAPIEventIteratorContent(
|
|
528
|
-
await this.converter.convert(eventIteratorSchemaDetails.yields, { strategy: "output" }),
|
|
529
|
-
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" })
|
|
530
754
|
)
|
|
531
755
|
};
|
|
532
756
|
return;
|
|
533
757
|
}
|
|
534
|
-
const [required, json] = await this.converter.convert(
|
|
758
|
+
const [required, json] = await this.converter.convert(
|
|
759
|
+
outputSchema,
|
|
760
|
+
{
|
|
761
|
+
...baseSchemaConvertOptions,
|
|
762
|
+
strategy: "output",
|
|
763
|
+
minStructureDepthForRef: outputStructure === "detailed" ? 1 : 0
|
|
764
|
+
}
|
|
765
|
+
);
|
|
535
766
|
if (outputStructure === "compact") {
|
|
536
767
|
ref.responses ??= {};
|
|
537
768
|
ref.responses[status] = {
|
|
@@ -552,17 +783,19 @@ ${errors.join("\n\n")}`
|
|
|
552
783
|
|
|
553
784
|
But got: ${stringifyJSON(item)}
|
|
554
785
|
`);
|
|
555
|
-
|
|
786
|
+
const simplifiedItem = simplifyComposedObjectJsonSchemasAndRefs(item, doc);
|
|
787
|
+
if (!isObjectSchema(simplifiedItem)) {
|
|
556
788
|
throw error;
|
|
557
789
|
}
|
|
558
790
|
let schemaStatus;
|
|
559
791
|
let schemaDescription;
|
|
560
|
-
if (
|
|
561
|
-
|
|
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)) {
|
|
562
795
|
throw error;
|
|
563
796
|
}
|
|
564
|
-
schemaStatus =
|
|
565
|
-
schemaDescription =
|
|
797
|
+
schemaStatus = statusSchema.const;
|
|
798
|
+
schemaDescription = statusSchema.description;
|
|
566
799
|
}
|
|
567
800
|
const itemStatus = schemaStatus ?? status;
|
|
568
801
|
const itemDescription = schemaDescription ?? description;
|
|
@@ -577,71 +810,64 @@ ${errors.join("\n\n")}`
|
|
|
577
810
|
ref.responses[itemStatus] = {
|
|
578
811
|
description: itemDescription
|
|
579
812
|
};
|
|
580
|
-
if (
|
|
581
|
-
|
|
813
|
+
if (simplifiedItem.properties?.headers !== void 0) {
|
|
814
|
+
const headersSchema = simplifyComposedObjectJsonSchemasAndRefs(simplifiedItem.properties.headers, doc);
|
|
815
|
+
if (!isObjectSchema(headersSchema)) {
|
|
582
816
|
throw error;
|
|
583
817
|
}
|
|
584
|
-
for (const key in
|
|
585
|
-
const headerSchema =
|
|
818
|
+
for (const key in headersSchema.properties) {
|
|
819
|
+
const headerSchema = headersSchema.properties[key];
|
|
586
820
|
if (headerSchema !== void 0) {
|
|
587
821
|
ref.responses[itemStatus].headers ??= {};
|
|
588
822
|
ref.responses[itemStatus].headers[key] = {
|
|
589
823
|
schema: toOpenAPISchema(headerSchema),
|
|
590
|
-
required:
|
|
824
|
+
required: simplifiedItem.required?.includes("headers") && headersSchema.required?.includes(key)
|
|
591
825
|
};
|
|
592
826
|
}
|
|
593
827
|
}
|
|
594
828
|
}
|
|
595
|
-
if (
|
|
829
|
+
if (simplifiedItem.properties?.body !== void 0) {
|
|
596
830
|
ref.responses[itemStatus].content = toOpenAPIContent(
|
|
597
|
-
applySchemaOptionality(
|
|
831
|
+
applySchemaOptionality(simplifiedItem.required?.includes("body") ?? false, simplifiedItem.properties.body)
|
|
598
832
|
);
|
|
599
833
|
}
|
|
600
834
|
}
|
|
601
835
|
}
|
|
602
|
-
async #errorResponse(ref, def) {
|
|
836
|
+
async #errorResponse(ref, def, baseSchemaConvertOptions, undefinedErrorSchema, customErrorResponseBodySchema) {
|
|
603
837
|
const errorMap = def.errorMap;
|
|
604
|
-
const
|
|
838
|
+
const errorResponsesByStatus = {};
|
|
605
839
|
for (const code in errorMap) {
|
|
606
840
|
const config = errorMap[code];
|
|
607
841
|
if (!config) {
|
|
608
842
|
continue;
|
|
609
843
|
}
|
|
610
844
|
const status = fallbackORPCErrorStatus(code, config.status);
|
|
611
|
-
const
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
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({
|
|
615
850
|
type: "object",
|
|
616
851
|
properties: {
|
|
617
852
|
defined: { const: true },
|
|
618
853
|
code: { const: code },
|
|
619
854
|
status: { const: status },
|
|
620
|
-
message: { type: "string", default:
|
|
855
|
+
message: { type: "string", default: defaultMessage },
|
|
621
856
|
data: dataSchema
|
|
622
857
|
},
|
|
623
858
|
required: dataRequired ? ["defined", "code", "status", "message", "data"] : ["defined", "code", "status", "message"]
|
|
624
859
|
});
|
|
625
860
|
}
|
|
626
861
|
ref.responses ??= {};
|
|
627
|
-
for (const
|
|
628
|
-
const
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
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 ?? {
|
|
632
868
|
oneOf: [
|
|
633
|
-
...
|
|
634
|
-
|
|
635
|
-
type: "object",
|
|
636
|
-
properties: {
|
|
637
|
-
defined: { const: false },
|
|
638
|
-
code: { type: "string" },
|
|
639
|
-
status: { type: "number" },
|
|
640
|
-
message: { type: "string" },
|
|
641
|
-
data: {}
|
|
642
|
-
},
|
|
643
|
-
required: ["defined", "code", "status", "message"]
|
|
644
|
-
}
|
|
869
|
+
...errorResponse.errorSchemaVariants,
|
|
870
|
+
undefinedErrorSchema
|
|
645
871
|
]
|
|
646
872
|
})
|
|
647
873
|
};
|
|
@@ -649,4 +875,4 @@ ${errors.join("\n\n")}`
|
|
|
649
875
|
}
|
|
650
876
|
}
|
|
651
877
|
|
|
652
|
-
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,
|
|
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 };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { StandardOpenAPISerializer, StandardOpenAPIJsonSerializerOptions, StandardBracketNotationSerializerOptions } from '@orpc/openapi-client/standard';
|
|
2
|
+
import { AnyProcedure, TraverseContractProcedureCallbackOptions, AnyRouter, Context, Router } from '@orpc/server';
|
|
3
|
+
import { StandardCodec, StandardParams, StandardMatcher, StandardMatchResult, StandardHandlerOptions, StandardHandler } from '@orpc/server/standard';
|
|
4
|
+
import { ORPCError, HTTPPath } from '@orpc/client';
|
|
5
|
+
import { StandardLazyRequest, StandardResponse } from '@orpc/standard-server';
|
|
6
|
+
import { Value } from '@orpc/shared';
|
|
7
|
+
|
|
8
|
+
interface StandardOpenAPICodecOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Customize how an ORPC error is encoded into a response body.
|
|
11
|
+
* Use this if your API needs a different error output structure.
|
|
12
|
+
*
|
|
13
|
+
* @remarks
|
|
14
|
+
* - Return `null | undefined` to fallback to default behavior
|
|
15
|
+
*
|
|
16
|
+
* @default ((e) => e.toJSON())
|
|
17
|
+
*/
|
|
18
|
+
customErrorResponseBodyEncoder?: (error: ORPCError<any, any>) => unknown;
|
|
19
|
+
}
|
|
20
|
+
declare class StandardOpenAPICodec implements StandardCodec {
|
|
21
|
+
#private;
|
|
22
|
+
private readonly serializer;
|
|
23
|
+
private readonly customErrorResponseBodyEncoder;
|
|
24
|
+
constructor(serializer: StandardOpenAPISerializer, options?: StandardOpenAPICodecOptions);
|
|
25
|
+
decode(request: StandardLazyRequest, params: StandardParams | undefined, procedure: AnyProcedure): Promise<unknown>;
|
|
26
|
+
encode(output: unknown, procedure: AnyProcedure): StandardResponse;
|
|
27
|
+
encodeError(error: ORPCError<any, any>): StandardResponse;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface StandardOpenAPIMatcherOptions {
|
|
31
|
+
/**
|
|
32
|
+
* Filter procedures. Return `false` to exclude a procedure from matching.
|
|
33
|
+
*
|
|
34
|
+
* @default true
|
|
35
|
+
*/
|
|
36
|
+
filter?: Value<boolean, [options: TraverseContractProcedureCallbackOptions]>;
|
|
37
|
+
}
|
|
38
|
+
declare class StandardOpenAPIMatcher implements StandardMatcher {
|
|
39
|
+
private readonly filter;
|
|
40
|
+
private readonly tree;
|
|
41
|
+
private pendingRouters;
|
|
42
|
+
constructor(options?: StandardOpenAPIMatcherOptions);
|
|
43
|
+
init(router: AnyRouter, path?: readonly string[]): void;
|
|
44
|
+
match(method: string, pathname: HTTPPath): Promise<StandardMatchResult>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface StandardOpenAPIHandlerOptions<T extends Context> extends StandardHandlerOptions<T>, StandardOpenAPIJsonSerializerOptions, StandardBracketNotationSerializerOptions, StandardOpenAPIMatcherOptions, StandardOpenAPICodecOptions {
|
|
48
|
+
}
|
|
49
|
+
declare class StandardOpenAPIHandler<T extends Context> extends StandardHandler<T> {
|
|
50
|
+
constructor(router: Router<any, T>, options: NoInfer<StandardOpenAPIHandlerOptions<T>>);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { StandardOpenAPICodec as a, StandardOpenAPIHandler as c, StandardOpenAPIMatcher as e };
|
|
54
|
+
export type { StandardOpenAPICodecOptions as S, StandardOpenAPIHandlerOptions as b, StandardOpenAPIMatcherOptions as d };
|