@orpc/openapi 2.0.0-beta.20 → 2.0.0-beta.21

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/index.d.mts CHANGED
@@ -64,11 +64,16 @@ interface OpenAPIGeneratorOptions {
64
64
  interface OpenAPIGeneratorGenerateOptions {
65
65
  base?: Partial<OpenAPIDocument> | undefined;
66
66
  /**
67
- * Controls whether a generated json schema `$defs` at root-level should be moved into `components.schemas`.
67
+ * Root-level `$defs` are always moved into `components.schemas`.
68
+ * Use this to customize the component name of a hoisted def.
68
69
  *
69
- * @default true
70
+ * @remarks
71
+ * - The returned name is a preference, conflicting names are still postfixed (`Planet`, `PlanetInput`, `Planet2`, ...).
72
+ * - Return `undefined` to keep the original def name.
73
+ *
74
+ * @default defName => defName
70
75
  */
71
- shouldHoistDef?: Value<boolean, [defName: string, defSchema: JsonSchema]>;
76
+ customComponentName?: (defName: string, defSchema: JsonSchema) => string | undefined;
72
77
  /**
73
78
  * Filter procedures. Return `false` to exclude a procedure from the OpenAPI specification.
74
79
  *
package/dist/index.d.ts CHANGED
@@ -64,11 +64,16 @@ interface OpenAPIGeneratorOptions {
64
64
  interface OpenAPIGeneratorGenerateOptions {
65
65
  base?: Partial<OpenAPIDocument> | undefined;
66
66
  /**
67
- * Controls whether a generated json schema `$defs` at root-level should be moved into `components.schemas`.
67
+ * Root-level `$defs` are always moved into `components.schemas`.
68
+ * Use this to customize the component name of a hoisted def.
68
69
  *
69
- * @default true
70
+ * @remarks
71
+ * - The returned name is a preference, conflicting names are still postfixed (`Planet`, `PlanetInput`, `Planet2`, ...).
72
+ * - Return `undefined` to keep the original def name.
73
+ *
74
+ * @default defName => defName
70
75
  */
71
- shouldHoistDef?: Value<boolean, [defName: string, defSchema: JsonSchema]>;
76
+ customComponentName?: (defName: string, defSchema: JsonSchema) => string | undefined;
72
77
  /**
73
78
  * Filter procedures. Return `false` to exclude a procedure from the OpenAPI specification.
74
79
  *
package/dist/index.mjs CHANGED
@@ -5,9 +5,9 @@ export { d as OpenAPIJsonSerializer } from './shared/openapi.zZH_UksW.mjs';
5
5
  import { g as getOpenAPIMeta, o as openapi } from './shared/openapi.B9PQzqBn.mjs';
6
6
  import { COMMON_ERROR_STATUS_MAP } from '@orpc/client';
7
7
  export { COMMON_ERROR_STATUS_MAP } from '@orpc/client';
8
- import { encodeJsonPointerSegment, ensureJsonSchemaObject, mapJsonSchemaRefs, visitJsonSchemaRefs, decodeJsonPointerSegment, isUnconstrainedSchema, combineJsonSchemasWithComposition, extractJsonObjectSchemaEntries, combineJsonObjectSchemaEntries, flattenJsonUnionSchema, isJsonPrimitiveSchema, matchArrayableJsonSchema, isJsonFileSchema, DelegatingJsonSchemaConverter, StandardJsonSchemaConverter } from '@orpc/json-schema';
8
+ import { encodeJsonPointerSegment, ensureJsonSchemaObject, mapJsonSchemaRefs, decodeJsonPointerSegment, isUnconstrainedSchema, combineJsonSchemasWithComposition, extractJsonObjectSchemaEntries, combineJsonObjectSchemaEntries, flattenJsonUnionSchema, isJsonPrimitiveSchema, matchArrayableJsonSchema, isJsonFileSchema, DelegatingJsonSchemaConverter, StandardJsonSchemaConverter } from '@orpc/json-schema';
9
9
  import { DEFAULT_SUCCESS_STATUS, DEFAULT_ERROR_STATUS, walkProcedureContractsAsync } from '@orpc/server';
10
- import { value, isDeepEqual, findDeepMatches, isPlainObject, stringifyJSON, toArray, clone, pathToHttpPath, mergeHttpPath, isTypescriptObject } from '@orpc/shared';
10
+ import { isDeepEqual, value, findDeepMatches, isPlainObject, stringifyJSON, toArray, clone, pathToHttpPath, mergeHttpPath, isTypescriptObject } from '@orpc/shared';
11
11
  import '@standardserver/core';
12
12
 
13
13
  function createContractJsonifiedClientFactory(link, options = {}) {
@@ -15,14 +15,13 @@ function createContractJsonifiedClientFactory(link, options = {}) {
15
15
  }
16
16
 
17
17
  class OpenAPIComponentRegistry {
18
- constructor(doc, shouldHoistDef) {
18
+ constructor(doc, customComponentName) {
19
19
  this.doc = doc;
20
- this.shouldHoistDef = shouldHoistDef;
20
+ this.customComponentName = customComponentName;
21
21
  }
22
22
  /**
23
23
  * Registers `schema` as a component under `preferredName` (or an equivalent/postfixed name)
24
- * and returns a `$ref` to it. When hoisting is declined via `shouldHoistDef`, the schema is
25
- * returned in its local `$defs` form instead.
24
+ * and returns a `$ref` to it.
26
25
  */
27
26
  register(preferredName, schema) {
28
27
  const { $defs, ...body } = schema;
@@ -39,59 +38,53 @@ class OpenAPIComponentRegistry {
39
38
  }
40
39
  /**
41
40
  * Moves a schema's root-level `$defs` into `doc.components.schemas` and rewrites
42
- * its refs accordingly. Defs declined by `shouldHoistDef` stay local unless a
43
- * hoisted def references them.
41
+ * its refs accordingly.
44
42
  */
45
43
  hoistDefs(schema, direction) {
46
44
  if (typeof schema !== "object" || !schema.$defs) {
47
45
  return schema;
48
46
  }
49
47
  const { $defs, ...rest } = schema;
50
- const localDefs = {};
51
- const hoistedDefs = {};
48
+ const defs = {};
49
+ const preferredNames = {};
52
50
  for (const defName of Object.keys($defs)) {
53
51
  const defSchema = $defs[defName];
54
52
  if (defSchema === void 0) {
55
53
  continue;
56
54
  }
57
55
  const normalized = ensureJsonSchemaObject(defSchema);
58
- if (value(this.shouldHoistDef, defName, normalized) !== false) {
59
- hoistedDefs[defName] = normalized;
60
- } else {
61
- localDefs[defName] = normalized;
62
- }
56
+ defs[defName] = normalized;
57
+ preferredNames[defName] = this.customComponentName?.(defName, normalized) ?? defName;
63
58
  }
64
- hoistReferencedLocalDefs(hoistedDefs, localDefs);
65
- if (Object.keys(hoistedDefs).length === 0) {
59
+ const defNames = Object.keys(defs);
60
+ if (defNames.length === 0) {
66
61
  return schema;
67
62
  }
68
63
  this.doc.components ??= {};
69
64
  this.doc.components.schemas ??= {};
70
65
  const componentsSchemas = this.doc.components.schemas;
71
66
  const identityRenameMap = Object.fromEntries(
72
- Object.keys(hoistedDefs).map((defName) => [defName, defName])
67
+ defNames.map((defName) => [defName, preferredNames[defName]])
73
68
  );
74
69
  const renameMap = {};
75
70
  const pendingSchemas = [];
76
- for (const defName of Object.keys(hoistedDefs)) {
77
- const cleanSchema = hoistedDefs[defName];
71
+ for (const defName of defNames) {
72
+ const cleanSchema = defs[defName];
78
73
  const candidateSchemas = Object.fromEntries(
79
- Object.keys(hoistedDefs).map((currentDefName) => [
80
- currentDefName,
81
- rewriteComponentSchemaRefs(
82
- withReferencedLocalDefs(hoistedDefs[currentDefName], localDefs),
83
- {
84
- ...identityRenameMap,
85
- ...renameMap
86
- }
87
- )
74
+ defNames.map((currentDefName) => [
75
+ preferredNames[currentDefName],
76
+ rewriteComponentSchemaRefs(defs[currentDefName], {
77
+ ...identityRenameMap,
78
+ ...renameMap
79
+ })
88
80
  ])
89
81
  );
90
- const prelimSchema = candidateSchemas[defName];
82
+ const preferredName = preferredNames[defName];
83
+ const prelimSchema = candidateSchemas[preferredName];
91
84
  const [componentName, reuseExisting] = resolveComponentName(
92
85
  componentsSchemas,
93
86
  new Set(Object.values(renameMap)),
94
- defName,
87
+ preferredName,
95
88
  prelimSchema,
96
89
  candidateSchemas,
97
90
  direction
@@ -103,77 +96,20 @@ class OpenAPIComponentRegistry {
103
96
  }
104
97
  for (const { cleanSchema, componentName } of pendingSchemas) {
105
98
  componentsSchemas[componentName] = rewriteComponentSchemaRefs(
106
- withReferencedLocalDefs(cleanSchema, localDefs),
99
+ cleanSchema,
107
100
  renameMap
108
101
  );
109
102
  }
110
- return rewriteComponentSchemaRefs(withReferencedLocalDefs(rest, localDefs), renameMap);
103
+ return rewriteComponentSchemaRefs(rest, renameMap);
111
104
  }
112
105
  toOpenAPISchema(schema, direction) {
113
106
  return ensureJsonSchemaObject(this.hoistDefs(schema, direction));
114
107
  }
115
108
  }
116
- function visitLocalDefRefs(schema, onRef) {
117
- visitJsonSchemaRefs(schema, (ref) => {
118
- const refName = parseLocalDefRefName(ref);
119
- if (refName !== void 0) {
120
- onRef(refName);
121
- }
122
- });
123
- }
124
- function hoistReferencedLocalDefs(hoistedDefs, localDefs) {
125
- const queue = Object.values(hoistedDefs);
126
- while (queue.length > 0) {
127
- const current = queue.shift();
128
- visitLocalDefRefs(current, (refName) => {
129
- const referenced = localDefs[refName];
130
- if (referenced === void 0) {
131
- return;
132
- }
133
- hoistedDefs[refName] = referenced;
134
- delete localDefs[refName];
135
- queue.push(referenced);
136
- });
137
- }
138
- }
139
- function withReferencedLocalDefs(schema, localDefs) {
140
- const referencedLocalDefs = collectReferencedLocalDefNames(schema, localDefs);
141
- if (referencedLocalDefs.length === 0) {
142
- return schema;
143
- }
144
- const mergedDefs = {
145
- ...schema.$defs
146
- };
147
- for (const defName of referencedLocalDefs) {
148
- mergedDefs[defName] = localDefs[defName];
149
- }
150
- return {
151
- ...schema,
152
- $defs: mergedDefs
153
- };
154
- }
155
- function collectReferencedLocalDefNames(schema, localDefs) {
156
- if (Object.keys(localDefs).length === 0) {
157
- return [];
158
- }
159
- const referenced = /* @__PURE__ */ new Set();
160
- const queue = [schema];
161
- while (queue.length > 0) {
162
- const current = queue.shift();
163
- visitLocalDefRefs(current, (refName) => {
164
- if (localDefs[refName] === void 0 || referenced.has(refName)) {
165
- return;
166
- }
167
- referenced.add(refName);
168
- queue.push(localDefs[refName]);
169
- });
170
- }
171
- return [...referenced];
172
- }
173
- function resolveComponentName(componentsSchemas, claimedNames, defName, schema, candidateSchemas, direction) {
109
+ function resolveComponentName(componentsSchemas, claimedNames, preferredName, schema, candidateSchemas, direction) {
174
110
  let mintName;
175
111
  for (let i = 1; ; i++) {
176
- const [componentName, mintable, tail] = componentNameCandidate(defName, direction, i);
112
+ const [componentName, mintable, tail] = componentNameCandidate(preferredName, direction, i);
177
113
  const existingSchema = componentsSchemas[componentName];
178
114
  if (existingSchema === void 0) {
179
115
  if (mintable && !claimedNames.has(componentName)) {
@@ -191,27 +127,27 @@ function resolveComponentName(componentsSchemas, claimedNames, defName, schema,
191
127
  existingSchema,
192
128
  candidateSchemas,
193
129
  componentsSchemas,
194
- /* @__PURE__ */ new Map([[defName, componentName]]),
195
- /* @__PURE__ */ new Map([[componentName, defName]])
130
+ /* @__PURE__ */ new Map([[preferredName, componentName]]),
131
+ /* @__PURE__ */ new Map([[componentName, preferredName]])
196
132
  )) {
197
133
  return [componentName, true];
198
134
  }
199
135
  }
200
136
  }
201
- function componentNameCandidate(defName, direction, attempt) {
137
+ function componentNameCandidate(preferredName, direction, attempt) {
202
138
  if (attempt === 1) {
203
- return [defName, true, false];
139
+ return [preferredName, true, false];
204
140
  }
205
141
  if (direction !== void 0) {
206
142
  if (attempt === 2) {
207
- return [`${defName}${direction === "input" ? "Input" : "Output"}`, true, false];
143
+ return [`${preferredName}${direction === "input" ? "Input" : "Output"}`, true, false];
208
144
  }
209
145
  if (attempt === 3) {
210
- return [`${defName}${direction === "input" ? "Output" : "Input"}`, false, false];
146
+ return [`${preferredName}${direction === "input" ? "Output" : "Input"}`, false, false];
211
147
  }
212
- return [`${defName}${attempt - 2}`, true, true];
148
+ return [`${preferredName}${attempt - 2}`, true, true];
213
149
  }
214
- return [`${defName}${attempt}`, true, true];
150
+ return [`${preferredName}${attempt}`, true, true];
215
151
  }
216
152
  function definedKeysOf(object) {
217
153
  return Object.keys(object).filter((key) => object[key] !== void 0).sort();
@@ -678,13 +614,14 @@ function buildErrorResponse(ctx, operation, def) {
678
614
  status
679
615
  );
680
616
  const responseSchema = customBodySchema ?? combineJsonSchemasWithComposition("oneOf", [
681
- ...definitions.map(({ code, dataJsonSchema, dataOptional, defaultMessage }) => {
617
+ ...definitions.map(({ code, dataJsonSchema, dataOptional }) => {
682
618
  return ctx.registry.register(toErrorComponentName(code), combineJsonObjectSchemaEntries([
683
619
  ["defined", { const: true }, false],
684
620
  ["inferable", { type: "boolean" }, false],
685
621
  ["code", { const: code }, false],
686
622
  ["status", { const: status }, false],
687
- ["message", { type: "string", default: defaultMessage }, false],
623
+ // avoid using the defaultMessage here to improve component reusability
624
+ ["message", { type: "string" }, false],
688
625
  ["data", ctx.registry.hoistDefs(dataJsonSchema, "output"), dataOptional]
689
626
  ]));
690
627
  }),
@@ -811,7 +748,7 @@ class OpenAPIGenerator {
811
748
  info: options.base?.info ?? { title: "API Reference", version: "0.0.0" }
812
749
  };
813
750
  const ctx = {
814
- registry: new OpenAPIComponentRegistry(doc, options.shouldHoistDef),
751
+ registry: new OpenAPIComponentRegistry(doc, options.customComponentName),
815
752
  convertSchemas: (schemas, direction) => this.convertSchemas(schemas, direction),
816
753
  errorStatusMap: options.errorStatusMap ?? COMMON_ERROR_STATUS_MAP,
817
754
  customErrorResponseBodySchema: options.customErrorResponseBodySchema
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@orpc/openapi",
3
3
  "type": "module",
4
- "version": "2.0.0-beta.20",
4
+ "version": "2.0.0-beta.21",
5
5
  "license": "MIT",
6
6
  "homepage": "https://orpc.dev",
7
7
  "repository": {
@@ -78,17 +78,17 @@
78
78
  "@standardserver/core": "^0.5.0",
79
79
  "@standardserver/fetch": "^0.5.0",
80
80
  "rou3": "^0.9.1",
81
- "@orpc/client": "2.0.0-beta.20",
82
- "@orpc/contract": "2.0.0-beta.20",
83
- "@orpc/server": "2.0.0-beta.20",
84
- "@orpc/shared": "2.0.0-beta.20",
85
- "@orpc/json-schema": "2.0.0-beta.20"
81
+ "@orpc/client": "2.0.0-beta.21",
82
+ "@orpc/server": "2.0.0-beta.21",
83
+ "@orpc/contract": "2.0.0-beta.21",
84
+ "@orpc/shared": "2.0.0-beta.21",
85
+ "@orpc/json-schema": "2.0.0-beta.21"
86
86
  },
87
87
  "devDependencies": {
88
- "@scalar/api-reference": "^1.57.2",
88
+ "@scalar/api-reference": "^1.63.0",
89
89
  "@types/swagger-ui": "^5.32.0",
90
90
  "fastify": "^5.8.5",
91
- "swagger-ui": "^5.32.6",
91
+ "swagger-ui": "^5.32.11",
92
92
  "zod": "^4.4.3"
93
93
  },
94
94
  "scripts": {