@orpc/openapi 0.0.0-next.d929a5e → 0.0.0-next.d92c7aa

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.
Files changed (33) hide show
  1. package/README.md +7 -2
  2. package/dist/adapters/aws-lambda/index.d.mts +6 -3
  3. package/dist/adapters/aws-lambda/index.d.ts +6 -3
  4. package/dist/adapters/aws-lambda/index.mjs +3 -3
  5. package/dist/adapters/fastify/index.d.mts +23 -0
  6. package/dist/adapters/fastify/index.d.ts +23 -0
  7. package/dist/adapters/fastify/index.mjs +18 -0
  8. package/dist/adapters/fetch/index.d.mts +9 -3
  9. package/dist/adapters/fetch/index.d.ts +9 -3
  10. package/dist/adapters/fetch/index.mjs +1 -1
  11. package/dist/adapters/node/index.d.mts +9 -3
  12. package/dist/adapters/node/index.d.ts +9 -3
  13. package/dist/adapters/node/index.mjs +1 -1
  14. package/dist/adapters/standard/index.d.mts +8 -23
  15. package/dist/adapters/standard/index.d.ts +8 -23
  16. package/dist/adapters/standard/index.mjs +1 -1
  17. package/dist/index.d.mts +5 -4
  18. package/dist/index.d.ts +5 -4
  19. package/dist/index.mjs +3 -3
  20. package/dist/plugins/index.d.mts +19 -4
  21. package/dist/plugins/index.d.ts +19 -4
  22. package/dist/plugins/index.mjs +59 -19
  23. package/dist/shared/openapi.BfNjg7j9.d.mts +120 -0
  24. package/dist/shared/openapi.BfNjg7j9.d.ts +120 -0
  25. package/dist/shared/{openapi.C_UtQ8Us.mjs → openapi.DIt-Z9W1.mjs} +19 -8
  26. package/dist/shared/{openapi.DaYgbD_w.mjs → openapi.DrTcell5.mjs} +164 -66
  27. package/dist/shared/openapi.DwaweYRb.d.mts +54 -0
  28. package/dist/shared/openapi.DwaweYRb.d.ts +54 -0
  29. package/package.json +16 -10
  30. package/dist/shared/openapi.D3j94c9n.d.mts +0 -12
  31. package/dist/shared/openapi.D3j94c9n.d.ts +0 -12
  32. package/dist/shared/openapi.qZLdpE0a.d.mts +0 -52
  33. package/dist/shared/openapi.qZLdpE0a.d.ts +0 -52
@@ -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((k) => k !== "type" && k !== "properties" && k !== "required" && LOGIC_KEYWORDS.includes(k))) {
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 = schema.properties && Object.entries(schema.properties).filter(([key]) => separatedProperties.includes(key)).reduce((acc, [key, value]) => {
123
- acc[key] = value;
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;
@@ -375,41 +389,47 @@ class OpenAPIGenerator {
375
389
  *
376
390
  * @see {@link https://orpc.unnoq.com/docs/openapi/openapi-specification OpenAPI Specification Docs}
377
391
  */
378
- async generate(router, options = {}) {
379
- const exclude = options.exclude ?? (() => false);
392
+ async generate(router, { customErrorResponseBodySchema, commonSchemas, filter: baseFilter, exclude, ...baseDoc } = {}) {
393
+ const filter = baseFilter ?? (({ contract, path }) => {
394
+ return !(exclude?.(contract, path) ?? false);
395
+ });
380
396
  const doc = {
381
- ...clone(options),
382
- info: options.info ?? { title: "API Reference", version: "0.0.0" },
383
- openapi: "3.1.1",
384
- exclude: void 0
397
+ ...clone(baseDoc),
398
+ info: baseDoc.info ?? { title: "API Reference", version: "0.0.0" },
399
+ openapi: "3.1.1"
385
400
  };
401
+ const { baseSchemaConvertOptions, undefinedErrorJsonSchema } = await this.#resolveCommonSchemas(doc, commonSchemas);
386
402
  const contracts = [];
387
- await resolveContractProcedures({ path: [], router }, ({ contract, path }) => {
388
- if (!exclude(contract, path)) {
389
- contracts.push({ contract, path });
403
+ await resolveContractProcedures({ path: [], router }, (traverseOptions) => {
404
+ if (!value(filter, traverseOptions)) {
405
+ return;
390
406
  }
407
+ contracts.push(traverseOptions);
391
408
  });
392
409
  const errors = [];
393
410
  for (const { contract, path } of contracts) {
394
- const operationId = path.join(".");
411
+ const stringPath = path.join(".");
395
412
  try {
396
413
  const def = contract["~orpc"];
397
414
  const method = toOpenAPIMethod(fallbackContractConfig("defaultMethod", def.route.method));
398
415
  const httpPath = toOpenAPIPath(def.route.path ?? toHttpPath(path));
399
416
  let operationObjectRef;
400
- if (def.route.spec !== void 0) {
417
+ if (def.route.spec !== void 0 && typeof def.route.spec !== "function") {
401
418
  operationObjectRef = def.route.spec;
402
419
  } else {
403
420
  operationObjectRef = {
404
- operationId,
421
+ operationId: def.route.operationId ?? stringPath,
405
422
  summary: def.route.summary,
406
423
  description: def.route.description,
407
424
  deprecated: def.route.deprecated,
408
425
  tags: def.route.tags?.map((tag) => tag)
409
426
  };
410
- await this.#request(operationObjectRef, def);
411
- await this.#successResponse(operationObjectRef, def);
412
- await this.#errorResponse(operationObjectRef, def);
427
+ await this.#request(doc, operationObjectRef, def, baseSchemaConvertOptions);
428
+ await this.#successResponse(doc, operationObjectRef, def, baseSchemaConvertOptions);
429
+ await this.#errorResponse(operationObjectRef, def, baseSchemaConvertOptions, undefinedErrorJsonSchema, customErrorResponseBodySchema);
430
+ }
431
+ if (typeof def.route.spec === "function") {
432
+ operationObjectRef = def.route.spec(operationObjectRef);
413
433
  }
414
434
  doc.paths ??= {};
415
435
  doc.paths[httpPath] ??= {};
@@ -419,7 +439,7 @@ class OpenAPIGenerator {
419
439
  throw e;
420
440
  }
421
441
  errors.push(
422
- `[OpenAPIGenerator] Error occurred while generating OpenAPI for procedure at path: ${operationId}
442
+ `[OpenAPIGenerator] Error occurred while generating OpenAPI for procedure at path: ${stringPath}
423
443
  ${e.message}`
424
444
  );
425
445
  }
@@ -433,22 +453,96 @@ ${errors.join("\n\n")}`
433
453
  }
434
454
  return this.serializer.serialize(doc)[0];
435
455
  }
436
- async #request(ref, def) {
456
+ async #resolveCommonSchemas(doc, commonSchemas) {
457
+ let undefinedErrorJsonSchema = {
458
+ type: "object",
459
+ properties: {
460
+ defined: { const: false },
461
+ code: { type: "string" },
462
+ status: { type: "number" },
463
+ message: { type: "string" },
464
+ data: {}
465
+ },
466
+ required: ["defined", "code", "status", "message"]
467
+ };
468
+ const baseSchemaConvertOptions = {};
469
+ if (commonSchemas) {
470
+ baseSchemaConvertOptions.components = [];
471
+ for (const key in commonSchemas) {
472
+ const options = commonSchemas[key];
473
+ if (options.schema === void 0) {
474
+ continue;
475
+ }
476
+ const { schema, strategy = "input" } = options;
477
+ const [required, json] = await this.converter.convert(schema, { strategy });
478
+ const allowedStrategies = [strategy];
479
+ if (strategy === "input") {
480
+ const [outputRequired, outputJson] = await this.converter.convert(schema, { strategy: "output" });
481
+ if (outputRequired === required && stringifyJSON(outputJson) === stringifyJSON(json)) {
482
+ allowedStrategies.push("output");
483
+ }
484
+ } else if (strategy === "output") {
485
+ const [inputRequired, inputJson] = await this.converter.convert(schema, { strategy: "input" });
486
+ if (inputRequired === required && stringifyJSON(inputJson) === stringifyJSON(json)) {
487
+ allowedStrategies.push("input");
488
+ }
489
+ }
490
+ baseSchemaConvertOptions.components.push({
491
+ schema,
492
+ required,
493
+ ref: `#/components/schemas/${key}`,
494
+ allowedStrategies
495
+ });
496
+ }
497
+ doc.components ??= {};
498
+ doc.components.schemas ??= {};
499
+ for (const key in commonSchemas) {
500
+ const options = commonSchemas[key];
501
+ if (options.schema === void 0) {
502
+ if (options.error === "UndefinedError") {
503
+ doc.components.schemas[key] = toOpenAPISchema(undefinedErrorJsonSchema);
504
+ undefinedErrorJsonSchema = { $ref: `#/components/schemas/${key}` };
505
+ }
506
+ continue;
507
+ }
508
+ const { schema, strategy = "input" } = options;
509
+ const [, json] = await this.converter.convert(
510
+ schema,
511
+ {
512
+ ...baseSchemaConvertOptions,
513
+ strategy,
514
+ minStructureDepthForRef: 1
515
+ // not allow use $ref for root schemas
516
+ }
517
+ );
518
+ doc.components.schemas[key] = toOpenAPISchema(json);
519
+ }
520
+ }
521
+ return { baseSchemaConvertOptions, undefinedErrorJsonSchema };
522
+ }
523
+ async #request(doc, ref, def, baseSchemaConvertOptions) {
437
524
  const method = fallbackContractConfig("defaultMethod", def.route.method);
438
525
  const details = getEventIteratorSchemaDetails(def.inputSchema);
439
526
  if (details) {
440
527
  ref.requestBody = {
441
528
  required: true,
442
529
  content: toOpenAPIEventIteratorContent(
443
- await this.converter.convert(details.yields, { strategy: "input" }),
444
- await this.converter.convert(details.returns, { strategy: "input" })
530
+ await this.converter.convert(details.yields, { ...baseSchemaConvertOptions, strategy: "input" }),
531
+ await this.converter.convert(details.returns, { ...baseSchemaConvertOptions, strategy: "input" })
445
532
  )
446
533
  };
447
534
  return;
448
535
  }
449
536
  const dynamicParams = getDynamicParams(def.route.path)?.map((v) => v.name);
450
537
  const inputStructure = fallbackContractConfig("defaultInputStructure", def.route.inputStructure);
451
- let [required, schema] = await this.converter.convert(def.inputSchema, { strategy: "input" });
538
+ let [required, schema] = await this.converter.convert(
539
+ def.inputSchema,
540
+ {
541
+ ...baseSchemaConvertOptions,
542
+ strategy: "input",
543
+ minStructureDepthForRef: dynamicParams?.length || inputStructure === "detailed" ? 1 : 0
544
+ }
545
+ );
452
546
  if (isAnySchema(schema) && !dynamicParams?.length) {
453
547
  return;
454
548
  }
@@ -470,13 +564,14 @@ ${errors.join("\n\n")}`
470
564
  ref.parameters.push(...toOpenAPIParameters(paramsSchema, "path"));
471
565
  }
472
566
  if (method === "GET") {
473
- if (!isObjectSchema(schema)) {
567
+ const resolvedSchema = resolveOpenAPIJsonSchemaRef(doc, schema);
568
+ if (!isObjectSchema(resolvedSchema)) {
474
569
  throw new OpenAPIGeneratorError(
475
570
  'When method is "GET", input schema must satisfy: object | any | unknown'
476
571
  );
477
572
  }
478
573
  ref.parameters ??= [];
479
- ref.parameters.push(...toOpenAPIParameters(schema, "query"));
574
+ ref.parameters.push(...toOpenAPIParameters(resolvedSchema, "query"));
480
575
  } else {
481
576
  ref.requestBody = {
482
577
  required,
@@ -491,7 +586,8 @@ ${errors.join("\n\n")}`
491
586
  if (!isObjectSchema(schema)) {
492
587
  throw error;
493
588
  }
494
- if (dynamicParams?.length && (schema.properties?.params === void 0 || !isObjectSchema(schema.properties.params) || !checkParamsSchema(schema.properties.params, dynamicParams))) {
589
+ const resolvedParamSchema = schema.properties?.params !== void 0 ? resolveOpenAPIJsonSchemaRef(doc, schema.properties.params) : void 0;
590
+ if (dynamicParams?.length && (resolvedParamSchema === void 0 || !isObjectSchema(resolvedParamSchema) || !checkParamsSchema(resolvedParamSchema, dynamicParams))) {
495
591
  throw new OpenAPIGeneratorError(
496
592
  'When input structure is "detailed" and path has dynamic params, the "params" schema must be an object with all dynamic params as required.'
497
593
  );
@@ -499,12 +595,13 @@ ${errors.join("\n\n")}`
499
595
  for (const from of ["params", "query", "headers"]) {
500
596
  const fromSchema = schema.properties?.[from];
501
597
  if (fromSchema !== void 0) {
502
- if (!isObjectSchema(fromSchema)) {
598
+ const resolvedSchema = resolveOpenAPIJsonSchemaRef(doc, fromSchema);
599
+ if (!isObjectSchema(resolvedSchema)) {
503
600
  throw error;
504
601
  }
505
602
  const parameterIn = from === "params" ? "path" : from === "headers" ? "header" : "query";
506
603
  ref.parameters ??= [];
507
- ref.parameters.push(...toOpenAPIParameters(fromSchema, parameterIn));
604
+ ref.parameters.push(...toOpenAPIParameters(resolvedSchema, parameterIn));
508
605
  }
509
606
  }
510
607
  if (schema.properties?.body !== void 0) {
@@ -514,7 +611,7 @@ ${errors.join("\n\n")}`
514
611
  };
515
612
  }
516
613
  }
517
- async #successResponse(ref, def) {
614
+ async #successResponse(doc, ref, def, baseSchemaConvertOptions) {
518
615
  const outputSchema = def.outputSchema;
519
616
  const status = fallbackContractConfig("defaultSuccessStatus", def.route.successStatus);
520
617
  const description = fallbackContractConfig("defaultSuccessDescription", def.route?.successDescription);
@@ -525,13 +622,20 @@ ${errors.join("\n\n")}`
525
622
  ref.responses[status] = {
526
623
  description,
527
624
  content: toOpenAPIEventIteratorContent(
528
- await this.converter.convert(eventIteratorSchemaDetails.yields, { strategy: "output" }),
529
- await this.converter.convert(eventIteratorSchemaDetails.returns, { strategy: "output" })
625
+ await this.converter.convert(eventIteratorSchemaDetails.yields, { ...baseSchemaConvertOptions, strategy: "output" }),
626
+ await this.converter.convert(eventIteratorSchemaDetails.returns, { ...baseSchemaConvertOptions, strategy: "output" })
530
627
  )
531
628
  };
532
629
  return;
533
630
  }
534
- const [required, json] = await this.converter.convert(outputSchema, { strategy: "output" });
631
+ const [required, json] = await this.converter.convert(
632
+ outputSchema,
633
+ {
634
+ ...baseSchemaConvertOptions,
635
+ strategy: "output",
636
+ minStructureDepthForRef: outputStructure === "detailed" ? 1 : 0
637
+ }
638
+ );
535
639
  if (outputStructure === "compact") {
536
640
  ref.responses ??= {};
537
641
  ref.responses[status] = {
@@ -558,11 +662,12 @@ ${errors.join("\n\n")}`
558
662
  let schemaStatus;
559
663
  let schemaDescription;
560
664
  if (item.properties?.status !== void 0) {
561
- if (typeof item.properties.status !== "object" || item.properties.status.const === void 0 || typeof item.properties.status.const !== "number" || !Number.isInteger(item.properties.status.const) || isORPCErrorStatus(item.properties.status.const)) {
665
+ const statusSchema = resolveOpenAPIJsonSchemaRef(doc, item.properties.status);
666
+ if (typeof statusSchema !== "object" || statusSchema.const === void 0 || typeof statusSchema.const !== "number" || !Number.isInteger(statusSchema.const) || isORPCErrorStatus(statusSchema.const)) {
562
667
  throw error;
563
668
  }
564
- schemaStatus = item.properties.status.const;
565
- schemaDescription = item.properties.status.description;
669
+ schemaStatus = statusSchema.const;
670
+ schemaDescription = statusSchema.description;
566
671
  }
567
672
  const itemStatus = schemaStatus ?? status;
568
673
  const itemDescription = schemaDescription ?? description;
@@ -578,16 +683,17 @@ ${errors.join("\n\n")}`
578
683
  description: itemDescription
579
684
  };
580
685
  if (item.properties?.headers !== void 0) {
581
- if (!isObjectSchema(item.properties.headers)) {
686
+ const headersSchema = resolveOpenAPIJsonSchemaRef(doc, item.properties.headers);
687
+ if (!isObjectSchema(headersSchema)) {
582
688
  throw error;
583
689
  }
584
- for (const key in item.properties.headers.properties) {
585
- const headerSchema = item.properties.headers.properties[key];
690
+ for (const key in headersSchema.properties) {
691
+ const headerSchema = headersSchema.properties[key];
586
692
  if (headerSchema !== void 0) {
587
693
  ref.responses[itemStatus].headers ??= {};
588
694
  ref.responses[itemStatus].headers[key] = {
589
695
  schema: toOpenAPISchema(headerSchema),
590
- required: item.properties.headers.required?.includes(key)
696
+ required: item.required?.includes("headers") && headersSchema.required?.includes(key)
591
697
  };
592
698
  }
593
699
  }
@@ -599,49 +705,41 @@ ${errors.join("\n\n")}`
599
705
  }
600
706
  }
601
707
  }
602
- async #errorResponse(ref, def) {
708
+ async #errorResponse(ref, def, baseSchemaConvertOptions, undefinedErrorSchema, customErrorResponseBodySchema) {
603
709
  const errorMap = def.errorMap;
604
- const errors = {};
710
+ const errorResponsesByStatus = {};
605
711
  for (const code in errorMap) {
606
712
  const config = errorMap[code];
607
713
  if (!config) {
608
714
  continue;
609
715
  }
610
716
  const status = fallbackORPCErrorStatus(code, config.status);
611
- const message = fallbackORPCErrorMessage(code, config.message);
612
- const [dataRequired, dataSchema] = await this.converter.convert(config.data, { strategy: "output" });
613
- errors[status] ??= [];
614
- errors[status].push({
717
+ const defaultMessage = fallbackORPCErrorMessage(code, config.message);
718
+ errorResponsesByStatus[status] ??= { status, definedErrorDefinitions: [], errorSchemaVariants: [] };
719
+ const [dataRequired, dataSchema] = await this.converter.convert(config.data, { ...baseSchemaConvertOptions, strategy: "output" });
720
+ errorResponsesByStatus[status].definedErrorDefinitions.push([code, defaultMessage, dataRequired, dataSchema]);
721
+ errorResponsesByStatus[status].errorSchemaVariants.push({
615
722
  type: "object",
616
723
  properties: {
617
724
  defined: { const: true },
618
725
  code: { const: code },
619
726
  status: { const: status },
620
- message: { type: "string", default: message },
727
+ message: { type: "string", default: defaultMessage },
621
728
  data: dataSchema
622
729
  },
623
730
  required: dataRequired ? ["defined", "code", "status", "message", "data"] : ["defined", "code", "status", "message"]
624
731
  });
625
732
  }
626
733
  ref.responses ??= {};
627
- for (const status in errors) {
628
- const schemas = errors[status];
629
- ref.responses[status] = {
630
- description: status,
631
- content: toOpenAPIContent({
734
+ for (const statusString in errorResponsesByStatus) {
735
+ const errorResponse = errorResponsesByStatus[statusString];
736
+ const customBodySchema = value(customErrorResponseBodySchema, errorResponse.definedErrorDefinitions, errorResponse.status);
737
+ ref.responses[statusString] = {
738
+ description: statusString,
739
+ content: toOpenAPIContent(customBodySchema ?? {
632
740
  oneOf: [
633
- ...schemas,
634
- {
635
- type: "object",
636
- properties: {
637
- defined: { const: false },
638
- code: { type: "string" },
639
- status: { type: "number" },
640
- message: { type: "string" },
641
- data: {}
642
- },
643
- required: ["defined", "code", "status", "message"]
644
- }
741
+ ...errorResponse.errorSchemaVariants,
742
+ undefinedErrorSchema
645
743
  ]
646
744
  })
647
745
  };
@@ -649,4 +747,4 @@ ${errors.join("\n\n")}`
649
747
  }
650
748
  }
651
749
 
652
- 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 };
750
+ 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,54 @@
1
+ import { StandardOpenAPISerializer, StandardOpenAPIJsonSerializerOptions, StandardBracketNotationSerializerOptions } from '@orpc/openapi-client/standard';
2
+ import { AnyProcedure, TraverseContractProcedureCallbackOptions, AnyRouter, Context, Router } from '@orpc/server';
3
+ import { StandardCodec, StandardParams, StandardMatcher, StandardMatchResult, StandardHandlerOptions, StandardHandler } from '@orpc/server/standard';
4
+ import { ORPCError, HTTPPath } from '@orpc/client';
5
+ import { StandardLazyRequest, StandardResponse } from '@orpc/standard-server';
6
+ import { Value } from '@orpc/shared';
7
+
8
+ interface StandardOpenAPICodecOptions {
9
+ /**
10
+ * Customize how an ORPC error is encoded into a response body.
11
+ * Use this if your API needs a different error output structure.
12
+ *
13
+ * @remarks
14
+ * - Return `null | undefined` to fallback to default behavior
15
+ *
16
+ * @default ((e) => e.toJSON())
17
+ */
18
+ customErrorResponseBodyEncoder?: (error: ORPCError<any, any>) => unknown;
19
+ }
20
+ declare class StandardOpenAPICodec implements StandardCodec {
21
+ #private;
22
+ private readonly serializer;
23
+ private readonly customErrorResponseBodyEncoder;
24
+ constructor(serializer: StandardOpenAPISerializer, options?: StandardOpenAPICodecOptions);
25
+ decode(request: StandardLazyRequest, params: StandardParams | undefined, procedure: AnyProcedure): Promise<unknown>;
26
+ encode(output: unknown, procedure: AnyProcedure): StandardResponse;
27
+ encodeError(error: ORPCError<any, any>): StandardResponse;
28
+ }
29
+
30
+ interface StandardOpenAPIMatcherOptions {
31
+ /**
32
+ * Filter procedures. Return `false` to exclude a procedure from matching.
33
+ *
34
+ * @default true
35
+ */
36
+ filter?: Value<boolean, [options: TraverseContractProcedureCallbackOptions]>;
37
+ }
38
+ declare class StandardOpenAPIMatcher implements StandardMatcher {
39
+ private readonly filter;
40
+ private readonly tree;
41
+ private pendingRouters;
42
+ constructor(options?: StandardOpenAPIMatcherOptions);
43
+ init(router: AnyRouter, path?: readonly string[]): void;
44
+ match(method: string, pathname: HTTPPath): Promise<StandardMatchResult>;
45
+ }
46
+
47
+ interface StandardOpenAPIHandlerOptions<T extends Context> extends StandardHandlerOptions<T>, StandardOpenAPIJsonSerializerOptions, StandardBracketNotationSerializerOptions, StandardOpenAPIMatcherOptions, StandardOpenAPICodecOptions {
48
+ }
49
+ declare class StandardOpenAPIHandler<T extends Context> extends StandardHandler<T> {
50
+ constructor(router: Router<any, T>, options: NoInfer<StandardOpenAPIHandlerOptions<T>>);
51
+ }
52
+
53
+ export { StandardOpenAPICodec as a, StandardOpenAPIHandler as c, StandardOpenAPIMatcher as e };
54
+ export type { StandardOpenAPICodecOptions as S, StandardOpenAPIHandlerOptions as b, StandardOpenAPIMatcherOptions as d };
@@ -0,0 +1,54 @@
1
+ import { StandardOpenAPISerializer, StandardOpenAPIJsonSerializerOptions, StandardBracketNotationSerializerOptions } from '@orpc/openapi-client/standard';
2
+ import { AnyProcedure, TraverseContractProcedureCallbackOptions, AnyRouter, Context, Router } from '@orpc/server';
3
+ import { StandardCodec, StandardParams, StandardMatcher, StandardMatchResult, StandardHandlerOptions, StandardHandler } from '@orpc/server/standard';
4
+ import { ORPCError, HTTPPath } from '@orpc/client';
5
+ import { StandardLazyRequest, StandardResponse } from '@orpc/standard-server';
6
+ import { Value } from '@orpc/shared';
7
+
8
+ interface StandardOpenAPICodecOptions {
9
+ /**
10
+ * Customize how an ORPC error is encoded into a response body.
11
+ * Use this if your API needs a different error output structure.
12
+ *
13
+ * @remarks
14
+ * - Return `null | undefined` to fallback to default behavior
15
+ *
16
+ * @default ((e) => e.toJSON())
17
+ */
18
+ customErrorResponseBodyEncoder?: (error: ORPCError<any, any>) => unknown;
19
+ }
20
+ declare class StandardOpenAPICodec implements StandardCodec {
21
+ #private;
22
+ private readonly serializer;
23
+ private readonly customErrorResponseBodyEncoder;
24
+ constructor(serializer: StandardOpenAPISerializer, options?: StandardOpenAPICodecOptions);
25
+ decode(request: StandardLazyRequest, params: StandardParams | undefined, procedure: AnyProcedure): Promise<unknown>;
26
+ encode(output: unknown, procedure: AnyProcedure): StandardResponse;
27
+ encodeError(error: ORPCError<any, any>): StandardResponse;
28
+ }
29
+
30
+ interface StandardOpenAPIMatcherOptions {
31
+ /**
32
+ * Filter procedures. Return `false` to exclude a procedure from matching.
33
+ *
34
+ * @default true
35
+ */
36
+ filter?: Value<boolean, [options: TraverseContractProcedureCallbackOptions]>;
37
+ }
38
+ declare class StandardOpenAPIMatcher implements StandardMatcher {
39
+ private readonly filter;
40
+ private readonly tree;
41
+ private pendingRouters;
42
+ constructor(options?: StandardOpenAPIMatcherOptions);
43
+ init(router: AnyRouter, path?: readonly string[]): void;
44
+ match(method: string, pathname: HTTPPath): Promise<StandardMatchResult>;
45
+ }
46
+
47
+ interface StandardOpenAPIHandlerOptions<T extends Context> extends StandardHandlerOptions<T>, StandardOpenAPIJsonSerializerOptions, StandardBracketNotationSerializerOptions, StandardOpenAPIMatcherOptions, StandardOpenAPICodecOptions {
48
+ }
49
+ declare class StandardOpenAPIHandler<T extends Context> extends StandardHandler<T> {
50
+ constructor(router: Router<any, T>, options: NoInfer<StandardOpenAPIHandlerOptions<T>>);
51
+ }
52
+
53
+ export { StandardOpenAPICodec as a, StandardOpenAPIHandler as c, StandardOpenAPIMatcher as e };
54
+ export type { StandardOpenAPICodecOptions as S, StandardOpenAPIHandlerOptions as b, StandardOpenAPIMatcherOptions as d };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@orpc/openapi",
3
3
  "type": "module",
4
- "version": "0.0.0-next.d929a5e",
4
+ "version": "0.0.0-next.d92c7aa",
5
5
  "license": "MIT",
6
6
  "homepage": "https://orpc.unnoq.com",
7
7
  "repository": {
@@ -39,6 +39,11 @@
39
39
  "import": "./dist/adapters/node/index.mjs",
40
40
  "default": "./dist/adapters/node/index.mjs"
41
41
  },
42
+ "./fastify": {
43
+ "types": "./dist/adapters/fastify/index.d.mts",
44
+ "import": "./dist/adapters/fastify/index.mjs",
45
+ "default": "./dist/adapters/fastify/index.mjs"
46
+ },
42
47
  "./aws-lambda": {
43
48
  "types": "./dist/adapters/aws-lambda/index.d.mts",
44
49
  "import": "./dist/adapters/aws-lambda/index.mjs",
@@ -49,17 +54,18 @@
49
54
  "dist"
50
55
  ],
51
56
  "dependencies": {
52
- "json-schema-typed": "^8.0.1",
53
- "rou3": "^0.6.0",
54
- "@orpc/contract": "0.0.0-next.d929a5e",
55
- "@orpc/client": "0.0.0-next.d929a5e",
56
- "@orpc/openapi-client": "0.0.0-next.d929a5e",
57
- "@orpc/shared": "0.0.0-next.d929a5e",
58
- "@orpc/server": "0.0.0-next.d929a5e",
59
- "@orpc/standard-server": "0.0.0-next.d929a5e"
57
+ "rou3": "^0.7.9",
58
+ "@orpc/client": "0.0.0-next.d92c7aa",
59
+ "@orpc/openapi-client": "0.0.0-next.d92c7aa",
60
+ "@orpc/interop": "0.0.0-next.d92c7aa",
61
+ "@orpc/server": "0.0.0-next.d92c7aa",
62
+ "@orpc/standard-server": "0.0.0-next.d92c7aa",
63
+ "@orpc/shared": "0.0.0-next.d92c7aa",
64
+ "@orpc/contract": "0.0.0-next.d92c7aa"
60
65
  },
61
66
  "devDependencies": {
62
- "zod": "^3.25.49"
67
+ "fastify": "^5.6.1",
68
+ "zod": "^4.1.12"
63
69
  },
64
70
  "scripts": {
65
71
  "build": "unbuild",
@@ -1,12 +0,0 @@
1
- import { StandardOpenAPIJsonSerializerOptions } from '@orpc/openapi-client/standard';
2
- import { Context, Router } from '@orpc/server';
3
- import { StandardHandlerOptions, StandardHandler } from '@orpc/server/standard';
4
-
5
- interface StandardOpenAPIHandlerOptions<T extends Context> extends StandardHandlerOptions<T>, StandardOpenAPIJsonSerializerOptions {
6
- }
7
- declare class StandardOpenAPIHandler<T extends Context> extends StandardHandler<T> {
8
- constructor(router: Router<any, T>, options: NoInfer<StandardOpenAPIHandlerOptions<T>>);
9
- }
10
-
11
- export { StandardOpenAPIHandler as a };
12
- export type { StandardOpenAPIHandlerOptions as S };
@@ -1,12 +0,0 @@
1
- import { StandardOpenAPIJsonSerializerOptions } from '@orpc/openapi-client/standard';
2
- import { Context, Router } from '@orpc/server';
3
- import { StandardHandlerOptions, StandardHandler } from '@orpc/server/standard';
4
-
5
- interface StandardOpenAPIHandlerOptions<T extends Context> extends StandardHandlerOptions<T>, StandardOpenAPIJsonSerializerOptions {
6
- }
7
- declare class StandardOpenAPIHandler<T extends Context> extends StandardHandler<T> {
8
- constructor(router: Router<any, T>, options: NoInfer<StandardOpenAPIHandlerOptions<T>>);
9
- }
10
-
11
- export { StandardOpenAPIHandler as a };
12
- export type { StandardOpenAPIHandlerOptions as S };
@@ -1,52 +0,0 @@
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 SchemaConvertOptions {
8
- strategy: 'input' | 'output';
9
- }
10
- interface SchemaConverter {
11
- convert(schema: AnySchema | undefined, options: SchemaConvertOptions): Promisable<[required: boolean, jsonSchema: JSONSchema]>;
12
- }
13
- interface ConditionalSchemaConverter extends SchemaConverter {
14
- condition(schema: AnySchema | undefined, options: SchemaConvertOptions): Promisable<boolean>;
15
- }
16
- declare class CompositeSchemaConverter implements SchemaConverter {
17
- private readonly converters;
18
- constructor(converters: ConditionalSchemaConverter[]);
19
- convert(schema: AnySchema | undefined, options: SchemaConvertOptions): Promise<[required: boolean, jsonSchema: JSONSchema]>;
20
- }
21
-
22
- interface OpenAPIGeneratorOptions extends StandardOpenAPIJsonSerializerOptions {
23
- schemaConverters?: ConditionalSchemaConverter[];
24
- }
25
- interface OpenAPIGeneratorGenerateOptions extends Partial<Omit<OpenAPI.Document, 'openapi'>> {
26
- /**
27
- * Exclude procedures from the OpenAPI specification.
28
- *
29
- * @default () => false
30
- */
31
- exclude?: (procedure: AnyProcedure | AnyContractProcedure, path: readonly string[]) => boolean;
32
- }
33
- /**
34
- * The generator that converts oRPC routers/contracts to OpenAPI specifications.
35
- *
36
- * @see {@link https://orpc.unnoq.com/docs/openapi/openapi-specification OpenAPI Specification Docs}
37
- */
38
- declare class OpenAPIGenerator {
39
- #private;
40
- private readonly serializer;
41
- private readonly converter;
42
- constructor(options?: OpenAPIGeneratorOptions);
43
- /**
44
- * Generates OpenAPI specifications from oRPC routers/contracts.
45
- *
46
- * @see {@link https://orpc.unnoq.com/docs/openapi/openapi-specification OpenAPI Specification Docs}
47
- */
48
- generate(router: AnyContractRouter | AnyRouter, options?: OpenAPIGeneratorGenerateOptions): Promise<OpenAPI.Document>;
49
- }
50
-
51
- export { OpenAPIGenerator as b, CompositeSchemaConverter as d };
52
- export type { ConditionalSchemaConverter as C, OpenAPIGeneratorOptions as O, SchemaConvertOptions as S, OpenAPIGeneratorGenerateOptions as a, SchemaConverter as c };