@orpc/openapi 0.21.0 → 0.23.0
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/dist/fetch.js +100 -33
- package/dist/index.js +213 -94
- package/dist/src/fetch/index.d.ts +2 -2
- package/dist/src/fetch/input-structure-compact.d.ts +6 -0
- package/dist/src/fetch/{input-builder-full.d.ts → input-structure-detailed.d.ts} +3 -3
- package/dist/src/fetch/openapi-handler.d.ts +10 -7
- package/dist/src/fetch/openapi-payload-codec.d.ts +1 -1
- package/dist/src/openapi-error.d.ts +3 -0
- package/dist/src/openapi-generator.d.ts +6 -1
- package/dist/src/openapi-input-structure-parser.d.ts +22 -0
- package/dist/src/openapi-output-structure-parser.d.ts +18 -0
- package/dist/src/openapi-parameters-builder.d.ts +3 -0
- package/package.json +4 -4
- package/dist/src/fetch/input-builder-simple.d.ts +0 -6
package/dist/fetch.js
CHANGED
|
@@ -213,21 +213,9 @@ function parsePath(path) {
|
|
|
213
213
|
return result;
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
-
// src/fetch/input-
|
|
217
|
-
var InputBuilderFull = class {
|
|
218
|
-
build(params, query, headers, body) {
|
|
219
|
-
return {
|
|
220
|
-
params,
|
|
221
|
-
query,
|
|
222
|
-
headers,
|
|
223
|
-
body
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
// src/fetch/input-builder-simple.ts
|
|
216
|
+
// src/fetch/input-structure-compact.ts
|
|
229
217
|
import { isPlainObject as isPlainObject2 } from "@orpc/shared";
|
|
230
|
-
var
|
|
218
|
+
var InputStructureCompact = class {
|
|
231
219
|
build(params, payload) {
|
|
232
220
|
if (Object.keys(params).length === 0) {
|
|
233
221
|
return payload;
|
|
@@ -242,9 +230,21 @@ var InputBuilderSimple = class {
|
|
|
242
230
|
}
|
|
243
231
|
};
|
|
244
232
|
|
|
233
|
+
// src/fetch/input-structure-detailed.ts
|
|
234
|
+
var InputStructureDetailed = class {
|
|
235
|
+
build(params, query, headers, body) {
|
|
236
|
+
return {
|
|
237
|
+
params,
|
|
238
|
+
query,
|
|
239
|
+
headers,
|
|
240
|
+
body
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
|
|
245
245
|
// src/fetch/openapi-handler.ts
|
|
246
246
|
import { createProcedureClient, ORPCError as ORPCError2 } from "@orpc/server";
|
|
247
|
-
import { executeWithHooks, ORPC_HANDLER_HEADER, trim } from "@orpc/shared";
|
|
247
|
+
import { executeWithHooks, isPlainObject as isPlainObject3, ORPC_HANDLER_HEADER, trim } from "@orpc/shared";
|
|
248
248
|
|
|
249
249
|
// src/fetch/openapi-payload-codec.ts
|
|
250
250
|
import { ORPCError } from "@orpc/server";
|
|
@@ -483,14 +483,14 @@ var OpenAPIHandler = class {
|
|
|
483
483
|
const jsonSerializer = options?.jsonSerializer ?? new JSONSerializer();
|
|
484
484
|
this.procedureMatcher = options?.procedureMatcher ?? new OpenAPIProcedureMatcher(hono, router);
|
|
485
485
|
this.payloadCodec = options?.payloadCodec ?? new OpenAPIPayloadCodec(jsonSerializer);
|
|
486
|
-
this.
|
|
487
|
-
this.
|
|
486
|
+
this.inputStructureCompact = options?.inputBuilderSimple ?? new InputStructureCompact();
|
|
487
|
+
this.inputStructureDetailed = options?.inputBuilderFull ?? new InputStructureDetailed();
|
|
488
488
|
this.compositeSchemaCoercer = new CompositeSchemaCoercer(options?.schemaCoercers ?? []);
|
|
489
489
|
}
|
|
490
490
|
procedureMatcher;
|
|
491
491
|
payloadCodec;
|
|
492
|
-
|
|
493
|
-
|
|
492
|
+
inputStructureCompact;
|
|
493
|
+
inputStructureDetailed;
|
|
494
494
|
compositeSchemaCoercer;
|
|
495
495
|
condition(request) {
|
|
496
496
|
return request.headers.get(ORPC_HANDLER_HEADER) === null;
|
|
@@ -504,24 +504,24 @@ var OpenAPIHandler = class {
|
|
|
504
504
|
const pathname = `/${trim(url.pathname.replace(options?.prefix ?? "", ""), "/")}`;
|
|
505
505
|
const query = url.searchParams;
|
|
506
506
|
const customMethod = request.method === "POST" ? query.get("method")?.toUpperCase() : void 0;
|
|
507
|
-
const
|
|
508
|
-
const
|
|
509
|
-
if (!
|
|
507
|
+
const matchedMethod = customMethod || request.method;
|
|
508
|
+
const matched = await this.procedureMatcher.match(matchedMethod, pathname);
|
|
509
|
+
if (!matched) {
|
|
510
510
|
throw new ORPCError2({ code: "NOT_FOUND", message: "Not found" });
|
|
511
511
|
}
|
|
512
|
-
const
|
|
513
|
-
const input = this.
|
|
514
|
-
const coercedInput = this.compositeSchemaCoercer.coerce(
|
|
512
|
+
const contractDef = matched.procedure["~orpc"].contract["~orpc"];
|
|
513
|
+
const input = await this.decodeInput(matched.procedure, matched.params, request);
|
|
514
|
+
const coercedInput = this.compositeSchemaCoercer.coerce(contractDef.InputSchema, input);
|
|
515
515
|
const client = createProcedureClient({
|
|
516
516
|
context,
|
|
517
|
-
procedure:
|
|
518
|
-
path:
|
|
517
|
+
procedure: matched.procedure,
|
|
518
|
+
path: matched.path
|
|
519
519
|
});
|
|
520
520
|
const output = await client(coercedInput, { signal: options?.signal });
|
|
521
|
-
const { body, headers:
|
|
521
|
+
const { body, headers: resHeaders } = this.encodeOutput(matched.procedure, output, accept);
|
|
522
522
|
return new Response(body, {
|
|
523
|
-
headers:
|
|
524
|
-
status:
|
|
523
|
+
headers: resHeaders,
|
|
524
|
+
status: contractDef.route?.successStatus ?? 200
|
|
525
525
|
});
|
|
526
526
|
};
|
|
527
527
|
try {
|
|
@@ -544,7 +544,7 @@ var OpenAPIHandler = class {
|
|
|
544
544
|
});
|
|
545
545
|
} catch (e2) {
|
|
546
546
|
const error2 = this.convertToORPCError(e2);
|
|
547
|
-
const { body, headers: headers2 } = this.payloadCodec.encode(error2.toJSON());
|
|
547
|
+
const { body, headers: headers2 } = this.payloadCodec.encode(error2.toJSON(), void 0);
|
|
548
548
|
return new Response(body, {
|
|
549
549
|
status: error2.status,
|
|
550
550
|
headers: headers2
|
|
@@ -552,6 +552,73 @@ var OpenAPIHandler = class {
|
|
|
552
552
|
}
|
|
553
553
|
}
|
|
554
554
|
}
|
|
555
|
+
async decodeInput(procedure, params, request) {
|
|
556
|
+
const inputStructure = procedure["~orpc"].contract["~orpc"].route?.inputStructure;
|
|
557
|
+
const url = new URL(request.url);
|
|
558
|
+
const query = url.searchParams;
|
|
559
|
+
const headers = request.headers;
|
|
560
|
+
if (!inputStructure || inputStructure === "compact") {
|
|
561
|
+
return this.inputStructureCompact.build(
|
|
562
|
+
params,
|
|
563
|
+
request.method === "GET" ? await this.payloadCodec.decode(query) : await this.payloadCodec.decode(request)
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
const _expect = inputStructure;
|
|
567
|
+
const decodedQuery = await this.payloadCodec.decode(query);
|
|
568
|
+
const decodedHeaders = await this.payloadCodec.decode(headers);
|
|
569
|
+
const decodedBody = await this.payloadCodec.decode(request);
|
|
570
|
+
return this.inputStructureDetailed.build(params, decodedQuery, decodedHeaders, decodedBody);
|
|
571
|
+
}
|
|
572
|
+
encodeOutput(procedure, output, accept) {
|
|
573
|
+
const outputStructure = procedure["~orpc"].contract["~orpc"].route?.outputStructure;
|
|
574
|
+
if (!outputStructure || outputStructure === "compact") {
|
|
575
|
+
return this.payloadCodec.encode(output, accept);
|
|
576
|
+
}
|
|
577
|
+
const _expect = outputStructure;
|
|
578
|
+
this.assertDetailedOutput(output);
|
|
579
|
+
const headers = new Headers();
|
|
580
|
+
if (output.headers) {
|
|
581
|
+
for (const [key, value] of Object.entries(output.headers)) {
|
|
582
|
+
headers.append(key, value);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
const { body, headers: encodedHeaders } = this.payloadCodec.encode(output.body, accept);
|
|
586
|
+
if (encodedHeaders) {
|
|
587
|
+
for (const [key, value] of encodedHeaders.entries()) {
|
|
588
|
+
headers.append(key, value);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
return { body, headers };
|
|
592
|
+
}
|
|
593
|
+
assertDetailedOutput(output) {
|
|
594
|
+
const error = new Error(`
|
|
595
|
+
Invalid output structure for 'detailed' output.
|
|
596
|
+
Expected format:
|
|
597
|
+
{
|
|
598
|
+
body?: unknown; // The main response content (optional)
|
|
599
|
+
headers?: { // Additional headers (optional)
|
|
600
|
+
[key: string]: string;
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
Example:
|
|
605
|
+
{
|
|
606
|
+
body: { message: "Success" },
|
|
607
|
+
headers: { "X-Custom-Header": "Custom-Value" },
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
Fix: Ensure your output matches the expected structure.
|
|
611
|
+
`);
|
|
612
|
+
if (!isPlainObject3(output) || Object.keys(output).some((key) => key !== "body" && key !== "headers")) {
|
|
613
|
+
throw error;
|
|
614
|
+
}
|
|
615
|
+
if (output.headers !== void 0 && !isPlainObject3(output.headers)) {
|
|
616
|
+
throw error;
|
|
617
|
+
}
|
|
618
|
+
if (output.headers && Object.entries(output.headers).some(([key, value]) => typeof key !== "string" || typeof value !== "string")) {
|
|
619
|
+
throw error;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
555
622
|
convertToORPCError(e) {
|
|
556
623
|
return e instanceof ORPCError2 ? e : new ORPCError2({
|
|
557
624
|
code: "INTERNAL_SERVER_ERROR",
|
|
@@ -578,8 +645,8 @@ var OpenAPIServerlessHandler = class extends OpenAPIHandler {
|
|
|
578
645
|
};
|
|
579
646
|
export {
|
|
580
647
|
CompositeSchemaCoercer,
|
|
581
|
-
|
|
582
|
-
|
|
648
|
+
InputStructureCompact,
|
|
649
|
+
InputStructureDetailed,
|
|
583
650
|
OpenAPIHandler,
|
|
584
651
|
OpenAPIPayloadCodec,
|
|
585
652
|
OpenAPIProcedureMatcher,
|
package/dist/index.js
CHANGED
|
@@ -35,8 +35,151 @@ var OpenAPIContentBuilder = class {
|
|
|
35
35
|
}
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
+
// src/openapi-error.ts
|
|
39
|
+
var OpenAPIError = class extends Error {
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// src/openapi-input-structure-parser.ts
|
|
43
|
+
var OpenAPIInputStructureParser = class {
|
|
44
|
+
constructor(schemaConverter, schemaUtils, pathParser) {
|
|
45
|
+
this.schemaConverter = schemaConverter;
|
|
46
|
+
this.schemaUtils = schemaUtils;
|
|
47
|
+
this.pathParser = pathParser;
|
|
48
|
+
}
|
|
49
|
+
parse(contract, structure) {
|
|
50
|
+
const inputSchema = this.schemaConverter.convert(contract["~orpc"].InputSchema, { strategy: "input" });
|
|
51
|
+
const method = contract["~orpc"].route?.method ?? "POST";
|
|
52
|
+
const httpPath = contract["~orpc"].route?.path;
|
|
53
|
+
if (this.schemaUtils.isAnySchema(inputSchema)) {
|
|
54
|
+
return {
|
|
55
|
+
paramsSchema: void 0,
|
|
56
|
+
querySchema: void 0,
|
|
57
|
+
headersSchema: void 0,
|
|
58
|
+
bodySchema: void 0
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
if (structure === "detailed") {
|
|
62
|
+
return this.parseDetailedSchema(inputSchema);
|
|
63
|
+
} else {
|
|
64
|
+
return this.parseCompactSchema(inputSchema, method, httpPath);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
parseDetailedSchema(inputSchema) {
|
|
68
|
+
if (!this.schemaUtils.isObjectSchema(inputSchema)) {
|
|
69
|
+
throw new OpenAPIError(`When input structure is 'detailed', input schema must be an object.`);
|
|
70
|
+
}
|
|
71
|
+
if (inputSchema.properties && Object.keys(inputSchema.properties).some((key) => !["params", "query", "headers", "body"].includes(key))) {
|
|
72
|
+
throw new OpenAPIError(`When input structure is 'detailed', input schema must be only can contain 'params', 'query', 'headers' and 'body' properties.`);
|
|
73
|
+
}
|
|
74
|
+
let paramsSchema = inputSchema.properties?.params;
|
|
75
|
+
let querySchema = inputSchema.properties?.query;
|
|
76
|
+
let headersSchema = inputSchema.properties?.headers;
|
|
77
|
+
const bodySchema = inputSchema.properties?.body;
|
|
78
|
+
if (paramsSchema !== void 0 && this.schemaUtils.isAnySchema(paramsSchema)) {
|
|
79
|
+
paramsSchema = void 0;
|
|
80
|
+
}
|
|
81
|
+
if (paramsSchema !== void 0 && !this.schemaUtils.isObjectSchema(paramsSchema)) {
|
|
82
|
+
throw new OpenAPIError(`When input structure is 'detailed', params schema in input schema must be an object.`);
|
|
83
|
+
}
|
|
84
|
+
if (querySchema !== void 0 && this.schemaUtils.isAnySchema(querySchema)) {
|
|
85
|
+
querySchema = void 0;
|
|
86
|
+
}
|
|
87
|
+
if (querySchema !== void 0 && !this.schemaUtils.isObjectSchema(querySchema)) {
|
|
88
|
+
throw new OpenAPIError(`When input structure is 'detailed', query schema in input schema must be an object.`);
|
|
89
|
+
}
|
|
90
|
+
if (headersSchema !== void 0 && this.schemaUtils.isAnySchema(headersSchema)) {
|
|
91
|
+
headersSchema = void 0;
|
|
92
|
+
}
|
|
93
|
+
if (headersSchema !== void 0 && !this.schemaUtils.isObjectSchema(headersSchema)) {
|
|
94
|
+
throw new OpenAPIError(`When input structure is 'detailed', headers schema in input schema must be an object.`);
|
|
95
|
+
}
|
|
96
|
+
return { paramsSchema, querySchema, headersSchema, bodySchema };
|
|
97
|
+
}
|
|
98
|
+
parseCompactSchema(inputSchema, method, httpPath) {
|
|
99
|
+
const dynamic = httpPath ? this.pathParser.parseDynamicParams(httpPath) : [];
|
|
100
|
+
if (dynamic.length === 0) {
|
|
101
|
+
if (method === "GET") {
|
|
102
|
+
let querySchema = inputSchema;
|
|
103
|
+
if (querySchema !== void 0 && this.schemaUtils.isAnySchema(querySchema)) {
|
|
104
|
+
querySchema = void 0;
|
|
105
|
+
}
|
|
106
|
+
if (querySchema !== void 0 && !this.schemaUtils.isObjectSchema(querySchema)) {
|
|
107
|
+
throw new OpenAPIError(`When input structure is 'compact' and method is 'GET', input schema must be an object.`);
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
paramsSchema: void 0,
|
|
111
|
+
querySchema,
|
|
112
|
+
headersSchema: void 0,
|
|
113
|
+
bodySchema: void 0
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
paramsSchema: void 0,
|
|
118
|
+
querySchema: void 0,
|
|
119
|
+
headersSchema: void 0,
|
|
120
|
+
bodySchema: inputSchema
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
if (!this.schemaUtils.isObjectSchema(inputSchema)) {
|
|
124
|
+
throw new OpenAPIError(`When input structure is 'compact' and path has dynamic parameters, input schema must be an object.`);
|
|
125
|
+
}
|
|
126
|
+
const [params, rest] = this.schemaUtils.separateObjectSchema(inputSchema, dynamic.map((v) => v.name));
|
|
127
|
+
return {
|
|
128
|
+
paramsSchema: params,
|
|
129
|
+
querySchema: method === "GET" ? rest : void 0,
|
|
130
|
+
headersSchema: void 0,
|
|
131
|
+
bodySchema: method !== "GET" ? rest : void 0
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// src/openapi-output-structure-parser.ts
|
|
137
|
+
var OpenAPIOutputStructureParser = class {
|
|
138
|
+
constructor(schemaConverter, schemaUtils) {
|
|
139
|
+
this.schemaConverter = schemaConverter;
|
|
140
|
+
this.schemaUtils = schemaUtils;
|
|
141
|
+
}
|
|
142
|
+
parse(contract, structure) {
|
|
143
|
+
const outputSchema = this.schemaConverter.convert(contract["~orpc"].OutputSchema, { strategy: "output" });
|
|
144
|
+
if (this.schemaUtils.isAnySchema(outputSchema)) {
|
|
145
|
+
return {
|
|
146
|
+
headersSchema: void 0,
|
|
147
|
+
bodySchema: void 0
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
if (structure === "detailed") {
|
|
151
|
+
return this.parseDetailedSchema(outputSchema);
|
|
152
|
+
} else {
|
|
153
|
+
return this.parseCompactSchema(outputSchema);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
parseDetailedSchema(outputSchema) {
|
|
157
|
+
if (!this.schemaUtils.isObjectSchema(outputSchema)) {
|
|
158
|
+
throw new OpenAPIError(`When output structure is 'detailed', output schema must be an object.`);
|
|
159
|
+
}
|
|
160
|
+
if (outputSchema.properties && Object.keys(outputSchema.properties).some((key) => !["headers", "body"].includes(key))) {
|
|
161
|
+
throw new OpenAPIError(`When output structure is 'detailed', output schema must be only can contain 'headers' and 'body' properties.`);
|
|
162
|
+
}
|
|
163
|
+
let headersSchema = outputSchema.properties?.headers;
|
|
164
|
+
const bodySchema = outputSchema.properties?.body;
|
|
165
|
+
if (headersSchema !== void 0 && this.schemaUtils.isAnySchema(headersSchema)) {
|
|
166
|
+
headersSchema = void 0;
|
|
167
|
+
}
|
|
168
|
+
if (headersSchema !== void 0 && !this.schemaUtils.isObjectSchema(headersSchema)) {
|
|
169
|
+
throw new OpenAPIError(`When output structure is 'detailed', headers schema in output schema must be an object.`);
|
|
170
|
+
}
|
|
171
|
+
return { headersSchema, bodySchema };
|
|
172
|
+
}
|
|
173
|
+
parseCompactSchema(outputSchema) {
|
|
174
|
+
return {
|
|
175
|
+
headersSchema: void 0,
|
|
176
|
+
bodySchema: outputSchema
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
38
181
|
// src/openapi-parameters-builder.ts
|
|
39
|
-
import { get, isPlainObject } from "@orpc/shared";
|
|
182
|
+
import { get, isPlainObject, omit } from "@orpc/shared";
|
|
40
183
|
var OpenAPIParametersBuilder = class {
|
|
41
184
|
build(paramIn, jsonSchema, options) {
|
|
42
185
|
const parameters = [];
|
|
@@ -63,6 +206,14 @@ var OpenAPIParametersBuilder = class {
|
|
|
63
206
|
}
|
|
64
207
|
return parameters;
|
|
65
208
|
}
|
|
209
|
+
buildHeadersObject(jsonSchema, options) {
|
|
210
|
+
const parameters = this.build("header", jsonSchema, options);
|
|
211
|
+
const headersObject = {};
|
|
212
|
+
for (const param of parameters) {
|
|
213
|
+
headersObject[param.name] = omit(param, ["name", "in"]);
|
|
214
|
+
}
|
|
215
|
+
return headersObject;
|
|
216
|
+
}
|
|
66
217
|
};
|
|
67
218
|
|
|
68
219
|
// src/openapi-path-parser.ts
|
|
@@ -226,6 +377,8 @@ var OpenAPIGenerator = class {
|
|
|
226
377
|
this.jsonSerializer = options?.jsonSerializer ?? new JSONSerializer();
|
|
227
378
|
this.contentBuilder = options?.contentBuilder ?? new OpenAPIContentBuilder(this.schemaUtils);
|
|
228
379
|
this.pathParser = new OpenAPIPathParser();
|
|
380
|
+
this.inputStructureParser = options?.inputStructureParser ?? new OpenAPIInputStructureParser(this.schemaConverter, this.schemaUtils, this.pathParser);
|
|
381
|
+
this.outputStructureParser = options?.outputStructureParser ?? new OpenAPIOutputStructureParser(this.schemaConverter, this.schemaUtils);
|
|
229
382
|
}
|
|
230
383
|
contentBuilder;
|
|
231
384
|
parametersBuilder;
|
|
@@ -233,6 +386,8 @@ var OpenAPIGenerator = class {
|
|
|
233
386
|
schemaUtils;
|
|
234
387
|
jsonSerializer;
|
|
235
388
|
pathParser;
|
|
389
|
+
inputStructureParser;
|
|
390
|
+
outputStructureParser;
|
|
236
391
|
async generate(router, doc) {
|
|
237
392
|
const builder = new OpenApiBuilder({
|
|
238
393
|
...doc,
|
|
@@ -240,108 +395,72 @@ var OpenAPIGenerator = class {
|
|
|
240
395
|
});
|
|
241
396
|
const rootTags = doc.tags?.map((tag) => tag.name) ?? [];
|
|
242
397
|
await forEachAllContractProcedure(router, ({ contract, path }) => {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const method = def.route?.method ?? "POST";
|
|
248
|
-
const httpPath = def.route?.path ? standardizeHTTPPath(def.route?.path) : `/${path.map(encodeURIComponent).join("/")}`;
|
|
249
|
-
let inputSchema = this.schemaConverter.convert(def.InputSchema, { strategy: "input" });
|
|
250
|
-
const outputSchema = this.schemaConverter.convert(def.OutputSchema, { strategy: "output" });
|
|
251
|
-
const params = (() => {
|
|
252
|
-
const dynamic = this.pathParser.parseDynamicParams(httpPath);
|
|
253
|
-
if (!dynamic.length) {
|
|
254
|
-
return void 0;
|
|
255
|
-
}
|
|
256
|
-
if (this.schemaUtils.isAnySchema(inputSchema)) {
|
|
257
|
-
return void 0;
|
|
398
|
+
try {
|
|
399
|
+
const def = contract["~orpc"];
|
|
400
|
+
if (this.options?.ignoreUndefinedPathProcedures && def.route?.path === void 0) {
|
|
401
|
+
return;
|
|
258
402
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
);
|
|
265
|
-
return void 0;
|
|
266
|
-
}
|
|
267
|
-
const [matched, rest] = this.schemaUtils.separateObjectSchema(inputSchema, dynamic.map((v) => v.name));
|
|
268
|
-
inputSchema = rest;
|
|
269
|
-
return this.parametersBuilder.build("path", matched, {
|
|
270
|
-
example: def.inputExample,
|
|
403
|
+
const method = def.route?.method ?? "POST";
|
|
404
|
+
const httpPath = def.route?.path ? standardizeHTTPPath(def.route?.path) : `/${path.map(encodeURIComponent).join("/")}`;
|
|
405
|
+
const { paramsSchema, querySchema, headersSchema, bodySchema } = this.inputStructureParser.parse(contract, def.route?.inputStructure ?? "compact");
|
|
406
|
+
const { headersSchema: resHeadersSchema, bodySchema: resBodySchema } = this.outputStructureParser.parse(contract, def.route?.outputStructure ?? "compact");
|
|
407
|
+
const params = paramsSchema ? this.parametersBuilder.build("path", paramsSchema, {
|
|
271
408
|
required: true
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
this.
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
return void 0;
|
|
409
|
+
}) : [];
|
|
410
|
+
const query = querySchema ? this.parametersBuilder.build("query", querySchema) : [];
|
|
411
|
+
const headers = headersSchema ? this.parametersBuilder.build("header", headersSchema) : [];
|
|
412
|
+
const parameters = [...params, ...query, ...headers];
|
|
413
|
+
const requestBody = bodySchema !== void 0 ? {
|
|
414
|
+
required: this.schemaUtils.isUndefinableSchema(bodySchema),
|
|
415
|
+
content: this.contentBuilder.build(bodySchema)
|
|
416
|
+
} : void 0;
|
|
417
|
+
const successResponse = {
|
|
418
|
+
description: "OK",
|
|
419
|
+
content: resBodySchema !== void 0 ? this.contentBuilder.build(resBodySchema, {
|
|
420
|
+
example: def.outputExample
|
|
421
|
+
}) : void 0,
|
|
422
|
+
headers: resHeadersSchema !== void 0 ? this.parametersBuilder.buildHeadersObject(resHeadersSchema, {
|
|
423
|
+
example: def.outputExample
|
|
424
|
+
}) : void 0
|
|
425
|
+
};
|
|
426
|
+
if (this.options?.considerMissingTagDefinitionAsError && def.route?.tags) {
|
|
427
|
+
const missingTag = def.route?.tags.find((tag) => !rootTags.includes(tag));
|
|
428
|
+
if (missingTag !== void 0) {
|
|
429
|
+
throw new OpenAPIError(
|
|
430
|
+
`Tag "${missingTag}" is missing definition. Please define it in OpenAPI root tags object`
|
|
431
|
+
);
|
|
432
|
+
}
|
|
297
433
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
434
|
+
const operation = {
|
|
435
|
+
summary: def.route?.summary,
|
|
436
|
+
description: def.route?.description,
|
|
437
|
+
deprecated: def.route?.deprecated,
|
|
438
|
+
tags: def.route?.tags ? [...def.route.tags] : void 0,
|
|
439
|
+
operationId: path.join("."),
|
|
440
|
+
parameters: parameters.length ? parameters : void 0,
|
|
441
|
+
requestBody,
|
|
442
|
+
responses: {
|
|
443
|
+
[def.route?.successStatus ?? 200]: successResponse
|
|
444
|
+
}
|
|
303
445
|
};
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
)
|
|
318
|
-
);
|
|
446
|
+
builder.addPath(httpPath, {
|
|
447
|
+
[method.toLocaleLowerCase()]: operation
|
|
448
|
+
});
|
|
449
|
+
} catch (e) {
|
|
450
|
+
if (e instanceof OpenAPIError) {
|
|
451
|
+
const error = new OpenAPIError(`
|
|
452
|
+
Generate OpenAPI Error: ${e.message}
|
|
453
|
+
Happened at path: ${path.join(".")}
|
|
454
|
+
`, { cause: e });
|
|
455
|
+
if (this.options?.throwOnError) {
|
|
456
|
+
throw error;
|
|
457
|
+
}
|
|
458
|
+
console.error(error);
|
|
319
459
|
}
|
|
320
460
|
}
|
|
321
|
-
const operation = {
|
|
322
|
-
summary: def.route?.summary,
|
|
323
|
-
description: def.route?.description,
|
|
324
|
-
deprecated: def.route?.deprecated,
|
|
325
|
-
tags: def.route?.tags ? [...def.route.tags] : void 0,
|
|
326
|
-
operationId: path.join("."),
|
|
327
|
-
parameters: parameters.length ? parameters : void 0,
|
|
328
|
-
requestBody,
|
|
329
|
-
responses: {
|
|
330
|
-
[def.route?.successStatus ?? 200]: successResponse
|
|
331
|
-
}
|
|
332
|
-
};
|
|
333
|
-
builder.addPath(httpPath, {
|
|
334
|
-
[method.toLocaleLowerCase()]: operation
|
|
335
|
-
});
|
|
336
461
|
});
|
|
337
462
|
return this.jsonSerializer.serialize(builder.getSpec());
|
|
338
463
|
}
|
|
339
|
-
handleError(error) {
|
|
340
|
-
if (this.options?.throwOnError) {
|
|
341
|
-
throw error;
|
|
342
|
-
}
|
|
343
|
-
console.error(error);
|
|
344
|
-
}
|
|
345
464
|
};
|
|
346
465
|
export {
|
|
347
466
|
CompositeSchemaConverter,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export * from './bracket-notation';
|
|
2
|
-
export * from './input-
|
|
3
|
-
export * from './input-
|
|
2
|
+
export * from './input-structure-compact';
|
|
3
|
+
export * from './input-structure-detailed';
|
|
4
4
|
export * from './openapi-handler';
|
|
5
5
|
export * from './openapi-handler-server';
|
|
6
6
|
export * from './openapi-handler-serverless';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Params } from 'hono/router';
|
|
2
|
+
export declare class InputStructureCompact {
|
|
3
|
+
build(params: Params, payload: unknown): unknown;
|
|
4
|
+
}
|
|
5
|
+
export type PublicInputStructureCompact = Pick<InputStructureCompact, keyof InputStructureCompact>;
|
|
6
|
+
//# sourceMappingURL=input-structure-compact.d.ts.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Params } from 'hono/router';
|
|
2
|
-
export declare class
|
|
2
|
+
export declare class InputStructureDetailed {
|
|
3
3
|
build(params: Params, query: unknown, headers: unknown, body: unknown): {
|
|
4
4
|
params: Params;
|
|
5
5
|
query: unknown;
|
|
@@ -7,5 +7,5 @@ export declare class InputBuilderFull {
|
|
|
7
7
|
body: unknown;
|
|
8
8
|
};
|
|
9
9
|
}
|
|
10
|
-
export type
|
|
11
|
-
//# sourceMappingURL=input-
|
|
10
|
+
export type PublicInputStructureDetailed = Pick<InputStructureDetailed, keyof InputStructureDetailed>;
|
|
11
|
+
//# sourceMappingURL=input-structure-detailed.d.ts.map
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import type { Context, Router, WithSignal } from '@orpc/server';
|
|
1
2
|
import type { ConditionalFetchHandler, FetchOptions } from '@orpc/server/fetch';
|
|
2
|
-
import type {
|
|
3
|
-
import { type Context, type Router, type WithSignal } from '@orpc/server';
|
|
3
|
+
import type { PublicInputStructureCompact } from './input-structure-compact';
|
|
4
4
|
import { type Hooks } from '@orpc/shared';
|
|
5
5
|
import { type PublicJSONSerializer } from '../json-serializer';
|
|
6
|
-
import { type
|
|
6
|
+
import { type PublicInputStructureDetailed } from './input-structure-detailed';
|
|
7
7
|
import { type PublicOpenAPIPayloadCodec } from './openapi-payload-codec';
|
|
8
8
|
import { type Hono, type PublicOpenAPIProcedureMatcher } from './openapi-procedure-matcher';
|
|
9
9
|
import { type SchemaCoercer } from './schema-coercer';
|
|
@@ -11,20 +11,23 @@ export type OpenAPIHandlerOptions<T extends Context> = Hooks<Request, Response,
|
|
|
11
11
|
jsonSerializer?: PublicJSONSerializer;
|
|
12
12
|
procedureMatcher?: PublicOpenAPIProcedureMatcher;
|
|
13
13
|
payloadCodec?: PublicOpenAPIPayloadCodec;
|
|
14
|
-
inputBuilderSimple?:
|
|
15
|
-
inputBuilderFull?:
|
|
14
|
+
inputBuilderSimple?: PublicInputStructureCompact;
|
|
15
|
+
inputBuilderFull?: PublicInputStructureDetailed;
|
|
16
16
|
schemaCoercers?: SchemaCoercer[];
|
|
17
17
|
};
|
|
18
18
|
export declare class OpenAPIHandler<T extends Context> implements ConditionalFetchHandler<T> {
|
|
19
19
|
private readonly options?;
|
|
20
20
|
private readonly procedureMatcher;
|
|
21
21
|
private readonly payloadCodec;
|
|
22
|
-
private readonly
|
|
23
|
-
private readonly
|
|
22
|
+
private readonly inputStructureCompact;
|
|
23
|
+
private readonly inputStructureDetailed;
|
|
24
24
|
private readonly compositeSchemaCoercer;
|
|
25
25
|
constructor(hono: Hono, router: Router<T, any>, options?: NoInfer<OpenAPIHandlerOptions<T>> | undefined);
|
|
26
26
|
condition(request: Request): boolean;
|
|
27
27
|
fetch(request: Request, ...[options]: [options: FetchOptions<T>] | (undefined extends T ? [] : never)): Promise<Response>;
|
|
28
|
+
private decodeInput;
|
|
29
|
+
private encodeOutput;
|
|
30
|
+
private assertDetailedOutput;
|
|
28
31
|
private convertToORPCError;
|
|
29
32
|
}
|
|
30
33
|
//# sourceMappingURL=openapi-handler.d.ts.map
|
|
@@ -2,7 +2,7 @@ import type { PublicJSONSerializer } from '../json-serializer';
|
|
|
2
2
|
export declare class OpenAPIPayloadCodec {
|
|
3
3
|
private readonly jsonSerializer;
|
|
4
4
|
constructor(jsonSerializer: PublicJSONSerializer);
|
|
5
|
-
encode(payload: unknown, accept
|
|
5
|
+
encode(payload: unknown, accept: string | undefined): {
|
|
6
6
|
body: FormData | Blob | string | undefined;
|
|
7
7
|
headers?: Headers;
|
|
8
8
|
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { ContractRouter } from '@orpc/contract';
|
|
2
2
|
import type { ANY_ROUTER } from '@orpc/server';
|
|
3
|
+
import type { PublicOpenAPIInputStructureParser } from './openapi-input-structure-parser';
|
|
4
|
+
import type { PublicOpenAPIOutputStructureParser } from './openapi-output-structure-parser';
|
|
3
5
|
import type { PublicOpenAPIPathParser } from './openapi-path-parser';
|
|
4
6
|
import type { SchemaConverter } from './schema-converter';
|
|
5
7
|
import { type PublicJSONSerializer } from './json-serializer';
|
|
@@ -14,6 +16,8 @@ export interface OpenAPIGeneratorOptions {
|
|
|
14
16
|
schemaUtils?: PublicSchemaUtils;
|
|
15
17
|
jsonSerializer?: PublicJSONSerializer;
|
|
16
18
|
pathParser?: PublicOpenAPIPathParser;
|
|
19
|
+
inputStructureParser?: PublicOpenAPIInputStructureParser;
|
|
20
|
+
outputStructureParser?: PublicOpenAPIOutputStructureParser;
|
|
17
21
|
/**
|
|
18
22
|
* Throw error when you missing define tag definition on OpenAPI root tags
|
|
19
23
|
*
|
|
@@ -44,8 +48,9 @@ export declare class OpenAPIGenerator {
|
|
|
44
48
|
private readonly schemaUtils;
|
|
45
49
|
private readonly jsonSerializer;
|
|
46
50
|
private readonly pathParser;
|
|
51
|
+
private readonly inputStructureParser;
|
|
52
|
+
private readonly outputStructureParser;
|
|
47
53
|
constructor(options?: OpenAPIGeneratorOptions | undefined);
|
|
48
54
|
generate(router: ContractRouter | ANY_ROUTER, doc: Omit<OpenAPI.OpenAPIObject, 'openapi'>): Promise<OpenAPI.OpenAPIObject>;
|
|
49
|
-
private handleError;
|
|
50
55
|
}
|
|
51
56
|
//# sourceMappingURL=openapi-generator.d.ts.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ANY_CONTRACT_PROCEDURE } from '@orpc/contract';
|
|
2
|
+
import type { PublicOpenAPIPathParser } from './openapi-path-parser';
|
|
3
|
+
import type { JSONSchema, ObjectSchema } from './schema';
|
|
4
|
+
import type { SchemaConverter } from './schema-converter';
|
|
5
|
+
import type { PublicSchemaUtils } from './schema-utils';
|
|
6
|
+
export interface OpenAPIInputStructureParseResult {
|
|
7
|
+
paramsSchema: ObjectSchema | undefined;
|
|
8
|
+
querySchema: ObjectSchema | undefined;
|
|
9
|
+
headersSchema: ObjectSchema | undefined;
|
|
10
|
+
bodySchema: JSONSchema.JSONSchema | undefined;
|
|
11
|
+
}
|
|
12
|
+
export declare class OpenAPIInputStructureParser {
|
|
13
|
+
private readonly schemaConverter;
|
|
14
|
+
private readonly schemaUtils;
|
|
15
|
+
private readonly pathParser;
|
|
16
|
+
constructor(schemaConverter: SchemaConverter, schemaUtils: PublicSchemaUtils, pathParser: PublicOpenAPIPathParser);
|
|
17
|
+
parse(contract: ANY_CONTRACT_PROCEDURE, structure: 'compact' | 'detailed'): OpenAPIInputStructureParseResult;
|
|
18
|
+
private parseDetailedSchema;
|
|
19
|
+
private parseCompactSchema;
|
|
20
|
+
}
|
|
21
|
+
export type PublicOpenAPIInputStructureParser = Pick<OpenAPIInputStructureParser, keyof OpenAPIInputStructureParser>;
|
|
22
|
+
//# sourceMappingURL=openapi-input-structure-parser.d.ts.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ANY_CONTRACT_PROCEDURE } from '@orpc/contract';
|
|
2
|
+
import type { JSONSchema, ObjectSchema } from './schema';
|
|
3
|
+
import type { SchemaConverter } from './schema-converter';
|
|
4
|
+
import type { PublicSchemaUtils } from './schema-utils';
|
|
5
|
+
export interface OpenAPIOutputStructureParseResult {
|
|
6
|
+
headersSchema: ObjectSchema | undefined;
|
|
7
|
+
bodySchema: JSONSchema.JSONSchema | undefined;
|
|
8
|
+
}
|
|
9
|
+
export declare class OpenAPIOutputStructureParser {
|
|
10
|
+
private readonly schemaConverter;
|
|
11
|
+
private readonly schemaUtils;
|
|
12
|
+
constructor(schemaConverter: SchemaConverter, schemaUtils: PublicSchemaUtils);
|
|
13
|
+
parse(contract: ANY_CONTRACT_PROCEDURE, structure: 'compact' | 'detailed'): OpenAPIOutputStructureParseResult;
|
|
14
|
+
private parseDetailedSchema;
|
|
15
|
+
private parseCompactSchema;
|
|
16
|
+
}
|
|
17
|
+
export type PublicOpenAPIOutputStructureParser = Pick<OpenAPIOutputStructureParser, keyof OpenAPIOutputStructureParser>;
|
|
18
|
+
//# sourceMappingURL=openapi-output-structure-parser.d.ts.map
|
|
@@ -4,6 +4,9 @@ export declare class OpenAPIParametersBuilder {
|
|
|
4
4
|
build(paramIn: OpenAPI.ParameterObject['in'], jsonSchema: JSONSchema.JSONSchema & {
|
|
5
5
|
type: 'object';
|
|
6
6
|
} & object, options?: Pick<OpenAPI.ParameterObject, 'example' | 'style' | 'required'>): OpenAPI.ParameterObject[];
|
|
7
|
+
buildHeadersObject(jsonSchema: JSONSchema.JSONSchema & {
|
|
8
|
+
type: 'object';
|
|
9
|
+
} & object, options?: Pick<OpenAPI.ParameterObject, 'example' | 'style' | 'required'>): OpenAPI.HeadersObject;
|
|
7
10
|
}
|
|
8
11
|
export type PublicOpenAPIParametersBuilder = Pick<OpenAPIParametersBuilder, keyof OpenAPIParametersBuilder>;
|
|
9
12
|
//# sourceMappingURL=openapi-parameters-builder.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orpc/openapi",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.23.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://orpc.unnoq.com",
|
|
7
7
|
"repository": {
|
|
@@ -43,9 +43,9 @@
|
|
|
43
43
|
"json-schema-typed": "^8.0.1",
|
|
44
44
|
"openapi3-ts": "^4.4.0",
|
|
45
45
|
"wildcard-match": "^5.1.3",
|
|
46
|
-
"@orpc/contract": "0.
|
|
47
|
-
"@orpc/shared": "0.
|
|
48
|
-
"@orpc/server": "0.
|
|
46
|
+
"@orpc/contract": "0.23.0",
|
|
47
|
+
"@orpc/shared": "0.23.0",
|
|
48
|
+
"@orpc/server": "0.23.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@readme/openapi-parser": "^2.6.0",
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { Params } from 'hono/router';
|
|
2
|
-
export declare class InputBuilderSimple {
|
|
3
|
-
build(params: Params, payload: unknown): unknown;
|
|
4
|
-
}
|
|
5
|
-
export type PublicInputBuilderSimple = Pick<InputBuilderSimple, keyof InputBuilderSimple>;
|
|
6
|
-
//# sourceMappingURL=input-builder-simple.d.ts.map
|