@orpc/openapi 0.0.0-next.4748dc5 → 0.0.0-next.47dbd93
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 +132 -13
- package/dist/adapters/aws-lambda/index.d.mts +4 -3
- package/dist/adapters/aws-lambda/index.d.ts +4 -3
- package/dist/adapters/aws-lambda/index.mjs +3 -3
- 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 +4 -3
- package/dist/adapters/fetch/index.d.ts +4 -3
- package/dist/adapters/fetch/index.mjs +3 -3
- package/dist/adapters/node/index.d.mts +4 -3
- package/dist/adapters/node/index.d.ts +4 -3
- package/dist/adapters/node/index.mjs +3 -3
- package/dist/adapters/standard/index.d.mts +7 -16
- package/dist/adapters/standard/index.d.ts +7 -16
- package/dist/adapters/standard/index.mjs +1 -1
- package/dist/index.d.mts +12 -5
- package/dist/index.d.ts +12 -5
- package/dist/index.mjs +3 -3
- package/dist/plugins/index.d.mts +4 -2
- package/dist/plugins/index.d.ts +4 -2
- package/dist/plugins/index.mjs +21 -12
- package/dist/shared/{openapi.BVXcB0u4.mjs → openapi.BB-W-NKv.mjs} +20 -3
- package/dist/shared/{openapi.CfjfVeBJ.d.mts → openapi.BGy4N6eR.d.mts} +16 -4
- package/dist/shared/{openapi.CfjfVeBJ.d.ts → openapi.BGy4N6eR.d.ts} +16 -4
- package/dist/shared/{openapi.BlSv9FKY.mjs → openapi.BwdtJjDu.mjs} +173 -46
- 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.CQmjvnb0.d.mts +0 -31
- package/dist/shared/openapi.CQmjvnb0.d.ts +0 -31
|
@@ -4,7 +4,7 @@ import { fallbackContractConfig, getEventIteratorSchemaDetails } from '@orpc/con
|
|
|
4
4
|
import { standardizeHTTPPath, StandardOpenAPIJsonSerializer, getDynamicParams } from '@orpc/openapi-client/standard';
|
|
5
5
|
import { isProcedure, resolveContractProcedures } from '@orpc/server';
|
|
6
6
|
import { isObject, stringifyJSON, findDeepMatches, toArray, clone, value } from '@orpc/shared';
|
|
7
|
-
import { TypeName } from '
|
|
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,11 +108,25 @@ 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
131
|
if (Object.keys(schema).some(
|
|
118
132
|
(k) => !["type", "properties", "required", "additionalProperties"].includes(k) && LOGIC_KEYWORDS.includes(k) && schema[k] !== void 0
|
|
@@ -128,7 +142,13 @@ function separateObjectSchema(schema, separatedProperties) {
|
|
|
128
142
|
}
|
|
129
143
|
return acc;
|
|
130
144
|
}, {});
|
|
145
|
+
if (Object.keys(matched.properties).length === 0) {
|
|
146
|
+
matched.properties = void 0;
|
|
147
|
+
}
|
|
131
148
|
matched.required = schema.required?.filter((key) => separatedProperties.includes(key));
|
|
149
|
+
if (matched.required?.length === 0) {
|
|
150
|
+
matched.required = void 0;
|
|
151
|
+
}
|
|
132
152
|
matched.examples = schema.examples?.map((example) => {
|
|
133
153
|
if (!isObject(example)) {
|
|
134
154
|
return example;
|
|
@@ -140,11 +160,14 @@ function separateObjectSchema(schema, separatedProperties) {
|
|
|
140
160
|
return acc;
|
|
141
161
|
}, {});
|
|
142
162
|
});
|
|
143
|
-
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]) => {
|
|
144
164
|
acc[key] = value;
|
|
145
165
|
return acc;
|
|
146
|
-
},
|
|
166
|
+
}, void 0);
|
|
147
167
|
rest.required = schema.required?.filter((key) => !separatedProperties.includes(key));
|
|
168
|
+
if (rest.required?.length === 0) {
|
|
169
|
+
rest.required = void 0;
|
|
170
|
+
}
|
|
148
171
|
rest.examples = schema.examples?.map((example) => {
|
|
149
172
|
if (!isObject(example)) {
|
|
150
173
|
return example;
|
|
@@ -255,7 +278,7 @@ function toOpenAPIContent(schema) {
|
|
|
255
278
|
schema: toOpenAPISchema(file)
|
|
256
279
|
};
|
|
257
280
|
}
|
|
258
|
-
if (restSchema !== void 0) {
|
|
281
|
+
if (restSchema !== void 0 && !isAnySchema(restSchema) && !isNeverSchema(restSchema)) {
|
|
259
282
|
content["application/json"] = {
|
|
260
283
|
schema: toOpenAPISchema(restSchema)
|
|
261
284
|
};
|
|
@@ -359,6 +382,107 @@ function resolveOpenAPIJsonSchemaRef(doc, schema) {
|
|
|
359
382
|
const resolved = doc.components?.schemas?.[name];
|
|
360
383
|
return resolved ?? schema;
|
|
361
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
|
+
}
|
|
362
486
|
|
|
363
487
|
class CompositeSchemaConverter {
|
|
364
488
|
converters;
|
|
@@ -387,21 +511,18 @@ class OpenAPIGenerator {
|
|
|
387
511
|
/**
|
|
388
512
|
* Generates OpenAPI specifications from oRPC routers/contracts.
|
|
389
513
|
*
|
|
390
|
-
* @see {@link https://orpc.
|
|
514
|
+
* @see {@link https://orpc.dev/docs/openapi/openapi-specification OpenAPI Specification Docs}
|
|
391
515
|
*/
|
|
392
|
-
async generate(router,
|
|
393
|
-
const filter =
|
|
394
|
-
return !(
|
|
516
|
+
async generate(router, { customErrorResponseBodySchema, commonSchemas, filter: baseFilter, exclude, ...baseDoc } = {}) {
|
|
517
|
+
const filter = baseFilter ?? (({ contract, path }) => {
|
|
518
|
+
return !(exclude?.(contract, path) ?? false);
|
|
395
519
|
});
|
|
396
520
|
const doc = {
|
|
397
|
-
...clone(
|
|
398
|
-
info:
|
|
399
|
-
openapi: "3.1.1"
|
|
400
|
-
exclude: void 0,
|
|
401
|
-
filter: void 0,
|
|
402
|
-
commonSchemas: void 0
|
|
521
|
+
...clone(baseDoc),
|
|
522
|
+
info: baseDoc.info ?? { title: "API Reference", version: "0.0.0" },
|
|
523
|
+
openapi: "3.1.1"
|
|
403
524
|
};
|
|
404
|
-
const { baseSchemaConvertOptions, undefinedErrorJsonSchema } = await this.#resolveCommonSchemas(doc,
|
|
525
|
+
const { baseSchemaConvertOptions, undefinedErrorJsonSchema } = await this.#resolveCommonSchemas(doc, commonSchemas);
|
|
405
526
|
const contracts = [];
|
|
406
527
|
await resolveContractProcedures({ path: [], router }, (traverseOptions) => {
|
|
407
528
|
if (!value(filter, traverseOptions)) {
|
|
@@ -429,7 +550,7 @@ class OpenAPIGenerator {
|
|
|
429
550
|
};
|
|
430
551
|
await this.#request(doc, operationObjectRef, def, baseSchemaConvertOptions);
|
|
431
552
|
await this.#successResponse(doc, operationObjectRef, def, baseSchemaConvertOptions);
|
|
432
|
-
await this.#errorResponse(operationObjectRef, def, baseSchemaConvertOptions, undefinedErrorJsonSchema);
|
|
553
|
+
await this.#errorResponse(operationObjectRef, def, baseSchemaConvertOptions, undefinedErrorJsonSchema, customErrorResponseBodySchema);
|
|
433
554
|
}
|
|
434
555
|
if (typeof def.route.spec === "function") {
|
|
435
556
|
operationObjectRef = def.route.spec(operationObjectRef);
|
|
@@ -542,13 +663,16 @@ ${errors.join("\n\n")}`
|
|
|
542
663
|
def.inputSchema,
|
|
543
664
|
{
|
|
544
665
|
...baseSchemaConvertOptions,
|
|
545
|
-
strategy: "input"
|
|
546
|
-
minStructureDepthForRef: dynamicParams?.length || inputStructure === "detailed" ? 1 : 0
|
|
666
|
+
strategy: "input"
|
|
547
667
|
}
|
|
548
668
|
);
|
|
669
|
+
let omitResponseBody = false;
|
|
549
670
|
if (isAnySchema(schema) && !dynamicParams?.length) {
|
|
550
671
|
return;
|
|
551
672
|
}
|
|
673
|
+
if (inputStructure === "detailed" || inputStructure === "compact" && (dynamicParams?.length || method === "GET")) {
|
|
674
|
+
schema = simplifyComposedObjectJsonSchemasAndRefs(schema, doc);
|
|
675
|
+
}
|
|
552
676
|
if (inputStructure === "compact") {
|
|
553
677
|
if (dynamicParams?.length) {
|
|
554
678
|
const error2 = new OpenAPIGeneratorError(
|
|
@@ -560,6 +684,7 @@ ${errors.join("\n\n")}`
|
|
|
560
684
|
const [paramsSchema, rest] = separateObjectSchema(schema, dynamicParams);
|
|
561
685
|
schema = rest;
|
|
562
686
|
required = rest.required ? rest.required.length !== 0 : false;
|
|
687
|
+
omitResponseBody = !required && !rest.properties;
|
|
563
688
|
if (!checkParamsSchema(paramsSchema, dynamicParams)) {
|
|
564
689
|
throw error2;
|
|
565
690
|
}
|
|
@@ -567,15 +692,14 @@ ${errors.join("\n\n")}`
|
|
|
567
692
|
ref.parameters.push(...toOpenAPIParameters(paramsSchema, "path"));
|
|
568
693
|
}
|
|
569
694
|
if (method === "GET") {
|
|
570
|
-
|
|
571
|
-
if (!isObjectSchema(resolvedSchema)) {
|
|
695
|
+
if (!isObjectSchema(schema)) {
|
|
572
696
|
throw new OpenAPIGeneratorError(
|
|
573
697
|
'When method is "GET", input schema must satisfy: object | any | unknown'
|
|
574
698
|
);
|
|
575
699
|
}
|
|
576
700
|
ref.parameters ??= [];
|
|
577
|
-
ref.parameters.push(...toOpenAPIParameters(
|
|
578
|
-
} else {
|
|
701
|
+
ref.parameters.push(...toOpenAPIParameters(schema, "query"));
|
|
702
|
+
} else if (!omitResponseBody) {
|
|
579
703
|
ref.requestBody = {
|
|
580
704
|
required,
|
|
581
705
|
content: toOpenAPIContent(schema)
|
|
@@ -589,7 +713,7 @@ ${errors.join("\n\n")}`
|
|
|
589
713
|
if (!isObjectSchema(schema)) {
|
|
590
714
|
throw error;
|
|
591
715
|
}
|
|
592
|
-
const resolvedParamSchema = schema.properties?.params !== void 0 ?
|
|
716
|
+
const resolvedParamSchema = schema.properties?.params !== void 0 ? simplifyComposedObjectJsonSchemasAndRefs(schema.properties.params, doc) : void 0;
|
|
593
717
|
if (dynamicParams?.length && (resolvedParamSchema === void 0 || !isObjectSchema(resolvedParamSchema) || !checkParamsSchema(resolvedParamSchema, dynamicParams))) {
|
|
594
718
|
throw new OpenAPIGeneratorError(
|
|
595
719
|
'When input structure is "detailed" and path has dynamic params, the "params" schema must be an object with all dynamic params as required.'
|
|
@@ -598,7 +722,7 @@ ${errors.join("\n\n")}`
|
|
|
598
722
|
for (const from of ["params", "query", "headers"]) {
|
|
599
723
|
const fromSchema = schema.properties?.[from];
|
|
600
724
|
if (fromSchema !== void 0) {
|
|
601
|
-
const resolvedSchema =
|
|
725
|
+
const resolvedSchema = simplifyComposedObjectJsonSchemasAndRefs(fromSchema, doc);
|
|
602
726
|
if (!isObjectSchema(resolvedSchema)) {
|
|
603
727
|
throw error;
|
|
604
728
|
}
|
|
@@ -659,13 +783,14 @@ ${errors.join("\n\n")}`
|
|
|
659
783
|
|
|
660
784
|
But got: ${stringifyJSON(item)}
|
|
661
785
|
`);
|
|
662
|
-
|
|
786
|
+
const simplifiedItem = simplifyComposedObjectJsonSchemasAndRefs(item, doc);
|
|
787
|
+
if (!isObjectSchema(simplifiedItem)) {
|
|
663
788
|
throw error;
|
|
664
789
|
}
|
|
665
790
|
let schemaStatus;
|
|
666
791
|
let schemaDescription;
|
|
667
|
-
if (
|
|
668
|
-
const statusSchema = resolveOpenAPIJsonSchemaRef(doc,
|
|
792
|
+
if (simplifiedItem.properties?.status !== void 0) {
|
|
793
|
+
const statusSchema = resolveOpenAPIJsonSchemaRef(doc, simplifiedItem.properties.status);
|
|
669
794
|
if (typeof statusSchema !== "object" || statusSchema.const === void 0 || typeof statusSchema.const !== "number" || !Number.isInteger(statusSchema.const) || isORPCErrorStatus(statusSchema.const)) {
|
|
670
795
|
throw error;
|
|
671
796
|
}
|
|
@@ -685,8 +810,8 @@ ${errors.join("\n\n")}`
|
|
|
685
810
|
ref.responses[itemStatus] = {
|
|
686
811
|
description: itemDescription
|
|
687
812
|
};
|
|
688
|
-
if (
|
|
689
|
-
const headersSchema =
|
|
813
|
+
if (simplifiedItem.properties?.headers !== void 0) {
|
|
814
|
+
const headersSchema = simplifyComposedObjectJsonSchemasAndRefs(simplifiedItem.properties.headers, doc);
|
|
690
815
|
if (!isObjectSchema(headersSchema)) {
|
|
691
816
|
throw error;
|
|
692
817
|
}
|
|
@@ -696,50 +821,52 @@ ${errors.join("\n\n")}`
|
|
|
696
821
|
ref.responses[itemStatus].headers ??= {};
|
|
697
822
|
ref.responses[itemStatus].headers[key] = {
|
|
698
823
|
schema: toOpenAPISchema(headerSchema),
|
|
699
|
-
required:
|
|
824
|
+
required: simplifiedItem.required?.includes("headers") && headersSchema.required?.includes(key)
|
|
700
825
|
};
|
|
701
826
|
}
|
|
702
827
|
}
|
|
703
828
|
}
|
|
704
|
-
if (
|
|
829
|
+
if (simplifiedItem.properties?.body !== void 0) {
|
|
705
830
|
ref.responses[itemStatus].content = toOpenAPIContent(
|
|
706
|
-
applySchemaOptionality(
|
|
831
|
+
applySchemaOptionality(simplifiedItem.required?.includes("body") ?? false, simplifiedItem.properties.body)
|
|
707
832
|
);
|
|
708
833
|
}
|
|
709
834
|
}
|
|
710
835
|
}
|
|
711
|
-
async #errorResponse(ref, def, baseSchemaConvertOptions, undefinedErrorSchema) {
|
|
836
|
+
async #errorResponse(ref, def, baseSchemaConvertOptions, undefinedErrorSchema, customErrorResponseBodySchema) {
|
|
712
837
|
const errorMap = def.errorMap;
|
|
713
|
-
const
|
|
838
|
+
const errorResponsesByStatus = {};
|
|
714
839
|
for (const code in errorMap) {
|
|
715
840
|
const config = errorMap[code];
|
|
716
841
|
if (!config) {
|
|
717
842
|
continue;
|
|
718
843
|
}
|
|
719
844
|
const status = fallbackORPCErrorStatus(code, config.status);
|
|
720
|
-
const
|
|
845
|
+
const defaultMessage = fallbackORPCErrorMessage(code, config.message);
|
|
846
|
+
errorResponsesByStatus[status] ??= { status, definedErrorDefinitions: [], errorSchemaVariants: [] };
|
|
721
847
|
const [dataRequired, dataSchema] = await this.converter.convert(config.data, { ...baseSchemaConvertOptions, strategy: "output" });
|
|
722
|
-
|
|
723
|
-
|
|
848
|
+
errorResponsesByStatus[status].definedErrorDefinitions.push([code, defaultMessage, dataRequired, dataSchema]);
|
|
849
|
+
errorResponsesByStatus[status].errorSchemaVariants.push({
|
|
724
850
|
type: "object",
|
|
725
851
|
properties: {
|
|
726
852
|
defined: { const: true },
|
|
727
853
|
code: { const: code },
|
|
728
854
|
status: { const: status },
|
|
729
|
-
message: { type: "string", default:
|
|
855
|
+
message: { type: "string", default: defaultMessage },
|
|
730
856
|
data: dataSchema
|
|
731
857
|
},
|
|
732
858
|
required: dataRequired ? ["defined", "code", "status", "message", "data"] : ["defined", "code", "status", "message"]
|
|
733
859
|
});
|
|
734
860
|
}
|
|
735
861
|
ref.responses ??= {};
|
|
736
|
-
for (const
|
|
737
|
-
const
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
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 ?? {
|
|
741
868
|
oneOf: [
|
|
742
|
-
...
|
|
869
|
+
...errorResponse.errorSchemaVariants,
|
|
743
870
|
undefinedErrorSchema
|
|
744
871
|
]
|
|
745
872
|
})
|
|
@@ -748,4 +875,4 @@ ${errors.join("\n\n")}`
|
|
|
748
875
|
}
|
|
749
876
|
}
|
|
750
877
|
|
|
751
|
-
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 };
|
|
@@ -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 };
|
package/package.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orpc/openapi",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.0-next.
|
|
4
|
+
"version": "0.0.0-next.47dbd93",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"homepage": "https://orpc.
|
|
6
|
+
"homepage": "https://orpc.dev",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "git+https://github.com/
|
|
9
|
+
"url": "git+https://github.com/middleapi/orpc.git",
|
|
10
10
|
"directory": "packages/openapi"
|
|
11
11
|
},
|
|
12
12
|
"keywords": [
|
|
13
|
-
"unnoq",
|
|
14
13
|
"orpc"
|
|
15
14
|
],
|
|
15
|
+
"sideEffects": false,
|
|
16
16
|
"exports": {
|
|
17
17
|
".": {
|
|
18
18
|
"types": "./dist/index.d.mts",
|
|
@@ -39,6 +39,11 @@
|
|
|
39
39
|
"import": "./dist/adapters/node/index.mjs",
|
|
40
40
|
"default": "./dist/adapters/node/index.mjs"
|
|
41
41
|
},
|
|
42
|
+
"./fastify": {
|
|
43
|
+
"types": "./dist/adapters/fastify/index.d.mts",
|
|
44
|
+
"import": "./dist/adapters/fastify/index.mjs",
|
|
45
|
+
"default": "./dist/adapters/fastify/index.mjs"
|
|
46
|
+
},
|
|
42
47
|
"./aws-lambda": {
|
|
43
48
|
"types": "./dist/adapters/aws-lambda/index.d.mts",
|
|
44
49
|
"import": "./dist/adapters/aws-lambda/index.mjs",
|
|
@@ -49,17 +54,19 @@
|
|
|
49
54
|
"dist"
|
|
50
55
|
],
|
|
51
56
|
"dependencies": {
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"@orpc/contract": "0.0.0-next.
|
|
55
|
-
"@orpc/
|
|
56
|
-
"@orpc/
|
|
57
|
-
"@orpc/
|
|
58
|
-
"@orpc/
|
|
59
|
-
"@orpc/
|
|
57
|
+
"json-schema-typed": "^8.0.2",
|
|
58
|
+
"rou3": "^0.7.12",
|
|
59
|
+
"@orpc/contract": "0.0.0-next.47dbd93",
|
|
60
|
+
"@orpc/client": "0.0.0-next.47dbd93",
|
|
61
|
+
"@orpc/interop": "0.0.0-next.47dbd93",
|
|
62
|
+
"@orpc/openapi-client": "0.0.0-next.47dbd93",
|
|
63
|
+
"@orpc/shared": "0.0.0-next.47dbd93",
|
|
64
|
+
"@orpc/server": "0.0.0-next.47dbd93",
|
|
65
|
+
"@orpc/standard-server": "0.0.0-next.47dbd93"
|
|
60
66
|
},
|
|
61
67
|
"devDependencies": {
|
|
62
|
-
"
|
|
68
|
+
"fastify": "^5.8.3",
|
|
69
|
+
"zod": "^4.3.6"
|
|
63
70
|
},
|
|
64
71
|
"scripts": {
|
|
65
72
|
"build": "unbuild",
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { StandardOpenAPIJsonSerializerOptions, StandardBracketNotationSerializerOptions } from '@orpc/openapi-client/standard';
|
|
2
|
-
import { TraverseContractProcedureCallbackOptions, AnyRouter, Context, Router } from '@orpc/server';
|
|
3
|
-
import { StandardMatcher, StandardMatchResult, StandardHandlerOptions, StandardHandler } from '@orpc/server/standard';
|
|
4
|
-
import { HTTPPath } from '@orpc/client';
|
|
5
|
-
import { Value } from '@orpc/shared';
|
|
6
|
-
|
|
7
|
-
interface StandardOpenAPIMatcherOptions {
|
|
8
|
-
/**
|
|
9
|
-
* Filter procedures. Return `false` to exclude a procedure from matching.
|
|
10
|
-
*
|
|
11
|
-
* @default true
|
|
12
|
-
*/
|
|
13
|
-
filter?: Value<boolean, [options: TraverseContractProcedureCallbackOptions]>;
|
|
14
|
-
}
|
|
15
|
-
declare class StandardOpenAPIMatcher implements StandardMatcher {
|
|
16
|
-
private readonly filter;
|
|
17
|
-
private readonly tree;
|
|
18
|
-
private pendingRouters;
|
|
19
|
-
constructor(options?: StandardOpenAPIMatcherOptions);
|
|
20
|
-
init(router: AnyRouter, path?: readonly string[]): void;
|
|
21
|
-
match(method: string, pathname: HTTPPath): Promise<StandardMatchResult>;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
interface StandardOpenAPIHandlerOptions<T extends Context> extends StandardHandlerOptions<T>, StandardOpenAPIJsonSerializerOptions, StandardBracketNotationSerializerOptions, StandardOpenAPIMatcherOptions {
|
|
25
|
-
}
|
|
26
|
-
declare class StandardOpenAPIHandler<T extends Context> extends StandardHandler<T> {
|
|
27
|
-
constructor(router: Router<any, T>, options: NoInfer<StandardOpenAPIHandlerOptions<T>>);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export { StandardOpenAPIHandler as a, StandardOpenAPIMatcher as c };
|
|
31
|
-
export type { StandardOpenAPIHandlerOptions as S, StandardOpenAPIMatcherOptions as b };
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { StandardOpenAPIJsonSerializerOptions, StandardBracketNotationSerializerOptions } from '@orpc/openapi-client/standard';
|
|
2
|
-
import { TraverseContractProcedureCallbackOptions, AnyRouter, Context, Router } from '@orpc/server';
|
|
3
|
-
import { StandardMatcher, StandardMatchResult, StandardHandlerOptions, StandardHandler } from '@orpc/server/standard';
|
|
4
|
-
import { HTTPPath } from '@orpc/client';
|
|
5
|
-
import { Value } from '@orpc/shared';
|
|
6
|
-
|
|
7
|
-
interface StandardOpenAPIMatcherOptions {
|
|
8
|
-
/**
|
|
9
|
-
* Filter procedures. Return `false` to exclude a procedure from matching.
|
|
10
|
-
*
|
|
11
|
-
* @default true
|
|
12
|
-
*/
|
|
13
|
-
filter?: Value<boolean, [options: TraverseContractProcedureCallbackOptions]>;
|
|
14
|
-
}
|
|
15
|
-
declare class StandardOpenAPIMatcher implements StandardMatcher {
|
|
16
|
-
private readonly filter;
|
|
17
|
-
private readonly tree;
|
|
18
|
-
private pendingRouters;
|
|
19
|
-
constructor(options?: StandardOpenAPIMatcherOptions);
|
|
20
|
-
init(router: AnyRouter, path?: readonly string[]): void;
|
|
21
|
-
match(method: string, pathname: HTTPPath): Promise<StandardMatchResult>;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
interface StandardOpenAPIHandlerOptions<T extends Context> extends StandardHandlerOptions<T>, StandardOpenAPIJsonSerializerOptions, StandardBracketNotationSerializerOptions, StandardOpenAPIMatcherOptions {
|
|
25
|
-
}
|
|
26
|
-
declare class StandardOpenAPIHandler<T extends Context> extends StandardHandler<T> {
|
|
27
|
-
constructor(router: Router<any, T>, options: NoInfer<StandardOpenAPIHandlerOptions<T>>);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export { StandardOpenAPIHandler as a, StandardOpenAPIMatcher as c };
|
|
31
|
-
export type { StandardOpenAPIHandlerOptions as S, StandardOpenAPIMatcherOptions as b };
|