@orpc/openapi 0.0.0-next.f635909 → 0.0.0-next.f677f1d
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 +11 -7
- package/dist/adapters/aws-lambda/index.d.mts +19 -0
- package/dist/adapters/aws-lambda/index.d.ts +19 -0
- package/dist/adapters/aws-lambda/index.mjs +18 -0
- package/dist/adapters/fetch/index.d.mts +9 -4
- package/dist/adapters/fetch/index.d.ts +9 -4
- package/dist/adapters/fetch/index.mjs +2 -1
- package/dist/adapters/node/index.d.mts +9 -4
- package/dist/adapters/node/index.d.ts +9 -4
- package/dist/adapters/node/index.mjs +2 -1
- package/dist/adapters/standard/index.d.mts +6 -11
- package/dist/adapters/standard/index.d.ts +6 -11
- package/dist/adapters/standard/index.mjs +2 -1
- package/dist/index.d.mts +29 -15
- package/dist/index.d.ts +29 -15
- package/dist/index.mjs +34 -8
- package/dist/plugins/index.d.mts +26 -12
- package/dist/plugins/index.d.ts +26 -12
- package/dist/plugins/index.mjs +74 -23
- package/dist/shared/{openapi.p5tsmBXx.mjs → openapi.BVXcB0u4.mjs} +39 -10
- package/dist/shared/{openapi.fMEQd3Yd.mjs → openapi.BlSv9FKY.mjs} +281 -74
- package/dist/shared/openapi.CQmjvnb0.d.mts +31 -0
- package/dist/shared/openapi.CQmjvnb0.d.ts +31 -0
- package/dist/shared/openapi.CfjfVeBJ.d.mts +108 -0
- package/dist/shared/openapi.CfjfVeBJ.d.ts +108 -0
- package/package.json +15 -11
- package/dist/shared/openapi.D3j94c9n.d.mts +0 -12
- package/dist/shared/openapi.D3j94c9n.d.ts +0 -12
- package/dist/shared/openapi.DP97kr00.d.mts +0 -47
- package/dist/shared/openapi.DP97kr00.d.ts +0 -47
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { fallbackORPCErrorStatus, fallbackORPCErrorMessage } from '@orpc/client';
|
|
1
|
+
import { isORPCErrorStatus, fallbackORPCErrorStatus, fallbackORPCErrorMessage } from '@orpc/client';
|
|
2
2
|
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 } from '@orpc/shared';
|
|
7
|
-
import '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));
|
|
@@ -184,6 +189,57 @@ function applySchemaOptionality(required, schema) {
|
|
|
184
189
|
]
|
|
185
190
|
};
|
|
186
191
|
}
|
|
192
|
+
function expandUnionSchema(schema) {
|
|
193
|
+
if (typeof schema === "object") {
|
|
194
|
+
for (const keyword of ["anyOf", "oneOf"]) {
|
|
195
|
+
if (schema[keyword] && Object.keys(schema).every(
|
|
196
|
+
(k) => k === keyword || !LOGIC_KEYWORDS.includes(k)
|
|
197
|
+
)) {
|
|
198
|
+
return schema[keyword].flatMap((s) => expandUnionSchema(s));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return [schema];
|
|
203
|
+
}
|
|
204
|
+
function expandArrayableSchema(schema) {
|
|
205
|
+
const schemas = expandUnionSchema(schema);
|
|
206
|
+
if (schemas.length !== 2) {
|
|
207
|
+
return void 0;
|
|
208
|
+
}
|
|
209
|
+
const arraySchema = schemas.find(
|
|
210
|
+
(s) => typeof s === "object" && s.type === "array" && Object.keys(s).filter((k) => LOGIC_KEYWORDS.includes(k)).every((k) => k === "type" || k === "items")
|
|
211
|
+
);
|
|
212
|
+
if (arraySchema === void 0) {
|
|
213
|
+
return void 0;
|
|
214
|
+
}
|
|
215
|
+
const items1 = arraySchema.items;
|
|
216
|
+
const items2 = schemas.find((s) => s !== arraySchema);
|
|
217
|
+
if (stringifyJSON(items1) !== stringifyJSON(items2)) {
|
|
218
|
+
return void 0;
|
|
219
|
+
}
|
|
220
|
+
return [items2, arraySchema];
|
|
221
|
+
}
|
|
222
|
+
const PRIMITIVE_SCHEMA_TYPES = /* @__PURE__ */ new Set([
|
|
223
|
+
TypeName.String,
|
|
224
|
+
TypeName.Number,
|
|
225
|
+
TypeName.Integer,
|
|
226
|
+
TypeName.Boolean,
|
|
227
|
+
TypeName.Null
|
|
228
|
+
]);
|
|
229
|
+
function isPrimitiveSchema(schema) {
|
|
230
|
+
return expandUnionSchema(schema).every((s) => {
|
|
231
|
+
if (typeof s === "boolean") {
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
if (typeof s.type === "string" && PRIMITIVE_SCHEMA_TYPES.has(s.type)) {
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
if (s.const !== void 0) {
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
return false;
|
|
241
|
+
});
|
|
242
|
+
}
|
|
187
243
|
|
|
188
244
|
function toOpenAPIPath(path) {
|
|
189
245
|
return standardizeHTTPPath(path).replace(/\/\{\+([^}]+)\}/g, "/{$1}");
|
|
@@ -256,13 +312,26 @@ function toOpenAPIParameters(schema, parameterIn) {
|
|
|
256
312
|
const parameters = [];
|
|
257
313
|
for (const key in schema.properties) {
|
|
258
314
|
const keySchema = schema.properties[key];
|
|
315
|
+
let isDeepObjectStyle = true;
|
|
316
|
+
if (parameterIn !== "query") {
|
|
317
|
+
isDeepObjectStyle = false;
|
|
318
|
+
} else if (isPrimitiveSchema(keySchema)) {
|
|
319
|
+
isDeepObjectStyle = false;
|
|
320
|
+
} else {
|
|
321
|
+
const [item] = expandArrayableSchema(keySchema) ?? [];
|
|
322
|
+
if (item !== void 0 && isPrimitiveSchema(item)) {
|
|
323
|
+
isDeepObjectStyle = false;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
259
326
|
parameters.push({
|
|
260
327
|
name: key,
|
|
261
328
|
in: parameterIn,
|
|
262
329
|
required: schema.required?.includes(key),
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
330
|
+
schema: toOpenAPISchema(keySchema),
|
|
331
|
+
style: isDeepObjectStyle ? "deepObject" : void 0,
|
|
332
|
+
explode: isDeepObjectStyle ? true : void 0,
|
|
333
|
+
allowEmptyValue: parameterIn === "query" ? true : void 0,
|
|
334
|
+
allowReserved: parameterIn === "query" ? true : void 0
|
|
266
335
|
});
|
|
267
336
|
}
|
|
268
337
|
return parameters;
|
|
@@ -281,6 +350,15 @@ function checkParamsSchema(schema, params) {
|
|
|
281
350
|
function toOpenAPISchema(schema) {
|
|
282
351
|
return schema === true ? {} : schema === false ? { not: {} } : schema;
|
|
283
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
|
+
}
|
|
284
362
|
|
|
285
363
|
class CompositeSchemaConverter {
|
|
286
364
|
converters;
|
|
@@ -312,32 +390,50 @@ class OpenAPIGenerator {
|
|
|
312
390
|
* @see {@link https://orpc.unnoq.com/docs/openapi/openapi-specification OpenAPI Specification Docs}
|
|
313
391
|
*/
|
|
314
392
|
async generate(router, options = {}) {
|
|
393
|
+
const filter = options.filter ?? (({ contract, path }) => {
|
|
394
|
+
return !(options.exclude?.(contract, path) ?? false);
|
|
395
|
+
});
|
|
315
396
|
const doc = {
|
|
316
397
|
...clone(options),
|
|
317
398
|
info: options.info ?? { title: "API Reference", version: "0.0.0" },
|
|
318
|
-
openapi: "3.1.1"
|
|
399
|
+
openapi: "3.1.1",
|
|
400
|
+
exclude: void 0,
|
|
401
|
+
filter: void 0,
|
|
402
|
+
commonSchemas: void 0
|
|
319
403
|
};
|
|
404
|
+
const { baseSchemaConvertOptions, undefinedErrorJsonSchema } = await this.#resolveCommonSchemas(doc, options.commonSchemas);
|
|
320
405
|
const contracts = [];
|
|
321
|
-
await resolveContractProcedures({ path: [], router }, (
|
|
322
|
-
|
|
406
|
+
await resolveContractProcedures({ path: [], router }, (traverseOptions) => {
|
|
407
|
+
if (!value(filter, traverseOptions)) {
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
contracts.push(traverseOptions);
|
|
323
411
|
});
|
|
324
412
|
const errors = [];
|
|
325
413
|
for (const { contract, path } of contracts) {
|
|
326
|
-
const
|
|
414
|
+
const stringPath = path.join(".");
|
|
327
415
|
try {
|
|
328
416
|
const def = contract["~orpc"];
|
|
329
417
|
const method = toOpenAPIMethod(fallbackContractConfig("defaultMethod", def.route.method));
|
|
330
418
|
const httpPath = toOpenAPIPath(def.route.path ?? toHttpPath(path));
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
419
|
+
let operationObjectRef;
|
|
420
|
+
if (def.route.spec !== void 0 && typeof def.route.spec !== "function") {
|
|
421
|
+
operationObjectRef = def.route.spec;
|
|
422
|
+
} else {
|
|
423
|
+
operationObjectRef = {
|
|
424
|
+
operationId: def.route.operationId ?? stringPath,
|
|
425
|
+
summary: def.route.summary,
|
|
426
|
+
description: def.route.description,
|
|
427
|
+
deprecated: def.route.deprecated,
|
|
428
|
+
tags: def.route.tags?.map((tag) => tag)
|
|
429
|
+
};
|
|
430
|
+
await this.#request(doc, operationObjectRef, def, baseSchemaConvertOptions);
|
|
431
|
+
await this.#successResponse(doc, operationObjectRef, def, baseSchemaConvertOptions);
|
|
432
|
+
await this.#errorResponse(operationObjectRef, def, baseSchemaConvertOptions, undefinedErrorJsonSchema);
|
|
433
|
+
}
|
|
434
|
+
if (typeof def.route.spec === "function") {
|
|
435
|
+
operationObjectRef = def.route.spec(operationObjectRef);
|
|
436
|
+
}
|
|
341
437
|
doc.paths ??= {};
|
|
342
438
|
doc.paths[httpPath] ??= {};
|
|
343
439
|
doc.paths[httpPath][method] = applyCustomOpenAPIOperation(operationObjectRef, contract);
|
|
@@ -346,7 +442,7 @@ class OpenAPIGenerator {
|
|
|
346
442
|
throw e;
|
|
347
443
|
}
|
|
348
444
|
errors.push(
|
|
349
|
-
`[OpenAPIGenerator] Error occurred while generating OpenAPI for procedure at path: ${
|
|
445
|
+
`[OpenAPIGenerator] Error occurred while generating OpenAPI for procedure at path: ${stringPath}
|
|
350
446
|
${e.message}`
|
|
351
447
|
);
|
|
352
448
|
}
|
|
@@ -360,22 +456,96 @@ ${errors.join("\n\n")}`
|
|
|
360
456
|
}
|
|
361
457
|
return this.serializer.serialize(doc)[0];
|
|
362
458
|
}
|
|
363
|
-
async #
|
|
459
|
+
async #resolveCommonSchemas(doc, commonSchemas) {
|
|
460
|
+
let undefinedErrorJsonSchema = {
|
|
461
|
+
type: "object",
|
|
462
|
+
properties: {
|
|
463
|
+
defined: { const: false },
|
|
464
|
+
code: { type: "string" },
|
|
465
|
+
status: { type: "number" },
|
|
466
|
+
message: { type: "string" },
|
|
467
|
+
data: {}
|
|
468
|
+
},
|
|
469
|
+
required: ["defined", "code", "status", "message"]
|
|
470
|
+
};
|
|
471
|
+
const baseSchemaConvertOptions = {};
|
|
472
|
+
if (commonSchemas) {
|
|
473
|
+
baseSchemaConvertOptions.components = [];
|
|
474
|
+
for (const key in commonSchemas) {
|
|
475
|
+
const options = commonSchemas[key];
|
|
476
|
+
if (options.schema === void 0) {
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
const { schema, strategy = "input" } = options;
|
|
480
|
+
const [required, json] = await this.converter.convert(schema, { strategy });
|
|
481
|
+
const allowedStrategies = [strategy];
|
|
482
|
+
if (strategy === "input") {
|
|
483
|
+
const [outputRequired, outputJson] = await this.converter.convert(schema, { strategy: "output" });
|
|
484
|
+
if (outputRequired === required && stringifyJSON(outputJson) === stringifyJSON(json)) {
|
|
485
|
+
allowedStrategies.push("output");
|
|
486
|
+
}
|
|
487
|
+
} else if (strategy === "output") {
|
|
488
|
+
const [inputRequired, inputJson] = await this.converter.convert(schema, { strategy: "input" });
|
|
489
|
+
if (inputRequired === required && stringifyJSON(inputJson) === stringifyJSON(json)) {
|
|
490
|
+
allowedStrategies.push("input");
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
baseSchemaConvertOptions.components.push({
|
|
494
|
+
schema,
|
|
495
|
+
required,
|
|
496
|
+
ref: `#/components/schemas/${key}`,
|
|
497
|
+
allowedStrategies
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
doc.components ??= {};
|
|
501
|
+
doc.components.schemas ??= {};
|
|
502
|
+
for (const key in commonSchemas) {
|
|
503
|
+
const options = commonSchemas[key];
|
|
504
|
+
if (options.schema === void 0) {
|
|
505
|
+
if (options.error === "UndefinedError") {
|
|
506
|
+
doc.components.schemas[key] = toOpenAPISchema(undefinedErrorJsonSchema);
|
|
507
|
+
undefinedErrorJsonSchema = { $ref: `#/components/schemas/${key}` };
|
|
508
|
+
}
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
const { schema, strategy = "input" } = options;
|
|
512
|
+
const [, json] = await this.converter.convert(
|
|
513
|
+
schema,
|
|
514
|
+
{
|
|
515
|
+
...baseSchemaConvertOptions,
|
|
516
|
+
strategy,
|
|
517
|
+
minStructureDepthForRef: 1
|
|
518
|
+
// not allow use $ref for root schemas
|
|
519
|
+
}
|
|
520
|
+
);
|
|
521
|
+
doc.components.schemas[key] = toOpenAPISchema(json);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
return { baseSchemaConvertOptions, undefinedErrorJsonSchema };
|
|
525
|
+
}
|
|
526
|
+
async #request(doc, ref, def, baseSchemaConvertOptions) {
|
|
364
527
|
const method = fallbackContractConfig("defaultMethod", def.route.method);
|
|
365
528
|
const details = getEventIteratorSchemaDetails(def.inputSchema);
|
|
366
529
|
if (details) {
|
|
367
530
|
ref.requestBody = {
|
|
368
531
|
required: true,
|
|
369
532
|
content: toOpenAPIEventIteratorContent(
|
|
370
|
-
await this.converter.convert(details.yields, { strategy: "input" }),
|
|
371
|
-
await this.converter.convert(details.returns, { strategy: "input" })
|
|
533
|
+
await this.converter.convert(details.yields, { ...baseSchemaConvertOptions, strategy: "input" }),
|
|
534
|
+
await this.converter.convert(details.returns, { ...baseSchemaConvertOptions, strategy: "input" })
|
|
372
535
|
)
|
|
373
536
|
};
|
|
374
537
|
return;
|
|
375
538
|
}
|
|
376
539
|
const dynamicParams = getDynamicParams(def.route.path)?.map((v) => v.name);
|
|
377
540
|
const inputStructure = fallbackContractConfig("defaultInputStructure", def.route.inputStructure);
|
|
378
|
-
let [required, schema] = await this.converter.convert(
|
|
541
|
+
let [required, schema] = await this.converter.convert(
|
|
542
|
+
def.inputSchema,
|
|
543
|
+
{
|
|
544
|
+
...baseSchemaConvertOptions,
|
|
545
|
+
strategy: "input",
|
|
546
|
+
minStructureDepthForRef: dynamicParams?.length || inputStructure === "detailed" ? 1 : 0
|
|
547
|
+
}
|
|
548
|
+
);
|
|
379
549
|
if (isAnySchema(schema) && !dynamicParams?.length) {
|
|
380
550
|
return;
|
|
381
551
|
}
|
|
@@ -397,13 +567,14 @@ ${errors.join("\n\n")}`
|
|
|
397
567
|
ref.parameters.push(...toOpenAPIParameters(paramsSchema, "path"));
|
|
398
568
|
}
|
|
399
569
|
if (method === "GET") {
|
|
400
|
-
|
|
570
|
+
const resolvedSchema = resolveOpenAPIJsonSchemaRef(doc, schema);
|
|
571
|
+
if (!isObjectSchema(resolvedSchema)) {
|
|
401
572
|
throw new OpenAPIGeneratorError(
|
|
402
573
|
'When method is "GET", input schema must satisfy: object | any | unknown'
|
|
403
574
|
);
|
|
404
575
|
}
|
|
405
576
|
ref.parameters ??= [];
|
|
406
|
-
ref.parameters.push(...toOpenAPIParameters(
|
|
577
|
+
ref.parameters.push(...toOpenAPIParameters(resolvedSchema, "query"));
|
|
407
578
|
} else {
|
|
408
579
|
ref.requestBody = {
|
|
409
580
|
required,
|
|
@@ -418,7 +589,8 @@ ${errors.join("\n\n")}`
|
|
|
418
589
|
if (!isObjectSchema(schema)) {
|
|
419
590
|
throw error;
|
|
420
591
|
}
|
|
421
|
-
|
|
592
|
+
const resolvedParamSchema = schema.properties?.params !== void 0 ? resolveOpenAPIJsonSchemaRef(doc, schema.properties.params) : void 0;
|
|
593
|
+
if (dynamicParams?.length && (resolvedParamSchema === void 0 || !isObjectSchema(resolvedParamSchema) || !checkParamsSchema(resolvedParamSchema, dynamicParams))) {
|
|
422
594
|
throw new OpenAPIGeneratorError(
|
|
423
595
|
'When input structure is "detailed" and path has dynamic params, the "params" schema must be an object with all dynamic params as required.'
|
|
424
596
|
);
|
|
@@ -426,12 +598,13 @@ ${errors.join("\n\n")}`
|
|
|
426
598
|
for (const from of ["params", "query", "headers"]) {
|
|
427
599
|
const fromSchema = schema.properties?.[from];
|
|
428
600
|
if (fromSchema !== void 0) {
|
|
429
|
-
|
|
601
|
+
const resolvedSchema = resolveOpenAPIJsonSchemaRef(doc, fromSchema);
|
|
602
|
+
if (!isObjectSchema(resolvedSchema)) {
|
|
430
603
|
throw error;
|
|
431
604
|
}
|
|
432
605
|
const parameterIn = from === "params" ? "path" : from === "headers" ? "header" : "query";
|
|
433
606
|
ref.parameters ??= [];
|
|
434
|
-
ref.parameters.push(...toOpenAPIParameters(
|
|
607
|
+
ref.parameters.push(...toOpenAPIParameters(resolvedSchema, parameterIn));
|
|
435
608
|
}
|
|
436
609
|
}
|
|
437
610
|
if (schema.properties?.body !== void 0) {
|
|
@@ -441,7 +614,7 @@ ${errors.join("\n\n")}`
|
|
|
441
614
|
};
|
|
442
615
|
}
|
|
443
616
|
}
|
|
444
|
-
async #successResponse(ref, def) {
|
|
617
|
+
async #successResponse(doc, ref, def, baseSchemaConvertOptions) {
|
|
445
618
|
const outputSchema = def.outputSchema;
|
|
446
619
|
const status = fallbackContractConfig("defaultSuccessStatus", def.route.successStatus);
|
|
447
620
|
const description = fallbackContractConfig("defaultSuccessDescription", def.route?.successDescription);
|
|
@@ -452,46 +625,90 @@ ${errors.join("\n\n")}`
|
|
|
452
625
|
ref.responses[status] = {
|
|
453
626
|
description,
|
|
454
627
|
content: toOpenAPIEventIteratorContent(
|
|
455
|
-
await this.converter.convert(eventIteratorSchemaDetails.yields, { strategy: "output" }),
|
|
456
|
-
await this.converter.convert(eventIteratorSchemaDetails.returns, { strategy: "output" })
|
|
628
|
+
await this.converter.convert(eventIteratorSchemaDetails.yields, { ...baseSchemaConvertOptions, strategy: "output" }),
|
|
629
|
+
await this.converter.convert(eventIteratorSchemaDetails.returns, { ...baseSchemaConvertOptions, strategy: "output" })
|
|
457
630
|
)
|
|
458
631
|
};
|
|
459
632
|
return;
|
|
460
633
|
}
|
|
461
|
-
const [required, json] = await this.converter.convert(
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
634
|
+
const [required, json] = await this.converter.convert(
|
|
635
|
+
outputSchema,
|
|
636
|
+
{
|
|
637
|
+
...baseSchemaConvertOptions,
|
|
638
|
+
strategy: "output",
|
|
639
|
+
minStructureDepthForRef: outputStructure === "detailed" ? 1 : 0
|
|
640
|
+
}
|
|
641
|
+
);
|
|
466
642
|
if (outputStructure === "compact") {
|
|
643
|
+
ref.responses ??= {};
|
|
644
|
+
ref.responses[status] = {
|
|
645
|
+
description
|
|
646
|
+
};
|
|
467
647
|
ref.responses[status].content = toOpenAPIContent(applySchemaOptionality(required, json));
|
|
468
648
|
return;
|
|
469
649
|
}
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
650
|
+
const handledStatuses = /* @__PURE__ */ new Set();
|
|
651
|
+
for (const item of expandUnionSchema(json)) {
|
|
652
|
+
const error = new OpenAPIGeneratorError(`
|
|
653
|
+
When output structure is "detailed", output schema must satisfy:
|
|
654
|
+
{
|
|
655
|
+
status?: number, // must be a literal number and in the range of 200-399
|
|
656
|
+
headers?: Record<string, unknown>,
|
|
657
|
+
body?: unknown
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
But got: ${stringifyJSON(item)}
|
|
661
|
+
`);
|
|
662
|
+
if (!isObjectSchema(item)) {
|
|
478
663
|
throw error;
|
|
479
664
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
665
|
+
let schemaStatus;
|
|
666
|
+
let schemaDescription;
|
|
667
|
+
if (item.properties?.status !== void 0) {
|
|
668
|
+
const statusSchema = resolveOpenAPIJsonSchemaRef(doc, item.properties.status);
|
|
669
|
+
if (typeof statusSchema !== "object" || statusSchema.const === void 0 || typeof statusSchema.const !== "number" || !Number.isInteger(statusSchema.const) || isORPCErrorStatus(statusSchema.const)) {
|
|
670
|
+
throw error;
|
|
671
|
+
}
|
|
672
|
+
schemaStatus = statusSchema.const;
|
|
673
|
+
schemaDescription = statusSchema.description;
|
|
674
|
+
}
|
|
675
|
+
const itemStatus = schemaStatus ?? status;
|
|
676
|
+
const itemDescription = schemaDescription ?? description;
|
|
677
|
+
if (handledStatuses.has(itemStatus)) {
|
|
678
|
+
throw new OpenAPIGeneratorError(`
|
|
679
|
+
When output structure is "detailed", each success status must be unique.
|
|
680
|
+
But got status: ${itemStatus} used more than once.
|
|
681
|
+
`);
|
|
682
|
+
}
|
|
683
|
+
handledStatuses.add(itemStatus);
|
|
684
|
+
ref.responses ??= {};
|
|
685
|
+
ref.responses[itemStatus] = {
|
|
686
|
+
description: itemDescription
|
|
687
|
+
};
|
|
688
|
+
if (item.properties?.headers !== void 0) {
|
|
689
|
+
const headersSchema = resolveOpenAPIJsonSchemaRef(doc, item.properties.headers);
|
|
690
|
+
if (!isObjectSchema(headersSchema)) {
|
|
691
|
+
throw error;
|
|
692
|
+
}
|
|
693
|
+
for (const key in headersSchema.properties) {
|
|
694
|
+
const headerSchema = headersSchema.properties[key];
|
|
695
|
+
if (headerSchema !== void 0) {
|
|
696
|
+
ref.responses[itemStatus].headers ??= {};
|
|
697
|
+
ref.responses[itemStatus].headers[key] = {
|
|
698
|
+
schema: toOpenAPISchema(headerSchema),
|
|
699
|
+
required: item.required?.includes("headers") && headersSchema.required?.includes(key)
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
if (item.properties?.body !== void 0) {
|
|
705
|
+
ref.responses[itemStatus].content = toOpenAPIContent(
|
|
706
|
+
applySchemaOptionality(item.required?.includes("body") ?? false, item.properties.body)
|
|
707
|
+
);
|
|
486
708
|
}
|
|
487
|
-
}
|
|
488
|
-
if (json.properties?.body !== void 0) {
|
|
489
|
-
ref.responses[status].content = toOpenAPIContent(
|
|
490
|
-
applySchemaOptionality(json.required?.includes("body") ?? false, json.properties.body)
|
|
491
|
-
);
|
|
492
709
|
}
|
|
493
710
|
}
|
|
494
|
-
async #errorResponse(ref, def) {
|
|
711
|
+
async #errorResponse(ref, def, baseSchemaConvertOptions, undefinedErrorSchema) {
|
|
495
712
|
const errorMap = def.errorMap;
|
|
496
713
|
const errors = {};
|
|
497
714
|
for (const code in errorMap) {
|
|
@@ -501,7 +718,7 @@ ${errors.join("\n\n")}`
|
|
|
501
718
|
}
|
|
502
719
|
const status = fallbackORPCErrorStatus(code, config.status);
|
|
503
720
|
const message = fallbackORPCErrorMessage(code, config.message);
|
|
504
|
-
const [dataRequired, dataSchema] = await this.converter.convert(config.data, { strategy: "output" });
|
|
721
|
+
const [dataRequired, dataSchema] = await this.converter.convert(config.data, { ...baseSchemaConvertOptions, strategy: "output" });
|
|
505
722
|
errors[status] ??= [];
|
|
506
723
|
errors[status].push({
|
|
507
724
|
type: "object",
|
|
@@ -523,17 +740,7 @@ ${errors.join("\n\n")}`
|
|
|
523
740
|
content: toOpenAPIContent({
|
|
524
741
|
oneOf: [
|
|
525
742
|
...schemas,
|
|
526
|
-
|
|
527
|
-
type: "object",
|
|
528
|
-
properties: {
|
|
529
|
-
defined: { const: false },
|
|
530
|
-
code: { type: "string" },
|
|
531
|
-
status: { type: "number" },
|
|
532
|
-
message: { type: "string" },
|
|
533
|
-
data: {}
|
|
534
|
-
},
|
|
535
|
-
required: ["defined", "code", "status", "message"]
|
|
536
|
-
}
|
|
743
|
+
undefinedErrorSchema
|
|
537
744
|
]
|
|
538
745
|
})
|
|
539
746
|
};
|
|
@@ -541,4 +748,4 @@ ${errors.join("\n\n")}`
|
|
|
541
748
|
}
|
|
542
749
|
}
|
|
543
750
|
|
|
544
|
-
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, separateObjectSchema as s, toOpenAPIPath as t };
|
|
751
|
+
export { CompositeSchemaConverter as C, LOGIC_KEYWORDS as L, OpenAPIGenerator as O, applyCustomOpenAPIOperation as a, toOpenAPIMethod as b, customOpenAPIOperation as c, toOpenAPIContent as d, toOpenAPIEventIteratorContent as e, toOpenAPIParameters as f, getCustomOpenAPIOperation as g, checkParamsSchema as h, toOpenAPISchema as i, isFileSchema as j, isObjectSchema as k, isAnySchema as l, 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 };
|