@orpc/openapi 0.0.0-next.fd6982a → 0.0.0-next.fe5c63f
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 +5 -7
- package/dist/adapters/aws-lambda/index.d.mts +5 -3
- package/dist/adapters/aws-lambda/index.d.ts +5 -3
- package/dist/adapters/aws-lambda/index.mjs +3 -3
- package/dist/adapters/fetch/index.d.mts +3 -1
- package/dist/adapters/fetch/index.d.ts +3 -1
- package/dist/adapters/fetch/index.mjs +1 -1
- package/dist/adapters/node/index.d.mts +3 -1
- package/dist/adapters/node/index.d.ts +3 -1
- package/dist/adapters/node/index.mjs +1 -1
- package/dist/adapters/standard/index.d.mts +5 -11
- package/dist/adapters/standard/index.d.ts +5 -11
- package/dist/adapters/standard/index.mjs +1 -1
- package/dist/index.d.mts +17 -12
- package/dist/index.d.ts +17 -12
- package/dist/index.mjs +3 -3
- package/dist/plugins/index.d.mts +3 -4
- package/dist/plugins/index.d.ts +3 -4
- package/dist/plugins/index.mjs +1 -1
- package/dist/shared/{openapi.C_UtQ8Us.mjs → openapi.BkBlt1Qf.mjs} +12 -4
- package/dist/shared/{openapi.PDTdnRIU.mjs → openapi.BtoY8ZFF.mjs} +199 -52
- package/dist/shared/openapi.CQmjvnb0.d.mts +31 -0
- package/dist/shared/openapi.CQmjvnb0.d.ts +31 -0
- package/dist/shared/openapi.DPAN3GVs.d.mts +108 -0
- package/dist/shared/openapi.DPAN3GVs.d.ts +108 -0
- package/package.json +9 -10
- package/dist/shared/openapi.CwdCLgSU.d.mts +0 -53
- package/dist/shared/openapi.CwdCLgSU.d.ts +0 -53
- package/dist/shared/openapi.D3j94c9n.d.mts +0 -12
- package/dist/shared/openapi.D3j94c9n.d.ts +0 -12
|
@@ -3,8 +3,8 @@ import { toHttpPath } from '@orpc/client/standard';
|
|
|
3
3
|
import { fallbackContractConfig, getEventIteratorSchemaDetails } from '@orpc/contract';
|
|
4
4
|
import { standardizeHTTPPath, StandardOpenAPIJsonSerializer, getDynamicParams } from '@orpc/openapi-client/standard';
|
|
5
5
|
import { isProcedure, resolveContractProcedures } from '@orpc/server';
|
|
6
|
-
import { isObject, findDeepMatches, toArray, clone,
|
|
7
|
-
import 'json-schema-typed/draft-2020-12';
|
|
6
|
+
import { isObject, stringifyJSON, findDeepMatches, toArray, clone, value } from '@orpc/shared';
|
|
7
|
+
import { TypeName } from 'json-schema-typed/draft-2020-12';
|
|
8
8
|
|
|
9
9
|
const OPERATION_EXTENDER_SYMBOL = Symbol("ORPC_OPERATION_EXTENDER");
|
|
10
10
|
function customOpenAPIOperation(o, extend) {
|
|
@@ -196,6 +196,45 @@ function expandUnionSchema(schema) {
|
|
|
196
196
|
}
|
|
197
197
|
return [schema];
|
|
198
198
|
}
|
|
199
|
+
function expandArrayableSchema(schema) {
|
|
200
|
+
const schemas = expandUnionSchema(schema);
|
|
201
|
+
if (schemas.length !== 2) {
|
|
202
|
+
return void 0;
|
|
203
|
+
}
|
|
204
|
+
const arraySchema = schemas.find(
|
|
205
|
+
(s) => typeof s === "object" && s.type === "array" && Object.keys(s).filter((k) => LOGIC_KEYWORDS.includes(k)).every((k) => k === "type" || k === "items")
|
|
206
|
+
);
|
|
207
|
+
if (arraySchema === void 0) {
|
|
208
|
+
return void 0;
|
|
209
|
+
}
|
|
210
|
+
const items1 = arraySchema.items;
|
|
211
|
+
const items2 = schemas.find((s) => s !== arraySchema);
|
|
212
|
+
if (stringifyJSON(items1) !== stringifyJSON(items2)) {
|
|
213
|
+
return void 0;
|
|
214
|
+
}
|
|
215
|
+
return [items2, arraySchema];
|
|
216
|
+
}
|
|
217
|
+
const PRIMITIVE_SCHEMA_TYPES = /* @__PURE__ */ new Set([
|
|
218
|
+
TypeName.String,
|
|
219
|
+
TypeName.Number,
|
|
220
|
+
TypeName.Integer,
|
|
221
|
+
TypeName.Boolean,
|
|
222
|
+
TypeName.Null
|
|
223
|
+
]);
|
|
224
|
+
function isPrimitiveSchema(schema) {
|
|
225
|
+
return expandUnionSchema(schema).every((s) => {
|
|
226
|
+
if (typeof s === "boolean") {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
if (typeof s.type === "string" && PRIMITIVE_SCHEMA_TYPES.has(s.type)) {
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
if (s.const !== void 0) {
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
return false;
|
|
236
|
+
});
|
|
237
|
+
}
|
|
199
238
|
|
|
200
239
|
function toOpenAPIPath(path) {
|
|
201
240
|
return standardizeHTTPPath(path).replace(/\/\{\+([^}]+)\}/g, "/{$1}");
|
|
@@ -268,13 +307,26 @@ function toOpenAPIParameters(schema, parameterIn) {
|
|
|
268
307
|
const parameters = [];
|
|
269
308
|
for (const key in schema.properties) {
|
|
270
309
|
const keySchema = schema.properties[key];
|
|
310
|
+
let isDeepObjectStyle = true;
|
|
311
|
+
if (parameterIn !== "query") {
|
|
312
|
+
isDeepObjectStyle = false;
|
|
313
|
+
} else if (isPrimitiveSchema(keySchema)) {
|
|
314
|
+
isDeepObjectStyle = false;
|
|
315
|
+
} else {
|
|
316
|
+
const [item] = expandArrayableSchema(keySchema) ?? [];
|
|
317
|
+
if (item !== void 0 && isPrimitiveSchema(item)) {
|
|
318
|
+
isDeepObjectStyle = false;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
271
321
|
parameters.push({
|
|
272
322
|
name: key,
|
|
273
323
|
in: parameterIn,
|
|
274
324
|
required: schema.required?.includes(key),
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
325
|
+
schema: toOpenAPISchema(keySchema),
|
|
326
|
+
style: isDeepObjectStyle ? "deepObject" : void 0,
|
|
327
|
+
explode: isDeepObjectStyle ? true : void 0,
|
|
328
|
+
allowEmptyValue: parameterIn === "query" ? true : void 0,
|
|
329
|
+
allowReserved: parameterIn === "query" ? true : void 0
|
|
278
330
|
});
|
|
279
331
|
}
|
|
280
332
|
return parameters;
|
|
@@ -293,6 +345,15 @@ function checkParamsSchema(schema, params) {
|
|
|
293
345
|
function toOpenAPISchema(schema) {
|
|
294
346
|
return schema === true ? {} : schema === false ? { not: {} } : schema;
|
|
295
347
|
}
|
|
348
|
+
const OPENAPI_JSON_SCHEMA_REF_PREFIX = "#/components/schemas/";
|
|
349
|
+
function resolveOpenAPIJsonSchemaRef(doc, schema) {
|
|
350
|
+
if (typeof schema !== "object" || !schema.$ref?.startsWith(OPENAPI_JSON_SCHEMA_REF_PREFIX)) {
|
|
351
|
+
return schema;
|
|
352
|
+
}
|
|
353
|
+
const name = schema.$ref.slice(OPENAPI_JSON_SCHEMA_REF_PREFIX.length);
|
|
354
|
+
const resolved = doc.components?.schemas?.[name];
|
|
355
|
+
return resolved ?? schema;
|
|
356
|
+
}
|
|
296
357
|
|
|
297
358
|
class CompositeSchemaConverter {
|
|
298
359
|
converters;
|
|
@@ -324,18 +385,24 @@ class OpenAPIGenerator {
|
|
|
324
385
|
* @see {@link https://orpc.unnoq.com/docs/openapi/openapi-specification OpenAPI Specification Docs}
|
|
325
386
|
*/
|
|
326
387
|
async generate(router, options = {}) {
|
|
327
|
-
const
|
|
388
|
+
const filter = options.filter ?? (({ contract, path }) => {
|
|
389
|
+
return !(options.exclude?.(contract, path) ?? false);
|
|
390
|
+
});
|
|
328
391
|
const doc = {
|
|
329
392
|
...clone(options),
|
|
330
393
|
info: options.info ?? { title: "API Reference", version: "0.0.0" },
|
|
331
394
|
openapi: "3.1.1",
|
|
332
|
-
exclude: void 0
|
|
395
|
+
exclude: void 0,
|
|
396
|
+
filter: void 0,
|
|
397
|
+
commonSchemas: void 0
|
|
333
398
|
};
|
|
399
|
+
const { baseSchemaConvertOptions, undefinedErrorJsonSchema } = await this.#resolveCommonSchemas(doc, options.commonSchemas);
|
|
334
400
|
const contracts = [];
|
|
335
|
-
await resolveContractProcedures({ path: [], router }, (
|
|
336
|
-
if (!
|
|
337
|
-
|
|
401
|
+
await resolveContractProcedures({ path: [], router }, (traverseOptions) => {
|
|
402
|
+
if (!value(filter, traverseOptions)) {
|
|
403
|
+
return;
|
|
338
404
|
}
|
|
405
|
+
contracts.push(traverseOptions);
|
|
339
406
|
});
|
|
340
407
|
const errors = [];
|
|
341
408
|
for (const { contract, path } of contracts) {
|
|
@@ -344,16 +411,21 @@ class OpenAPIGenerator {
|
|
|
344
411
|
const def = contract["~orpc"];
|
|
345
412
|
const method = toOpenAPIMethod(fallbackContractConfig("defaultMethod", def.route.method));
|
|
346
413
|
const httpPath = toOpenAPIPath(def.route.path ?? toHttpPath(path));
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
414
|
+
let operationObjectRef;
|
|
415
|
+
if (def.route.spec !== void 0) {
|
|
416
|
+
operationObjectRef = def.route.spec;
|
|
417
|
+
} else {
|
|
418
|
+
operationObjectRef = {
|
|
419
|
+
operationId,
|
|
420
|
+
summary: def.route.summary,
|
|
421
|
+
description: def.route.description,
|
|
422
|
+
deprecated: def.route.deprecated,
|
|
423
|
+
tags: def.route.tags?.map((tag) => tag)
|
|
424
|
+
};
|
|
425
|
+
await this.#request(doc, operationObjectRef, def, baseSchemaConvertOptions);
|
|
426
|
+
await this.#successResponse(doc, operationObjectRef, def, baseSchemaConvertOptions);
|
|
427
|
+
await this.#errorResponse(operationObjectRef, def, baseSchemaConvertOptions, undefinedErrorJsonSchema);
|
|
428
|
+
}
|
|
357
429
|
doc.paths ??= {};
|
|
358
430
|
doc.paths[httpPath] ??= {};
|
|
359
431
|
doc.paths[httpPath][method] = applyCustomOpenAPIOperation(operationObjectRef, contract);
|
|
@@ -376,22 +448,96 @@ ${errors.join("\n\n")}`
|
|
|
376
448
|
}
|
|
377
449
|
return this.serializer.serialize(doc)[0];
|
|
378
450
|
}
|
|
379
|
-
async #
|
|
451
|
+
async #resolveCommonSchemas(doc, commonSchemas) {
|
|
452
|
+
let undefinedErrorJsonSchema = {
|
|
453
|
+
type: "object",
|
|
454
|
+
properties: {
|
|
455
|
+
defined: { const: false },
|
|
456
|
+
code: { type: "string" },
|
|
457
|
+
status: { type: "number" },
|
|
458
|
+
message: { type: "string" },
|
|
459
|
+
data: {}
|
|
460
|
+
},
|
|
461
|
+
required: ["defined", "code", "status", "message"]
|
|
462
|
+
};
|
|
463
|
+
const baseSchemaConvertOptions = {};
|
|
464
|
+
if (commonSchemas) {
|
|
465
|
+
baseSchemaConvertOptions.components = [];
|
|
466
|
+
for (const key in commonSchemas) {
|
|
467
|
+
const options = commonSchemas[key];
|
|
468
|
+
if (options.schema === void 0) {
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
const { schema, strategy = "input" } = options;
|
|
472
|
+
const [required, json] = await this.converter.convert(schema, { strategy });
|
|
473
|
+
const allowedStrategies = [strategy];
|
|
474
|
+
if (strategy === "input") {
|
|
475
|
+
const [outputRequired, outputJson] = await this.converter.convert(schema, { strategy: "output" });
|
|
476
|
+
if (outputRequired === required && stringifyJSON(outputJson) === stringifyJSON(json)) {
|
|
477
|
+
allowedStrategies.push("output");
|
|
478
|
+
}
|
|
479
|
+
} else if (strategy === "output") {
|
|
480
|
+
const [inputRequired, inputJson] = await this.converter.convert(schema, { strategy: "input" });
|
|
481
|
+
if (inputRequired === required && stringifyJSON(inputJson) === stringifyJSON(json)) {
|
|
482
|
+
allowedStrategies.push("input");
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
baseSchemaConvertOptions.components.push({
|
|
486
|
+
schema,
|
|
487
|
+
required,
|
|
488
|
+
ref: `#/components/schemas/${key}`,
|
|
489
|
+
allowedStrategies
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
doc.components ??= {};
|
|
493
|
+
doc.components.schemas ??= {};
|
|
494
|
+
for (const key in commonSchemas) {
|
|
495
|
+
const options = commonSchemas[key];
|
|
496
|
+
if (options.schema === void 0) {
|
|
497
|
+
if (options.error === "UndefinedError") {
|
|
498
|
+
doc.components.schemas[key] = toOpenAPISchema(undefinedErrorJsonSchema);
|
|
499
|
+
undefinedErrorJsonSchema = { $ref: `#/components/schemas/${key}` };
|
|
500
|
+
}
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
const { schema, strategy = "input" } = options;
|
|
504
|
+
const [, json] = await this.converter.convert(
|
|
505
|
+
schema,
|
|
506
|
+
{
|
|
507
|
+
...baseSchemaConvertOptions,
|
|
508
|
+
strategy,
|
|
509
|
+
minStructureDepthForRef: 1
|
|
510
|
+
// not allow use $ref for root schemas
|
|
511
|
+
}
|
|
512
|
+
);
|
|
513
|
+
doc.components.schemas[key] = toOpenAPISchema(json);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
return { baseSchemaConvertOptions, undefinedErrorJsonSchema };
|
|
517
|
+
}
|
|
518
|
+
async #request(doc, ref, def, baseSchemaConvertOptions) {
|
|
380
519
|
const method = fallbackContractConfig("defaultMethod", def.route.method);
|
|
381
520
|
const details = getEventIteratorSchemaDetails(def.inputSchema);
|
|
382
521
|
if (details) {
|
|
383
522
|
ref.requestBody = {
|
|
384
523
|
required: true,
|
|
385
524
|
content: toOpenAPIEventIteratorContent(
|
|
386
|
-
await this.converter.convert(details.yields, { strategy: "input" }),
|
|
387
|
-
await this.converter.convert(details.returns, { strategy: "input" })
|
|
525
|
+
await this.converter.convert(details.yields, { ...baseSchemaConvertOptions, strategy: "input" }),
|
|
526
|
+
await this.converter.convert(details.returns, { ...baseSchemaConvertOptions, strategy: "input" })
|
|
388
527
|
)
|
|
389
528
|
};
|
|
390
529
|
return;
|
|
391
530
|
}
|
|
392
531
|
const dynamicParams = getDynamicParams(def.route.path)?.map((v) => v.name);
|
|
393
532
|
const inputStructure = fallbackContractConfig("defaultInputStructure", def.route.inputStructure);
|
|
394
|
-
let [required, schema] = await this.converter.convert(
|
|
533
|
+
let [required, schema] = await this.converter.convert(
|
|
534
|
+
def.inputSchema,
|
|
535
|
+
{
|
|
536
|
+
...baseSchemaConvertOptions,
|
|
537
|
+
strategy: "input",
|
|
538
|
+
minStructureDepthForRef: dynamicParams?.length || inputStructure === "detailed" ? 1 : 0
|
|
539
|
+
}
|
|
540
|
+
);
|
|
395
541
|
if (isAnySchema(schema) && !dynamicParams?.length) {
|
|
396
542
|
return;
|
|
397
543
|
}
|
|
@@ -434,7 +580,8 @@ ${errors.join("\n\n")}`
|
|
|
434
580
|
if (!isObjectSchema(schema)) {
|
|
435
581
|
throw error;
|
|
436
582
|
}
|
|
437
|
-
|
|
583
|
+
const resolvedParamSchema = schema.properties?.params !== void 0 ? resolveOpenAPIJsonSchemaRef(doc, schema.properties.params) : void 0;
|
|
584
|
+
if (dynamicParams?.length && (resolvedParamSchema === void 0 || !isObjectSchema(resolvedParamSchema) || !checkParamsSchema(resolvedParamSchema, dynamicParams))) {
|
|
438
585
|
throw new OpenAPIGeneratorError(
|
|
439
586
|
'When input structure is "detailed" and path has dynamic params, the "params" schema must be an object with all dynamic params as required.'
|
|
440
587
|
);
|
|
@@ -442,12 +589,13 @@ ${errors.join("\n\n")}`
|
|
|
442
589
|
for (const from of ["params", "query", "headers"]) {
|
|
443
590
|
const fromSchema = schema.properties?.[from];
|
|
444
591
|
if (fromSchema !== void 0) {
|
|
445
|
-
|
|
592
|
+
const resolvedSchema = resolveOpenAPIJsonSchemaRef(doc, fromSchema);
|
|
593
|
+
if (!isObjectSchema(resolvedSchema)) {
|
|
446
594
|
throw error;
|
|
447
595
|
}
|
|
448
596
|
const parameterIn = from === "params" ? "path" : from === "headers" ? "header" : "query";
|
|
449
597
|
ref.parameters ??= [];
|
|
450
|
-
ref.parameters.push(...toOpenAPIParameters(
|
|
598
|
+
ref.parameters.push(...toOpenAPIParameters(resolvedSchema, parameterIn));
|
|
451
599
|
}
|
|
452
600
|
}
|
|
453
601
|
if (schema.properties?.body !== void 0) {
|
|
@@ -457,7 +605,7 @@ ${errors.join("\n\n")}`
|
|
|
457
605
|
};
|
|
458
606
|
}
|
|
459
607
|
}
|
|
460
|
-
async #successResponse(ref, def) {
|
|
608
|
+
async #successResponse(doc, ref, def, baseSchemaConvertOptions) {
|
|
461
609
|
const outputSchema = def.outputSchema;
|
|
462
610
|
const status = fallbackContractConfig("defaultSuccessStatus", def.route.successStatus);
|
|
463
611
|
const description = fallbackContractConfig("defaultSuccessDescription", def.route?.successDescription);
|
|
@@ -468,13 +616,20 @@ ${errors.join("\n\n")}`
|
|
|
468
616
|
ref.responses[status] = {
|
|
469
617
|
description,
|
|
470
618
|
content: toOpenAPIEventIteratorContent(
|
|
471
|
-
await this.converter.convert(eventIteratorSchemaDetails.yields, { strategy: "output" }),
|
|
472
|
-
await this.converter.convert(eventIteratorSchemaDetails.returns, { strategy: "output" })
|
|
619
|
+
await this.converter.convert(eventIteratorSchemaDetails.yields, { ...baseSchemaConvertOptions, strategy: "output" }),
|
|
620
|
+
await this.converter.convert(eventIteratorSchemaDetails.returns, { ...baseSchemaConvertOptions, strategy: "output" })
|
|
473
621
|
)
|
|
474
622
|
};
|
|
475
623
|
return;
|
|
476
624
|
}
|
|
477
|
-
const [required, json] = await this.converter.convert(
|
|
625
|
+
const [required, json] = await this.converter.convert(
|
|
626
|
+
outputSchema,
|
|
627
|
+
{
|
|
628
|
+
...baseSchemaConvertOptions,
|
|
629
|
+
strategy: "output",
|
|
630
|
+
minStructureDepthForRef: outputStructure === "detailed" ? 1 : 0
|
|
631
|
+
}
|
|
632
|
+
);
|
|
478
633
|
if (outputStructure === "compact") {
|
|
479
634
|
ref.responses ??= {};
|
|
480
635
|
ref.responses[status] = {
|
|
@@ -501,11 +656,12 @@ ${errors.join("\n\n")}`
|
|
|
501
656
|
let schemaStatus;
|
|
502
657
|
let schemaDescription;
|
|
503
658
|
if (item.properties?.status !== void 0) {
|
|
504
|
-
|
|
659
|
+
const statusSchema = resolveOpenAPIJsonSchemaRef(doc, item.properties.status);
|
|
660
|
+
if (typeof statusSchema !== "object" || statusSchema.const === void 0 || typeof statusSchema.const !== "number" || !Number.isInteger(statusSchema.const) || isORPCErrorStatus(statusSchema.const)) {
|
|
505
661
|
throw error;
|
|
506
662
|
}
|
|
507
|
-
schemaStatus =
|
|
508
|
-
schemaDescription =
|
|
663
|
+
schemaStatus = statusSchema.const;
|
|
664
|
+
schemaDescription = statusSchema.description;
|
|
509
665
|
}
|
|
510
666
|
const itemStatus = schemaStatus ?? status;
|
|
511
667
|
const itemDescription = schemaDescription ?? description;
|
|
@@ -521,16 +677,17 @@ ${errors.join("\n\n")}`
|
|
|
521
677
|
description: itemDescription
|
|
522
678
|
};
|
|
523
679
|
if (item.properties?.headers !== void 0) {
|
|
524
|
-
|
|
680
|
+
const headersSchema = resolveOpenAPIJsonSchemaRef(doc, item.properties.headers);
|
|
681
|
+
if (!isObjectSchema(headersSchema)) {
|
|
525
682
|
throw error;
|
|
526
683
|
}
|
|
527
|
-
for (const key in
|
|
528
|
-
const headerSchema =
|
|
684
|
+
for (const key in headersSchema.properties) {
|
|
685
|
+
const headerSchema = headersSchema.properties[key];
|
|
529
686
|
if (headerSchema !== void 0) {
|
|
530
687
|
ref.responses[itemStatus].headers ??= {};
|
|
531
688
|
ref.responses[itemStatus].headers[key] = {
|
|
532
689
|
schema: toOpenAPISchema(headerSchema),
|
|
533
|
-
required: item.
|
|
690
|
+
required: item.required?.includes("headers") && headersSchema.required?.includes(key)
|
|
534
691
|
};
|
|
535
692
|
}
|
|
536
693
|
}
|
|
@@ -542,7 +699,7 @@ ${errors.join("\n\n")}`
|
|
|
542
699
|
}
|
|
543
700
|
}
|
|
544
701
|
}
|
|
545
|
-
async #errorResponse(ref, def) {
|
|
702
|
+
async #errorResponse(ref, def, baseSchemaConvertOptions, undefinedErrorSchema) {
|
|
546
703
|
const errorMap = def.errorMap;
|
|
547
704
|
const errors = {};
|
|
548
705
|
for (const code in errorMap) {
|
|
@@ -552,7 +709,7 @@ ${errors.join("\n\n")}`
|
|
|
552
709
|
}
|
|
553
710
|
const status = fallbackORPCErrorStatus(code, config.status);
|
|
554
711
|
const message = fallbackORPCErrorMessage(code, config.message);
|
|
555
|
-
const [dataRequired, dataSchema] = await this.converter.convert(config.data, { strategy: "output" });
|
|
712
|
+
const [dataRequired, dataSchema] = await this.converter.convert(config.data, { ...baseSchemaConvertOptions, strategy: "output" });
|
|
556
713
|
errors[status] ??= [];
|
|
557
714
|
errors[status].push({
|
|
558
715
|
type: "object",
|
|
@@ -574,17 +731,7 @@ ${errors.join("\n\n")}`
|
|
|
574
731
|
content: toOpenAPIContent({
|
|
575
732
|
oneOf: [
|
|
576
733
|
...schemas,
|
|
577
|
-
|
|
578
|
-
type: "object",
|
|
579
|
-
properties: {
|
|
580
|
-
defined: { const: false },
|
|
581
|
-
code: { type: "string" },
|
|
582
|
-
status: { type: "number" },
|
|
583
|
-
message: { type: "string" },
|
|
584
|
-
data: {}
|
|
585
|
-
},
|
|
586
|
-
required: ["defined", "code", "status", "message"]
|
|
587
|
-
}
|
|
734
|
+
undefinedErrorSchema
|
|
588
735
|
]
|
|
589
736
|
})
|
|
590
737
|
};
|
|
@@ -592,4 +739,4 @@ ${errors.join("\n\n")}`
|
|
|
592
739
|
}
|
|
593
740
|
}
|
|
594
741
|
|
|
595
|
-
export { CompositeSchemaConverter as C, LOGIC_KEYWORDS as L, OpenAPIGenerator as O, applyCustomOpenAPIOperation as a, toOpenAPIMethod as b, customOpenAPIOperation as c, toOpenAPIContent as d, toOpenAPIEventIteratorContent as e, toOpenAPIParameters as f, getCustomOpenAPIOperation as g, checkParamsSchema as h, toOpenAPISchema as i, isFileSchema as j, isObjectSchema as k, isAnySchema as l, filterSchemaBranches as m, applySchemaOptionality as n, expandUnionSchema as o, separateObjectSchema as s, toOpenAPIPath as t };
|
|
742
|
+
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,31 @@
|
|
|
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 };
|
|
@@ -0,0 +1,31 @@
|
|
|
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 };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { AnySchema, OpenAPI, AnyContractProcedure, AnyContractRouter } from '@orpc/contract';
|
|
2
|
+
import { StandardOpenAPIJsonSerializerOptions } from '@orpc/openapi-client/standard';
|
|
3
|
+
import { AnyProcedure, TraverseContractProcedureCallbackOptions, AnyRouter } from '@orpc/server';
|
|
4
|
+
import { Promisable, Value } from '@orpc/shared';
|
|
5
|
+
import { JSONSchema } from 'json-schema-typed/draft-2020-12';
|
|
6
|
+
|
|
7
|
+
interface SchemaConverterComponent {
|
|
8
|
+
allowedStrategies: readonly SchemaConvertOptions['strategy'][];
|
|
9
|
+
schema: AnySchema;
|
|
10
|
+
required: boolean;
|
|
11
|
+
ref: string;
|
|
12
|
+
}
|
|
13
|
+
interface SchemaConvertOptions {
|
|
14
|
+
strategy: 'input' | 'output';
|
|
15
|
+
/**
|
|
16
|
+
* Common components should use `$ref` to represent themselves if matched.
|
|
17
|
+
*/
|
|
18
|
+
components?: readonly SchemaConverterComponent[];
|
|
19
|
+
/**
|
|
20
|
+
* Minimum schema structure depth required before using `$ref` for components.
|
|
21
|
+
*
|
|
22
|
+
* For example, if set to 2, `$ref` will only be used for schemas nested at depth 2 or greater.
|
|
23
|
+
*
|
|
24
|
+
* @default 0 - No depth limit;
|
|
25
|
+
*/
|
|
26
|
+
minStructureDepthForRef?: number;
|
|
27
|
+
}
|
|
28
|
+
interface SchemaConverter {
|
|
29
|
+
convert(schema: AnySchema | undefined, options: SchemaConvertOptions): Promisable<[required: boolean, jsonSchema: JSONSchema]>;
|
|
30
|
+
}
|
|
31
|
+
interface ConditionalSchemaConverter extends SchemaConverter {
|
|
32
|
+
condition(schema: AnySchema | undefined, options: SchemaConvertOptions): Promisable<boolean>;
|
|
33
|
+
}
|
|
34
|
+
declare class CompositeSchemaConverter implements SchemaConverter {
|
|
35
|
+
private readonly converters;
|
|
36
|
+
constructor(converters: readonly ConditionalSchemaConverter[]);
|
|
37
|
+
convert(schema: AnySchema | undefined, options: SchemaConvertOptions): Promise<[required: boolean, jsonSchema: JSONSchema]>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface OpenAPIGeneratorOptions extends StandardOpenAPIJsonSerializerOptions {
|
|
41
|
+
schemaConverters?: ConditionalSchemaConverter[];
|
|
42
|
+
}
|
|
43
|
+
interface OpenAPIGeneratorGenerateOptions extends Partial<Omit<OpenAPI.Document, 'openapi'>> {
|
|
44
|
+
/**
|
|
45
|
+
* Exclude procedures from the OpenAPI specification.
|
|
46
|
+
*
|
|
47
|
+
* @deprecated Use `filter` option instead.
|
|
48
|
+
* @default () => false
|
|
49
|
+
*/
|
|
50
|
+
exclude?: (procedure: AnyProcedure | AnyContractProcedure, path: readonly string[]) => boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Filter procedures. Return `false` to exclude a procedure from the OpenAPI specification.
|
|
53
|
+
*
|
|
54
|
+
* @default true
|
|
55
|
+
*/
|
|
56
|
+
filter?: Value<boolean, [options: TraverseContractProcedureCallbackOptions]>;
|
|
57
|
+
/**
|
|
58
|
+
* Common schemas to be used for $ref resolution.
|
|
59
|
+
*/
|
|
60
|
+
commonSchemas?: Record<string, {
|
|
61
|
+
/**
|
|
62
|
+
* Determines which schema definition to use when input and output schemas differ.
|
|
63
|
+
* This is needed because some schemas transform data differently between input and output,
|
|
64
|
+
* making it impossible to use a single $ref for both cases.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```ts
|
|
68
|
+
* // This schema transforms a string input into a number output
|
|
69
|
+
* const Schema = z.string()
|
|
70
|
+
* .transform(v => Number(v))
|
|
71
|
+
* .pipe(z.number())
|
|
72
|
+
*
|
|
73
|
+
* // Input schema: { type: 'string' }
|
|
74
|
+
* // Output schema: { type: 'number' }
|
|
75
|
+
* ```
|
|
76
|
+
*
|
|
77
|
+
* When schemas differ between input and output, you must explicitly choose
|
|
78
|
+
* which version to use for the OpenAPI specification.
|
|
79
|
+
*
|
|
80
|
+
* @default 'input' - Uses the input schema definition by default
|
|
81
|
+
*/
|
|
82
|
+
strategy?: SchemaConvertOptions['strategy'];
|
|
83
|
+
schema: AnySchema;
|
|
84
|
+
} | {
|
|
85
|
+
error: 'UndefinedError';
|
|
86
|
+
schema?: never;
|
|
87
|
+
}>;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The generator that converts oRPC routers/contracts to OpenAPI specifications.
|
|
91
|
+
*
|
|
92
|
+
* @see {@link https://orpc.unnoq.com/docs/openapi/openapi-specification OpenAPI Specification Docs}
|
|
93
|
+
*/
|
|
94
|
+
declare class OpenAPIGenerator {
|
|
95
|
+
#private;
|
|
96
|
+
private readonly serializer;
|
|
97
|
+
private readonly converter;
|
|
98
|
+
constructor(options?: OpenAPIGeneratorOptions);
|
|
99
|
+
/**
|
|
100
|
+
* Generates OpenAPI specifications from oRPC routers/contracts.
|
|
101
|
+
*
|
|
102
|
+
* @see {@link https://orpc.unnoq.com/docs/openapi/openapi-specification OpenAPI Specification Docs}
|
|
103
|
+
*/
|
|
104
|
+
generate(router: AnyContractRouter | AnyRouter, options?: OpenAPIGeneratorGenerateOptions): Promise<OpenAPI.Document>;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export { OpenAPIGenerator as b, CompositeSchemaConverter as e };
|
|
108
|
+
export type { ConditionalSchemaConverter as C, OpenAPIGeneratorOptions as O, SchemaConverterComponent as S, OpenAPIGeneratorGenerateOptions as a, SchemaConvertOptions as c, SchemaConverter as d };
|