@orpc/openapi 0.0.0-next.3cc45a9 → 0.0.0-next.3d25567
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 +17 -0
- package/dist/adapters/aws-lambda/index.d.ts +17 -0
- package/dist/adapters/aws-lambda/index.mjs +18 -0
- package/dist/adapters/fetch/index.d.mts +1 -1
- package/dist/adapters/fetch/index.d.ts +1 -1
- package/dist/adapters/fetch/index.mjs +2 -1
- package/dist/adapters/node/index.d.mts +1 -1
- package/dist/adapters/node/index.d.ts +1 -1
- package/dist/adapters/node/index.mjs +2 -1
- package/dist/adapters/standard/index.d.mts +2 -1
- package/dist/adapters/standard/index.d.ts +2 -1
- package/dist/adapters/standard/index.mjs +2 -1
- package/dist/index.d.mts +23 -12
- package/dist/index.d.ts +23 -12
- package/dist/index.mjs +3 -3
- package/dist/plugins/index.d.mts +9 -10
- package/dist/plugins/index.d.ts +9 -10
- package/dist/plugins/index.mjs +1 -1
- package/dist/shared/{openapi.p5tsmBXx.mjs → openapi.-sXpEIAO.mjs} +28 -7
- package/dist/shared/openapi.B3hexduL.d.mts +101 -0
- package/dist/shared/openapi.B3hexduL.d.ts +101 -0
- package/dist/shared/{openapi.D3j94c9n.d.mts → openapi.BWrlhfev.d.mts} +2 -2
- package/dist/shared/{openapi.D3j94c9n.d.ts → openapi.BWrlhfev.d.ts} +2 -2
- package/dist/shared/{openapi.DqPCYbM9.mjs → openapi.DrrBsJ0w.mjs} +255 -65
- package/package.json +14 -10
- package/dist/shared/openapi.CwdCLgSU.d.mts +0 -53
- package/dist/shared/openapi.CwdCLgSU.d.ts +0 -53
|
@@ -0,0 +1,101 @@
|
|
|
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 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: 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
|
+
* @default () => false
|
|
48
|
+
*/
|
|
49
|
+
exclude?: (procedure: AnyProcedure | AnyContractProcedure, path: readonly string[]) => boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Common schemas to be used for $ref resolution.
|
|
52
|
+
*/
|
|
53
|
+
commonSchemas?: Record<string, {
|
|
54
|
+
/**
|
|
55
|
+
* Determines which schema definition to use when input and output schemas differ.
|
|
56
|
+
* This is needed because some schemas transform data differently between input and output,
|
|
57
|
+
* making it impossible to use a single $ref for both cases.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* // This schema transforms a string input into a number output
|
|
62
|
+
* const Schema = z.string()
|
|
63
|
+
* .transform(v => Number(v))
|
|
64
|
+
* .pipe(z.number())
|
|
65
|
+
*
|
|
66
|
+
* // Input schema: { type: 'string' }
|
|
67
|
+
* // Output schema: { type: 'number' }
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
* When schemas differ between input and output, you must explicitly choose
|
|
71
|
+
* which version to use for the OpenAPI specification.
|
|
72
|
+
*
|
|
73
|
+
* @default 'input' - Uses the input schema definition by default
|
|
74
|
+
*/
|
|
75
|
+
strategy?: SchemaConvertOptions['strategy'];
|
|
76
|
+
schema: AnySchema;
|
|
77
|
+
} | {
|
|
78
|
+
error: 'UndefinedError';
|
|
79
|
+
schema?: never;
|
|
80
|
+
}>;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The generator that converts oRPC routers/contracts to OpenAPI specifications.
|
|
84
|
+
*
|
|
85
|
+
* @see {@link https://orpc.unnoq.com/docs/openapi/openapi-specification OpenAPI Specification Docs}
|
|
86
|
+
*/
|
|
87
|
+
declare class OpenAPIGenerator {
|
|
88
|
+
#private;
|
|
89
|
+
private readonly serializer;
|
|
90
|
+
private readonly converter;
|
|
91
|
+
constructor(options?: OpenAPIGeneratorOptions);
|
|
92
|
+
/**
|
|
93
|
+
* Generates OpenAPI specifications from oRPC routers/contracts.
|
|
94
|
+
*
|
|
95
|
+
* @see {@link https://orpc.unnoq.com/docs/openapi/openapi-specification OpenAPI Specification Docs}
|
|
96
|
+
*/
|
|
97
|
+
generate(router: AnyContractRouter | AnyRouter, options?: OpenAPIGeneratorGenerateOptions): Promise<OpenAPI.Document>;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export { OpenAPIGenerator as b, CompositeSchemaConverter as e };
|
|
101
|
+
export type { ConditionalSchemaConverter as C, OpenAPIGeneratorOptions as O, SchemaConverterComponent as S, OpenAPIGeneratorGenerateOptions as a, SchemaConvertOptions as c, SchemaConverter as d };
|
|
@@ -0,0 +1,101 @@
|
|
|
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 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: 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
|
+
* @default () => false
|
|
48
|
+
*/
|
|
49
|
+
exclude?: (procedure: AnyProcedure | AnyContractProcedure, path: readonly string[]) => boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Common schemas to be used for $ref resolution.
|
|
52
|
+
*/
|
|
53
|
+
commonSchemas?: Record<string, {
|
|
54
|
+
/**
|
|
55
|
+
* Determines which schema definition to use when input and output schemas differ.
|
|
56
|
+
* This is needed because some schemas transform data differently between input and output,
|
|
57
|
+
* making it impossible to use a single $ref for both cases.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* // This schema transforms a string input into a number output
|
|
62
|
+
* const Schema = z.string()
|
|
63
|
+
* .transform(v => Number(v))
|
|
64
|
+
* .pipe(z.number())
|
|
65
|
+
*
|
|
66
|
+
* // Input schema: { type: 'string' }
|
|
67
|
+
* // Output schema: { type: 'number' }
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
* When schemas differ between input and output, you must explicitly choose
|
|
71
|
+
* which version to use for the OpenAPI specification.
|
|
72
|
+
*
|
|
73
|
+
* @default 'input' - Uses the input schema definition by default
|
|
74
|
+
*/
|
|
75
|
+
strategy?: SchemaConvertOptions['strategy'];
|
|
76
|
+
schema: AnySchema;
|
|
77
|
+
} | {
|
|
78
|
+
error: 'UndefinedError';
|
|
79
|
+
schema?: never;
|
|
80
|
+
}>;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The generator that converts oRPC routers/contracts to OpenAPI specifications.
|
|
84
|
+
*
|
|
85
|
+
* @see {@link https://orpc.unnoq.com/docs/openapi/openapi-specification OpenAPI Specification Docs}
|
|
86
|
+
*/
|
|
87
|
+
declare class OpenAPIGenerator {
|
|
88
|
+
#private;
|
|
89
|
+
private readonly serializer;
|
|
90
|
+
private readonly converter;
|
|
91
|
+
constructor(options?: OpenAPIGeneratorOptions);
|
|
92
|
+
/**
|
|
93
|
+
* Generates OpenAPI specifications from oRPC routers/contracts.
|
|
94
|
+
*
|
|
95
|
+
* @see {@link https://orpc.unnoq.com/docs/openapi/openapi-specification OpenAPI Specification Docs}
|
|
96
|
+
*/
|
|
97
|
+
generate(router: AnyContractRouter | AnyRouter, options?: OpenAPIGeneratorGenerateOptions): Promise<OpenAPI.Document>;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export { OpenAPIGenerator as b, CompositeSchemaConverter as e };
|
|
101
|
+
export type { ConditionalSchemaConverter as C, OpenAPIGeneratorOptions as O, SchemaConverterComponent as S, OpenAPIGeneratorGenerateOptions as a, SchemaConvertOptions as c, SchemaConverter as d };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { StandardOpenAPIJsonSerializerOptions } from '@orpc/openapi-client/standard';
|
|
1
|
+
import { StandardOpenAPIJsonSerializerOptions, StandardBracketNotationSerializerOptions } from '@orpc/openapi-client/standard';
|
|
2
2
|
import { Context, Router } from '@orpc/server';
|
|
3
3
|
import { StandardHandlerOptions, StandardHandler } from '@orpc/server/standard';
|
|
4
4
|
|
|
5
|
-
interface StandardOpenAPIHandlerOptions<T extends Context> extends StandardHandlerOptions<T>, StandardOpenAPIJsonSerializerOptions {
|
|
5
|
+
interface StandardOpenAPIHandlerOptions<T extends Context> extends StandardHandlerOptions<T>, StandardOpenAPIJsonSerializerOptions, StandardBracketNotationSerializerOptions {
|
|
6
6
|
}
|
|
7
7
|
declare class StandardOpenAPIHandler<T extends Context> extends StandardHandler<T> {
|
|
8
8
|
constructor(router: Router<any, T>, options: NoInfer<StandardOpenAPIHandlerOptions<T>>);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { StandardOpenAPIJsonSerializerOptions } from '@orpc/openapi-client/standard';
|
|
1
|
+
import { StandardOpenAPIJsonSerializerOptions, StandardBracketNotationSerializerOptions } from '@orpc/openapi-client/standard';
|
|
2
2
|
import { Context, Router } from '@orpc/server';
|
|
3
3
|
import { StandardHandlerOptions, StandardHandler } from '@orpc/server/standard';
|
|
4
4
|
|
|
5
|
-
interface StandardOpenAPIHandlerOptions<T extends Context> extends StandardHandlerOptions<T>, StandardOpenAPIJsonSerializerOptions {
|
|
5
|
+
interface StandardOpenAPIHandlerOptions<T extends Context> extends StandardHandlerOptions<T>, StandardOpenAPIJsonSerializerOptions, StandardBracketNotationSerializerOptions {
|
|
6
6
|
}
|
|
7
7
|
declare class StandardOpenAPIHandler<T extends Context> extends StandardHandler<T> {
|
|
8
8
|
constructor(router: Router<any, T>, options: NoInfer<StandardOpenAPIHandlerOptions<T>>);
|
|
@@ -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 } 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) {
|
|
@@ -184,6 +184,57 @@ function applySchemaOptionality(required, schema) {
|
|
|
184
184
|
]
|
|
185
185
|
};
|
|
186
186
|
}
|
|
187
|
+
function expandUnionSchema(schema) {
|
|
188
|
+
if (typeof schema === "object") {
|
|
189
|
+
for (const keyword of ["anyOf", "oneOf"]) {
|
|
190
|
+
if (schema[keyword] && Object.keys(schema).every(
|
|
191
|
+
(k) => k === keyword || !LOGIC_KEYWORDS.includes(k)
|
|
192
|
+
)) {
|
|
193
|
+
return schema[keyword].flatMap((s) => expandUnionSchema(s));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return [schema];
|
|
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
|
+
}
|
|
187
238
|
|
|
188
239
|
function toOpenAPIPath(path) {
|
|
189
240
|
return standardizeHTTPPath(path).replace(/\/\{\+([^}]+)\}/g, "/{$1}");
|
|
@@ -256,13 +307,26 @@ function toOpenAPIParameters(schema, parameterIn) {
|
|
|
256
307
|
const parameters = [];
|
|
257
308
|
for (const key in schema.properties) {
|
|
258
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
|
+
}
|
|
259
321
|
parameters.push({
|
|
260
322
|
name: key,
|
|
261
323
|
in: parameterIn,
|
|
262
324
|
required: schema.required?.includes(key),
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
|
266
330
|
});
|
|
267
331
|
}
|
|
268
332
|
return parameters;
|
|
@@ -281,6 +345,15 @@ function checkParamsSchema(schema, params) {
|
|
|
281
345
|
function toOpenAPISchema(schema) {
|
|
282
346
|
return schema === true ? {} : schema === false ? { not: {} } : schema;
|
|
283
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
|
+
}
|
|
284
357
|
|
|
285
358
|
class CompositeSchemaConverter {
|
|
286
359
|
converters;
|
|
@@ -317,8 +390,10 @@ class OpenAPIGenerator {
|
|
|
317
390
|
...clone(options),
|
|
318
391
|
info: options.info ?? { title: "API Reference", version: "0.0.0" },
|
|
319
392
|
openapi: "3.1.1",
|
|
320
|
-
exclude: void 0
|
|
393
|
+
exclude: void 0,
|
|
394
|
+
commonSchemas: void 0
|
|
321
395
|
};
|
|
396
|
+
const { baseSchemaConvertOptions, undefinedErrorJsonSchema } = await this.#resolveCommonSchemas(doc, options.commonSchemas);
|
|
322
397
|
const contracts = [];
|
|
323
398
|
await resolveContractProcedures({ path: [], router }, ({ contract, path }) => {
|
|
324
399
|
if (!exclude(contract, path)) {
|
|
@@ -332,16 +407,21 @@ class OpenAPIGenerator {
|
|
|
332
407
|
const def = contract["~orpc"];
|
|
333
408
|
const method = toOpenAPIMethod(fallbackContractConfig("defaultMethod", def.route.method));
|
|
334
409
|
const httpPath = toOpenAPIPath(def.route.path ?? toHttpPath(path));
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
410
|
+
let operationObjectRef;
|
|
411
|
+
if (def.route.spec !== void 0) {
|
|
412
|
+
operationObjectRef = def.route.spec;
|
|
413
|
+
} else {
|
|
414
|
+
operationObjectRef = {
|
|
415
|
+
operationId,
|
|
416
|
+
summary: def.route.summary,
|
|
417
|
+
description: def.route.description,
|
|
418
|
+
deprecated: def.route.deprecated,
|
|
419
|
+
tags: def.route.tags?.map((tag) => tag)
|
|
420
|
+
};
|
|
421
|
+
await this.#request(doc, operationObjectRef, def, baseSchemaConvertOptions);
|
|
422
|
+
await this.#successResponse(doc, operationObjectRef, def, baseSchemaConvertOptions);
|
|
423
|
+
await this.#errorResponse(operationObjectRef, def, baseSchemaConvertOptions, undefinedErrorJsonSchema);
|
|
424
|
+
}
|
|
345
425
|
doc.paths ??= {};
|
|
346
426
|
doc.paths[httpPath] ??= {};
|
|
347
427
|
doc.paths[httpPath][method] = applyCustomOpenAPIOperation(operationObjectRef, contract);
|
|
@@ -364,22 +444,96 @@ ${errors.join("\n\n")}`
|
|
|
364
444
|
}
|
|
365
445
|
return this.serializer.serialize(doc)[0];
|
|
366
446
|
}
|
|
367
|
-
async #
|
|
447
|
+
async #resolveCommonSchemas(doc, commonSchemas) {
|
|
448
|
+
let undefinedErrorJsonSchema = {
|
|
449
|
+
type: "object",
|
|
450
|
+
properties: {
|
|
451
|
+
defined: { const: false },
|
|
452
|
+
code: { type: "string" },
|
|
453
|
+
status: { type: "number" },
|
|
454
|
+
message: { type: "string" },
|
|
455
|
+
data: {}
|
|
456
|
+
},
|
|
457
|
+
required: ["defined", "code", "status", "message"]
|
|
458
|
+
};
|
|
459
|
+
const baseSchemaConvertOptions = {};
|
|
460
|
+
if (commonSchemas) {
|
|
461
|
+
baseSchemaConvertOptions.components = [];
|
|
462
|
+
for (const key in commonSchemas) {
|
|
463
|
+
const options = commonSchemas[key];
|
|
464
|
+
if (options.schema === void 0) {
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
const { schema, strategy = "input" } = options;
|
|
468
|
+
const [required, json] = await this.converter.convert(schema, { strategy });
|
|
469
|
+
const allowedStrategies = [strategy];
|
|
470
|
+
if (strategy === "input") {
|
|
471
|
+
const [outputRequired, outputJson] = await this.converter.convert(schema, { strategy: "output" });
|
|
472
|
+
if (outputRequired === required && stringifyJSON(outputJson) === stringifyJSON(json)) {
|
|
473
|
+
allowedStrategies.push("output");
|
|
474
|
+
}
|
|
475
|
+
} else if (strategy === "output") {
|
|
476
|
+
const [inputRequired, inputJson] = await this.converter.convert(schema, { strategy: "input" });
|
|
477
|
+
if (inputRequired === required && stringifyJSON(inputJson) === stringifyJSON(json)) {
|
|
478
|
+
allowedStrategies.push("input");
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
baseSchemaConvertOptions.components.push({
|
|
482
|
+
schema,
|
|
483
|
+
required,
|
|
484
|
+
ref: `#/components/schemas/${key}`,
|
|
485
|
+
allowedStrategies
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
doc.components ??= {};
|
|
489
|
+
doc.components.schemas ??= {};
|
|
490
|
+
for (const key in commonSchemas) {
|
|
491
|
+
const options = commonSchemas[key];
|
|
492
|
+
if (options.schema === void 0) {
|
|
493
|
+
if (options.error === "UndefinedError") {
|
|
494
|
+
doc.components.schemas[key] = toOpenAPISchema(undefinedErrorJsonSchema);
|
|
495
|
+
undefinedErrorJsonSchema = { $ref: `#/components/schemas/${key}` };
|
|
496
|
+
}
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
const { schema, strategy = "input" } = options;
|
|
500
|
+
const [, json] = await this.converter.convert(
|
|
501
|
+
schema,
|
|
502
|
+
{
|
|
503
|
+
...baseSchemaConvertOptions,
|
|
504
|
+
strategy,
|
|
505
|
+
minStructureDepthForRef: 1
|
|
506
|
+
// not allow use $ref for root schemas
|
|
507
|
+
}
|
|
508
|
+
);
|
|
509
|
+
doc.components.schemas[key] = toOpenAPISchema(json);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
return { baseSchemaConvertOptions, undefinedErrorJsonSchema };
|
|
513
|
+
}
|
|
514
|
+
async #request(doc, ref, def, baseSchemaConvertOptions) {
|
|
368
515
|
const method = fallbackContractConfig("defaultMethod", def.route.method);
|
|
369
516
|
const details = getEventIteratorSchemaDetails(def.inputSchema);
|
|
370
517
|
if (details) {
|
|
371
518
|
ref.requestBody = {
|
|
372
519
|
required: true,
|
|
373
520
|
content: toOpenAPIEventIteratorContent(
|
|
374
|
-
await this.converter.convert(details.yields, { strategy: "input" }),
|
|
375
|
-
await this.converter.convert(details.returns, { strategy: "input" })
|
|
521
|
+
await this.converter.convert(details.yields, { ...baseSchemaConvertOptions, strategy: "input" }),
|
|
522
|
+
await this.converter.convert(details.returns, { ...baseSchemaConvertOptions, strategy: "input" })
|
|
376
523
|
)
|
|
377
524
|
};
|
|
378
525
|
return;
|
|
379
526
|
}
|
|
380
527
|
const dynamicParams = getDynamicParams(def.route.path)?.map((v) => v.name);
|
|
381
528
|
const inputStructure = fallbackContractConfig("defaultInputStructure", def.route.inputStructure);
|
|
382
|
-
let [required, schema] = await this.converter.convert(
|
|
529
|
+
let [required, schema] = await this.converter.convert(
|
|
530
|
+
def.inputSchema,
|
|
531
|
+
{
|
|
532
|
+
...baseSchemaConvertOptions,
|
|
533
|
+
strategy: "input",
|
|
534
|
+
minStructureDepthForRef: dynamicParams?.length || inputStructure === "detailed" ? 1 : 0
|
|
535
|
+
}
|
|
536
|
+
);
|
|
383
537
|
if (isAnySchema(schema) && !dynamicParams?.length) {
|
|
384
538
|
return;
|
|
385
539
|
}
|
|
@@ -422,7 +576,8 @@ ${errors.join("\n\n")}`
|
|
|
422
576
|
if (!isObjectSchema(schema)) {
|
|
423
577
|
throw error;
|
|
424
578
|
}
|
|
425
|
-
|
|
579
|
+
const resolvedParamSchema = schema.properties?.params !== void 0 ? resolveOpenAPIJsonSchemaRef(doc, schema.properties.params) : void 0;
|
|
580
|
+
if (dynamicParams?.length && (resolvedParamSchema === void 0 || !isObjectSchema(resolvedParamSchema) || !checkParamsSchema(resolvedParamSchema, dynamicParams))) {
|
|
426
581
|
throw new OpenAPIGeneratorError(
|
|
427
582
|
'When input structure is "detailed" and path has dynamic params, the "params" schema must be an object with all dynamic params as required.'
|
|
428
583
|
);
|
|
@@ -430,12 +585,13 @@ ${errors.join("\n\n")}`
|
|
|
430
585
|
for (const from of ["params", "query", "headers"]) {
|
|
431
586
|
const fromSchema = schema.properties?.[from];
|
|
432
587
|
if (fromSchema !== void 0) {
|
|
433
|
-
|
|
588
|
+
const resolvedSchema = resolveOpenAPIJsonSchemaRef(doc, fromSchema);
|
|
589
|
+
if (!isObjectSchema(resolvedSchema)) {
|
|
434
590
|
throw error;
|
|
435
591
|
}
|
|
436
592
|
const parameterIn = from === "params" ? "path" : from === "headers" ? "header" : "query";
|
|
437
593
|
ref.parameters ??= [];
|
|
438
|
-
ref.parameters.push(...toOpenAPIParameters(
|
|
594
|
+
ref.parameters.push(...toOpenAPIParameters(resolvedSchema, parameterIn));
|
|
439
595
|
}
|
|
440
596
|
}
|
|
441
597
|
if (schema.properties?.body !== void 0) {
|
|
@@ -445,7 +601,7 @@ ${errors.join("\n\n")}`
|
|
|
445
601
|
};
|
|
446
602
|
}
|
|
447
603
|
}
|
|
448
|
-
async #successResponse(ref, def) {
|
|
604
|
+
async #successResponse(doc, ref, def, baseSchemaConvertOptions) {
|
|
449
605
|
const outputSchema = def.outputSchema;
|
|
450
606
|
const status = fallbackContractConfig("defaultSuccessStatus", def.route.successStatus);
|
|
451
607
|
const description = fallbackContractConfig("defaultSuccessDescription", def.route?.successDescription);
|
|
@@ -456,46 +612,90 @@ ${errors.join("\n\n")}`
|
|
|
456
612
|
ref.responses[status] = {
|
|
457
613
|
description,
|
|
458
614
|
content: toOpenAPIEventIteratorContent(
|
|
459
|
-
await this.converter.convert(eventIteratorSchemaDetails.yields, { strategy: "output" }),
|
|
460
|
-
await this.converter.convert(eventIteratorSchemaDetails.returns, { strategy: "output" })
|
|
615
|
+
await this.converter.convert(eventIteratorSchemaDetails.yields, { ...baseSchemaConvertOptions, strategy: "output" }),
|
|
616
|
+
await this.converter.convert(eventIteratorSchemaDetails.returns, { ...baseSchemaConvertOptions, strategy: "output" })
|
|
461
617
|
)
|
|
462
618
|
};
|
|
463
619
|
return;
|
|
464
620
|
}
|
|
465
|
-
const [required, json] = await this.converter.convert(
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
621
|
+
const [required, json] = await this.converter.convert(
|
|
622
|
+
outputSchema,
|
|
623
|
+
{
|
|
624
|
+
...baseSchemaConvertOptions,
|
|
625
|
+
strategy: "output",
|
|
626
|
+
minStructureDepthForRef: outputStructure === "detailed" ? 1 : 0
|
|
627
|
+
}
|
|
628
|
+
);
|
|
470
629
|
if (outputStructure === "compact") {
|
|
630
|
+
ref.responses ??= {};
|
|
631
|
+
ref.responses[status] = {
|
|
632
|
+
description
|
|
633
|
+
};
|
|
471
634
|
ref.responses[status].content = toOpenAPIContent(applySchemaOptionality(required, json));
|
|
472
635
|
return;
|
|
473
636
|
}
|
|
474
|
-
const
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
637
|
+
const handledStatuses = /* @__PURE__ */ new Set();
|
|
638
|
+
for (const item of expandUnionSchema(json)) {
|
|
639
|
+
const error = new OpenAPIGeneratorError(`
|
|
640
|
+
When output structure is "detailed", output schema must satisfy:
|
|
641
|
+
{
|
|
642
|
+
status?: number, // must be a literal number and in the range of 200-399
|
|
643
|
+
headers?: Record<string, unknown>,
|
|
644
|
+
body?: unknown
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
But got: ${stringifyJSON(item)}
|
|
648
|
+
`);
|
|
649
|
+
if (!isObjectSchema(item)) {
|
|
482
650
|
throw error;
|
|
483
651
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
652
|
+
let schemaStatus;
|
|
653
|
+
let schemaDescription;
|
|
654
|
+
if (item.properties?.status !== void 0) {
|
|
655
|
+
const statusSchema = resolveOpenAPIJsonSchemaRef(doc, item.properties.status);
|
|
656
|
+
if (typeof statusSchema !== "object" || statusSchema.const === void 0 || typeof statusSchema.const !== "number" || !Number.isInteger(statusSchema.const) || isORPCErrorStatus(statusSchema.const)) {
|
|
657
|
+
throw error;
|
|
658
|
+
}
|
|
659
|
+
schemaStatus = statusSchema.const;
|
|
660
|
+
schemaDescription = statusSchema.description;
|
|
661
|
+
}
|
|
662
|
+
const itemStatus = schemaStatus ?? status;
|
|
663
|
+
const itemDescription = schemaDescription ?? description;
|
|
664
|
+
if (handledStatuses.has(itemStatus)) {
|
|
665
|
+
throw new OpenAPIGeneratorError(`
|
|
666
|
+
When output structure is "detailed", each success status must be unique.
|
|
667
|
+
But got status: ${itemStatus} used more than once.
|
|
668
|
+
`);
|
|
669
|
+
}
|
|
670
|
+
handledStatuses.add(itemStatus);
|
|
671
|
+
ref.responses ??= {};
|
|
672
|
+
ref.responses[itemStatus] = {
|
|
673
|
+
description: itemDescription
|
|
674
|
+
};
|
|
675
|
+
if (item.properties?.headers !== void 0) {
|
|
676
|
+
const headersSchema = resolveOpenAPIJsonSchemaRef(doc, item.properties.headers);
|
|
677
|
+
if (!isObjectSchema(headersSchema)) {
|
|
678
|
+
throw error;
|
|
679
|
+
}
|
|
680
|
+
for (const key in headersSchema.properties) {
|
|
681
|
+
const headerSchema = headersSchema.properties[key];
|
|
682
|
+
if (headerSchema !== void 0) {
|
|
683
|
+
ref.responses[itemStatus].headers ??= {};
|
|
684
|
+
ref.responses[itemStatus].headers[key] = {
|
|
685
|
+
schema: toOpenAPISchema(headerSchema),
|
|
686
|
+
required: item.required?.includes("headers") && headersSchema.required?.includes(key)
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
if (item.properties?.body !== void 0) {
|
|
692
|
+
ref.responses[itemStatus].content = toOpenAPIContent(
|
|
693
|
+
applySchemaOptionality(item.required?.includes("body") ?? false, item.properties.body)
|
|
694
|
+
);
|
|
490
695
|
}
|
|
491
|
-
}
|
|
492
|
-
if (json.properties?.body !== void 0) {
|
|
493
|
-
ref.responses[status].content = toOpenAPIContent(
|
|
494
|
-
applySchemaOptionality(json.required?.includes("body") ?? false, json.properties.body)
|
|
495
|
-
);
|
|
496
696
|
}
|
|
497
697
|
}
|
|
498
|
-
async #errorResponse(ref, def) {
|
|
698
|
+
async #errorResponse(ref, def, baseSchemaConvertOptions, undefinedErrorSchema) {
|
|
499
699
|
const errorMap = def.errorMap;
|
|
500
700
|
const errors = {};
|
|
501
701
|
for (const code in errorMap) {
|
|
@@ -505,7 +705,7 @@ ${errors.join("\n\n")}`
|
|
|
505
705
|
}
|
|
506
706
|
const status = fallbackORPCErrorStatus(code, config.status);
|
|
507
707
|
const message = fallbackORPCErrorMessage(code, config.message);
|
|
508
|
-
const [dataRequired, dataSchema] = await this.converter.convert(config.data, { strategy: "output" });
|
|
708
|
+
const [dataRequired, dataSchema] = await this.converter.convert(config.data, { ...baseSchemaConvertOptions, strategy: "output" });
|
|
509
709
|
errors[status] ??= [];
|
|
510
710
|
errors[status].push({
|
|
511
711
|
type: "object",
|
|
@@ -527,17 +727,7 @@ ${errors.join("\n\n")}`
|
|
|
527
727
|
content: toOpenAPIContent({
|
|
528
728
|
oneOf: [
|
|
529
729
|
...schemas,
|
|
530
|
-
|
|
531
|
-
type: "object",
|
|
532
|
-
properties: {
|
|
533
|
-
defined: { const: false },
|
|
534
|
-
code: { type: "string" },
|
|
535
|
-
status: { type: "number" },
|
|
536
|
-
message: { type: "string" },
|
|
537
|
-
data: {}
|
|
538
|
-
},
|
|
539
|
-
required: ["defined", "code", "status", "message"]
|
|
540
|
-
}
|
|
730
|
+
undefinedErrorSchema
|
|
541
731
|
]
|
|
542
732
|
})
|
|
543
733
|
};
|
|
@@ -545,4 +735,4 @@ ${errors.join("\n\n")}`
|
|
|
545
735
|
}
|
|
546
736
|
}
|
|
547
737
|
|
|
548
|
-
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 };
|
|
738
|
+
export { CompositeSchemaConverter as C, LOGIC_KEYWORDS as L, OpenAPIGenerator as O, applyCustomOpenAPIOperation as a, toOpenAPIMethod as b, customOpenAPIOperation as c, toOpenAPIContent as d, toOpenAPIEventIteratorContent as e, toOpenAPIParameters as f, getCustomOpenAPIOperation as g, checkParamsSchema as h, toOpenAPISchema as i, isFileSchema as j, isObjectSchema as k, isAnySchema as l, filterSchemaBranches as m, applySchemaOptionality as n, expandUnionSchema as o, expandArrayableSchema as p, isPrimitiveSchema as q, resolveOpenAPIJsonSchemaRef as r, separateObjectSchema as s, toOpenAPIPath as t };
|