@apollo-deploy/tesseract 0.9.0 → 1.0.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.
Files changed (112) hide show
  1. package/dist/adapters/csharp/index.d.ts +12 -0
  2. package/dist/adapters/csharp/index.d.ts.map +1 -0
  3. package/dist/adapters/csharp/index.js +99 -0
  4. package/dist/adapters/csharp/index.js.map +1 -0
  5. package/dist/adapters/go/index.d.ts +12 -0
  6. package/dist/adapters/go/index.d.ts.map +1 -0
  7. package/dist/adapters/go/index.js +135 -0
  8. package/dist/adapters/go/index.js.map +1 -0
  9. package/dist/adapters/kotlin/index.d.ts +13 -0
  10. package/dist/adapters/kotlin/index.d.ts.map +1 -0
  11. package/dist/adapters/kotlin/index.js +186 -0
  12. package/dist/adapters/kotlin/index.js.map +1 -0
  13. package/dist/adapters/php/index.d.ts +20 -0
  14. package/dist/adapters/php/index.d.ts.map +1 -0
  15. package/dist/adapters/php/index.js +123 -0
  16. package/dist/adapters/php/index.js.map +1 -0
  17. package/dist/adapters/python/index.d.ts +12 -0
  18. package/dist/adapters/python/index.d.ts.map +1 -0
  19. package/dist/adapters/python/index.js +145 -0
  20. package/dist/adapters/python/index.js.map +1 -0
  21. package/dist/adapters/ruby/index.d.ts +12 -0
  22. package/dist/adapters/ruby/index.d.ts.map +1 -0
  23. package/dist/adapters/ruby/index.js +86 -0
  24. package/dist/adapters/ruby/index.js.map +1 -0
  25. package/dist/adapters/rust/index.d.ts +13 -0
  26. package/dist/adapters/rust/index.d.ts.map +1 -0
  27. package/dist/adapters/rust/index.js +213 -0
  28. package/dist/adapters/rust/index.js.map +1 -0
  29. package/dist/adapters/typescript/emitter/emitSchema.d.ts +1 -1
  30. package/dist/adapters/typescript/emitter/emitSchema.d.ts.map +1 -1
  31. package/dist/adapters/typescript/emitter/emitSchema.js +1 -1
  32. package/dist/adapters/typescript/emitter/emitSchema.js.map +1 -1
  33. package/dist/adapters/typescript/emitter/emitTypeScriptModels.d.ts +2 -2
  34. package/dist/adapters/typescript/emitter/emitTypeScriptModels.d.ts.map +1 -1
  35. package/dist/adapters/typescript/emitter/emitTypeScriptModels.js +2 -2
  36. package/dist/adapters/typescript/emitter/emitTypeScriptModels.js.map +1 -1
  37. package/dist/adapters/typescript/emitter/importCollector.d.ts +1 -1
  38. package/dist/adapters/typescript/emitter/importCollector.d.ts.map +1 -1
  39. package/dist/cli.js +2 -1
  40. package/dist/cli.js.map +1 -1
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +14 -0
  43. package/dist/index.js.map +1 -1
  44. package/dist/pipeline/intake.d.ts +3 -3
  45. package/dist/pipeline/intake.d.ts.map +1 -1
  46. package/dist/pipeline/intake.js +194 -121
  47. package/dist/pipeline/intake.js.map +1 -1
  48. package/dist/types/ir.d.ts +1 -1
  49. package/dist/types/ir.d.ts.map +1 -1
  50. package/dist/types/sdk-module.d.ts +22 -2
  51. package/dist/types/sdk-module.d.ts.map +1 -1
  52. package/dist/types/sdk-module.js +1 -1
  53. package/package.json +1 -1
  54. package/templates/csharp/client.hbs +95 -0
  55. package/templates/csharp/csproj.hbs +18 -0
  56. package/templates/csharp/domain.hbs +183 -0
  57. package/templates/csharp/errors.hbs +45 -0
  58. package/templates/csharp/readme.hbs +76 -0
  59. package/templates/csharp/transport.hbs +291 -0
  60. package/templates/csharp/types.hbs +104 -0
  61. package/templates/go/client.hbs +158 -0
  62. package/templates/go/domain.hbs +89 -0
  63. package/templates/go/errors.hbs +38 -0
  64. package/templates/go/go-mod.hbs +6 -0
  65. package/templates/go/readme.hbs +73 -0
  66. package/templates/go/transport.hbs +379 -0
  67. package/templates/go/types.hbs +80 -0
  68. package/templates/go/utils.hbs +45 -0
  69. package/templates/kotlin/build-gradle.hbs +41 -0
  70. package/templates/kotlin/client.hbs +57 -0
  71. package/templates/kotlin/domain.hbs +73 -0
  72. package/templates/kotlin/errors.hbs +42 -0
  73. package/templates/kotlin/gradle-properties.hbs +2 -0
  74. package/templates/kotlin/readme.hbs +105 -0
  75. package/templates/kotlin/settings-gradle.hbs +1 -0
  76. package/templates/kotlin/transport.hbs +246 -0
  77. package/templates/kotlin/types.hbs +100 -0
  78. package/templates/php/client.hbs +74 -0
  79. package/templates/php/composer.hbs +19 -0
  80. package/templates/php/domain.hbs +173 -0
  81. package/templates/php/errors.hbs +131 -0
  82. package/templates/php/readme.hbs +89 -0
  83. package/templates/php/transport.hbs +285 -0
  84. package/templates/php/types.hbs +95 -0
  85. package/templates/python/client.hbs +102 -0
  86. package/templates/python/domain.hbs +139 -0
  87. package/templates/python/index.hbs +20 -0
  88. package/templates/python/pyproject.hbs +26 -0
  89. package/templates/python/readme.hbs +64 -0
  90. package/templates/python/resources-init.hbs +11 -0
  91. package/templates/python/transport.hbs +191 -0
  92. package/templates/python/types-errors.hbs +75 -0
  93. package/templates/python/types.hbs +79 -0
  94. package/templates/ruby/Gemfile.hbs +5 -0
  95. package/templates/ruby/client.hbs +86 -0
  96. package/templates/ruby/domain.hbs +110 -0
  97. package/templates/ruby/errors.hbs +150 -0
  98. package/templates/ruby/gemspec.hbs +30 -0
  99. package/templates/ruby/index.hbs +54 -0
  100. package/templates/ruby/readme.hbs +113 -0
  101. package/templates/ruby/transport.hbs +177 -0
  102. package/templates/ruby/types.hbs +126 -0
  103. package/templates/ruby/version.hbs +5 -0
  104. package/templates/rust/api-mod.hbs +3 -0
  105. package/templates/rust/cargo.hbs +22 -0
  106. package/templates/rust/client.hbs +114 -0
  107. package/templates/rust/domain.hbs +94 -0
  108. package/templates/rust/error.hbs +74 -0
  109. package/templates/rust/lib.hbs +85 -0
  110. package/templates/rust/readme.hbs +77 -0
  111. package/templates/rust/transport.hbs +287 -0
  112. package/templates/rust/types.hbs +142 -0
@@ -3,10 +3,10 @@
3
3
  *
4
4
  * This is the only intake path in Tesseract. No OpenAPI parsing.
5
5
  */
6
- import { readFileSync } from 'node:fs';
7
- import { camelCase, pascalCase, kebabCase } from 'change-case';
8
- import { deriveCleanMethodName } from '../utils/naming.js';
9
- import { validateManifest } from '../types/manifest.js';
6
+ import { readFileSync } from "node:fs";
7
+ import { camelCase, pascalCase, kebabCase } from "change-case";
8
+ import { deriveCleanMethodName } from "../utils/naming.js";
9
+ import { validateManifest } from "../types/manifest.js";
10
10
  // ── Entry point ──────────────────────────────────────────────────────────────
11
11
  export function intake(config) {
12
12
  let manifest;
@@ -15,7 +15,7 @@ export function intake(config) {
15
15
  }
16
16
  else {
17
17
  try {
18
- const raw = readFileSync(config.input, 'utf-8');
18
+ const raw = readFileSync(config.input, "utf-8");
19
19
  manifest = JSON.parse(raw);
20
20
  }
21
21
  catch (err) {
@@ -23,7 +23,7 @@ export function intake(config) {
23
23
  }
24
24
  const validation = validateManifest(manifest);
25
25
  if (!validation.valid) {
26
- throw new Error(`Invalid manifest:\n ${validation.errors.join('\n ')}`);
26
+ throw new Error(`Invalid manifest:\n ${validation.errors.join("\n ")}`);
27
27
  }
28
28
  }
29
29
  const schemaPackage = manifest.schemaPackage;
@@ -38,7 +38,7 @@ export function intake(config) {
38
38
  isEnum: false,
39
39
  properties: [],
40
40
  ownership: {
41
- kind: 'external',
41
+ kind: "external",
42
42
  externalImport: {
43
43
  packageName: schemaPackage.name,
44
44
  importPath: importPath,
@@ -49,7 +49,10 @@ export function intake(config) {
49
49
  : [];
50
50
  // Local definitions — skip types already covered by the external package
51
51
  const localDefinitions = Object.fromEntries(Object.entries(manifest.definitions ?? {}).filter(([k]) => !externalRefNames.has(pascalCase(k))));
52
- const schemas = [...externalSchemas, ...buildSchemas(localDefinitions)];
52
+ const schemas = [
53
+ ...externalSchemas,
54
+ ...buildSchemas(localDefinitions),
55
+ ];
53
56
  const groups = [];
54
57
  const allInlineSchemas = [];
55
58
  for (const domain of manifest.domains) {
@@ -65,7 +68,7 @@ export function intake(config) {
65
68
  // When undefined the TypeScript adapter queries npm and bumps the patch.
66
69
  packageVersion: config.packageVersion,
67
70
  description: manifest.info.description,
68
- baseUrl: config.baseUrl ?? manifest.info.baseUrl ?? 'http://localhost',
71
+ baseUrl: config.baseUrl ?? manifest.info.baseUrl ?? "http://localhost",
69
72
  packageName: config.packageName ?? kebabCase(manifest.info.title).toLowerCase(),
70
73
  clientName: config.clientName ?? pascalCase(manifest.info.title),
71
74
  schemaPackage: schemaPackage
@@ -83,7 +86,13 @@ function collectExternalRefNames(manifest) {
83
86
  const s = route.schema;
84
87
  if (!s)
85
88
  continue;
86
- for (const field of [s.params, s.querystring, s.query, s.body, s.headers]) {
89
+ for (const field of [
90
+ s.params,
91
+ s.querystring,
92
+ s.query,
93
+ s.body,
94
+ s.headers,
95
+ ]) {
87
96
  if (field)
88
97
  scanSchemaForExternalRefs(field, names);
89
98
  }
@@ -102,9 +111,9 @@ function collectExternalRefNames(manifest) {
102
111
  }
103
112
  function scanSchemaForExternalRefs(schema, out) {
104
113
  const s = schema;
105
- if (typeof s.$ref === 'string') {
106
- if (!s.$ref.startsWith('#')) {
107
- out.add(pascalCase(s.$ref.split('/').pop()));
114
+ if (typeof s.$ref === "string") {
115
+ if (!s.$ref.startsWith("#")) {
116
+ out.add(pascalCase(s.$ref.split("/").pop()));
108
117
  }
109
118
  return;
110
119
  }
@@ -113,12 +122,12 @@ function scanSchemaForExternalRefs(schema, out) {
113
122
  for (const v of Object.values(props))
114
123
  scanSchemaForExternalRefs(v, out);
115
124
  }
116
- if (s.additionalProperties && typeof s.additionalProperties === 'object') {
125
+ if (s.additionalProperties && typeof s.additionalProperties === "object") {
117
126
  scanSchemaForExternalRefs(s.additionalProperties, out);
118
127
  }
119
128
  if (s.items)
120
129
  scanSchemaForExternalRefs(s.items, out);
121
- for (const key of ['allOf', 'oneOf', 'anyOf']) {
130
+ for (const key of ["allOf", "oneOf", "anyOf"]) {
122
131
  const arr = s[key];
123
132
  if (Array.isArray(arr)) {
124
133
  for (const m of arr)
@@ -132,7 +141,7 @@ function buildSchemas(definitions) {
132
141
  }
133
142
  function jsonSchemaToDefinition(name, schema) {
134
143
  const s = schema;
135
- if ('enum' in s && Array.isArray(s.enum)) {
144
+ if ("enum" in s && Array.isArray(s.enum)) {
136
145
  return {
137
146
  name,
138
147
  description: s.description,
@@ -142,7 +151,7 @@ function jsonSchemaToDefinition(name, schema) {
142
151
  isTypeAlias: true,
143
152
  };
144
153
  }
145
- if ('oneOf' in s && Array.isArray(s.oneOf)) {
154
+ if ("oneOf" in s && Array.isArray(s.oneOf)) {
146
155
  return {
147
156
  name,
148
157
  description: s.description,
@@ -153,7 +162,7 @@ function jsonSchemaToDefinition(name, schema) {
153
162
  isTypeAlias: true,
154
163
  };
155
164
  }
156
- if ('anyOf' in s && Array.isArray(s.anyOf)) {
165
+ if ("anyOf" in s && Array.isArray(s.anyOf)) {
157
166
  return {
158
167
  name,
159
168
  description: s.description,
@@ -164,11 +173,11 @@ function jsonSchemaToDefinition(name, schema) {
164
173
  isTypeAlias: true,
165
174
  };
166
175
  }
167
- if ('allOf' in s && Array.isArray(s.allOf)) {
176
+ if ("allOf" in s && Array.isArray(s.allOf)) {
168
177
  const members = s.allOf;
169
178
  if (members.length === 2) {
170
- const refMember = members.find((m) => '$ref' in m);
171
- const objectMember = members.find((m) => !('$ref' in m) && m.type === 'object');
179
+ const refMember = members.find((m) => "$ref" in m);
180
+ const objectMember = members.find((m) => !("$ref" in m) && m.type === "object");
172
181
  if (refMember && objectMember) {
173
182
  const baseDef = jsonSchemaToDefinition(name, objectMember);
174
183
  return { ...baseDef, extends: jsonSchemaToType(refMember) };
@@ -184,7 +193,7 @@ function jsonSchemaToDefinition(name, schema) {
184
193
  isTypeAlias: true,
185
194
  };
186
195
  }
187
- if (s.type === 'object') {
196
+ if (s.type === "object") {
188
197
  const required = new Set(Array.isArray(s.required) ? s.required : []);
189
198
  const rawProps = s.properties ?? {};
190
199
  const properties = Object.entries(rawProps).map(([propName, propSchema]) => {
@@ -201,7 +210,9 @@ function jsonSchemaToDefinition(name, schema) {
201
210
  };
202
211
  });
203
212
  const ap = s.additionalProperties;
204
- const additionalPropertiesType = ap && typeof ap === 'object' ? jsonSchemaToType(ap) : undefined;
213
+ const additionalPropertiesType = ap && typeof ap === "object"
214
+ ? jsonSchemaToType(ap)
215
+ : undefined;
205
216
  return {
206
217
  name,
207
218
  description: s.description,
@@ -221,72 +232,72 @@ function jsonSchemaToDefinition(name, schema) {
221
232
  // ── Type string conversion ───────────────────────────────────────────────────
222
233
  export function jsonSchemaToType(schema) {
223
234
  const s = schema;
224
- if ('$ref' in s && typeof s.$ref === 'string') {
225
- return pascalCase(s.$ref.split('/').pop());
235
+ if ("$ref" in s && typeof s.$ref === "string") {
236
+ return pascalCase(s.$ref.split("/").pop());
226
237
  }
227
- if ('enum' in s && Array.isArray(s.enum)) {
228
- return s.enum.map((v) => JSON.stringify(v)).join(' | ');
238
+ if ("enum" in s && Array.isArray(s.enum)) {
239
+ return s.enum.map((v) => JSON.stringify(v)).join(" | ");
229
240
  }
230
241
  if (Array.isArray(s.allOf))
231
- return s.allOf.map(jsonSchemaToType).join(' & ');
242
+ return s.allOf.map(jsonSchemaToType).join(" & ");
232
243
  if (Array.isArray(s.oneOf))
233
- return s.oneOf.map(jsonSchemaToType).join(' | ');
244
+ return s.oneOf.map(jsonSchemaToType).join(" | ");
234
245
  if (Array.isArray(s.anyOf))
235
- return s.anyOf.map(jsonSchemaToType).join(' | ');
246
+ return s.anyOf.map(jsonSchemaToType).join(" | ");
236
247
  const type = s.type;
237
- if (type === 'object') {
248
+ if (type === "object") {
238
249
  const required = new Set(Array.isArray(s.required) ? s.required : []);
239
250
  const rawProps = s.properties;
240
251
  if (rawProps) {
241
252
  const entries = Object.entries(rawProps)
242
- .map(([k, v]) => `${k}${required.has(k) ? '' : '?'}: ${jsonSchemaToType(v)}`)
243
- .join('; ');
253
+ .map(([k, v]) => `${k}${required.has(k) ? "" : "?"}: ${jsonSchemaToType(v)}`)
254
+ .join("; ");
244
255
  const ap = s.additionalProperties;
245
- if (ap && typeof ap === 'object') {
256
+ if (ap && typeof ap === "object") {
246
257
  return `{ ${entries}; [key: string]: ${jsonSchemaToType(ap)} }`;
247
258
  }
248
- return entries ? `{ ${entries} }` : 'Record<string, unknown>';
259
+ return entries ? `{ ${entries} }` : "Record<string, unknown>";
249
260
  }
250
- if (s.additionalProperties && typeof s.additionalProperties === 'object') {
261
+ if (s.additionalProperties && typeof s.additionalProperties === "object") {
251
262
  return `Record<string, ${jsonSchemaToType(s.additionalProperties)}>`;
252
263
  }
253
- return 'Record<string, unknown>';
264
+ return "Record<string, unknown>";
254
265
  }
255
- if (type === 'array') {
266
+ if (type === "array") {
256
267
  const items = s.items;
257
- return `Array<${items ? jsonSchemaToType(items) : 'unknown'}>`;
268
+ return `Array<${items ? jsonSchemaToType(items) : "unknown"}>`;
258
269
  }
259
- if (type === 'string' || type === 'date')
260
- return 'string';
261
- if (type === 'number' || type === 'integer')
262
- return 'number';
263
- if (type === 'boolean')
264
- return 'boolean';
265
- if (type === 'null')
266
- return 'null';
267
- if (type === 'never')
268
- return 'never';
269
- if (type === 'nullable') {
270
+ if (type === "string" || type === "date")
271
+ return "string";
272
+ if (type === "number" || type === "integer")
273
+ return "number";
274
+ if (type === "boolean")
275
+ return "boolean";
276
+ if (type === "null")
277
+ return "null";
278
+ if (type === "never")
279
+ return "never";
280
+ if (type === "nullable") {
270
281
  const def = s.def;
271
282
  if (def?.innerType)
272
- return jsonSchemaToType(def.innerType) + ' | null';
273
- return 'unknown | null';
283
+ return jsonSchemaToType(def.innerType) + " | null";
284
+ return "unknown | null";
274
285
  }
275
- if (type === 'union') {
286
+ if (type === "union") {
276
287
  const options = s.options;
277
288
  if (options)
278
- return options.map(jsonSchemaToType).join(' | ');
289
+ return options.map(jsonSchemaToType).join(" | ");
279
290
  }
280
- return 'unknown';
291
+ return "unknown";
281
292
  }
282
293
  // ── Group / operation building ───────────────────────────────────────────────
283
294
  function isComplexInlineSchema(s) {
284
- if (s.type === 'object')
295
+ if (s.type === "object")
285
296
  return true;
286
297
  if (Array.isArray(s.allOf))
287
298
  return true;
288
299
  // anyOf / oneOf that is just `SomeRef | null` is not complex — emit inline
289
- for (const key of ['anyOf', 'oneOf']) {
300
+ for (const key of ["anyOf", "oneOf"]) {
290
301
  const arr = s[key];
291
302
  if (!Array.isArray(arr))
292
303
  continue;
@@ -300,8 +311,8 @@ function isComplexInlineSchema(s) {
300
311
  function isNullableRefUnion(members) {
301
312
  if (members.length !== 2)
302
313
  return false;
303
- const refMember = members.find((m) => '$ref' in m);
304
- const nullMember = members.find((m) => m.type === 'null');
314
+ const refMember = members.find((m) => "$ref" in m);
315
+ const nullMember = members.find((m) => m.type === "null");
305
316
  return !!refMember && !!nullMember;
306
317
  }
307
318
  function buildOperationGroup(domain, _schemas) {
@@ -314,11 +325,14 @@ function buildOperationGroup(domain, _schemas) {
314
325
  if (route.sdk?.exclude)
315
326
  continue;
316
327
  const { operation, inlineSchemas } = buildOperation(route, name, domain.prefix);
317
- operations.push({ ...operation, visibility: route.sdk?.internal ? 'internal' : 'public' });
328
+ operations.push({
329
+ ...operation,
330
+ visibility: route.sdk?.internal ? "internal" : "public",
331
+ });
318
332
  for (const s of inlineSchemas) {
319
333
  allInlineSchemas.push({
320
334
  ...s,
321
- ownership: { kind: 'generated', domainFile: fileName },
335
+ ownership: { kind: "generated", domainFile: fileName },
322
336
  });
323
337
  }
324
338
  }
@@ -326,18 +340,20 @@ function buildOperationGroup(domain, _schemas) {
326
340
  group: {
327
341
  name,
328
342
  fileName,
329
- interfaceName: pascalCase(domainName) + 'API',
330
- factoryName: 'create' + pascalCase(domainName) + 'API',
343
+ interfaceName: pascalCase(domainName) + "API",
344
+ factoryName: "create" + pascalCase(domainName) + "API",
331
345
  description: domain.description,
332
- visibility: domain.stability === 'internal' ? 'internal' : 'public',
346
+ visibility: domain.stability === "internal" ? "internal" : "public",
333
347
  operations,
334
348
  },
335
349
  inlineSchemas: allInlineSchemas,
336
350
  };
337
351
  }
338
352
  function buildOperation(route, groupName, domainPrefix) {
339
- const prefix = (route.sdk?.prefix ?? domainPrefix).replace(/\/$/, '');
340
- const fullUrl = prefix + (route.url.startsWith('/') ? route.url : '/' + route.url);
353
+ const prefix = (route.sdk?.prefix ?? domainPrefix).replace(/\/$/, "");
354
+ // When route.url is '/' it means the route URL exactly matched the domain
355
+ // prefix — avoid doubling the slash (e.g. /signal/projects + / = /signal/projects/).
356
+ const fullUrl = route.url === "/" ? prefix || "/" : prefix + route.url;
341
357
  const httpMethod = (Array.isArray(route.method) ? route.method[0] : route.method).toUpperCase();
342
358
  const pathParams = extractPathParams(fullUrl, route.schema?.params);
343
359
  const querySchema = route.schema?.querystring ?? route.schema?.query;
@@ -348,28 +364,36 @@ function buildOperation(route, groupName, domainPrefix) {
348
364
  deriveCleanMethodName({
349
365
  operationId: route.schema?.operationId ?? route.sdk?.operationId,
350
366
  summary: route.schema?.summary,
351
- path: route.url.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, '{$1}'),
367
+ path: route.url.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, "{$1}"),
352
368
  httpMethod: httpMethod.toLowerCase(),
353
369
  tagName: groupName,
354
370
  });
355
371
  // Response type
356
- let responseType = 'void';
372
+ let responseType = "void";
357
373
  let statusCode = 200;
358
- const successResponse = pickSuccessResponse(route.schema?.response);
359
- if (successResponse) {
360
- const { schema: successSchema, statusCode: sc } = successResponse;
361
- statusCode = sc;
362
- const rs = successSchema;
363
- if (rs.type === 'null' || (rs.type === 'object' && !rs.properties && !rs.$ref)) {
364
- responseType = 'void';
365
- }
366
- else if (typeof rs.$ref !== 'string' && isComplexInlineSchema(rs)) {
367
- const typeName = pascalCase(name) + 'Response';
368
- inlineSchemas.push(jsonSchemaToDefinition(typeName, successSchema));
369
- responseType = typeName;
370
- }
371
- else {
372
- responseType = jsonSchemaToType(successSchema);
374
+ // SDK config override: explicit returnType takes priority over schema derivation.
375
+ // Useful for paginated list endpoints that return a wrapper but should expose the inner array type.
376
+ if (route.sdk?.returnType) {
377
+ responseType = route.sdk.returnType;
378
+ }
379
+ else {
380
+ const successResponse = pickSuccessResponse(route.schema?.response);
381
+ if (successResponse) {
382
+ const { schema: successSchema, statusCode: sc } = successResponse;
383
+ statusCode = sc;
384
+ const rs = successSchema;
385
+ if (rs.type === "null" ||
386
+ (rs.type === "object" && !rs.properties && !rs.$ref)) {
387
+ responseType = "void";
388
+ }
389
+ else if (typeof rs.$ref !== "string" && isComplexInlineSchema(rs)) {
390
+ const typeName = pascalCase(name) + "Response";
391
+ inlineSchemas.push(jsonSchemaToDefinition(typeName, successSchema));
392
+ responseType = typeName;
393
+ }
394
+ else {
395
+ responseType = jsonSchemaToType(successSchema);
396
+ }
373
397
  }
374
398
  }
375
399
  // Request body
@@ -378,8 +402,8 @@ function buildOperation(route, groupName, domainPrefix) {
378
402
  const bodySchema = route.schema.body;
379
403
  const bs = bodySchema;
380
404
  let bodyType;
381
- if (typeof bs.$ref !== 'string' && isComplexInlineSchema(bs)) {
382
- const typeName = pascalCase(name) + 'Input';
405
+ if (typeof bs.$ref !== "string" && isComplexInlineSchema(bs)) {
406
+ const typeName = pascalCase(name) + "Input";
383
407
  inlineSchemas.push(jsonSchemaToDefinition(typeName, bodySchema));
384
408
  bodyType = typeName;
385
409
  }
@@ -389,18 +413,18 @@ function buildOperation(route, groupName, domainPrefix) {
389
413
  requestBody = {
390
414
  type: bodyType,
391
415
  required: true,
392
- contentType: route.sdk?.transport === 'multipart'
393
- ? 'multipart/form-data'
394
- : route.sdk?.transport === 'binary'
395
- ? 'application/octet-stream'
396
- : 'application/json',
416
+ contentType: route.sdk?.transport === "multipart"
417
+ ? "multipart/form-data"
418
+ : route.sdk?.transport === "binary"
419
+ ? "application/octet-stream"
420
+ : "application/json",
397
421
  };
398
422
  }
399
423
  // Query type
400
424
  let queryType;
401
425
  if (querySchema) {
402
426
  const qs = querySchema;
403
- if (typeof qs.$ref === 'string') {
427
+ if (typeof qs.$ref === "string") {
404
428
  queryType = jsonSchemaToType(querySchema);
405
429
  }
406
430
  else if (queryParams.length > 0) {
@@ -413,7 +437,7 @@ function buildOperation(route, groupName, domainPrefix) {
413
437
  headerParams.push({
414
438
  name: camelCase(rh.paramName),
415
439
  originalName: rh.name,
416
- type: 'string',
440
+ type: "string",
417
441
  required: true,
418
442
  description: rh.description,
419
443
  });
@@ -423,10 +447,10 @@ function buildOperation(route, groupName, domainPrefix) {
423
447
  let headerType;
424
448
  if (headerParams.length > 0) {
425
449
  const props = headerParams.map((p) => {
426
- const opt = p.required ? '' : '?';
450
+ const opt = p.required ? "" : "?";
427
451
  return `${p.name}${opt}: ${p.type}`;
428
452
  });
429
- headerType = `{ ${props.join('; ')} }`;
453
+ headerType = `{ ${props.join("; ")} }`;
430
454
  }
431
455
  return {
432
456
  operation: {
@@ -447,10 +471,12 @@ function buildOperation(route, groupName, domainPrefix) {
447
471
  responseType,
448
472
  statusCode,
449
473
  deprecated: route.sdk?.deprecated ? true : false,
450
- deprecationMessage: typeof route.sdk?.deprecated === 'string' ? route.sdk.deprecated : undefined,
474
+ deprecationMessage: typeof route.sdk?.deprecated === "string"
475
+ ? route.sdk.deprecated
476
+ : undefined,
451
477
  timeoutMs: route.sdk?.timeout,
452
- visibility: 'public',
453
- isEventStream: route.sse ?? route.sdk?.transport === 'stream',
478
+ visibility: "public",
479
+ isEventStream: route.sse ?? route.sdk?.transport === "stream",
454
480
  sseReturnType: route.sdk?.sseReturnType,
455
481
  },
456
482
  inlineSchemas,
@@ -458,15 +484,62 @@ function buildOperation(route, groupName, domainPrefix) {
458
484
  }
459
485
  // ── Parameter extraction ─────────────────────────────────────────────────────
460
486
  const TS_RESERVED_WORDS = new Set([
461
- 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger', 'default',
462
- 'delete', 'do', 'else', 'enum', 'export', 'extends', 'false', 'finally',
463
- 'for', 'function', 'if', 'import', 'in', 'instanceof', 'new', 'null',
464
- 'return', 'super', 'switch', 'this', 'throw', 'true', 'try', 'typeof',
465
- 'var', 'void', 'while', 'with', 'as', 'implements', 'interface', 'let',
466
- 'package', 'private', 'protected', 'public', 'static', 'yield', 'type',
487
+ "break",
488
+ "case",
489
+ "catch",
490
+ "class",
491
+ "const",
492
+ "continue",
493
+ "debugger",
494
+ "default",
495
+ "delete",
496
+ "do",
497
+ "else",
498
+ "enum",
499
+ "export",
500
+ "extends",
501
+ "false",
502
+ "finally",
503
+ "for",
504
+ "function",
505
+ "if",
506
+ "import",
507
+ "in",
508
+ "instanceof",
509
+ "new",
510
+ "null",
511
+ "return",
512
+ "super",
513
+ "switch",
514
+ "this",
515
+ "throw",
516
+ "true",
517
+ "try",
518
+ "typeof",
519
+ "var",
520
+ "void",
521
+ "while",
522
+ "with",
523
+ "as",
524
+ "implements",
525
+ "interface",
526
+ "let",
527
+ "package",
528
+ "private",
529
+ "protected",
530
+ "public",
531
+ "static",
532
+ "yield",
533
+ "type",
467
534
  ]);
468
535
  /** Parameter names reserved by the generated method signatures. */
469
- const FIXED_PARAM_NAMES = new Set(['input', 'query', 'headerOptions', 'cookies', 'options']);
536
+ const FIXED_PARAM_NAMES = new Set([
537
+ "input",
538
+ "query",
539
+ "headerOptions",
540
+ "cookies",
541
+ "options",
542
+ ]);
470
543
  function safeParamName(name) {
471
544
  if (TS_RESERVED_WORDS.has(name) || FIXED_PARAM_NAMES.has(name)) {
472
545
  return `${name}Param`;
@@ -478,7 +551,7 @@ function extractPathParams(url, paramsSchema) {
478
551
  if (urlParams.length === 0)
479
552
  return [];
480
553
  const s = paramsSchema;
481
- const props = typeof s?.properties === 'object' && !Array.isArray(s?.properties)
554
+ const props = typeof s?.properties === "object" && !Array.isArray(s?.properties)
482
555
  ? s.properties
483
556
  : undefined;
484
557
  const required = new Set(Array.isArray(s?.required) ? s.required : []);
@@ -487,7 +560,7 @@ function extractPathParams(url, paramsSchema) {
487
560
  return {
488
561
  name: safeParamName(camelCase(paramName)),
489
562
  originalName: paramName,
490
- type: propSchema ? jsonSchemaToType(propSchema) : 'string',
563
+ type: propSchema ? jsonSchemaToType(propSchema) : "string",
491
564
  required: required.size === 0 || required.has(paramName),
492
565
  description: propSchema
493
566
  ? propSchema.description
@@ -499,7 +572,7 @@ function extractQueryParams(querySchema) {
499
572
  if (!querySchema)
500
573
  return [];
501
574
  const s = querySchema;
502
- if (s.type !== 'object' || !s.properties)
575
+ if (s.type !== "object" || !s.properties)
503
576
  return [];
504
577
  const required = new Set(Array.isArray(s.required) ? s.required : []);
505
578
  const props = s.properties;
@@ -513,18 +586,18 @@ function extractQueryParams(querySchema) {
513
586
  }));
514
587
  }
515
588
  const SYSTEM_HEADERS = new Set([
516
- 'content-type',
517
- 'content-length',
518
- 'transfer-encoding',
519
- 'host',
520
- 'accept',
521
- 'accept-encoding',
589
+ "content-type",
590
+ "content-length",
591
+ "transfer-encoding",
592
+ "host",
593
+ "accept",
594
+ "accept-encoding",
522
595
  ]);
523
596
  function extractHeaderParams(headersSchema) {
524
597
  if (!headersSchema)
525
598
  return [];
526
599
  const s = headersSchema;
527
- if (s.type !== 'object' || !s.properties)
600
+ if (s.type !== "object" || !s.properties)
528
601
  return [];
529
602
  const required = new Set(Array.isArray(s.required) ? s.required : []);
530
603
  const props = s.properties;
@@ -543,19 +616,19 @@ function fastifyPathToIR(url, pathParams) {
543
616
  const nameMap = new Map(pathParams.map((p) => [p.originalName, p.name]));
544
617
  return url.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, (_, raw) => {
545
618
  const name = nameMap.get(raw) ?? raw;
546
- return '${' + name + '}';
619
+ return "${" + name + "}";
547
620
  });
548
621
  }
549
622
  function pickSuccessResponse(response) {
550
623
  if (!response)
551
624
  return undefined;
552
625
  for (const [code, schema] of [
553
- [200, response['200'] ?? response[200]],
554
- [201, response['201'] ?? response[201]],
555
- [202, response['202'] ?? response[202]],
556
- [204, response['204'] ?? response[204]],
557
- [200, response['2xx']],
558
- [200, response['default']],
626
+ [200, response["200"] ?? response[200]],
627
+ [201, response["201"] ?? response[201]],
628
+ [202, response["202"] ?? response[202]],
629
+ [204, response["204"] ?? response[204]],
630
+ [200, response["2xx"]],
631
+ [200, response["default"]],
559
632
  ]) {
560
633
  if (schema)
561
634
  return { schema, statusCode: code };