@orpc/openapi 0.0.0-next.e385fb7 → 0.0.0-next.e39a46e
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 -8
- 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 +8 -3
- package/dist/adapters/fetch/index.d.ts +8 -3
- package/dist/adapters/fetch/index.mjs +1 -1
- package/dist/adapters/node/index.d.mts +8 -3
- package/dist/adapters/node/index.d.ts +8 -3
- 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 +13 -13
- package/dist/index.d.ts +13 -13
- package/dist/index.mjs +3 -3
- package/dist/plugins/index.d.mts +20 -6
- package/dist/plugins/index.d.ts +20 -6
- package/dist/plugins/index.mjs +59 -19
- package/dist/shared/{openapi.C_UtQ8Us.mjs → openapi.BVXcB0u4.mjs} +13 -5
- package/dist/shared/{openapi.ExRBHuvW.mjs → openapi.BlSv9FKY.mjs} +160 -56
- 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 +10 -11
- 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
package/dist/plugins/index.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { stringifyJSON, once, value } from '@orpc/shared';
|
|
2
|
-
import { O as OpenAPIGenerator } from '../shared/openapi.
|
|
2
|
+
import { O as OpenAPIGenerator } from '../shared/openapi.BlSv9FKY.mjs';
|
|
3
3
|
import '@orpc/client';
|
|
4
4
|
import '@orpc/client/standard';
|
|
5
5
|
import '@orpc/contract';
|
|
6
6
|
import '@orpc/openapi-client/standard';
|
|
7
7
|
import '@orpc/server';
|
|
8
|
-
import 'json-schema-typed/draft-2020-12';
|
|
8
|
+
import '@orpc/interop/json-schema-typed/draft-2020-12';
|
|
9
9
|
|
|
10
10
|
class OpenAPIReferencePlugin {
|
|
11
11
|
generator;
|
|
@@ -14,7 +14,9 @@ class OpenAPIReferencePlugin {
|
|
|
14
14
|
docsPath;
|
|
15
15
|
docsTitle;
|
|
16
16
|
docsHead;
|
|
17
|
+
docsProvider;
|
|
17
18
|
docsScriptUrl;
|
|
19
|
+
docsCssUrl;
|
|
18
20
|
docsConfig;
|
|
19
21
|
renderDocsHtml;
|
|
20
22
|
constructor(options = {}) {
|
|
@@ -22,16 +24,59 @@ class OpenAPIReferencePlugin {
|
|
|
22
24
|
this.docsPath = options.docsPath ?? "/";
|
|
23
25
|
this.docsTitle = options.docsTitle ?? "API Reference";
|
|
24
26
|
this.docsConfig = options.docsConfig ?? void 0;
|
|
25
|
-
this.
|
|
27
|
+
this.docsProvider = options.docsProvider ?? "scalar";
|
|
28
|
+
this.docsScriptUrl = options.docsScriptUrl ?? (this.docsProvider === "swagger" ? "https://unpkg.com/swagger-ui-dist/swagger-ui-bundle.js" : "https://cdn.jsdelivr.net/npm/@scalar/api-reference");
|
|
29
|
+
this.docsCssUrl = options.docsCssUrl ?? (this.docsProvider === "swagger" ? "https://unpkg.com/swagger-ui-dist/swagger-ui.css" : void 0);
|
|
26
30
|
this.docsHead = options.docsHead ?? "";
|
|
27
31
|
this.specPath = options.specPath ?? "/spec.json";
|
|
28
32
|
this.generator = new OpenAPIGenerator(options);
|
|
29
33
|
const esc = (s) => s.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
30
|
-
this.renderDocsHtml = options.renderDocsHtml ?? ((specUrl, title, head, scriptUrl, config, spec) => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
this.renderDocsHtml = options.renderDocsHtml ?? ((specUrl, title, head, scriptUrl, config, spec, docsProvider, cssUrl) => {
|
|
35
|
+
let body;
|
|
36
|
+
if (docsProvider === "swagger") {
|
|
37
|
+
const swaggerConfig = {
|
|
38
|
+
dom_id: "#app",
|
|
39
|
+
spec,
|
|
40
|
+
deepLinking: true,
|
|
41
|
+
presets: [
|
|
42
|
+
"SwaggerUIBundle.presets.apis",
|
|
43
|
+
"SwaggerUIBundle.presets.standalone"
|
|
44
|
+
],
|
|
45
|
+
plugins: [
|
|
46
|
+
"SwaggerUIBundle.plugins.DownloadUrl"
|
|
47
|
+
],
|
|
48
|
+
...config
|
|
49
|
+
};
|
|
50
|
+
body = `
|
|
51
|
+
<body>
|
|
52
|
+
<div id="app"></div>
|
|
53
|
+
|
|
54
|
+
<script src="${esc(scriptUrl)}"><\/script>
|
|
55
|
+
|
|
56
|
+
<script>
|
|
57
|
+
window.onload = () => {
|
|
58
|
+
window.ui = SwaggerUIBundle(${stringifyJSON(swaggerConfig).replace(/"(SwaggerUIBundle\.[^"]+)"/g, "$1")})
|
|
59
|
+
}
|
|
60
|
+
<\/script>
|
|
61
|
+
</body>
|
|
62
|
+
`;
|
|
63
|
+
} else {
|
|
64
|
+
const scalarConfig = {
|
|
65
|
+
content: stringifyJSON(spec),
|
|
66
|
+
...config
|
|
67
|
+
};
|
|
68
|
+
body = `
|
|
69
|
+
<body>
|
|
70
|
+
<div id="app" data-config="${esc(stringifyJSON(scalarConfig))}"></div>
|
|
71
|
+
|
|
72
|
+
<script src="${esc(scriptUrl)}"><\/script>
|
|
73
|
+
|
|
74
|
+
<script>
|
|
75
|
+
Scalar.createApiReference('#app', JSON.parse(document.getElementById('app').dataset.config))
|
|
76
|
+
<\/script>
|
|
77
|
+
</body>
|
|
78
|
+
`;
|
|
79
|
+
}
|
|
35
80
|
return `
|
|
36
81
|
<!doctype html>
|
|
37
82
|
<html>
|
|
@@ -39,19 +84,12 @@ class OpenAPIReferencePlugin {
|
|
|
39
84
|
<meta charset="utf-8" />
|
|
40
85
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
41
86
|
<title>${esc(title)}</title>
|
|
87
|
+
${cssUrl ? `<link rel="stylesheet" type="text/css" href="${esc(cssUrl)}" />` : ""}
|
|
42
88
|
${head}
|
|
43
89
|
</head>
|
|
44
|
-
|
|
45
|
-
<div id="app" data-config="${esc(stringifyJSON(finalConfig))}"></div>
|
|
46
|
-
|
|
47
|
-
<script src="${esc(scriptUrl)}"><\/script>
|
|
48
|
-
|
|
49
|
-
<script>
|
|
50
|
-
Scalar.createApiReference('#app', JSON.parse(document.getElementById('app').dataset.config))
|
|
51
|
-
<\/script>
|
|
52
|
-
</body>
|
|
90
|
+
${body}
|
|
53
91
|
</html>
|
|
54
|
-
|
|
92
|
+
`;
|
|
55
93
|
});
|
|
56
94
|
}
|
|
57
95
|
init(options, router) {
|
|
@@ -89,7 +127,9 @@ class OpenAPIReferencePlugin {
|
|
|
89
127
|
await value(this.docsHead, options2),
|
|
90
128
|
await value(this.docsScriptUrl, options2),
|
|
91
129
|
await value(this.docsConfig, options2),
|
|
92
|
-
await generateSpec()
|
|
130
|
+
await generateSpec(),
|
|
131
|
+
this.docsProvider,
|
|
132
|
+
await value(this.docsCssUrl, options2)
|
|
93
133
|
);
|
|
94
134
|
return {
|
|
95
135
|
matched: true,
|
|
@@ -2,7 +2,7 @@ import { standardizeHTTPPath, StandardOpenAPIJsonSerializer, StandardBracketNota
|
|
|
2
2
|
import { StandardHandler } from '@orpc/server/standard';
|
|
3
3
|
import { isORPCErrorStatus } from '@orpc/client';
|
|
4
4
|
import { fallbackContractConfig } from '@orpc/contract';
|
|
5
|
-
import { isObject, stringifyJSON } from '@orpc/shared';
|
|
5
|
+
import { isObject, stringifyJSON, tryDecodeURIComponent, value } from '@orpc/shared';
|
|
6
6
|
import { toHttpPath } from '@orpc/client/standard';
|
|
7
7
|
import { traverseContractProcedures, isProcedure, getLazyMeta, unlazy, getRouter, createContractedProcedure } from '@orpc/server';
|
|
8
8
|
import { createRouter, addRoute, findRoute } from 'rou3';
|
|
@@ -97,14 +97,22 @@ function toRou3Pattern(path) {
|
|
|
97
97
|
return standardizeHTTPPath(path).replace(/\/\{\+([^}]+)\}/g, "/**:$1").replace(/\/\{([^}]+)\}/g, "/:$1");
|
|
98
98
|
}
|
|
99
99
|
function decodeParams(params) {
|
|
100
|
-
return Object.fromEntries(Object.entries(params).map(([key, value]) => [key,
|
|
100
|
+
return Object.fromEntries(Object.entries(params).map(([key, value]) => [key, tryDecodeURIComponent(value)]));
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
class StandardOpenAPIMatcher {
|
|
104
|
+
filter;
|
|
104
105
|
tree = createRouter();
|
|
105
106
|
pendingRouters = [];
|
|
107
|
+
constructor(options = {}) {
|
|
108
|
+
this.filter = options.filter ?? true;
|
|
109
|
+
}
|
|
106
110
|
init(router, path = []) {
|
|
107
|
-
const laziedOptions = traverseContractProcedures({ router, path }, (
|
|
111
|
+
const laziedOptions = traverseContractProcedures({ router, path }, (traverseOptions) => {
|
|
112
|
+
if (!value(this.filter, traverseOptions)) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const { path: path2, contract } = traverseOptions;
|
|
108
116
|
const method = fallbackContractConfig("defaultMethod", contract["~orpc"].route.method);
|
|
109
117
|
const httpPath = toRou3Pattern(contract["~orpc"].route.path ?? toHttpPath(path2));
|
|
110
118
|
if (isProcedure(contract)) {
|
|
@@ -168,9 +176,9 @@ class StandardOpenAPIMatcher {
|
|
|
168
176
|
class StandardOpenAPIHandler extends StandardHandler {
|
|
169
177
|
constructor(router, options) {
|
|
170
178
|
const jsonSerializer = new StandardOpenAPIJsonSerializer(options);
|
|
171
|
-
const bracketNotationSerializer = new StandardBracketNotationSerializer();
|
|
179
|
+
const bracketNotationSerializer = new StandardBracketNotationSerializer(options);
|
|
172
180
|
const serializer = new StandardOpenAPISerializer(jsonSerializer, bracketNotationSerializer);
|
|
173
|
-
const matcher = new StandardOpenAPIMatcher();
|
|
181
|
+
const matcher = new StandardOpenAPIMatcher(options);
|
|
174
182
|
const codec = new StandardOpenAPICodec(serializer);
|
|
175
183
|
super(router, matcher, codec, options);
|
|
176
184
|
}
|
|
@@ -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;
|
|
@@ -376,36 +390,50 @@ class OpenAPIGenerator {
|
|
|
376
390
|
* @see {@link https://orpc.unnoq.com/docs/openapi/openapi-specification OpenAPI Specification Docs}
|
|
377
391
|
*/
|
|
378
392
|
async generate(router, options = {}) {
|
|
379
|
-
const
|
|
393
|
+
const filter = options.filter ?? (({ contract, path }) => {
|
|
394
|
+
return !(options.exclude?.(contract, path) ?? false);
|
|
395
|
+
});
|
|
380
396
|
const doc = {
|
|
381
397
|
...clone(options),
|
|
382
398
|
info: options.info ?? { title: "API Reference", version: "0.0.0" },
|
|
383
399
|
openapi: "3.1.1",
|
|
384
|
-
exclude: void 0
|
|
400
|
+
exclude: void 0,
|
|
401
|
+
filter: void 0,
|
|
402
|
+
commonSchemas: void 0
|
|
385
403
|
};
|
|
404
|
+
const { baseSchemaConvertOptions, undefinedErrorJsonSchema } = await this.#resolveCommonSchemas(doc, options.commonSchemas);
|
|
386
405
|
const contracts = [];
|
|
387
|
-
await resolveContractProcedures({ path: [], router }, (
|
|
388
|
-
if (!
|
|
389
|
-
|
|
406
|
+
await resolveContractProcedures({ path: [], router }, (traverseOptions) => {
|
|
407
|
+
if (!value(filter, traverseOptions)) {
|
|
408
|
+
return;
|
|
390
409
|
}
|
|
410
|
+
contracts.push(traverseOptions);
|
|
391
411
|
});
|
|
392
412
|
const errors = [];
|
|
393
413
|
for (const { contract, path } of contracts) {
|
|
394
|
-
const
|
|
414
|
+
const stringPath = path.join(".");
|
|
395
415
|
try {
|
|
396
416
|
const def = contract["~orpc"];
|
|
397
417
|
const method = toOpenAPIMethod(fallbackContractConfig("defaultMethod", def.route.method));
|
|
398
418
|
const httpPath = toOpenAPIPath(def.route.path ?? toHttpPath(path));
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
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
|
+
}
|
|
409
437
|
doc.paths ??= {};
|
|
410
438
|
doc.paths[httpPath] ??= {};
|
|
411
439
|
doc.paths[httpPath][method] = applyCustomOpenAPIOperation(operationObjectRef, contract);
|
|
@@ -414,7 +442,7 @@ class OpenAPIGenerator {
|
|
|
414
442
|
throw e;
|
|
415
443
|
}
|
|
416
444
|
errors.push(
|
|
417
|
-
`[OpenAPIGenerator] Error occurred while generating OpenAPI for procedure at path: ${
|
|
445
|
+
`[OpenAPIGenerator] Error occurred while generating OpenAPI for procedure at path: ${stringPath}
|
|
418
446
|
${e.message}`
|
|
419
447
|
);
|
|
420
448
|
}
|
|
@@ -428,22 +456,96 @@ ${errors.join("\n\n")}`
|
|
|
428
456
|
}
|
|
429
457
|
return this.serializer.serialize(doc)[0];
|
|
430
458
|
}
|
|
431
|
-
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) {
|
|
432
527
|
const method = fallbackContractConfig("defaultMethod", def.route.method);
|
|
433
528
|
const details = getEventIteratorSchemaDetails(def.inputSchema);
|
|
434
529
|
if (details) {
|
|
435
530
|
ref.requestBody = {
|
|
436
531
|
required: true,
|
|
437
532
|
content: toOpenAPIEventIteratorContent(
|
|
438
|
-
await this.converter.convert(details.yields, { strategy: "input" }),
|
|
439
|
-
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" })
|
|
440
535
|
)
|
|
441
536
|
};
|
|
442
537
|
return;
|
|
443
538
|
}
|
|
444
539
|
const dynamicParams = getDynamicParams(def.route.path)?.map((v) => v.name);
|
|
445
540
|
const inputStructure = fallbackContractConfig("defaultInputStructure", def.route.inputStructure);
|
|
446
|
-
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
|
+
);
|
|
447
549
|
if (isAnySchema(schema) && !dynamicParams?.length) {
|
|
448
550
|
return;
|
|
449
551
|
}
|
|
@@ -465,13 +567,14 @@ ${errors.join("\n\n")}`
|
|
|
465
567
|
ref.parameters.push(...toOpenAPIParameters(paramsSchema, "path"));
|
|
466
568
|
}
|
|
467
569
|
if (method === "GET") {
|
|
468
|
-
|
|
570
|
+
const resolvedSchema = resolveOpenAPIJsonSchemaRef(doc, schema);
|
|
571
|
+
if (!isObjectSchema(resolvedSchema)) {
|
|
469
572
|
throw new OpenAPIGeneratorError(
|
|
470
573
|
'When method is "GET", input schema must satisfy: object | any | unknown'
|
|
471
574
|
);
|
|
472
575
|
}
|
|
473
576
|
ref.parameters ??= [];
|
|
474
|
-
ref.parameters.push(...toOpenAPIParameters(
|
|
577
|
+
ref.parameters.push(...toOpenAPIParameters(resolvedSchema, "query"));
|
|
475
578
|
} else {
|
|
476
579
|
ref.requestBody = {
|
|
477
580
|
required,
|
|
@@ -486,7 +589,8 @@ ${errors.join("\n\n")}`
|
|
|
486
589
|
if (!isObjectSchema(schema)) {
|
|
487
590
|
throw error;
|
|
488
591
|
}
|
|
489
|
-
|
|
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))) {
|
|
490
594
|
throw new OpenAPIGeneratorError(
|
|
491
595
|
'When input structure is "detailed" and path has dynamic params, the "params" schema must be an object with all dynamic params as required.'
|
|
492
596
|
);
|
|
@@ -494,12 +598,13 @@ ${errors.join("\n\n")}`
|
|
|
494
598
|
for (const from of ["params", "query", "headers"]) {
|
|
495
599
|
const fromSchema = schema.properties?.[from];
|
|
496
600
|
if (fromSchema !== void 0) {
|
|
497
|
-
|
|
601
|
+
const resolvedSchema = resolveOpenAPIJsonSchemaRef(doc, fromSchema);
|
|
602
|
+
if (!isObjectSchema(resolvedSchema)) {
|
|
498
603
|
throw error;
|
|
499
604
|
}
|
|
500
605
|
const parameterIn = from === "params" ? "path" : from === "headers" ? "header" : "query";
|
|
501
606
|
ref.parameters ??= [];
|
|
502
|
-
ref.parameters.push(...toOpenAPIParameters(
|
|
607
|
+
ref.parameters.push(...toOpenAPIParameters(resolvedSchema, parameterIn));
|
|
503
608
|
}
|
|
504
609
|
}
|
|
505
610
|
if (schema.properties?.body !== void 0) {
|
|
@@ -509,7 +614,7 @@ ${errors.join("\n\n")}`
|
|
|
509
614
|
};
|
|
510
615
|
}
|
|
511
616
|
}
|
|
512
|
-
async #successResponse(ref, def) {
|
|
617
|
+
async #successResponse(doc, ref, def, baseSchemaConvertOptions) {
|
|
513
618
|
const outputSchema = def.outputSchema;
|
|
514
619
|
const status = fallbackContractConfig("defaultSuccessStatus", def.route.successStatus);
|
|
515
620
|
const description = fallbackContractConfig("defaultSuccessDescription", def.route?.successDescription);
|
|
@@ -520,13 +625,20 @@ ${errors.join("\n\n")}`
|
|
|
520
625
|
ref.responses[status] = {
|
|
521
626
|
description,
|
|
522
627
|
content: toOpenAPIEventIteratorContent(
|
|
523
|
-
await this.converter.convert(eventIteratorSchemaDetails.yields, { strategy: "output" }),
|
|
524
|
-
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" })
|
|
525
630
|
)
|
|
526
631
|
};
|
|
527
632
|
return;
|
|
528
633
|
}
|
|
529
|
-
const [required, json] = await this.converter.convert(
|
|
634
|
+
const [required, json] = await this.converter.convert(
|
|
635
|
+
outputSchema,
|
|
636
|
+
{
|
|
637
|
+
...baseSchemaConvertOptions,
|
|
638
|
+
strategy: "output",
|
|
639
|
+
minStructureDepthForRef: outputStructure === "detailed" ? 1 : 0
|
|
640
|
+
}
|
|
641
|
+
);
|
|
530
642
|
if (outputStructure === "compact") {
|
|
531
643
|
ref.responses ??= {};
|
|
532
644
|
ref.responses[status] = {
|
|
@@ -553,11 +665,12 @@ ${errors.join("\n\n")}`
|
|
|
553
665
|
let schemaStatus;
|
|
554
666
|
let schemaDescription;
|
|
555
667
|
if (item.properties?.status !== void 0) {
|
|
556
|
-
|
|
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)) {
|
|
557
670
|
throw error;
|
|
558
671
|
}
|
|
559
|
-
schemaStatus =
|
|
560
|
-
schemaDescription =
|
|
672
|
+
schemaStatus = statusSchema.const;
|
|
673
|
+
schemaDescription = statusSchema.description;
|
|
561
674
|
}
|
|
562
675
|
const itemStatus = schemaStatus ?? status;
|
|
563
676
|
const itemDescription = schemaDescription ?? description;
|
|
@@ -573,16 +686,17 @@ ${errors.join("\n\n")}`
|
|
|
573
686
|
description: itemDescription
|
|
574
687
|
};
|
|
575
688
|
if (item.properties?.headers !== void 0) {
|
|
576
|
-
|
|
689
|
+
const headersSchema = resolveOpenAPIJsonSchemaRef(doc, item.properties.headers);
|
|
690
|
+
if (!isObjectSchema(headersSchema)) {
|
|
577
691
|
throw error;
|
|
578
692
|
}
|
|
579
|
-
for (const key in
|
|
580
|
-
const headerSchema =
|
|
693
|
+
for (const key in headersSchema.properties) {
|
|
694
|
+
const headerSchema = headersSchema.properties[key];
|
|
581
695
|
if (headerSchema !== void 0) {
|
|
582
696
|
ref.responses[itemStatus].headers ??= {};
|
|
583
697
|
ref.responses[itemStatus].headers[key] = {
|
|
584
698
|
schema: toOpenAPISchema(headerSchema),
|
|
585
|
-
required: item.
|
|
699
|
+
required: item.required?.includes("headers") && headersSchema.required?.includes(key)
|
|
586
700
|
};
|
|
587
701
|
}
|
|
588
702
|
}
|
|
@@ -594,7 +708,7 @@ ${errors.join("\n\n")}`
|
|
|
594
708
|
}
|
|
595
709
|
}
|
|
596
710
|
}
|
|
597
|
-
async #errorResponse(ref, def) {
|
|
711
|
+
async #errorResponse(ref, def, baseSchemaConvertOptions, undefinedErrorSchema) {
|
|
598
712
|
const errorMap = def.errorMap;
|
|
599
713
|
const errors = {};
|
|
600
714
|
for (const code in errorMap) {
|
|
@@ -604,7 +718,7 @@ ${errors.join("\n\n")}`
|
|
|
604
718
|
}
|
|
605
719
|
const status = fallbackORPCErrorStatus(code, config.status);
|
|
606
720
|
const message = fallbackORPCErrorMessage(code, config.message);
|
|
607
|
-
const [dataRequired, dataSchema] = await this.converter.convert(config.data, { strategy: "output" });
|
|
721
|
+
const [dataRequired, dataSchema] = await this.converter.convert(config.data, { ...baseSchemaConvertOptions, strategy: "output" });
|
|
608
722
|
errors[status] ??= [];
|
|
609
723
|
errors[status].push({
|
|
610
724
|
type: "object",
|
|
@@ -626,17 +740,7 @@ ${errors.join("\n\n")}`
|
|
|
626
740
|
content: toOpenAPIContent({
|
|
627
741
|
oneOf: [
|
|
628
742
|
...schemas,
|
|
629
|
-
|
|
630
|
-
type: "object",
|
|
631
|
-
properties: {
|
|
632
|
-
defined: { const: false },
|
|
633
|
-
code: { type: "string" },
|
|
634
|
-
status: { type: "number" },
|
|
635
|
-
message: { type: "string" },
|
|
636
|
-
data: {}
|
|
637
|
-
},
|
|
638
|
-
required: ["defined", "code", "status", "message"]
|
|
639
|
-
}
|
|
743
|
+
undefinedErrorSchema
|
|
640
744
|
]
|
|
641
745
|
})
|
|
642
746
|
};
|
|
@@ -644,4 +748,4 @@ ${errors.join("\n\n")}`
|
|
|
644
748
|
}
|
|
645
749
|
}
|
|
646
750
|
|
|
647
|
-
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 };
|
|
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 };
|