@orpc/openapi 0.0.0-next.d929a5e → 0.0.0-next.d92c7aa
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 +7 -2
- package/dist/adapters/aws-lambda/index.d.mts +6 -3
- package/dist/adapters/aws-lambda/index.d.ts +6 -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 +9 -3
- package/dist/adapters/fetch/index.d.ts +9 -3
- package/dist/adapters/fetch/index.mjs +1 -1
- package/dist/adapters/node/index.d.mts +9 -3
- package/dist/adapters/node/index.d.ts +9 -3
- package/dist/adapters/node/index.mjs +1 -1
- 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 +5 -4
- package/dist/index.d.ts +5 -4
- package/dist/index.mjs +3 -3
- package/dist/plugins/index.d.mts +19 -4
- package/dist/plugins/index.d.ts +19 -4
- package/dist/plugins/index.mjs +59 -19
- package/dist/shared/openapi.BfNjg7j9.d.mts +120 -0
- package/dist/shared/openapi.BfNjg7j9.d.ts +120 -0
- package/dist/shared/{openapi.C_UtQ8Us.mjs → openapi.DIt-Z9W1.mjs} +19 -8
- package/dist/shared/{openapi.DaYgbD_w.mjs → openapi.DrTcell5.mjs} +164 -66
- package/dist/shared/openapi.DwaweYRb.d.mts +54 -0
- package/dist/shared/openapi.DwaweYRb.d.ts +54 -0
- package/package.json +16 -10
- 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,8 +3,8 @@ import { toHttpPath } from '@orpc/client/standard';
|
|
|
3
3
|
import { fallbackContractConfig, getEventIteratorSchemaDetails } from '@orpc/contract';
|
|
4
4
|
import { standardizeHTTPPath, StandardOpenAPIJsonSerializer, getDynamicParams } from '@orpc/openapi-client/standard';
|
|
5
5
|
import { isProcedure, resolveContractProcedures } from '@orpc/server';
|
|
6
|
-
import { isObject, stringifyJSON, findDeepMatches, toArray, clone } from '@orpc/shared';
|
|
7
|
-
import { TypeName } from 'json-schema-typed/draft-2020-12';
|
|
6
|
+
import { isObject, stringifyJSON, findDeepMatches, toArray, clone, value } from '@orpc/shared';
|
|
7
|
+
import { TypeName } from '@orpc/interop/json-schema-typed/draft-2020-12';
|
|
8
8
|
|
|
9
9
|
const OPERATION_EXTENDER_SYMBOL = Symbol("ORPC_OPERATION_EXTENDER");
|
|
10
10
|
function customOpenAPIOperation(o, extend) {
|
|
@@ -114,13 +114,18 @@ function isAnySchema(schema) {
|
|
|
114
114
|
return false;
|
|
115
115
|
}
|
|
116
116
|
function separateObjectSchema(schema, separatedProperties) {
|
|
117
|
-
if (Object.keys(schema).some(
|
|
117
|
+
if (Object.keys(schema).some(
|
|
118
|
+
(k) => !["type", "properties", "required", "additionalProperties"].includes(k) && LOGIC_KEYWORDS.includes(k) && schema[k] !== void 0
|
|
119
|
+
)) {
|
|
118
120
|
return [{ type: "object" }, schema];
|
|
119
121
|
}
|
|
120
122
|
const matched = { ...schema };
|
|
121
123
|
const rest = { ...schema };
|
|
122
|
-
matched.properties =
|
|
123
|
-
|
|
124
|
+
matched.properties = separatedProperties.reduce((acc, key) => {
|
|
125
|
+
const keySchema = schema.properties?.[key] ?? schema.additionalProperties;
|
|
126
|
+
if (keySchema !== void 0) {
|
|
127
|
+
acc[key] = keySchema;
|
|
128
|
+
}
|
|
124
129
|
return acc;
|
|
125
130
|
}, {});
|
|
126
131
|
matched.required = schema.required?.filter((key) => separatedProperties.includes(key));
|
|
@@ -345,6 +350,15 @@ function checkParamsSchema(schema, params) {
|
|
|
345
350
|
function toOpenAPISchema(schema) {
|
|
346
351
|
return schema === true ? {} : schema === false ? { not: {} } : schema;
|
|
347
352
|
}
|
|
353
|
+
const OPENAPI_JSON_SCHEMA_REF_PREFIX = "#/components/schemas/";
|
|
354
|
+
function resolveOpenAPIJsonSchemaRef(doc, schema) {
|
|
355
|
+
if (typeof schema !== "object" || !schema.$ref?.startsWith(OPENAPI_JSON_SCHEMA_REF_PREFIX)) {
|
|
356
|
+
return schema;
|
|
357
|
+
}
|
|
358
|
+
const name = schema.$ref.slice(OPENAPI_JSON_SCHEMA_REF_PREFIX.length);
|
|
359
|
+
const resolved = doc.components?.schemas?.[name];
|
|
360
|
+
return resolved ?? schema;
|
|
361
|
+
}
|
|
348
362
|
|
|
349
363
|
class CompositeSchemaConverter {
|
|
350
364
|
converters;
|
|
@@ -375,41 +389,47 @@ class OpenAPIGenerator {
|
|
|
375
389
|
*
|
|
376
390
|
* @see {@link https://orpc.unnoq.com/docs/openapi/openapi-specification OpenAPI Specification Docs}
|
|
377
391
|
*/
|
|
378
|
-
async generate(router,
|
|
379
|
-
const
|
|
392
|
+
async generate(router, { customErrorResponseBodySchema, commonSchemas, filter: baseFilter, exclude, ...baseDoc } = {}) {
|
|
393
|
+
const filter = baseFilter ?? (({ contract, path }) => {
|
|
394
|
+
return !(exclude?.(contract, path) ?? false);
|
|
395
|
+
});
|
|
380
396
|
const doc = {
|
|
381
|
-
...clone(
|
|
382
|
-
info:
|
|
383
|
-
openapi: "3.1.1"
|
|
384
|
-
exclude: void 0
|
|
397
|
+
...clone(baseDoc),
|
|
398
|
+
info: baseDoc.info ?? { title: "API Reference", version: "0.0.0" },
|
|
399
|
+
openapi: "3.1.1"
|
|
385
400
|
};
|
|
401
|
+
const { baseSchemaConvertOptions, undefinedErrorJsonSchema } = await this.#resolveCommonSchemas(doc, commonSchemas);
|
|
386
402
|
const contracts = [];
|
|
387
|
-
await resolveContractProcedures({ path: [], router }, (
|
|
388
|
-
if (!
|
|
389
|
-
|
|
403
|
+
await resolveContractProcedures({ path: [], router }, (traverseOptions) => {
|
|
404
|
+
if (!value(filter, traverseOptions)) {
|
|
405
|
+
return;
|
|
390
406
|
}
|
|
407
|
+
contracts.push(traverseOptions);
|
|
391
408
|
});
|
|
392
409
|
const errors = [];
|
|
393
410
|
for (const { contract, path } of contracts) {
|
|
394
|
-
const
|
|
411
|
+
const stringPath = path.join(".");
|
|
395
412
|
try {
|
|
396
413
|
const def = contract["~orpc"];
|
|
397
414
|
const method = toOpenAPIMethod(fallbackContractConfig("defaultMethod", def.route.method));
|
|
398
415
|
const httpPath = toOpenAPIPath(def.route.path ?? toHttpPath(path));
|
|
399
416
|
let operationObjectRef;
|
|
400
|
-
if (def.route.spec !== void 0) {
|
|
417
|
+
if (def.route.spec !== void 0 && typeof def.route.spec !== "function") {
|
|
401
418
|
operationObjectRef = def.route.spec;
|
|
402
419
|
} else {
|
|
403
420
|
operationObjectRef = {
|
|
404
|
-
operationId,
|
|
421
|
+
operationId: def.route.operationId ?? stringPath,
|
|
405
422
|
summary: def.route.summary,
|
|
406
423
|
description: def.route.description,
|
|
407
424
|
deprecated: def.route.deprecated,
|
|
408
425
|
tags: def.route.tags?.map((tag) => tag)
|
|
409
426
|
};
|
|
410
|
-
await this.#request(operationObjectRef, def);
|
|
411
|
-
await this.#successResponse(operationObjectRef, def);
|
|
412
|
-
await this.#errorResponse(operationObjectRef, def);
|
|
427
|
+
await this.#request(doc, operationObjectRef, def, baseSchemaConvertOptions);
|
|
428
|
+
await this.#successResponse(doc, operationObjectRef, def, baseSchemaConvertOptions);
|
|
429
|
+
await this.#errorResponse(operationObjectRef, def, baseSchemaConvertOptions, undefinedErrorJsonSchema, customErrorResponseBodySchema);
|
|
430
|
+
}
|
|
431
|
+
if (typeof def.route.spec === "function") {
|
|
432
|
+
operationObjectRef = def.route.spec(operationObjectRef);
|
|
413
433
|
}
|
|
414
434
|
doc.paths ??= {};
|
|
415
435
|
doc.paths[httpPath] ??= {};
|
|
@@ -419,7 +439,7 @@ class OpenAPIGenerator {
|
|
|
419
439
|
throw e;
|
|
420
440
|
}
|
|
421
441
|
errors.push(
|
|
422
|
-
`[OpenAPIGenerator] Error occurred while generating OpenAPI for procedure at path: ${
|
|
442
|
+
`[OpenAPIGenerator] Error occurred while generating OpenAPI for procedure at path: ${stringPath}
|
|
423
443
|
${e.message}`
|
|
424
444
|
);
|
|
425
445
|
}
|
|
@@ -433,22 +453,96 @@ ${errors.join("\n\n")}`
|
|
|
433
453
|
}
|
|
434
454
|
return this.serializer.serialize(doc)[0];
|
|
435
455
|
}
|
|
436
|
-
async #
|
|
456
|
+
async #resolveCommonSchemas(doc, commonSchemas) {
|
|
457
|
+
let undefinedErrorJsonSchema = {
|
|
458
|
+
type: "object",
|
|
459
|
+
properties: {
|
|
460
|
+
defined: { const: false },
|
|
461
|
+
code: { type: "string" },
|
|
462
|
+
status: { type: "number" },
|
|
463
|
+
message: { type: "string" },
|
|
464
|
+
data: {}
|
|
465
|
+
},
|
|
466
|
+
required: ["defined", "code", "status", "message"]
|
|
467
|
+
};
|
|
468
|
+
const baseSchemaConvertOptions = {};
|
|
469
|
+
if (commonSchemas) {
|
|
470
|
+
baseSchemaConvertOptions.components = [];
|
|
471
|
+
for (const key in commonSchemas) {
|
|
472
|
+
const options = commonSchemas[key];
|
|
473
|
+
if (options.schema === void 0) {
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
const { schema, strategy = "input" } = options;
|
|
477
|
+
const [required, json] = await this.converter.convert(schema, { strategy });
|
|
478
|
+
const allowedStrategies = [strategy];
|
|
479
|
+
if (strategy === "input") {
|
|
480
|
+
const [outputRequired, outputJson] = await this.converter.convert(schema, { strategy: "output" });
|
|
481
|
+
if (outputRequired === required && stringifyJSON(outputJson) === stringifyJSON(json)) {
|
|
482
|
+
allowedStrategies.push("output");
|
|
483
|
+
}
|
|
484
|
+
} else if (strategy === "output") {
|
|
485
|
+
const [inputRequired, inputJson] = await this.converter.convert(schema, { strategy: "input" });
|
|
486
|
+
if (inputRequired === required && stringifyJSON(inputJson) === stringifyJSON(json)) {
|
|
487
|
+
allowedStrategies.push("input");
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
baseSchemaConvertOptions.components.push({
|
|
491
|
+
schema,
|
|
492
|
+
required,
|
|
493
|
+
ref: `#/components/schemas/${key}`,
|
|
494
|
+
allowedStrategies
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
doc.components ??= {};
|
|
498
|
+
doc.components.schemas ??= {};
|
|
499
|
+
for (const key in commonSchemas) {
|
|
500
|
+
const options = commonSchemas[key];
|
|
501
|
+
if (options.schema === void 0) {
|
|
502
|
+
if (options.error === "UndefinedError") {
|
|
503
|
+
doc.components.schemas[key] = toOpenAPISchema(undefinedErrorJsonSchema);
|
|
504
|
+
undefinedErrorJsonSchema = { $ref: `#/components/schemas/${key}` };
|
|
505
|
+
}
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
const { schema, strategy = "input" } = options;
|
|
509
|
+
const [, json] = await this.converter.convert(
|
|
510
|
+
schema,
|
|
511
|
+
{
|
|
512
|
+
...baseSchemaConvertOptions,
|
|
513
|
+
strategy,
|
|
514
|
+
minStructureDepthForRef: 1
|
|
515
|
+
// not allow use $ref for root schemas
|
|
516
|
+
}
|
|
517
|
+
);
|
|
518
|
+
doc.components.schemas[key] = toOpenAPISchema(json);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return { baseSchemaConvertOptions, undefinedErrorJsonSchema };
|
|
522
|
+
}
|
|
523
|
+
async #request(doc, ref, def, baseSchemaConvertOptions) {
|
|
437
524
|
const method = fallbackContractConfig("defaultMethod", def.route.method);
|
|
438
525
|
const details = getEventIteratorSchemaDetails(def.inputSchema);
|
|
439
526
|
if (details) {
|
|
440
527
|
ref.requestBody = {
|
|
441
528
|
required: true,
|
|
442
529
|
content: toOpenAPIEventIteratorContent(
|
|
443
|
-
await this.converter.convert(details.yields, { strategy: "input" }),
|
|
444
|
-
await this.converter.convert(details.returns, { strategy: "input" })
|
|
530
|
+
await this.converter.convert(details.yields, { ...baseSchemaConvertOptions, strategy: "input" }),
|
|
531
|
+
await this.converter.convert(details.returns, { ...baseSchemaConvertOptions, strategy: "input" })
|
|
445
532
|
)
|
|
446
533
|
};
|
|
447
534
|
return;
|
|
448
535
|
}
|
|
449
536
|
const dynamicParams = getDynamicParams(def.route.path)?.map((v) => v.name);
|
|
450
537
|
const inputStructure = fallbackContractConfig("defaultInputStructure", def.route.inputStructure);
|
|
451
|
-
let [required, schema] = await this.converter.convert(
|
|
538
|
+
let [required, schema] = await this.converter.convert(
|
|
539
|
+
def.inputSchema,
|
|
540
|
+
{
|
|
541
|
+
...baseSchemaConvertOptions,
|
|
542
|
+
strategy: "input",
|
|
543
|
+
minStructureDepthForRef: dynamicParams?.length || inputStructure === "detailed" ? 1 : 0
|
|
544
|
+
}
|
|
545
|
+
);
|
|
452
546
|
if (isAnySchema(schema) && !dynamicParams?.length) {
|
|
453
547
|
return;
|
|
454
548
|
}
|
|
@@ -470,13 +564,14 @@ ${errors.join("\n\n")}`
|
|
|
470
564
|
ref.parameters.push(...toOpenAPIParameters(paramsSchema, "path"));
|
|
471
565
|
}
|
|
472
566
|
if (method === "GET") {
|
|
473
|
-
|
|
567
|
+
const resolvedSchema = resolveOpenAPIJsonSchemaRef(doc, schema);
|
|
568
|
+
if (!isObjectSchema(resolvedSchema)) {
|
|
474
569
|
throw new OpenAPIGeneratorError(
|
|
475
570
|
'When method is "GET", input schema must satisfy: object | any | unknown'
|
|
476
571
|
);
|
|
477
572
|
}
|
|
478
573
|
ref.parameters ??= [];
|
|
479
|
-
ref.parameters.push(...toOpenAPIParameters(
|
|
574
|
+
ref.parameters.push(...toOpenAPIParameters(resolvedSchema, "query"));
|
|
480
575
|
} else {
|
|
481
576
|
ref.requestBody = {
|
|
482
577
|
required,
|
|
@@ -491,7 +586,8 @@ ${errors.join("\n\n")}`
|
|
|
491
586
|
if (!isObjectSchema(schema)) {
|
|
492
587
|
throw error;
|
|
493
588
|
}
|
|
494
|
-
|
|
589
|
+
const resolvedParamSchema = schema.properties?.params !== void 0 ? resolveOpenAPIJsonSchemaRef(doc, schema.properties.params) : void 0;
|
|
590
|
+
if (dynamicParams?.length && (resolvedParamSchema === void 0 || !isObjectSchema(resolvedParamSchema) || !checkParamsSchema(resolvedParamSchema, dynamicParams))) {
|
|
495
591
|
throw new OpenAPIGeneratorError(
|
|
496
592
|
'When input structure is "detailed" and path has dynamic params, the "params" schema must be an object with all dynamic params as required.'
|
|
497
593
|
);
|
|
@@ -499,12 +595,13 @@ ${errors.join("\n\n")}`
|
|
|
499
595
|
for (const from of ["params", "query", "headers"]) {
|
|
500
596
|
const fromSchema = schema.properties?.[from];
|
|
501
597
|
if (fromSchema !== void 0) {
|
|
502
|
-
|
|
598
|
+
const resolvedSchema = resolveOpenAPIJsonSchemaRef(doc, fromSchema);
|
|
599
|
+
if (!isObjectSchema(resolvedSchema)) {
|
|
503
600
|
throw error;
|
|
504
601
|
}
|
|
505
602
|
const parameterIn = from === "params" ? "path" : from === "headers" ? "header" : "query";
|
|
506
603
|
ref.parameters ??= [];
|
|
507
|
-
ref.parameters.push(...toOpenAPIParameters(
|
|
604
|
+
ref.parameters.push(...toOpenAPIParameters(resolvedSchema, parameterIn));
|
|
508
605
|
}
|
|
509
606
|
}
|
|
510
607
|
if (schema.properties?.body !== void 0) {
|
|
@@ -514,7 +611,7 @@ ${errors.join("\n\n")}`
|
|
|
514
611
|
};
|
|
515
612
|
}
|
|
516
613
|
}
|
|
517
|
-
async #successResponse(ref, def) {
|
|
614
|
+
async #successResponse(doc, ref, def, baseSchemaConvertOptions) {
|
|
518
615
|
const outputSchema = def.outputSchema;
|
|
519
616
|
const status = fallbackContractConfig("defaultSuccessStatus", def.route.successStatus);
|
|
520
617
|
const description = fallbackContractConfig("defaultSuccessDescription", def.route?.successDescription);
|
|
@@ -525,13 +622,20 @@ ${errors.join("\n\n")}`
|
|
|
525
622
|
ref.responses[status] = {
|
|
526
623
|
description,
|
|
527
624
|
content: toOpenAPIEventIteratorContent(
|
|
528
|
-
await this.converter.convert(eventIteratorSchemaDetails.yields, { strategy: "output" }),
|
|
529
|
-
await this.converter.convert(eventIteratorSchemaDetails.returns, { strategy: "output" })
|
|
625
|
+
await this.converter.convert(eventIteratorSchemaDetails.yields, { ...baseSchemaConvertOptions, strategy: "output" }),
|
|
626
|
+
await this.converter.convert(eventIteratorSchemaDetails.returns, { ...baseSchemaConvertOptions, strategy: "output" })
|
|
530
627
|
)
|
|
531
628
|
};
|
|
532
629
|
return;
|
|
533
630
|
}
|
|
534
|
-
const [required, json] = await this.converter.convert(
|
|
631
|
+
const [required, json] = await this.converter.convert(
|
|
632
|
+
outputSchema,
|
|
633
|
+
{
|
|
634
|
+
...baseSchemaConvertOptions,
|
|
635
|
+
strategy: "output",
|
|
636
|
+
minStructureDepthForRef: outputStructure === "detailed" ? 1 : 0
|
|
637
|
+
}
|
|
638
|
+
);
|
|
535
639
|
if (outputStructure === "compact") {
|
|
536
640
|
ref.responses ??= {};
|
|
537
641
|
ref.responses[status] = {
|
|
@@ -558,11 +662,12 @@ ${errors.join("\n\n")}`
|
|
|
558
662
|
let schemaStatus;
|
|
559
663
|
let schemaDescription;
|
|
560
664
|
if (item.properties?.status !== void 0) {
|
|
561
|
-
|
|
665
|
+
const statusSchema = resolveOpenAPIJsonSchemaRef(doc, item.properties.status);
|
|
666
|
+
if (typeof statusSchema !== "object" || statusSchema.const === void 0 || typeof statusSchema.const !== "number" || !Number.isInteger(statusSchema.const) || isORPCErrorStatus(statusSchema.const)) {
|
|
562
667
|
throw error;
|
|
563
668
|
}
|
|
564
|
-
schemaStatus =
|
|
565
|
-
schemaDescription =
|
|
669
|
+
schemaStatus = statusSchema.const;
|
|
670
|
+
schemaDescription = statusSchema.description;
|
|
566
671
|
}
|
|
567
672
|
const itemStatus = schemaStatus ?? status;
|
|
568
673
|
const itemDescription = schemaDescription ?? description;
|
|
@@ -578,16 +683,17 @@ ${errors.join("\n\n")}`
|
|
|
578
683
|
description: itemDescription
|
|
579
684
|
};
|
|
580
685
|
if (item.properties?.headers !== void 0) {
|
|
581
|
-
|
|
686
|
+
const headersSchema = resolveOpenAPIJsonSchemaRef(doc, item.properties.headers);
|
|
687
|
+
if (!isObjectSchema(headersSchema)) {
|
|
582
688
|
throw error;
|
|
583
689
|
}
|
|
584
|
-
for (const key in
|
|
585
|
-
const headerSchema =
|
|
690
|
+
for (const key in headersSchema.properties) {
|
|
691
|
+
const headerSchema = headersSchema.properties[key];
|
|
586
692
|
if (headerSchema !== void 0) {
|
|
587
693
|
ref.responses[itemStatus].headers ??= {};
|
|
588
694
|
ref.responses[itemStatus].headers[key] = {
|
|
589
695
|
schema: toOpenAPISchema(headerSchema),
|
|
590
|
-
required: item.
|
|
696
|
+
required: item.required?.includes("headers") && headersSchema.required?.includes(key)
|
|
591
697
|
};
|
|
592
698
|
}
|
|
593
699
|
}
|
|
@@ -599,49 +705,41 @@ ${errors.join("\n\n")}`
|
|
|
599
705
|
}
|
|
600
706
|
}
|
|
601
707
|
}
|
|
602
|
-
async #errorResponse(ref, def) {
|
|
708
|
+
async #errorResponse(ref, def, baseSchemaConvertOptions, undefinedErrorSchema, customErrorResponseBodySchema) {
|
|
603
709
|
const errorMap = def.errorMap;
|
|
604
|
-
const
|
|
710
|
+
const errorResponsesByStatus = {};
|
|
605
711
|
for (const code in errorMap) {
|
|
606
712
|
const config = errorMap[code];
|
|
607
713
|
if (!config) {
|
|
608
714
|
continue;
|
|
609
715
|
}
|
|
610
716
|
const status = fallbackORPCErrorStatus(code, config.status);
|
|
611
|
-
const
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
717
|
+
const defaultMessage = fallbackORPCErrorMessage(code, config.message);
|
|
718
|
+
errorResponsesByStatus[status] ??= { status, definedErrorDefinitions: [], errorSchemaVariants: [] };
|
|
719
|
+
const [dataRequired, dataSchema] = await this.converter.convert(config.data, { ...baseSchemaConvertOptions, strategy: "output" });
|
|
720
|
+
errorResponsesByStatus[status].definedErrorDefinitions.push([code, defaultMessage, dataRequired, dataSchema]);
|
|
721
|
+
errorResponsesByStatus[status].errorSchemaVariants.push({
|
|
615
722
|
type: "object",
|
|
616
723
|
properties: {
|
|
617
724
|
defined: { const: true },
|
|
618
725
|
code: { const: code },
|
|
619
726
|
status: { const: status },
|
|
620
|
-
message: { type: "string", default:
|
|
727
|
+
message: { type: "string", default: defaultMessage },
|
|
621
728
|
data: dataSchema
|
|
622
729
|
},
|
|
623
730
|
required: dataRequired ? ["defined", "code", "status", "message", "data"] : ["defined", "code", "status", "message"]
|
|
624
731
|
});
|
|
625
732
|
}
|
|
626
733
|
ref.responses ??= {};
|
|
627
|
-
for (const
|
|
628
|
-
const
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
734
|
+
for (const statusString in errorResponsesByStatus) {
|
|
735
|
+
const errorResponse = errorResponsesByStatus[statusString];
|
|
736
|
+
const customBodySchema = value(customErrorResponseBodySchema, errorResponse.definedErrorDefinitions, errorResponse.status);
|
|
737
|
+
ref.responses[statusString] = {
|
|
738
|
+
description: statusString,
|
|
739
|
+
content: toOpenAPIContent(customBodySchema ?? {
|
|
632
740
|
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
|
-
}
|
|
741
|
+
...errorResponse.errorSchemaVariants,
|
|
742
|
+
undefinedErrorSchema
|
|
645
743
|
]
|
|
646
744
|
})
|
|
647
745
|
};
|
|
@@ -649,4 +747,4 @@ ${errors.join("\n\n")}`
|
|
|
649
747
|
}
|
|
650
748
|
}
|
|
651
749
|
|
|
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, filterSchemaBranches as m, applySchemaOptionality as n, expandUnionSchema as o, expandArrayableSchema as p, isPrimitiveSchema as q, separateObjectSchema as s, toOpenAPIPath as t };
|
|
750
|
+
export { CompositeSchemaConverter as C, LOGIC_KEYWORDS as L, OpenAPIGenerator as O, applyCustomOpenAPIOperation as a, toOpenAPIMethod as b, customOpenAPIOperation as c, toOpenAPIContent as d, toOpenAPIEventIteratorContent as e, toOpenAPIParameters as f, getCustomOpenAPIOperation as g, checkParamsSchema as h, toOpenAPISchema as i, isFileSchema as j, isObjectSchema as k, isAnySchema as l, filterSchemaBranches as m, applySchemaOptionality as n, expandUnionSchema as o, expandArrayableSchema as p, isPrimitiveSchema as q, resolveOpenAPIJsonSchemaRef as r, separateObjectSchema as s, toOpenAPIPath as t };
|
|
@@ -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,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orpc/openapi",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.0-next.
|
|
4
|
+
"version": "0.0.0-next.d92c7aa",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://orpc.unnoq.com",
|
|
7
7
|
"repository": {
|
|
@@ -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,18 @@
|
|
|
49
54
|
"dist"
|
|
50
55
|
],
|
|
51
56
|
"dependencies": {
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"@orpc/
|
|
55
|
-
"@orpc/
|
|
56
|
-
"@orpc/
|
|
57
|
-
"@orpc/
|
|
58
|
-
"@orpc/
|
|
59
|
-
"@orpc/
|
|
57
|
+
"rou3": "^0.7.9",
|
|
58
|
+
"@orpc/client": "0.0.0-next.d92c7aa",
|
|
59
|
+
"@orpc/openapi-client": "0.0.0-next.d92c7aa",
|
|
60
|
+
"@orpc/interop": "0.0.0-next.d92c7aa",
|
|
61
|
+
"@orpc/server": "0.0.0-next.d92c7aa",
|
|
62
|
+
"@orpc/standard-server": "0.0.0-next.d92c7aa",
|
|
63
|
+
"@orpc/shared": "0.0.0-next.d92c7aa",
|
|
64
|
+
"@orpc/contract": "0.0.0-next.d92c7aa"
|
|
60
65
|
},
|
|
61
66
|
"devDependencies": {
|
|
62
|
-
"
|
|
67
|
+
"fastify": "^5.6.1",
|
|
68
|
+
"zod": "^4.1.12"
|
|
63
69
|
},
|
|
64
70
|
"scripts": {
|
|
65
71
|
"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 };
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { AnySchema, OpenAPI, AnyContractProcedure, AnyContractRouter } from '@orpc/contract';
|
|
2
|
-
import { StandardOpenAPIJsonSerializerOptions } from '@orpc/openapi-client/standard';
|
|
3
|
-
import { AnyProcedure, AnyRouter } from '@orpc/server';
|
|
4
|
-
import { Promisable } from '@orpc/shared';
|
|
5
|
-
import { JSONSchema } from 'json-schema-typed/draft-2020-12';
|
|
6
|
-
|
|
7
|
-
interface SchemaConvertOptions {
|
|
8
|
-
strategy: 'input' | 'output';
|
|
9
|
-
}
|
|
10
|
-
interface SchemaConverter {
|
|
11
|
-
convert(schema: AnySchema | undefined, options: SchemaConvertOptions): Promisable<[required: boolean, jsonSchema: JSONSchema]>;
|
|
12
|
-
}
|
|
13
|
-
interface ConditionalSchemaConverter extends SchemaConverter {
|
|
14
|
-
condition(schema: AnySchema | undefined, options: SchemaConvertOptions): Promisable<boolean>;
|
|
15
|
-
}
|
|
16
|
-
declare class CompositeSchemaConverter implements SchemaConverter {
|
|
17
|
-
private readonly converters;
|
|
18
|
-
constructor(converters: ConditionalSchemaConverter[]);
|
|
19
|
-
convert(schema: AnySchema | undefined, options: SchemaConvertOptions): Promise<[required: boolean, jsonSchema: JSONSchema]>;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
interface OpenAPIGeneratorOptions extends StandardOpenAPIJsonSerializerOptions {
|
|
23
|
-
schemaConverters?: ConditionalSchemaConverter[];
|
|
24
|
-
}
|
|
25
|
-
interface OpenAPIGeneratorGenerateOptions extends Partial<Omit<OpenAPI.Document, 'openapi'>> {
|
|
26
|
-
/**
|
|
27
|
-
* Exclude procedures from the OpenAPI specification.
|
|
28
|
-
*
|
|
29
|
-
* @default () => false
|
|
30
|
-
*/
|
|
31
|
-
exclude?: (procedure: AnyProcedure | AnyContractProcedure, path: readonly string[]) => boolean;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* The generator that converts oRPC routers/contracts to OpenAPI specifications.
|
|
35
|
-
*
|
|
36
|
-
* @see {@link https://orpc.unnoq.com/docs/openapi/openapi-specification OpenAPI Specification Docs}
|
|
37
|
-
*/
|
|
38
|
-
declare class OpenAPIGenerator {
|
|
39
|
-
#private;
|
|
40
|
-
private readonly serializer;
|
|
41
|
-
private readonly converter;
|
|
42
|
-
constructor(options?: OpenAPIGeneratorOptions);
|
|
43
|
-
/**
|
|
44
|
-
* Generates OpenAPI specifications from oRPC routers/contracts.
|
|
45
|
-
*
|
|
46
|
-
* @see {@link https://orpc.unnoq.com/docs/openapi/openapi-specification OpenAPI Specification Docs}
|
|
47
|
-
*/
|
|
48
|
-
generate(router: AnyContractRouter | AnyRouter, options?: OpenAPIGeneratorGenerateOptions): Promise<OpenAPI.Document>;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export { OpenAPIGenerator as b, CompositeSchemaConverter as d };
|
|
52
|
-
export type { ConditionalSchemaConverter as C, OpenAPIGeneratorOptions as O, SchemaConvertOptions as S, OpenAPIGeneratorGenerateOptions as a, SchemaConverter as c };
|