@orpc/openapi 0.0.0-next.dda04c5 → 0.0.0-next.de2bec7
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 +126 -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 +10 -3
- package/dist/index.d.ts +10 -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.B3hexduL.d.mts → openapi.BGy4N6eR.d.mts} +25 -6
- package/dist/shared/{openapi.B3hexduL.d.ts → openapi.BGy4N6eR.d.ts} +25 -6
- package/dist/shared/{openapi.DrrBsJ0w.mjs → openapi.BwdtJjDu.mjs} +191 -51
- 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
|
@@ -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
|
};
|
|
@@ -354,6 +382,107 @@ function resolveOpenAPIJsonSchemaRef(doc, schema) {
|
|
|
354
382
|
const resolved = doc.components?.schemas?.[name];
|
|
355
383
|
return resolved ?? schema;
|
|
356
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
|
+
}
|
|
357
486
|
|
|
358
487
|
class CompositeSchemaConverter {
|
|
359
488
|
converters;
|
|
@@ -382,37 +511,38 @@ class OpenAPIGenerator {
|
|
|
382
511
|
/**
|
|
383
512
|
* Generates OpenAPI specifications from oRPC routers/contracts.
|
|
384
513
|
*
|
|
385
|
-
* @see {@link https://orpc.
|
|
514
|
+
* @see {@link https://orpc.dev/docs/openapi/openapi-specification OpenAPI Specification Docs}
|
|
386
515
|
*/
|
|
387
|
-
async generate(router,
|
|
388
|
-
const
|
|
516
|
+
async generate(router, { customErrorResponseBodySchema, commonSchemas, filter: baseFilter, exclude, ...baseDoc } = {}) {
|
|
517
|
+
const filter = baseFilter ?? (({ contract, path }) => {
|
|
518
|
+
return !(exclude?.(contract, path) ?? false);
|
|
519
|
+
});
|
|
389
520
|
const doc = {
|
|
390
|
-
...clone(
|
|
391
|
-
info:
|
|
392
|
-
openapi: "3.1.1"
|
|
393
|
-
exclude: void 0,
|
|
394
|
-
commonSchemas: void 0
|
|
521
|
+
...clone(baseDoc),
|
|
522
|
+
info: baseDoc.info ?? { title: "API Reference", version: "0.0.0" },
|
|
523
|
+
openapi: "3.1.1"
|
|
395
524
|
};
|
|
396
|
-
const { baseSchemaConvertOptions, undefinedErrorJsonSchema } = await this.#resolveCommonSchemas(doc,
|
|
525
|
+
const { baseSchemaConvertOptions, undefinedErrorJsonSchema } = await this.#resolveCommonSchemas(doc, commonSchemas);
|
|
397
526
|
const contracts = [];
|
|
398
|
-
await resolveContractProcedures({ path: [], router }, (
|
|
399
|
-
if (!
|
|
400
|
-
|
|
527
|
+
await resolveContractProcedures({ path: [], router }, (traverseOptions) => {
|
|
528
|
+
if (!value(filter, traverseOptions)) {
|
|
529
|
+
return;
|
|
401
530
|
}
|
|
531
|
+
contracts.push(traverseOptions);
|
|
402
532
|
});
|
|
403
533
|
const errors = [];
|
|
404
534
|
for (const { contract, path } of contracts) {
|
|
405
|
-
const
|
|
535
|
+
const stringPath = path.join(".");
|
|
406
536
|
try {
|
|
407
537
|
const def = contract["~orpc"];
|
|
408
538
|
const method = toOpenAPIMethod(fallbackContractConfig("defaultMethod", def.route.method));
|
|
409
539
|
const httpPath = toOpenAPIPath(def.route.path ?? toHttpPath(path));
|
|
410
540
|
let operationObjectRef;
|
|
411
|
-
if (def.route.spec !== void 0) {
|
|
541
|
+
if (def.route.spec !== void 0 && typeof def.route.spec !== "function") {
|
|
412
542
|
operationObjectRef = def.route.spec;
|
|
413
543
|
} else {
|
|
414
544
|
operationObjectRef = {
|
|
415
|
-
operationId,
|
|
545
|
+
operationId: def.route.operationId ?? stringPath,
|
|
416
546
|
summary: def.route.summary,
|
|
417
547
|
description: def.route.description,
|
|
418
548
|
deprecated: def.route.deprecated,
|
|
@@ -420,7 +550,10 @@ class OpenAPIGenerator {
|
|
|
420
550
|
};
|
|
421
551
|
await this.#request(doc, operationObjectRef, def, baseSchemaConvertOptions);
|
|
422
552
|
await this.#successResponse(doc, operationObjectRef, def, baseSchemaConvertOptions);
|
|
423
|
-
await this.#errorResponse(operationObjectRef, def, baseSchemaConvertOptions, undefinedErrorJsonSchema);
|
|
553
|
+
await this.#errorResponse(operationObjectRef, def, baseSchemaConvertOptions, undefinedErrorJsonSchema, customErrorResponseBodySchema);
|
|
554
|
+
}
|
|
555
|
+
if (typeof def.route.spec === "function") {
|
|
556
|
+
operationObjectRef = def.route.spec(operationObjectRef);
|
|
424
557
|
}
|
|
425
558
|
doc.paths ??= {};
|
|
426
559
|
doc.paths[httpPath] ??= {};
|
|
@@ -430,7 +563,7 @@ class OpenAPIGenerator {
|
|
|
430
563
|
throw e;
|
|
431
564
|
}
|
|
432
565
|
errors.push(
|
|
433
|
-
`[OpenAPIGenerator] Error occurred while generating OpenAPI for procedure at path: ${
|
|
566
|
+
`[OpenAPIGenerator] Error occurred while generating OpenAPI for procedure at path: ${stringPath}
|
|
434
567
|
${e.message}`
|
|
435
568
|
);
|
|
436
569
|
}
|
|
@@ -530,13 +663,16 @@ ${errors.join("\n\n")}`
|
|
|
530
663
|
def.inputSchema,
|
|
531
664
|
{
|
|
532
665
|
...baseSchemaConvertOptions,
|
|
533
|
-
strategy: "input"
|
|
534
|
-
minStructureDepthForRef: dynamicParams?.length || inputStructure === "detailed" ? 1 : 0
|
|
666
|
+
strategy: "input"
|
|
535
667
|
}
|
|
536
668
|
);
|
|
669
|
+
let omitResponseBody = false;
|
|
537
670
|
if (isAnySchema(schema) && !dynamicParams?.length) {
|
|
538
671
|
return;
|
|
539
672
|
}
|
|
673
|
+
if (inputStructure === "detailed" || inputStructure === "compact" && (dynamicParams?.length || method === "GET")) {
|
|
674
|
+
schema = simplifyComposedObjectJsonSchemasAndRefs(schema, doc);
|
|
675
|
+
}
|
|
540
676
|
if (inputStructure === "compact") {
|
|
541
677
|
if (dynamicParams?.length) {
|
|
542
678
|
const error2 = new OpenAPIGeneratorError(
|
|
@@ -548,6 +684,7 @@ ${errors.join("\n\n")}`
|
|
|
548
684
|
const [paramsSchema, rest] = separateObjectSchema(schema, dynamicParams);
|
|
549
685
|
schema = rest;
|
|
550
686
|
required = rest.required ? rest.required.length !== 0 : false;
|
|
687
|
+
omitResponseBody = !required && !rest.properties;
|
|
551
688
|
if (!checkParamsSchema(paramsSchema, dynamicParams)) {
|
|
552
689
|
throw error2;
|
|
553
690
|
}
|
|
@@ -562,7 +699,7 @@ ${errors.join("\n\n")}`
|
|
|
562
699
|
}
|
|
563
700
|
ref.parameters ??= [];
|
|
564
701
|
ref.parameters.push(...toOpenAPIParameters(schema, "query"));
|
|
565
|
-
} else {
|
|
702
|
+
} else if (!omitResponseBody) {
|
|
566
703
|
ref.requestBody = {
|
|
567
704
|
required,
|
|
568
705
|
content: toOpenAPIContent(schema)
|
|
@@ -576,7 +713,7 @@ ${errors.join("\n\n")}`
|
|
|
576
713
|
if (!isObjectSchema(schema)) {
|
|
577
714
|
throw error;
|
|
578
715
|
}
|
|
579
|
-
const resolvedParamSchema = schema.properties?.params !== void 0 ?
|
|
716
|
+
const resolvedParamSchema = schema.properties?.params !== void 0 ? simplifyComposedObjectJsonSchemasAndRefs(schema.properties.params, doc) : void 0;
|
|
580
717
|
if (dynamicParams?.length && (resolvedParamSchema === void 0 || !isObjectSchema(resolvedParamSchema) || !checkParamsSchema(resolvedParamSchema, dynamicParams))) {
|
|
581
718
|
throw new OpenAPIGeneratorError(
|
|
582
719
|
'When input structure is "detailed" and path has dynamic params, the "params" schema must be an object with all dynamic params as required.'
|
|
@@ -585,7 +722,7 @@ ${errors.join("\n\n")}`
|
|
|
585
722
|
for (const from of ["params", "query", "headers"]) {
|
|
586
723
|
const fromSchema = schema.properties?.[from];
|
|
587
724
|
if (fromSchema !== void 0) {
|
|
588
|
-
const resolvedSchema =
|
|
725
|
+
const resolvedSchema = simplifyComposedObjectJsonSchemasAndRefs(fromSchema, doc);
|
|
589
726
|
if (!isObjectSchema(resolvedSchema)) {
|
|
590
727
|
throw error;
|
|
591
728
|
}
|
|
@@ -646,13 +783,14 @@ ${errors.join("\n\n")}`
|
|
|
646
783
|
|
|
647
784
|
But got: ${stringifyJSON(item)}
|
|
648
785
|
`);
|
|
649
|
-
|
|
786
|
+
const simplifiedItem = simplifyComposedObjectJsonSchemasAndRefs(item, doc);
|
|
787
|
+
if (!isObjectSchema(simplifiedItem)) {
|
|
650
788
|
throw error;
|
|
651
789
|
}
|
|
652
790
|
let schemaStatus;
|
|
653
791
|
let schemaDescription;
|
|
654
|
-
if (
|
|
655
|
-
const statusSchema = resolveOpenAPIJsonSchemaRef(doc,
|
|
792
|
+
if (simplifiedItem.properties?.status !== void 0) {
|
|
793
|
+
const statusSchema = resolveOpenAPIJsonSchemaRef(doc, simplifiedItem.properties.status);
|
|
656
794
|
if (typeof statusSchema !== "object" || statusSchema.const === void 0 || typeof statusSchema.const !== "number" || !Number.isInteger(statusSchema.const) || isORPCErrorStatus(statusSchema.const)) {
|
|
657
795
|
throw error;
|
|
658
796
|
}
|
|
@@ -672,8 +810,8 @@ ${errors.join("\n\n")}`
|
|
|
672
810
|
ref.responses[itemStatus] = {
|
|
673
811
|
description: itemDescription
|
|
674
812
|
};
|
|
675
|
-
if (
|
|
676
|
-
const headersSchema =
|
|
813
|
+
if (simplifiedItem.properties?.headers !== void 0) {
|
|
814
|
+
const headersSchema = simplifyComposedObjectJsonSchemasAndRefs(simplifiedItem.properties.headers, doc);
|
|
677
815
|
if (!isObjectSchema(headersSchema)) {
|
|
678
816
|
throw error;
|
|
679
817
|
}
|
|
@@ -683,50 +821,52 @@ ${errors.join("\n\n")}`
|
|
|
683
821
|
ref.responses[itemStatus].headers ??= {};
|
|
684
822
|
ref.responses[itemStatus].headers[key] = {
|
|
685
823
|
schema: toOpenAPISchema(headerSchema),
|
|
686
|
-
required:
|
|
824
|
+
required: simplifiedItem.required?.includes("headers") && headersSchema.required?.includes(key)
|
|
687
825
|
};
|
|
688
826
|
}
|
|
689
827
|
}
|
|
690
828
|
}
|
|
691
|
-
if (
|
|
829
|
+
if (simplifiedItem.properties?.body !== void 0) {
|
|
692
830
|
ref.responses[itemStatus].content = toOpenAPIContent(
|
|
693
|
-
applySchemaOptionality(
|
|
831
|
+
applySchemaOptionality(simplifiedItem.required?.includes("body") ?? false, simplifiedItem.properties.body)
|
|
694
832
|
);
|
|
695
833
|
}
|
|
696
834
|
}
|
|
697
835
|
}
|
|
698
|
-
async #errorResponse(ref, def, baseSchemaConvertOptions, undefinedErrorSchema) {
|
|
836
|
+
async #errorResponse(ref, def, baseSchemaConvertOptions, undefinedErrorSchema, customErrorResponseBodySchema) {
|
|
699
837
|
const errorMap = def.errorMap;
|
|
700
|
-
const
|
|
838
|
+
const errorResponsesByStatus = {};
|
|
701
839
|
for (const code in errorMap) {
|
|
702
840
|
const config = errorMap[code];
|
|
703
841
|
if (!config) {
|
|
704
842
|
continue;
|
|
705
843
|
}
|
|
706
844
|
const status = fallbackORPCErrorStatus(code, config.status);
|
|
707
|
-
const
|
|
845
|
+
const defaultMessage = fallbackORPCErrorMessage(code, config.message);
|
|
846
|
+
errorResponsesByStatus[status] ??= { status, definedErrorDefinitions: [], errorSchemaVariants: [] };
|
|
708
847
|
const [dataRequired, dataSchema] = await this.converter.convert(config.data, { ...baseSchemaConvertOptions, strategy: "output" });
|
|
709
|
-
|
|
710
|
-
|
|
848
|
+
errorResponsesByStatus[status].definedErrorDefinitions.push([code, defaultMessage, dataRequired, dataSchema]);
|
|
849
|
+
errorResponsesByStatus[status].errorSchemaVariants.push({
|
|
711
850
|
type: "object",
|
|
712
851
|
properties: {
|
|
713
852
|
defined: { const: true },
|
|
714
853
|
code: { const: code },
|
|
715
854
|
status: { const: status },
|
|
716
|
-
message: { type: "string", default:
|
|
855
|
+
message: { type: "string", default: defaultMessage },
|
|
717
856
|
data: dataSchema
|
|
718
857
|
},
|
|
719
858
|
required: dataRequired ? ["defined", "code", "status", "message", "data"] : ["defined", "code", "status", "message"]
|
|
720
859
|
});
|
|
721
860
|
}
|
|
722
861
|
ref.responses ??= {};
|
|
723
|
-
for (const
|
|
724
|
-
const
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
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 ?? {
|
|
728
868
|
oneOf: [
|
|
729
|
-
...
|
|
869
|
+
...errorResponse.errorSchemaVariants,
|
|
730
870
|
undefinedErrorSchema
|
|
731
871
|
]
|
|
732
872
|
})
|
|
@@ -735,4 +875,4 @@ ${errors.join("\n\n")}`
|
|
|
735
875
|
}
|
|
736
876
|
}
|
|
737
877
|
|
|
738
|
-
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.de2bec7",
|
|
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
|
-
"json-schema-typed": "^8.0.
|
|
53
|
-
"rou3": "^0.7.
|
|
54
|
-
"@orpc/client": "0.0.0-next.
|
|
55
|
-
"@orpc/contract": "0.0.0-next.
|
|
56
|
-
"@orpc/
|
|
57
|
-
"@orpc/server": "0.0.0-next.
|
|
58
|
-
"@orpc/shared": "0.0.0-next.
|
|
59
|
-
"@orpc/
|
|
57
|
+
"json-schema-typed": "^8.0.2",
|
|
58
|
+
"rou3": "^0.7.12",
|
|
59
|
+
"@orpc/client": "0.0.0-next.de2bec7",
|
|
60
|
+
"@orpc/contract": "0.0.0-next.de2bec7",
|
|
61
|
+
"@orpc/interop": "0.0.0-next.de2bec7",
|
|
62
|
+
"@orpc/server": "0.0.0-next.de2bec7",
|
|
63
|
+
"@orpc/shared": "0.0.0-next.de2bec7",
|
|
64
|
+
"@orpc/openapi-client": "0.0.0-next.de2bec7",
|
|
65
|
+
"@orpc/standard-server": "0.0.0-next.de2bec7"
|
|
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,12 +0,0 @@
|
|
|
1
|
-
import { StandardOpenAPIJsonSerializerOptions } from '@orpc/openapi-client/standard';
|
|
2
|
-
import { Context, Router } from '@orpc/server';
|
|
3
|
-
import { StandardHandlerOptions, StandardHandler } from '@orpc/server/standard';
|
|
4
|
-
|
|
5
|
-
interface StandardOpenAPIHandlerOptions<T extends Context> extends StandardHandlerOptions<T>, StandardOpenAPIJsonSerializerOptions {
|
|
6
|
-
}
|
|
7
|
-
declare class StandardOpenAPIHandler<T extends Context> extends StandardHandler<T> {
|
|
8
|
-
constructor(router: Router<any, T>, options: NoInfer<StandardOpenAPIHandlerOptions<T>>);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export { StandardOpenAPIHandler as a };
|
|
12
|
-
export type { StandardOpenAPIHandlerOptions as S };
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { StandardOpenAPIJsonSerializerOptions } from '@orpc/openapi-client/standard';
|
|
2
|
-
import { Context, Router } from '@orpc/server';
|
|
3
|
-
import { StandardHandlerOptions, StandardHandler } from '@orpc/server/standard';
|
|
4
|
-
|
|
5
|
-
interface StandardOpenAPIHandlerOptions<T extends Context> extends StandardHandlerOptions<T>, StandardOpenAPIJsonSerializerOptions {
|
|
6
|
-
}
|
|
7
|
-
declare class StandardOpenAPIHandler<T extends Context> extends StandardHandler<T> {
|
|
8
|
-
constructor(router: Router<any, T>, options: NoInfer<StandardOpenAPIHandlerOptions<T>>);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export { StandardOpenAPIHandler as a };
|
|
12
|
-
export type { StandardOpenAPIHandlerOptions as S };
|