@sdk-it/typescript 0.15.0 → 0.16.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/index.js CHANGED
@@ -1,25 +1,9 @@
1
1
  // packages/typescript/src/lib/generate.ts
2
- import { join } from "node:path";
2
+ import { join as join2 } from "node:path";
3
3
  import { npmRunPathEnv } from "npm-run-path";
4
4
  import { spinalcase as spinalcase3 } from "stringcase";
5
5
  import { getFolderExports, methods, writeFiles } from "@sdk-it/core";
6
6
 
7
- // packages/typescript/src/lib/generator.ts
8
- import { get as get2, merge } from "lodash-es";
9
- import { pascalcase, spinalcase as spinalcase2 } from "stringcase";
10
- import {
11
- forEachOperation,
12
- removeDuplicates as removeDuplicates3
13
- } from "@sdk-it/core";
14
-
15
- // packages/typescript/src/lib/utils.ts
16
- import { get } from "lodash-es";
17
- import { removeDuplicates as removeDuplicates2 } from "@sdk-it/core";
18
-
19
- // packages/typescript/src/lib/sdk.ts
20
- import { camelcase, spinalcase } from "stringcase";
21
- import { removeDuplicates, toLitObject as toLitObject2 } from "@sdk-it/core";
22
-
23
7
  // packages/typescript/src/lib/client.ts
24
8
  import { toLitObject } from "@sdk-it/core";
25
9
  var client_default = (spec) => {
@@ -46,8 +30,8 @@ var client_default = (spec) => {
46
30
  return `
47
31
  import { fetchType, sendRequest } from './http/${spec.makeImport("send-request")}';
48
32
  import z from 'zod';
49
- import type { Endpoints } from './${spec.makeImport("endpoints")}';
50
- import schemas from './${spec.makeImport("schemas")}';
33
+ import type { Endpoints } from './api/${spec.makeImport("schemas")}';
34
+ import schemas from './api/${spec.makeImport("schemas")}';
51
35
  import {
52
36
  createBaseUrlInterceptor,
53
37
  createHeadersInterceptor,
@@ -80,557 +64,35 @@ export class ${spec.name} {
80
64
  signal: options?.signal,
81
65
  });
82
66
  }
83
-
84
- get defaultHeaders() {
85
- return ${defaultHeaders}
86
- }
87
-
88
- get #defaultInputs() {
89
- return ${defaultInputs}
90
- }
91
-
92
- setOptions(options: Partial<${spec.name}Options>) {
93
- const validated = optionsSchema.partial().parse(options);
94
-
95
- for (const key of Object.keys(validated) as (keyof ${spec.name}Options)[]) {
96
- if (validated[key] !== undefined) {
97
- (this.options[key] as typeof validated[typeof key]) = validated[key]!;
98
- }
99
- }
100
- }
101
- }`;
102
- };
103
-
104
- // packages/typescript/src/lib/sdk.ts
105
- var SchemaEndpoint = class {
106
- #makeImport;
107
- #imports = [];
108
- constructor(makeImport) {
109
- this.#makeImport = makeImport;
110
- this.#imports = [
111
- `import z from 'zod';`,
112
- `import type { Endpoints } from '${this.#makeImport("./endpoints")}';`,
113
- `import { toRequest, json, urlencoded, nobody, formdata, createUrl } from '${this.#makeImport("./http/request")}';`,
114
- `import type { ParseError } from '${this.#makeImport("./http/parser")}';`,
115
- `import { chunked, buffered } from "${this.#makeImport("./http/parse-response")}";`
116
- ];
117
- }
118
- #endpoints = [];
119
- addEndpoint(endpoint, operation) {
120
- this.#endpoints.push(` "${endpoint}": ${operation},`);
121
- }
122
- addImport(value) {
123
- this.#imports.push(value);
124
- }
125
- complete() {
126
- return `${this.#imports.join("\n")}
127
- export default {
128
- ${this.#endpoints.join("\n")}
129
- }`;
130
- }
131
- };
132
- var Emitter = class {
133
- #makeImport;
134
- imports = [];
135
- constructor(makeImport) {
136
- this.#makeImport = makeImport;
137
- this.imports = [
138
- `import type z from 'zod';`,
139
- `import type { ParseError } from '${this.#makeImport("./http/parser")}';`
140
- ];
141
- }
142
- endpoints = [];
143
- addEndpoint(endpoint, operation) {
144
- this.endpoints.push(` "${endpoint}": ${operation};`);
145
- }
146
- addImport(value) {
147
- this.imports.push(value);
148
- }
149
- complete() {
150
- return `${this.imports.join("\n")}
151
- export interface Endpoints {
152
- ${this.endpoints.join("\n")}
153
- }`;
154
- }
155
- };
156
- function generateInputs(operationsSet, commonZod, makeImport) {
157
- const commonImports = commonZod.keys().toArray();
158
- const inputs = {};
159
- for (const [name, operations] of Object.entries(operationsSet)) {
160
- const output = [];
161
- const imports = /* @__PURE__ */ new Set(['import { z } from "zod";']);
162
- for (const operation of operations) {
163
- const schemaName = camelcase(`${operation.name} schema`);
164
- const schema = `export const ${schemaName} = ${Object.keys(operation.schemas).length === 1 ? Object.values(operation.schemas)[0] : toLitObject2(operation.schemas)};`;
165
- const inputContent = schema;
166
- for (const schema2 of commonImports) {
167
- if (inputContent.includes(schema2)) {
168
- imports.add(
169
- `import { ${schema2} } from './schemas/${makeImport(spinalcase(schema2))}';`
170
- );
171
- }
172
- }
173
- output.push(inputContent);
174
- }
175
- inputs[`inputs/${spinalcase(name)}.ts`] = [...imports, ...output].join("\n") + "\n";
176
- }
177
- const schemas = commonZod.entries().reduce((acc, [name, schema]) => {
178
- const output = [`import { z } from 'zod';`];
179
- const content = `export const ${name} = ${schema};`;
180
- for (const schema2 of commonImports) {
181
- const preciseMatch = new RegExp(`\\b${schema2}\\b`);
182
- if (preciseMatch.test(content) && schema2 !== name) {
183
- output.push(
184
- `import { ${schema2} } from './${makeImport(spinalcase(schema2))}';`
185
- );
186
- }
187
- }
188
- output.push(content);
189
- return [
190
- [`inputs/schemas/${spinalcase(name)}.ts`, output.join("\n")],
191
- ...acc
192
- ];
193
- }, []);
194
- return {
195
- ...Object.fromEntries(schemas),
196
- ...inputs
197
- };
198
- }
199
- function generateSDK(spec) {
200
- const emitter = new Emitter(spec.makeImport);
201
- const schemaEndpoint = new SchemaEndpoint(spec.makeImport);
202
- const errors = [];
203
- for (const [name, operations] of Object.entries(spec.operations)) {
204
- emitter.addImport(
205
- `import type * as ${camelcase(name)} from './inputs/${spec.makeImport(spinalcase(name))}';`
206
- );
207
- schemaEndpoint.addImport(
208
- `import * as ${camelcase(name)} from './inputs/${spec.makeImport(spinalcase(name))}';`
209
- );
210
- for (const operation of operations) {
211
- const schemaName = camelcase(`${operation.name} schema`);
212
- const schemaRef = `${camelcase(name)}.${schemaName}`;
213
- const output = operation.formatOutput();
214
- const inputHeaders = [];
215
- const inputQuery = [];
216
- const inputBody = [];
217
- const inputParams = [];
218
- for (const [name2, prop] of Object.entries(operation.inputs)) {
219
- if (prop.in === "headers" || prop.in === "header") {
220
- inputHeaders.push(`"${name2}"`);
221
- } else if (prop.in === "query") {
222
- inputQuery.push(`"${name2}"`);
223
- } else if (prop.in === "body") {
224
- inputBody.push(`"${name2}"`);
225
- } else if (prop.in === "path") {
226
- inputParams.push(`"${name2}"`);
227
- } else if (prop.in === "internal") {
228
- continue;
229
- } else {
230
- throw new Error(
231
- `Unknown source ${prop.in} in ${name2} ${JSON.stringify(
232
- prop
233
- )} in ${operation.name}`
234
- );
235
- }
236
- }
237
- emitter.addImport(
238
- `import type {${output.import}} from './outputs/${spec.makeImport(spinalcase(operation.name))}';`
239
- );
240
- errors.push(...operation.errors ?? []);
241
- const addTypeParser = Object.keys(operation.schemas).length > 1;
242
- for (const type in operation.schemas ?? {}) {
243
- let typePrefix = "";
244
- if (addTypeParser && type !== "json") {
245
- typePrefix = `${type} `;
246
- }
247
- const input = `typeof ${schemaRef}${addTypeParser ? `.${type}` : ""}`;
248
- const endpoint = `${typePrefix}${operation.trigger.method.toUpperCase()} ${operation.trigger.path}`;
249
- emitter.addEndpoint(
250
- endpoint,
251
- `{input: z.infer<${input}>; output: ${output.use}; error: ${(operation.errors ?? ["ServerError"]).concat(`ParseError<${input}>`).join("|")}}`
252
- );
253
- schemaEndpoint.addEndpoint(
254
- endpoint,
255
- `{
256
- schema: ${schemaRef}${addTypeParser ? `.${type}` : ""},
257
- deserializer: ${operation.parser === "chunked" ? "chunked" : "buffered"},
258
- toRequest(input: Endpoints['${endpoint}']['input']) {
259
- const endpoint = '${endpoint}';
260
- return toRequest(endpoint, ${operation.outgoingContentType || "nobody"}(input, {
261
- inputHeaders: [${inputHeaders}],
262
- inputQuery: [${inputQuery}],
263
- inputBody: [${inputBody}],
264
- inputParams: [${inputParams}],
265
- }));
266
- },
267
- }`
268
- );
269
- }
270
- }
271
- }
272
- emitter.addImport(
273
- `import type { ${removeDuplicates(errors, (it) => it).join(", ")} } from '${spec.makeImport("./http/response")}';`
274
- );
275
- return {
276
- "client.ts": client_default(spec),
277
- "schemas.ts": schemaEndpoint.complete(),
278
- "endpoints.ts": emitter.complete()
279
- };
280
- }
281
-
282
- // packages/typescript/src/lib/utils.ts
283
- function isRef(obj) {
284
- return obj && "$ref" in obj;
285
- }
286
- function cleanRef(ref) {
287
- return ref.replace(/^#\//, "");
288
- }
289
- function parseRef(ref) {
290
- const parts = ref.split("/");
291
- const [model] = parts.splice(-1);
292
- return { model, path: parts.join("/") };
293
- }
294
- function followRef(spec, ref) {
295
- const pathParts = cleanRef(ref).split("/");
296
- const entry = get(spec, pathParts);
297
- if (entry && "$ref" in entry) {
298
- return followRef(spec, entry.$ref);
299
- }
300
- return entry;
301
- }
302
- function securityToOptions(security2, securitySchemes, staticIn) {
303
- securitySchemes ??= {};
304
- const options = {};
305
- for (const it of security2) {
306
- const [name] = Object.keys(it);
307
- if (!name) {
308
- continue;
309
- }
310
- const schema = securitySchemes[name];
311
- if (isRef(schema)) {
312
- throw new Error(`Ref security schemas are not supported`);
313
- }
314
- if (schema.type === "http") {
315
- options["authorization"] = {
316
- in: staticIn ?? "header",
317
- schema: "z.string().optional().transform((val) => (val ? `Bearer ${val}` : undefined))",
318
- optionName: "token"
319
- };
320
- continue;
321
- }
322
- if (schema.type === "apiKey") {
323
- if (!schema.in) {
324
- throw new Error(`apiKey security schema must have an "in" field`);
325
- }
326
- if (!schema.name) {
327
- throw new Error(`apiKey security schema must have a "name" field`);
328
- }
329
- options[schema.name] = {
330
- in: staticIn ?? schema.in,
331
- schema: "z.string().optional()"
332
- };
333
- continue;
334
- }
335
- }
336
- return options;
337
- }
338
- function mergeImports(imports) {
339
- const merged = {};
340
- for (const i of imports) {
341
- merged[i.moduleSpecifier] = merged[i.moduleSpecifier] ?? {
342
- moduleSpecifier: i.moduleSpecifier,
343
- defaultImport: i.defaultImport,
344
- namespaceImport: i.namespaceImport,
345
- namedImports: []
346
- };
347
- if (i.namedImports) {
348
- merged[i.moduleSpecifier].namedImports.push(...i.namedImports);
349
- }
350
- }
351
- return Object.values(merged);
352
- }
353
- function importsToString(...imports) {
354
- return imports.map((it) => {
355
- if (it.defaultImport) {
356
- return `import ${it.defaultImport} from '${it.moduleSpecifier}'`;
357
- }
358
- if (it.namespaceImport) {
359
- return `import * as ${it.namespaceImport} from '${it.moduleSpecifier}'`;
360
- }
361
- if (it.namedImports) {
362
- return `import {${removeDuplicates2(it.namedImports, (it2) => it2.name).map((n) => `${n.isTypeOnly ? "type" : ""} ${n.name}`).join(", ")}} from '${it.moduleSpecifier}'`;
363
- }
364
- throw new Error(`Invalid import ${JSON.stringify(it)}`);
365
- });
366
- }
367
- function exclude(list, exclude2) {
368
- return list.filter((it) => !exclude2.includes(it));
369
- }
370
- function useImports(content, imports) {
371
- const output = [];
372
- for (const it of mergeImports(imports)) {
373
- const singleImport = it.defaultImport ?? it.namespaceImport;
374
- if (singleImport && content.includes(singleImport)) {
375
- output.push(importsToString(it).join("\n"));
376
- } else if (it.namedImports.length) {
377
- for (const namedImport of it.namedImports) {
378
- if (content.includes(namedImport.name)) {
379
- output.push(importsToString(it).join("\n"));
380
- }
381
- }
382
- }
383
- }
384
- return output;
385
- }
386
-
387
- // packages/typescript/src/lib/emitters/interface.ts
388
- var TypeScriptDeserialzer = class {
389
- circularRefTracker = /* @__PURE__ */ new Set();
390
- #spec;
391
- #onRef;
392
- constructor(spec, onRef) {
393
- this.#spec = spec;
394
- this.#onRef = onRef;
395
- }
396
- #stringifyKey = (key) => {
397
- const reservedWords = [
398
- "constructor",
399
- "prototype",
400
- "break",
401
- "case",
402
- "catch",
403
- "class",
404
- "const",
405
- "continue",
406
- "debugger",
407
- "default",
408
- "delete",
409
- "do",
410
- "else",
411
- "export",
412
- "extends",
413
- "false",
414
- "finally",
415
- "for",
416
- "function",
417
- "if",
418
- "import",
419
- "in",
420
- "instanceof",
421
- "new",
422
- "null",
423
- "return",
424
- "super",
425
- "switch",
426
- "this",
427
- "throw",
428
- "true",
429
- "try",
430
- "typeof",
431
- "var",
432
- "void",
433
- "while",
434
- "with",
435
- "yield"
436
- ];
437
- if (reservedWords.includes(key)) {
438
- return `'${key}'`;
439
- }
440
- if (key.trim() === "") {
441
- return `'${key}'`;
442
- }
443
- const firstChar = key.charAt(0);
444
- const validFirstChar = firstChar >= "a" && firstChar <= "z" || firstChar >= "A" && firstChar <= "Z" || firstChar === "_" || firstChar === "$";
445
- if (!validFirstChar) {
446
- return `'${key.replace(/'/g, "\\'")}'`;
447
- }
448
- for (let i = 1; i < key.length; i++) {
449
- const char = key.charAt(i);
450
- const validChar = char >= "a" && char <= "z" || char >= "A" && char <= "Z" || char >= "0" && char <= "9" || char === "_" || char === "$";
451
- if (!validChar) {
452
- return `'${key.replace(/'/g, "\\'")}'`;
453
- }
454
- }
455
- return key;
456
- };
457
- #stringifyKeyV2 = (value) => {
458
- return `'${value}'`;
459
- };
460
- /**
461
- * Handle objects (properties)
462
- */
463
- object(schema, required = false) {
464
- const properties = schema.properties || {};
465
- const propEntries = Object.entries(properties).map(([key, propSchema]) => {
466
- const isRequired = (schema.required ?? []).includes(key);
467
- const tsType = this.handle(propSchema, isRequired);
468
- return `${this.#stringifyKeyV2(key)}: ${tsType}`;
469
- });
470
- if (schema.additionalProperties) {
471
- if (typeof schema.additionalProperties === "object") {
472
- const indexType = this.handle(schema.additionalProperties, true);
473
- propEntries.push(`[key: string]: ${indexType}`);
474
- } else if (schema.additionalProperties === true) {
475
- propEntries.push("[key: string]: any");
476
- }
477
- }
478
- return `{ ${propEntries.join("; ")} }`;
479
- }
480
- /**
481
- * Handle arrays (items could be a single schema or a tuple)
482
- */
483
- array(schema, required = false) {
484
- const { items } = schema;
485
- if (!items) {
486
- return "any[]";
487
- }
488
- if (Array.isArray(items)) {
489
- const tupleItems = items.map((sub) => this.handle(sub, true));
490
- return `[${tupleItems.join(", ")}]`;
491
- }
492
- const itemsType = this.handle(items, true);
493
- return `${itemsType}[]`;
494
- }
495
- /**
496
- * Convert a basic type (string | number | boolean | object | array, etc.) to TypeScript
497
- */
498
- normal(type, schema, required = false) {
499
- switch (type) {
500
- case "string":
501
- return this.string(schema, required);
502
- case "number":
503
- case "integer":
504
- return this.number(schema, required);
505
- case "boolean":
506
- return appendOptional("boolean", required);
507
- case "object":
508
- return this.object(schema, required);
509
- case "array":
510
- return this.array(schema, required);
511
- case "null":
512
- return "null";
513
- default:
514
- return appendOptional("any", required);
515
- }
516
- }
517
- ref($ref, required) {
518
- const schemaName = cleanRef($ref).split("/").pop();
519
- if (this.circularRefTracker.has(schemaName)) {
520
- return schemaName;
521
- }
522
- this.circularRefTracker.add(schemaName);
523
- this.#onRef(schemaName, this.handle(followRef(this.#spec, $ref), true));
524
- this.circularRefTracker.delete(schemaName);
525
- return appendOptional(schemaName, required);
526
- }
527
- allOf(schemas) {
528
- const allOfTypes = schemas.map((sub) => this.handle(sub, true));
529
- return allOfTypes.length > 1 ? `${allOfTypes.join(" & ")}` : allOfTypes[0];
530
- }
531
- anyOf(schemas, required) {
532
- const anyOfTypes = schemas.map((sub) => this.handle(sub, true));
533
- return appendOptional(
534
- anyOfTypes.length > 1 ? `${anyOfTypes.join(" | ")}` : anyOfTypes[0],
535
- required
536
- );
537
- }
538
- oneOf(schemas, required) {
539
- const oneOfTypes = schemas.map((sub) => {
540
- if (isRef(sub)) {
541
- const { model } = parseRef(sub.$ref);
542
- if (this.circularRefTracker.has(model)) {
543
- return model;
544
- }
545
- }
546
- return this.handle(sub, false);
547
- });
548
- return appendOptional(
549
- oneOfTypes.length > 1 ? `${oneOfTypes.join(" | ")}` : oneOfTypes[0],
550
- required
551
- );
552
- }
553
- enum(values, required) {
554
- const enumValues = values.map((val) => typeof val === "string" ? `'${val}'` : `${val}`).join(" | ");
555
- return appendOptional(enumValues, required);
556
- }
557
- /**
558
- * Handle string type with formats
559
- */
560
- string(schema, required) {
561
- let type;
562
- switch (schema.format) {
563
- case "date-time":
564
- case "datetime":
565
- case "date":
566
- type = "Date";
567
- break;
568
- case "binary":
569
- case "byte":
570
- type = "Blob";
571
- break;
572
- case "int64":
573
- type = "bigint";
574
- break;
575
- default:
576
- type = "string";
577
- }
578
- return appendOptional(type, required);
579
- }
580
- /**
581
- * Handle number/integer types with formats
582
- */
583
- number(schema, required) {
584
- const type = schema.format === "int64" ? "bigint" : "number";
585
- return appendOptional(type, required);
586
- }
587
- handle(schema, required) {
588
- if (isRef(schema)) {
589
- return this.ref(schema.$ref, required);
590
- }
591
- if (schema.allOf && Array.isArray(schema.allOf)) {
592
- return this.allOf(schema.allOf);
593
- }
594
- if (schema.anyOf && Array.isArray(schema.anyOf)) {
595
- return this.anyOf(schema.anyOf, required);
596
- }
597
- if (schema.oneOf && Array.isArray(schema.oneOf)) {
598
- return this.oneOf(schema.oneOf, required);
599
- }
600
- if (schema.enum && Array.isArray(schema.enum)) {
601
- return this.enum(schema.enum, required);
602
- }
603
- const types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : [];
604
- if (!types.length) {
605
- if ("properties" in schema) {
606
- return this.object(schema, required);
607
- }
608
- return appendOptional("any", required);
609
- }
610
- if (types.length > 1) {
611
- const realTypes = types.filter((t) => t !== "null");
612
- if (realTypes.length === 1 && types.includes("null")) {
613
- const tsType = this.normal(realTypes[0], schema, false);
614
- return appendOptional(`${tsType} | null`, required);
67
+
68
+ get defaultHeaders() {
69
+ return ${defaultHeaders}
70
+ }
71
+
72
+ get #defaultInputs() {
73
+ return ${defaultInputs}
74
+ }
75
+
76
+ setOptions(options: Partial<${spec.name}Options>) {
77
+ const validated = optionsSchema.partial().parse(options);
78
+
79
+ for (const key of Object.keys(validated) as (keyof ${spec.name}Options)[]) {
80
+ if (validated[key] !== undefined) {
81
+ (this.options[key] as typeof validated[typeof key]) = validated[key]!;
615
82
  }
616
- const typeResults = types.map((t) => this.normal(t, schema, false));
617
- return appendOptional(typeResults.join(" | "), required);
618
83
  }
619
- return this.normal(types[0], schema, required);
620
- }
621
- /**
622
- * Generate an interface declaration
623
- */
624
- generateInterface(name, schema) {
625
- const content = this.handle(schema, true);
626
- return `interface ${name} ${content}`;
627
84
  }
85
+ }`;
628
86
  };
629
- function appendOptional(type, isRequired) {
630
- return isRequired ? type : `${type} | undefined`;
631
- }
87
+
88
+ // packages/typescript/src/lib/generator.ts
89
+ import { get as get2, merge } from "lodash-es";
90
+ import { join } from "node:path";
91
+ import { camelcase as camelcase2, pascalcase as pascalcase2, spinalcase as spinalcase2 } from "stringcase";
92
+ import { followRef as followRef4, forEachOperation, isRef as isRef5 } from "@sdk-it/core";
632
93
 
633
94
  // packages/typescript/src/lib/emitters/zod.ts
95
+ import { cleanRef, followRef, isRef, parseRef } from "@sdk-it/core";
634
96
  var ZodDeserialzer = class {
635
97
  circularRefTracker = /* @__PURE__ */ new Set();
636
98
  #spec;
@@ -642,12 +104,11 @@ var ZodDeserialzer = class {
642
104
  /**
643
105
  * Handle objects (properties, additionalProperties).
644
106
  */
645
- object(schema, required = false) {
107
+ object(schema) {
646
108
  const properties = schema.properties || {};
647
109
  const propEntries = Object.entries(properties).map(([key, propSchema]) => {
648
110
  const isRequired = (schema.required ?? []).includes(key);
649
- const zodPart = this.handle(propSchema, isRequired);
650
- return `'${key}': ${zodPart}`;
111
+ return `'${key}': ${this.handle(propSchema, isRequired)}`;
651
112
  });
652
113
  let additionalProps = "";
653
114
  if (schema.additionalProperties) {
@@ -658,8 +119,7 @@ var ZodDeserialzer = class {
658
119
  additionalProps = `.catchall(z.unknown())`;
659
120
  }
660
121
  }
661
- const objectSchema = `z.object({${propEntries.join(", ")}})${additionalProps}`;
662
- return `${objectSchema}${appendOptional2(required)}`;
122
+ return `z.object({${propEntries.join(", ")}})${additionalProps}`;
663
123
  }
664
124
  /**
665
125
  * Handle arrays (items could be a single schema or a tuple (array of schemas)).
@@ -668,18 +128,18 @@ var ZodDeserialzer = class {
668
128
  array(schema, required = false) {
669
129
  const { items } = schema;
670
130
  if (!items) {
671
- return `z.array(z.unknown())${appendOptional2(required)}`;
131
+ return `z.array(z.unknown())${appendOptional(required)}`;
672
132
  }
673
133
  if (Array.isArray(items)) {
674
134
  const tupleItems = items.map((sub) => this.handle(sub, true));
675
135
  const base = `z.tuple([${tupleItems.join(", ")}])`;
676
- return `${base}${appendOptional2(required)}`;
136
+ return `${base}${appendOptional(required)}`;
677
137
  }
678
138
  const itemsSchema = this.handle(items, true);
679
- return `z.array(${itemsSchema})${appendOptional2(required)}`;
139
+ return `z.array(${itemsSchema})${appendOptional(required)}`;
680
140
  }
681
141
  #suffixes = (defaultValue, required, nullable) => {
682
- return `${nullable ? ".nullable()" : ""}${appendDefault(defaultValue)}${appendOptional2(required)}`;
142
+ return `${nullable ? ".nullable()" : ""}${appendDefault(defaultValue)}${appendOptional(required)}`;
683
143
  };
684
144
  /**
685
145
  * Convert a basic type (string | number | boolean | object | array, etc.) to Zod.
@@ -697,13 +157,13 @@ var ZodDeserialzer = class {
697
157
  case "boolean":
698
158
  return `z.boolean()${this.#suffixes(schema.default, required, nullable)}`;
699
159
  case "object":
700
- return this.object(schema, true);
160
+ return `${this.object(schema)}${this.#suffixes(JSON.stringify(schema.default), required, nullable)}`;
701
161
  case "array":
702
162
  return this.array(schema, required);
703
163
  case "null":
704
- return `z.null()${appendOptional2(required)}`;
164
+ return `z.null()${appendOptional(required)}`;
705
165
  default:
706
- return `z.unknown()${appendOptional2(required)}`;
166
+ return `z.unknown()${appendOptional(required)}`;
707
167
  }
708
168
  }
709
169
  ref($ref, required) {
@@ -719,15 +179,15 @@ var ZodDeserialzer = class {
719
179
  this.circularRefTracker.delete(schemaName);
720
180
  return schemaName;
721
181
  }
722
- allOf(schemas) {
182
+ allOf(schemas, required) {
723
183
  const allOfSchemas = schemas.map((sub) => this.handle(sub, true));
724
184
  if (allOfSchemas.length === 0) {
725
185
  return `z.unknown()`;
726
186
  }
727
187
  if (allOfSchemas.length === 1) {
728
- return allOfSchemas[0];
188
+ return `${allOfSchemas[0]}${appendOptional(required)}`;
729
189
  }
730
- return this.#toIntersection(allOfSchemas);
190
+ return `${this.#toIntersection(allOfSchemas)}${appendOptional(required)}`;
731
191
  }
732
192
  #toIntersection(schemas) {
733
193
  const [left, ...right] = schemas;
@@ -737,39 +197,32 @@ var ZodDeserialzer = class {
737
197
  return `z.intersection(${left}, ${this.#toIntersection(right)})`;
738
198
  }
739
199
  anyOf(schemas, required) {
740
- const anyOfSchemas = schemas.map((sub) => this.handle(sub, false));
200
+ const anyOfSchemas = schemas.map((sub) => this.handle(sub, true));
741
201
  if (anyOfSchemas.length === 1) {
742
- return anyOfSchemas[0];
202
+ return `${anyOfSchemas[0]}${appendOptional(required)}`;
743
203
  }
744
- return anyOfSchemas.length > 1 ? `z.union([${anyOfSchemas.join(", ")}])${appendOptional2(required)}` : (
745
- // Handle an invalid anyOf with one schema
746
- anyOfSchemas[0]
747
- );
204
+ return `z.union([${anyOfSchemas.join(", ")}])${appendOptional(required)}`;
748
205
  }
749
206
  oneOf(schemas, required) {
750
207
  const oneOfSchemas = schemas.map((sub) => {
751
208
  if (isRef(sub)) {
752
209
  const { model } = parseRef(sub.$ref);
753
210
  if (this.circularRefTracker.has(model)) {
754
- return model;
211
+ return `${model}${appendOptional(required)}`;
755
212
  }
756
213
  }
757
214
  return this.handle(sub, true);
758
215
  });
759
216
  if (oneOfSchemas.length === 1) {
760
- return oneOfSchemas[0];
217
+ return `${oneOfSchemas[0]}${appendOptional(required)}`;
761
218
  }
762
- return oneOfSchemas.length > 1 ? `z.union([${oneOfSchemas.join(", ")}])${appendOptional2(required)}` : (
763
- // Handle an invalid oneOf with one schema
764
- oneOfSchemas[0]
765
- );
219
+ return `z.union([${oneOfSchemas.join(", ")}])${appendOptional(required)}`;
766
220
  }
767
- enum(values, required) {
768
- const enumVals = values.map((val) => JSON.stringify(val)).join(", ");
221
+ enum(values) {
769
222
  if (values.length === 1) {
770
- return `z.literal(${enumVals})${appendOptional2(required)}`;
223
+ return `z.literal(${values.join(", ")})`;
771
224
  }
772
- return `z.enum([${enumVals}])${appendOptional2(required)}`;
225
+ return `z.enum([${values.join(", ")}])`;
773
226
  }
774
227
  /**
775
228
  * Handle a `string` schema with possible format keywords (JSON Schema).
@@ -850,51 +303,527 @@ var ZodDeserialzer = class {
850
303
  if (typeof schema.multipleOf === "number") {
851
304
  base += `.refine((val) => Number.isInteger(val / ${schema.multipleOf}), "Must be a multiple of ${schema.multipleOf}")`;
852
305
  }
853
- return { base, defaultValue };
306
+ return { base, defaultValue };
307
+ }
308
+ handle(schema, required) {
309
+ if (isRef(schema)) {
310
+ return `${this.ref(schema.$ref, true)}${appendOptional(required)}`;
311
+ }
312
+ if (schema.allOf && Array.isArray(schema.allOf)) {
313
+ return this.allOf(schema.allOf ?? [], required);
314
+ }
315
+ if (schema.anyOf && Array.isArray(schema.anyOf)) {
316
+ return this.anyOf(schema.anyOf ?? [], required);
317
+ }
318
+ if (schema.oneOf && Array.isArray(schema.oneOf) && schema.oneOf.length) {
319
+ return this.oneOf(schema.oneOf ?? [], required);
320
+ }
321
+ if (schema.enum && Array.isArray(schema.enum)) {
322
+ const enumVals = schema.enum.map((val) => JSON.stringify(val));
323
+ const defaultValue = enumVals.includes(JSON.stringify(schema.default)) ? JSON.stringify(schema.default) : void 0;
324
+ return `${this.enum(enumVals)}${this.#suffixes(defaultValue, required, false)}`;
325
+ }
326
+ const types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : [];
327
+ if (!types.length) {
328
+ return `z.unknown()${appendOptional(required)}`;
329
+ }
330
+ if ("nullable" in schema && schema.nullable) {
331
+ types.push("null");
332
+ } else if (schema.default === null) {
333
+ types.push("null");
334
+ }
335
+ if (types.length > 1) {
336
+ const realTypes = types.filter((t) => t !== "null");
337
+ if (realTypes.length === 1 && types.includes("null")) {
338
+ return this.normal(realTypes[0], schema, required, true);
339
+ }
340
+ const subSchemas = types.map((t) => this.normal(t, schema, false));
341
+ return `z.union([${subSchemas.join(", ")}])${appendOptional(required)}`;
342
+ }
343
+ return this.normal(types[0], schema, required, false);
344
+ }
345
+ };
346
+ function appendOptional(isRequired) {
347
+ return isRequired ? "" : ".optional()";
348
+ }
349
+ function appendDefault(defaultValue) {
350
+ return defaultValue !== void 0 || typeof defaultValue !== "undefined" ? `.default(${defaultValue})` : "";
351
+ }
352
+
353
+ // packages/typescript/src/lib/sdk.ts
354
+ import { get } from "lodash-es";
355
+ import { camelcase, pascalcase, spinalcase } from "stringcase";
356
+ import { followRef as followRef3, isRef as isRef4, toLitObject as toLitObject2 } from "@sdk-it/core";
357
+
358
+ // packages/typescript/src/lib/emitters/interface.ts
359
+ import { cleanRef as cleanRef2, followRef as followRef2, isRef as isRef2, parseRef as parseRef2 } from "@sdk-it/core";
360
+ var TypeScriptDeserialzer = class {
361
+ circularRefTracker = /* @__PURE__ */ new Set();
362
+ #spec;
363
+ #onRef;
364
+ constructor(spec, onRef) {
365
+ this.#spec = spec;
366
+ this.#onRef = onRef;
367
+ }
368
+ #stringifyKey = (key) => {
369
+ const reservedWords = [
370
+ "constructor",
371
+ "prototype",
372
+ "break",
373
+ "case",
374
+ "catch",
375
+ "class",
376
+ "const",
377
+ "continue",
378
+ "debugger",
379
+ "default",
380
+ "delete",
381
+ "do",
382
+ "else",
383
+ "export",
384
+ "extends",
385
+ "false",
386
+ "finally",
387
+ "for",
388
+ "function",
389
+ "if",
390
+ "import",
391
+ "in",
392
+ "instanceof",
393
+ "new",
394
+ "null",
395
+ "return",
396
+ "super",
397
+ "switch",
398
+ "this",
399
+ "throw",
400
+ "true",
401
+ "try",
402
+ "typeof",
403
+ "var",
404
+ "void",
405
+ "while",
406
+ "with",
407
+ "yield"
408
+ ];
409
+ if (reservedWords.includes(key)) {
410
+ return `'${key}'`;
411
+ }
412
+ if (key.trim() === "") {
413
+ return `'${key}'`;
414
+ }
415
+ const firstChar = key.charAt(0);
416
+ const validFirstChar = firstChar >= "a" && firstChar <= "z" || firstChar >= "A" && firstChar <= "Z" || firstChar === "_" || firstChar === "$";
417
+ if (!validFirstChar) {
418
+ return `'${key.replace(/'/g, "\\'")}'`;
419
+ }
420
+ for (let i = 1; i < key.length; i++) {
421
+ const char = key.charAt(i);
422
+ const validChar = char >= "a" && char <= "z" || char >= "A" && char <= "Z" || char >= "0" && char <= "9" || char === "_" || char === "$";
423
+ if (!validChar) {
424
+ return `'${key.replace(/'/g, "\\'")}'`;
425
+ }
426
+ }
427
+ return key;
428
+ };
429
+ #stringifyKeyV2 = (value) => {
430
+ return `'${value}'`;
431
+ };
432
+ /**
433
+ * Handle objects (properties)
434
+ */
435
+ object(schema, required = false) {
436
+ const properties = schema.properties || {};
437
+ const propEntries = Object.entries(properties).map(([key, propSchema]) => {
438
+ const isRequired = (schema.required ?? []).includes(key);
439
+ const tsType = this.handle(propSchema, isRequired);
440
+ return `${this.#stringifyKeyV2(key)}: ${tsType}`;
441
+ });
442
+ if (schema.additionalProperties) {
443
+ if (typeof schema.additionalProperties === "object") {
444
+ const indexType = this.handle(schema.additionalProperties, true);
445
+ propEntries.push(`[key: string]: ${indexType}`);
446
+ } else if (schema.additionalProperties === true) {
447
+ propEntries.push("[key: string]: any");
448
+ }
449
+ }
450
+ return `{ ${propEntries.join("; ")} }`;
451
+ }
452
+ /**
453
+ * Handle arrays (items could be a single schema or a tuple)
454
+ */
455
+ array(schema, required = false) {
456
+ const { items } = schema;
457
+ if (!items) {
458
+ return "any[]";
459
+ }
460
+ if (Array.isArray(items)) {
461
+ const tupleItems = items.map((sub) => this.handle(sub, true));
462
+ return `[${tupleItems.join(", ")}]`;
463
+ }
464
+ const itemsType = this.handle(items, true);
465
+ return `${itemsType}[]`;
466
+ }
467
+ /**
468
+ * Convert a basic type (string | number | boolean | object | array, etc.) to TypeScript
469
+ */
470
+ normal(type, schema, required = false) {
471
+ switch (type) {
472
+ case "string":
473
+ return this.string(schema, required);
474
+ case "number":
475
+ case "integer":
476
+ return this.number(schema, required);
477
+ case "boolean":
478
+ return appendOptional2("boolean", required);
479
+ case "object":
480
+ return this.object(schema, required);
481
+ case "array":
482
+ return this.array(schema, required);
483
+ case "null":
484
+ return "null";
485
+ default:
486
+ console.warn(`Unknown type: ${type}`);
487
+ return appendOptional2("any", required);
488
+ }
489
+ }
490
+ ref($ref, required) {
491
+ const schemaName = cleanRef2($ref).split("/").pop();
492
+ if (this.circularRefTracker.has(schemaName)) {
493
+ return schemaName;
494
+ }
495
+ this.circularRefTracker.add(schemaName);
496
+ this.#onRef?.(
497
+ schemaName,
498
+ this.handle(followRef2(this.#spec, $ref), required)
499
+ );
500
+ this.circularRefTracker.delete(schemaName);
501
+ return appendOptional2(schemaName, required);
502
+ }
503
+ allOf(schemas) {
504
+ const allOfTypes = schemas.map((sub) => this.handle(sub, true));
505
+ return allOfTypes.length > 1 ? `${allOfTypes.join(" & ")}` : allOfTypes[0];
506
+ }
507
+ anyOf(schemas, required) {
508
+ const anyOfTypes = schemas.map((sub) => this.handle(sub, true));
509
+ return appendOptional2(
510
+ anyOfTypes.length > 1 ? `${anyOfTypes.join(" | ")}` : anyOfTypes[0],
511
+ required
512
+ );
513
+ }
514
+ oneOf(schemas, required) {
515
+ const oneOfTypes = schemas.map((sub) => {
516
+ if (isRef2(sub)) {
517
+ const { model } = parseRef2(sub.$ref);
518
+ if (this.circularRefTracker.has(model)) {
519
+ return model;
520
+ }
521
+ }
522
+ return this.handle(sub, false);
523
+ });
524
+ return appendOptional2(
525
+ oneOfTypes.length > 1 ? `${oneOfTypes.join(" | ")}` : oneOfTypes[0],
526
+ required
527
+ );
528
+ }
529
+ enum(values, required) {
530
+ const enumValues = values.map((val) => typeof val === "string" ? `'${val}'` : `${val}`).join(" | ");
531
+ return appendOptional2(enumValues, required);
532
+ }
533
+ /**
534
+ * Handle string type with formats
535
+ */
536
+ string(schema, required) {
537
+ let type;
538
+ switch (schema.format) {
539
+ case "date-time":
540
+ case "datetime":
541
+ case "date":
542
+ type = "Date";
543
+ break;
544
+ case "binary":
545
+ case "byte":
546
+ type = "Blob";
547
+ break;
548
+ case "int64":
549
+ type = "bigint";
550
+ break;
551
+ default:
552
+ type = "string";
553
+ }
554
+ return appendOptional2(type, required);
555
+ }
556
+ /**
557
+ * Handle number/integer types with formats
558
+ */
559
+ number(schema, required) {
560
+ const type = schema.format === "int64" ? "bigint" : "number";
561
+ return appendOptional2(type, required);
854
562
  }
855
563
  handle(schema, required) {
856
- if (isRef(schema)) {
564
+ if (isRef2(schema)) {
857
565
  return this.ref(schema.$ref, required);
858
566
  }
859
567
  if (schema.allOf && Array.isArray(schema.allOf)) {
860
- return this.allOf(schema.allOf ?? []);
568
+ return this.allOf(schema.allOf);
861
569
  }
862
570
  if (schema.anyOf && Array.isArray(schema.anyOf)) {
863
- return this.anyOf(schema.anyOf ?? [], required);
571
+ return this.anyOf(schema.anyOf, required);
864
572
  }
865
- if (schema.oneOf && Array.isArray(schema.oneOf) && schema.oneOf.length) {
866
- return this.oneOf(schema.oneOf ?? [], required);
573
+ if (schema.oneOf && Array.isArray(schema.oneOf)) {
574
+ return this.oneOf(schema.oneOf, required);
867
575
  }
868
576
  if (schema.enum && Array.isArray(schema.enum)) {
869
577
  return this.enum(schema.enum, required);
870
578
  }
871
579
  const types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : [];
872
580
  if (!types.length) {
873
- return `z.unknown()${appendOptional2(required)}`;
874
- }
875
- if ("nullable" in schema && schema.nullable) {
876
- types.push("null");
581
+ if ("properties" in schema) {
582
+ return this.object(schema, required);
583
+ }
584
+ return appendOptional2("any", required);
877
585
  }
878
586
  if (types.length > 1) {
879
587
  const realTypes = types.filter((t) => t !== "null");
880
588
  if (realTypes.length === 1 && types.includes("null")) {
881
- return this.normal(realTypes[0], schema, required, true);
589
+ const tsType = this.normal(realTypes[0], schema, false);
590
+ return appendOptional2(`${tsType} | null`, required);
882
591
  }
883
- const subSchemas = types.map((t) => this.normal(t, schema, false));
884
- return `z.union([${subSchemas.join(", ")}])${appendOptional2(required)}`;
592
+ const typeResults = types.map((t) => this.normal(t, schema, false));
593
+ return appendOptional2(typeResults.join(" | "), required);
885
594
  }
886
- return this.normal(types[0], schema, required, false);
595
+ return this.normal(types[0], schema, required);
596
+ }
597
+ /**
598
+ * Generate an interface declaration
599
+ */
600
+ generateInterface(name, schema) {
601
+ const content = this.handle(schema, true);
602
+ return `interface ${name} ${content}`;
887
603
  }
888
604
  };
889
- function appendOptional2(isRequired) {
890
- return isRequired ? "" : ".optional()";
605
+ function appendOptional2(type, isRequired) {
606
+ return isRequired ? type : `${type} | undefined`;
891
607
  }
892
- function appendDefault(defaultValue) {
893
- return defaultValue !== void 0 ? `.default(${defaultValue})` : "";
608
+
609
+ // packages/typescript/src/lib/utils.ts
610
+ import { isRef as isRef3, removeDuplicates } from "@sdk-it/core";
611
+ function securityToOptions(security2, securitySchemes, staticIn) {
612
+ securitySchemes ??= {};
613
+ const options = {};
614
+ for (const it of security2) {
615
+ const [name] = Object.keys(it);
616
+ if (!name) {
617
+ continue;
618
+ }
619
+ const schema = securitySchemes[name];
620
+ if (isRef3(schema)) {
621
+ throw new Error(`Ref security schemas are not supported`);
622
+ }
623
+ if (schema.type === "http") {
624
+ options["authorization"] = {
625
+ in: staticIn ?? "header",
626
+ schema: "z.string().optional().transform((val) => (val ? `Bearer ${val}` : undefined))",
627
+ optionName: "token"
628
+ };
629
+ continue;
630
+ }
631
+ if (schema.type === "apiKey") {
632
+ if (!schema.in) {
633
+ throw new Error(`apiKey security schema must have an "in" field`);
634
+ }
635
+ if (!schema.name) {
636
+ throw new Error(`apiKey security schema must have a "name" field`);
637
+ }
638
+ options[schema.name] = {
639
+ in: staticIn ?? schema.in,
640
+ schema: "z.string().optional()"
641
+ };
642
+ continue;
643
+ }
644
+ }
645
+ return options;
646
+ }
647
+ function mergeImports(...imports) {
648
+ const merged = {};
649
+ for (const it of imports) {
650
+ merged[it.moduleSpecifier] = merged[it.moduleSpecifier] ?? {
651
+ moduleSpecifier: it.moduleSpecifier,
652
+ defaultImport: it.defaultImport,
653
+ namespaceImport: it.namespaceImport,
654
+ namedImports: []
655
+ };
656
+ for (const named of it.namedImports) {
657
+ if (!merged[it.moduleSpecifier].namedImports.some(
658
+ (x) => x.name === named.name
659
+ )) {
660
+ merged[it.moduleSpecifier].namedImports.push(named);
661
+ }
662
+ }
663
+ }
664
+ return Object.values(merged);
665
+ }
666
+ function importsToString(...imports) {
667
+ return imports.map((it) => {
668
+ if (it.defaultImport) {
669
+ return `import ${it.defaultImport} from '${it.moduleSpecifier}'`;
670
+ }
671
+ if (it.namespaceImport) {
672
+ return `import * as ${it.namespaceImport} from '${it.moduleSpecifier}'`;
673
+ }
674
+ if (it.namedImports) {
675
+ return `import {${removeDuplicates(it.namedImports, (it2) => it2.name).map((n) => `${n.isTypeOnly ? "type" : ""} ${n.name}`).join(", ")}} from '${it.moduleSpecifier}'`;
676
+ }
677
+ throw new Error(`Invalid import ${JSON.stringify(it)}`);
678
+ });
679
+ }
680
+ function exclude(list, exclude2) {
681
+ return list.filter((it) => !exclude2.includes(it));
682
+ }
683
+ function useImports(content, ...imports) {
684
+ const output = [];
685
+ for (const it of mergeImports(...imports)) {
686
+ const singleImport = it.defaultImport ?? it.namespaceImport;
687
+ if (singleImport && content.includes(singleImport)) {
688
+ output.push(importsToString(it).join("\n"));
689
+ } else if (it.namedImports.length) {
690
+ for (const namedImport of it.namedImports) {
691
+ if (content.includes(namedImport.name)) {
692
+ output.push(importsToString(it).join("\n"));
693
+ }
694
+ }
695
+ }
696
+ }
697
+ return output;
894
698
  }
895
699
 
896
- // packages/typescript/src/lib/generator.ts
897
- var statusCdeToMessageMap = {
700
+ // packages/typescript/src/lib/sdk.ts
701
+ function generateInputs(operationsSet, commonZod, makeImport) {
702
+ const commonImports = commonZod.keys().toArray();
703
+ const inputs = {};
704
+ for (const [name, operations] of Object.entries(operationsSet)) {
705
+ const output = [];
706
+ const imports = /* @__PURE__ */ new Set(['import { z } from "zod";']);
707
+ for (const operation of operations) {
708
+ const schemaName = camelcase(`${operation.name} schema`);
709
+ const schema = `export const ${schemaName} = ${Object.keys(operation.schemas).length === 1 ? Object.values(operation.schemas)[0] : toLitObject2(operation.schemas)};`;
710
+ const inputContent = schema;
711
+ for (const schema2 of commonImports) {
712
+ if (inputContent.includes(schema2)) {
713
+ imports.add(
714
+ `import { ${schema2} } from './schemas/${makeImport(spinalcase(schema2))}';`
715
+ );
716
+ }
717
+ }
718
+ output.push(inputContent);
719
+ }
720
+ inputs[`inputs/${spinalcase(name)}.ts`] = [...imports, ...output].join("\n") + "\n";
721
+ }
722
+ const schemas = commonZod.entries().reduce((acc, [name, schema]) => {
723
+ const output = [`import { z } from 'zod';`];
724
+ const content = `export const ${name} = ${schema};`;
725
+ for (const schema2 of commonImports) {
726
+ const preciseMatch = new RegExp(`\\b${schema2}\\b`);
727
+ if (preciseMatch.test(content) && schema2 !== name) {
728
+ output.push(
729
+ `import { ${schema2} } from './${makeImport(spinalcase(schema2))}';`
730
+ );
731
+ }
732
+ }
733
+ output.push(content);
734
+ return [
735
+ [`inputs/schemas/${spinalcase(name)}.ts`, output.join("\n")],
736
+ ...acc
737
+ ];
738
+ }, []);
739
+ return {
740
+ ...Object.fromEntries(schemas),
741
+ ...inputs
742
+ };
743
+ }
744
+ function toEndpoint(groupName, spec, specOperation, operation, utils) {
745
+ const schemaName = camelcase(`${operation.name} schema`);
746
+ const schemaRef = `${camelcase(groupName)}.${schemaName}`;
747
+ const inputHeaders = [];
748
+ const inputQuery = [];
749
+ const inputBody = [];
750
+ const inputParams = [];
751
+ const schemas = [];
752
+ const responses = [];
753
+ for (const [name, prop] of Object.entries(operation.inputs)) {
754
+ if (prop.in === "headers" || prop.in === "header") {
755
+ inputHeaders.push(`"${name}"`);
756
+ } else if (prop.in === "query") {
757
+ inputQuery.push(`"${name}"`);
758
+ } else if (prop.in === "body") {
759
+ inputBody.push(`"${name}"`);
760
+ } else if (prop.in === "path") {
761
+ inputParams.push(`"${name}"`);
762
+ } else if (prop.in === "internal") {
763
+ continue;
764
+ } else {
765
+ throw new Error(
766
+ `Unknown source ${prop.in} in ${name} ${JSON.stringify(
767
+ prop
768
+ )} in ${operation.name}`
769
+ );
770
+ }
771
+ }
772
+ specOperation.responses ??= {};
773
+ const outputs = [];
774
+ const statusesCount = Object.keys(specOperation.responses).filter((status) => {
775
+ const statusCode = +status;
776
+ return statusCode >= 200 && statusCode < 300;
777
+ }).length > 1;
778
+ for (const status in specOperation.responses) {
779
+ const response = isRef4(
780
+ specOperation.responses[status]
781
+ ) ? followRef3(
782
+ spec,
783
+ specOperation.responses[status].$ref
784
+ ) : specOperation.responses[status];
785
+ const handled = handleResponse(
786
+ spec,
787
+ operation.name,
788
+ status,
789
+ response,
790
+ utils,
791
+ true
792
+ // statusesCount,
793
+ );
794
+ responses.push(handled);
795
+ outputs.push(...handled.outputs);
796
+ }
797
+ const addTypeParser = Object.keys(operation.schemas).length > 1;
798
+ for (const type in operation.schemas ?? {}) {
799
+ let typePrefix = "";
800
+ if (addTypeParser && type !== "json") {
801
+ typePrefix = `${type} `;
802
+ }
803
+ const endpoint = `${typePrefix}${operation.trigger.method.toUpperCase()} ${operation.trigger.path}`;
804
+ schemas.push(
805
+ `"${endpoint}": {
806
+ schema: ${schemaRef}${addTypeParser ? `.${type}` : ""},
807
+ output:[${outputs.join(",")}],
808
+ toRequest(input: z.infer<typeof ${schemaRef}${addTypeParser ? `.${type}` : ""}>) {
809
+ const endpoint = '${endpoint}';
810
+ return toRequest(endpoint, ${operation.outgoingContentType || "nobody"}(input, {
811
+ inputHeaders: [${inputHeaders}],
812
+ inputQuery: [${inputQuery}],
813
+ inputBody: [${inputBody}],
814
+ inputParams: [${inputParams}],
815
+ }));
816
+ },
817
+ }`
818
+ );
819
+ }
820
+ return { responses, schemas };
821
+ }
822
+ var statusCodeToResponseMap = {
823
+ "200": "Ok",
824
+ "201": "Created",
825
+ "202": "Accepted",
826
+ "204": "NoContent",
898
827
  "400": "BadRequest",
899
828
  "401": "Unauthorized",
900
829
  "402": "PaymentRequired",
@@ -913,8 +842,94 @@ var statusCdeToMessageMap = {
913
842
  "503": "ServiceUnavailable",
914
843
  "504": "GatewayTimeout"
915
844
  };
845
+ function handleResponse(spec, operationName, status, response, utils, numbered) {
846
+ const schemas = {};
847
+ const imports = {};
848
+ const endpointImports = {
849
+ ParseError: {
850
+ defaultImport: void 0,
851
+ isTypeOnly: false,
852
+ moduleSpecifier: utils.makeImport(`../http/parser`),
853
+ namedImports: [{ isTypeOnly: false, name: "ParseError" }],
854
+ namespaceImport: void 0
855
+ }
856
+ };
857
+ const responses = [];
858
+ const outputs = [];
859
+ const typeScriptDeserialzer = new TypeScriptDeserialzer(
860
+ spec,
861
+ (schemaName, zod) => {
862
+ schemas[schemaName] = zod;
863
+ imports[schemaName] = {
864
+ defaultImport: void 0,
865
+ isTypeOnly: true,
866
+ moduleSpecifier: `../models/${utils.makeImport(schemaName)}`,
867
+ namedImports: [{ isTypeOnly: true, name: schemaName }],
868
+ namespaceImport: void 0
869
+ };
870
+ }
871
+ );
872
+ const statusCode = +status;
873
+ const parser = (response.headers ?? {})["Transfer-Encoding"] ? "chunked" : "buffered";
874
+ const statusName = statusCodeToResponseMap[status] || "APIResponse";
875
+ const interfaceName = pascalcase(
876
+ operationName + ` output${numbered ? status : ""}`
877
+ );
878
+ if (statusCode === 204) {
879
+ outputs.push(statusName);
880
+ } else {
881
+ if (status.endsWith("XX")) {
882
+ outputs.push(`APIError<${interfaceName}>`);
883
+ } else {
884
+ outputs.push(
885
+ parser !== "buffered" ? `{type: ${statusName}<${interfaceName}>, parser: ${parser}}` : `${statusName}<${interfaceName}>`
886
+ );
887
+ }
888
+ }
889
+ const responseContent = get(response, ["content"]);
890
+ const isJson = responseContent && responseContent["application/json"];
891
+ const responseSchema = isJson ? typeScriptDeserialzer.handle(
892
+ responseContent["application/json"].schema,
893
+ true
894
+ ) : "void";
895
+ responses.push({
896
+ name: interfaceName,
897
+ schema: responseSchema
898
+ });
899
+ const statusGroup = +status.slice(0, 1);
900
+ if (statusCode >= 400 || statusGroup >= 4) {
901
+ endpointImports[statusCodeToResponseMap[status] ?? "APIError"] = {
902
+ moduleSpecifier: utils.makeImport("../http/response"),
903
+ namedImports: [{ name: statusCodeToResponseMap[status] ?? "APIError" }]
904
+ };
905
+ endpointImports[interfaceName] = {
906
+ isTypeOnly: true,
907
+ moduleSpecifier: `../outputs/${utils.makeImport(spinalcase(operationName))}`,
908
+ namedImports: [{ isTypeOnly: true, name: interfaceName }]
909
+ };
910
+ } else if (statusCode >= 200 && statusCode < 300 || statusCode >= 2 || statusGroup <= 3) {
911
+ endpointImports[statusName] = {
912
+ moduleSpecifier: utils.makeImport("../http/response"),
913
+ namedImports: [
914
+ {
915
+ isTypeOnly: false,
916
+ name: statusName
917
+ }
918
+ ]
919
+ };
920
+ endpointImports[interfaceName] = {
921
+ defaultImport: void 0,
922
+ isTypeOnly: true,
923
+ moduleSpecifier: `../outputs/${utils.makeImport(spinalcase(operationName))}`,
924
+ namedImports: [{ isTypeOnly: true, name: interfaceName }],
925
+ namespaceImport: void 0
926
+ };
927
+ }
928
+ return { schemas, imports, endpointImports, responses, outputs };
929
+ }
930
+
931
+ // packages/typescript/src/lib/generator.ts
916
932
  function generateCode(config) {
917
- const commonSchemas = {};
918
933
  const commonZod = /* @__PURE__ */ new Map();
919
934
  const commonZodImports = [];
920
935
  const zodDeserialzer = new ZodDeserialzer(config.spec, (model, schema) => {
@@ -929,14 +944,15 @@ function generateCode(config) {
929
944
  });
930
945
  const groups = {};
931
946
  const outputs = {};
947
+ const endpoints = {};
932
948
  forEachOperation(config, (entry, operation) => {
933
949
  console.log(`Processing ${entry.method} ${entry.path}`);
934
- const [groupName] = Array.isArray(operation.tags) ? operation.tags : ["unknown"];
935
- groups[groupName] ??= [];
950
+ groups[entry.groupName] ??= [];
951
+ endpoints[entry.groupName] ??= [];
936
952
  const inputs = {};
937
953
  const additionalProperties = [];
938
954
  for (const param of operation.parameters ?? []) {
939
- if (isRef(param)) {
955
+ if (isRef5(param)) {
940
956
  throw new Error(`Found reference in parameter ${param.$ref}`);
941
957
  }
942
958
  if (!param.schema) {
@@ -964,7 +980,7 @@ function generateCode(config) {
964
980
  })
965
981
  )
966
982
  );
967
- const types = {};
983
+ const schemas = {};
968
984
  const shortContenTypeMap = {
969
985
  "application/json": "json",
970
986
  "application/x-www-form-urlencoded": "urlencoded",
@@ -974,9 +990,9 @@ function generateCode(config) {
974
990
  };
975
991
  let outgoingContentType;
976
992
  if (operation.requestBody && Object.keys(operation.requestBody).length) {
977
- const content = isRef(operation.requestBody) ? get2(followRef(config.spec, operation.requestBody.$ref), ["content"]) : operation.requestBody.content;
993
+ const content = isRef5(operation.requestBody) ? get2(followRef4(config.spec, operation.requestBody.$ref), ["content"]) : operation.requestBody.content;
978
994
  for (const type in content) {
979
- const ctSchema = isRef(content[type].schema) ? followRef(config.spec, content[type].schema.$ref) : content[type].schema;
995
+ const ctSchema = isRef5(content[type].schema) ? followRef4(config.spec, content[type].schema.$ref) : content[type].schema;
980
996
  if (!ctSchema) {
981
997
  console.warn(`Schema not found for ${type}`);
982
998
  continue;
@@ -992,7 +1008,7 @@ function generateCode(config) {
992
1008
  )
993
1009
  });
994
1010
  Object.assign(inputs, bodyInputs(config, ctSchema));
995
- types[shortContenTypeMap[type]] = zodDeserialzer.handle(schema, true);
1011
+ schemas[shortContenTypeMap[type]] = zodDeserialzer.handle(schema, true);
996
1012
  }
997
1013
  if (content["application/json"]) {
998
1014
  outgoingContentType = "json";
@@ -1011,7 +1027,7 @@ function generateCode(config) {
1011
1027
  }),
1012
1028
  {}
1013
1029
  );
1014
- types[shortContenTypeMap["application/json"]] = zodDeserialzer.handle(
1030
+ schemas[shortContenTypeMap["application/json"]] = zodDeserialzer.handle(
1015
1031
  {
1016
1032
  type: "object",
1017
1033
  required: additionalProperties.filter((p) => p.required).map((p) => p.name),
@@ -1020,90 +1036,152 @@ function generateCode(config) {
1020
1036
  true
1021
1037
  );
1022
1038
  }
1023
- const errors = [];
1024
- operation.responses ??= {};
1025
- let foundResponse = false;
1039
+ const endpoint = toEndpoint(
1040
+ entry.groupName,
1041
+ config.spec,
1042
+ operation,
1043
+ {
1044
+ outgoingContentType,
1045
+ name: entry.name,
1046
+ type: "http",
1047
+ trigger: entry,
1048
+ schemas,
1049
+ inputs
1050
+ },
1051
+ { makeImport: config.makeImport }
1052
+ );
1026
1053
  const output = [`import z from 'zod';`];
1027
- let parser = "buffered";
1028
- const responses = [];
1029
- const responsesImports = {};
1030
- for (const status in operation.responses) {
1031
- const response = isRef(
1032
- operation.responses[status]
1033
- ) ? followRef(
1034
- config.spec,
1035
- operation.responses[status].$ref
1036
- ) : operation.responses[status];
1037
- const statusCode = +status;
1038
- if (statusCode >= 400) {
1039
- errors.push(statusCdeToMessageMap[status] ?? "ProblematicResponse");
1040
- }
1041
- if (statusCode >= 200 && statusCode < 300) {
1042
- foundResponse = true;
1043
- const responseContent = get2(response, ["content"]);
1044
- const isJson = responseContent && responseContent["application/json"];
1045
- if ((response.headers ?? {})["Transfer-Encoding"]) {
1046
- parser = "chunked";
1047
- }
1048
- const typeScriptDeserialzer = new TypeScriptDeserialzer(
1049
- config.spec,
1050
- (schemaName, zod) => {
1051
- commonSchemas[schemaName] = zod;
1052
- responsesImports[schemaName] = {
1053
- defaultImport: void 0,
1054
- isTypeOnly: true,
1055
- moduleSpecifier: `../models/${config.makeImport(schemaName)}`,
1056
- namedImports: [{ isTypeOnly: true, name: schemaName }],
1057
- namespaceImport: void 0
1058
- };
1059
- }
1060
- );
1061
- const responseSchema = isJson ? typeScriptDeserialzer.handle(
1062
- responseContent["application/json"].schema,
1063
- true
1064
- ) : statusCode === 204 ? "void" : "ReadableStream";
1065
- responses.push(responseSchema);
1066
- }
1067
- }
1068
- if (responses.length > 1) {
1054
+ const responses = endpoint.responses.flatMap((it) => it.responses);
1055
+ const responsesImports = endpoint.responses.flatMap(
1056
+ (it) => Object.values(it.imports)
1057
+ );
1058
+ if (responses.length) {
1069
1059
  output.push(
1070
- `export type ${pascalcase(entry.name + " output")} = ${removeDuplicates3(
1071
- responses,
1072
- (it) => it
1073
- ).join(" | ")};`
1060
+ ...responses.map((it) => `export type ${it.name} = ${it.schema};`)
1074
1061
  );
1075
1062
  } else {
1076
- output.push(
1077
- `export type ${pascalcase(entry.name + " output")} = ${responses[0]};`
1078
- );
1079
- }
1080
- output.push(
1081
- ...useImports(output.join(""), Object.values(responsesImports))
1082
- );
1083
- if (!foundResponse) {
1084
- output.push(`export type ${pascalcase(entry.name + " output")} = void`);
1063
+ output.push(`export type ${pascalcase2(entry.name + " output")} = void;`);
1085
1064
  }
1065
+ output.unshift(...useImports(output.join(""), ...responsesImports));
1086
1066
  outputs[`${spinalcase2(entry.name)}.ts`] = output.join("\n");
1087
- groups[groupName].push({
1067
+ endpoints[entry.groupName].push(endpoint);
1068
+ groups[entry.groupName].push({
1088
1069
  name: entry.name,
1089
1070
  type: "http",
1090
1071
  inputs,
1091
- errors: errors.length ? errors : ["ServerError"],
1092
1072
  outgoingContentType,
1093
- schemas: types,
1094
- parser,
1095
- formatOutput: () => ({
1096
- import: pascalcase(entry.name + " output"),
1097
- use: pascalcase(entry.name + " output")
1098
- }),
1073
+ schemas,
1099
1074
  trigger: entry
1100
1075
  });
1101
1076
  });
1102
- return { groups, commonSchemas, commonZod, outputs };
1077
+ const commonSchemas = Object.values(endpoints).reduce(
1078
+ (acc, endpoint) => ({
1079
+ ...acc,
1080
+ ...endpoint.reduce(
1081
+ (acc2, { responses }) => ({
1082
+ ...acc2,
1083
+ ...responses.reduce(
1084
+ (acc3, it) => ({ ...acc3, ...it.schemas }),
1085
+ {}
1086
+ )
1087
+ }),
1088
+ {}
1089
+ )
1090
+ }),
1091
+ {}
1092
+ );
1093
+ const allSchemas = Object.keys(endpoints).map((it) => ({
1094
+ import: `import ${camelcase2(it)} from './${config.makeImport(spinalcase2(it))}';`,
1095
+ use: ` ...${camelcase2(it)}`
1096
+ }));
1097
+ const imports = [
1098
+ 'import z from "zod";',
1099
+ `import type { ParseError } from '${config.makeImport("../http/parser")}';`,
1100
+ `import type { ServerError } from '${config.makeImport("../http/response")}';`,
1101
+ `import type { OutputType, Parser, Type } from '../http/send-request.ts';`
1102
+ ];
1103
+ return {
1104
+ groups,
1105
+ commonSchemas,
1106
+ commonZod,
1107
+ outputs,
1108
+ clientFiles: {},
1109
+ endpoints: {
1110
+ [`${join("api", config.makeImport("schemas"))}`]: `
1111
+ ${imports.join("\n")}
1112
+ ${allSchemas.map((it) => it.import).join("\n")}
1113
+
1114
+ const schemas = {
1115
+ ${allSchemas.map((it) => it.use).join(",\n")}
1116
+ };
1117
+
1118
+
1119
+ type Output<T extends OutputType> = T extends {
1120
+ parser: Parser;
1121
+ type: Type<any>;
1122
+ }
1123
+ ? InstanceType<T['type']>
1124
+ : T extends Type<any>
1125
+ ? InstanceType<T>
1126
+ : never;
1127
+
1128
+ export type Endpoints = {
1129
+ [K in keyof typeof schemas]: {
1130
+ input: z.infer<(typeof schemas)[K]['schema']>;
1131
+ output: (typeof schemas)[K]['output'] extends [
1132
+ infer Single extends OutputType,
1133
+ ]
1134
+ ? Output<Single>
1135
+ : (typeof schemas)[K]['output'] extends readonly [
1136
+ ...infer Tuple extends OutputType[],
1137
+ ]
1138
+ ? { [I in keyof Tuple]: Output<Tuple[I]> }[number]
1139
+ : never;
1140
+ error: ServerError | ParseError<(typeof schemas)[K]['schema']>;
1141
+ };
1142
+ };
1143
+
1144
+ export default schemas;
1145
+
1146
+
1147
+ `.trim(),
1148
+ ...Object.fromEntries(
1149
+ Object.entries(endpoints).map(([name, endpoint]) => {
1150
+ const imps = importsToString(
1151
+ ...mergeImports(
1152
+ ...endpoint.flatMap(
1153
+ (it) => it.responses.flatMap(
1154
+ (it2) => Object.values(it2.endpointImports)
1155
+ )
1156
+ )
1157
+ )
1158
+ );
1159
+ return [
1160
+ [
1161
+ join("api", config.makeImport(spinalcase2(name))),
1162
+ `${[
1163
+ ...imps,
1164
+ // ...imports,
1165
+ `import z from 'zod';`,
1166
+ `import { toRequest, json, urlencoded, nobody, formdata, createUrl } from '${config.makeImport("../http/request")}';`,
1167
+ `import { chunked, buffered } from "${config.makeImport("../http/parse-response")}";`,
1168
+ `import * as ${camelcase2(name)} from '../inputs/${config.makeImport(spinalcase2(name))}';`
1169
+ ].join(
1170
+ "\n"
1171
+ )}
1172
+ export default {
1173
+ ${endpoint.flatMap((it) => it.schemas).join(",\n")}
1174
+ }`
1175
+ ]
1176
+ ];
1177
+ }).flat()
1178
+ )
1179
+ }
1180
+ };
1103
1181
  }
1104
1182
  function toProps(spec, schemaOrRef, aggregator = []) {
1105
- if (isRef(schemaOrRef)) {
1106
- const schema = followRef(spec, schemaOrRef.$ref);
1183
+ if (isRef5(schemaOrRef)) {
1184
+ const schema = followRef4(spec, schemaOrRef.$ref);
1107
1185
  return toProps(spec, schema, aggregator);
1108
1186
  } else if (schemaOrRef.type === "object") {
1109
1187
  for (const [name] of Object.entries(schemaOrRef.properties ?? {})) {
@@ -1148,22 +1226,22 @@ function bodyInputs(config, ctSchema) {
1148
1226
  }
1149
1227
 
1150
1228
  // packages/typescript/src/lib/http/interceptors.txt
1151
- var interceptors_default = "import { type RequestConfig } from './request.ts';\n\nexport interface Interceptor {\n before?: (config: RequestConfig) => Promise<RequestConfig>|RequestConfig;\n after?: (response: Response) => Promise<Response> | Response;\n}\n\nexport const createHeadersInterceptor = (\n defaultHeaders: () => Record<string, string | undefined>,\n requestHeaders: HeadersInit,\n):Interceptor => {\n return {\n before({init, url}) {\n // Priority Levels\n // 1. Headers Input\n // 2. Request Headers\n // 3. Default Headers\n const headers = defaultHeaders();\n\n for (const [key, value] of new Headers(requestHeaders)) {\n // Only set the header if it doesn't already exist and has a value\n // even though these headers are passed at operation level\n // still they are lower priority compared to the headers input\n if (value !== undefined && !init.headers.has(key)) {\n init.headers.set(key, value);\n }\n }\n\n for (const [key, value] of Object.entries(headers)) {\n // Only set the header if it doesn't already exist and has a value\n if (value !== undefined && !init.headers.has(key)) {\n init.headers.set(key, value);\n }\n }\n\n return {init, url};\n },\n };\n};\n\nexport const createBaseUrlInterceptor = (getBaseUrl: () => string) :Interceptor => {\n return {\n before({init, url}) {\n const baseUrl = getBaseUrl();\n if (url.protocol === 'local:') {\n return {init, url: new URL(url.pathname, baseUrl)}\n }\n return {init, url}\n },\n };\n};\n\nexport const logInterceptor = {\n before(request: Request) {\n console.log('Request', request);\n return request;\n },\n after(response: Response) {\n console.log('Response', response);\n return response;\n },\n};\n\n/**\n * Creates an interceptor that logs detailed information about requests and responses.\n * @param options Configuration options for the logger\n * @returns An interceptor object with before and after handlers\n */\nexport const createDetailedLogInterceptor = (options?: {\n logLevel?: 'debug' | 'info' | 'warn' | 'error';\n includeRequestBody?: boolean;\n includeResponseBody?: boolean;\n}) => {\n const logLevel = options?.logLevel || 'info';\n const includeRequestBody = options?.includeRequestBody || false;\n const includeResponseBody = options?.includeResponseBody || false;\n\n return {\n async before(request: Request) {\n const logData = {\n url: request.url,\n method: request.method,\n contentType: request.headers.get('Content-Type'),\n headers: Object.fromEntries([...request.headers.entries()]),\n };\n\n console[logLevel]('\u{1F680} Outgoing Request:', logData);\n\n if (includeRequestBody) {\n try {\n // Clone the request to avoid consuming the body stream\n const clonedRequest = request.clone();\n if (clonedRequest.headers.get('Content-Type')?.includes('application/json')) {\n const body = await clonedRequest.json().catch(() => null);\n console[logLevel]('Request Body:', body);\n } else {\n const body = await clonedRequest.text().catch(() => null);\n console[logLevel]('Request Body:', body);\n }\n } catch (error) {\n console.error('Could not log request body:', error);\n }\n }\n\n return request;\n },\n\n async after(response: Response) {\n const logData = {\n status: response.status,\n statusText: response.statusText,\n url: response.url,\n headers: Object.fromEntries([...response.headers.entries()]),\n };\n\n console[logLevel]('\u{1F4E5} Incoming Response:', logData);\n\n if (includeResponseBody && response.body) {\n try {\n // Clone the response to avoid consuming the body stream\n const clonedResponse = response.clone();\n if (clonedResponse.headers.get('Content-Type')?.includes('application/json')) {\n const body = await clonedResponse.json().catch(() => null);\n console[logLevel]('Response Body:', body);\n } else {\n const body = await clonedResponse.text().catch(() => null);\n if (body) {\n console[logLevel]('Response Body:', body.substring(0, 500) + (body.length > 500 ? '...' : ''));\n } else {\n console[logLevel]('No response body');\n }\n }\n } catch (error) {\n console.error('Could not log response body:', error);\n }\n }\n\n return response;\n },\n };\n};\n";
1229
+ var interceptors_default = "import { type RequestConfig } from './request.ts';\n\nexport interface Interceptor {\n before?: (config: RequestConfig) => Promise<RequestConfig>|RequestConfig;\n after?: (response: Response) => Promise<Response> | Response;\n}\n\nexport const createHeadersInterceptor = (\n defaultHeaders: () => Record<string, string | undefined>,\n requestHeaders: HeadersInit,\n):Interceptor => {\n return {\n before({init, url}) {\n // Priority Levels\n // 1. Headers Input\n // 2. Request Headers\n // 3. Default Headers\n const headers = defaultHeaders();\n\n for (const [key, value] of new Headers(requestHeaders)) {\n // Only set the header if it doesn't already exist and has a value\n // even though these headers are passed at operation level\n // still they are lower priority compared to the headers input\n if (value !== undefined && !init.headers.has(key)) {\n init.headers.set(key, value);\n }\n }\n\n for (const [key, value] of Object.entries(headers)) {\n // Only set the header if it doesn't already exist and has a value\n if (value !== undefined && !init.headers.has(key)) {\n init.headers.set(key, value);\n }\n }\n\n return {init, url};\n },\n };\n};\n\nexport const createBaseUrlInterceptor = (\n getBaseUrl: () => string,\n): Interceptor => {\n return {\n before({ init, url }) {\n const baseUrl = getBaseUrl();\n if (url.protocol === 'local:') {\n return {\n init,\n url: new URL(url.href.replace('local://', baseUrl))\n };\n }\n return { init, url };\n },\n };\n};\n\nexport const logInterceptor: Interceptor = {\n before({ url, init }) {\n console.dir('Request:', { url, init });\n return { url, init };\n },\n after(response) {\n console.log('Response:', response);\n return response;\n },\n};\n\n/**\n * Creates an interceptor that logs detailed information about requests and responses.\n * @param options Configuration options for the logger\n * @returns An interceptor object with before and after handlers\n */\nexport const createDetailedLogInterceptor = (options?: {\n logLevel?: 'debug' | 'info' | 'warn' | 'error';\n includeRequestBody?: boolean;\n includeResponseBody?: boolean;\n}) => {\n const logLevel = options?.logLevel || 'info';\n const includeRequestBody = options?.includeRequestBody || false;\n const includeResponseBody = options?.includeResponseBody || false;\n\n return {\n async before(request: Request) {\n const logData = {\n url: request.url,\n method: request.method,\n contentType: request.headers.get('Content-Type'),\n headers: Object.fromEntries([...request.headers.entries()]),\n };\n\n console[logLevel]('\u{1F680} Outgoing Request:', logData);\n\n if (includeRequestBody) {\n try {\n // Clone the request to avoid consuming the body stream\n const clonedRequest = request.clone();\n if (clonedRequest.headers.get('Content-Type')?.includes('application/json')) {\n const body = await clonedRequest.json().catch(() => null);\n console[logLevel]('Request Body:', body);\n } else {\n const body = await clonedRequest.text().catch(() => null);\n console[logLevel]('Request Body:', body);\n }\n } catch (error) {\n console.error('Could not log request body:', error);\n }\n }\n\n return request;\n },\n\n async after(response: Response) {\n const logData = {\n status: response.status,\n statusText: response.statusText,\n url: response.url,\n headers: Object.fromEntries([...response.headers.entries()]),\n };\n\n console[logLevel]('\u{1F4E5} Incoming Response:', logData);\n\n if (includeResponseBody && response.body) {\n try {\n // Clone the response to avoid consuming the body stream\n const clonedResponse = response.clone();\n if (clonedResponse.headers.get('Content-Type')?.includes('application/json')) {\n const body = await clonedResponse.json().catch(() => null);\n console[logLevel]('Response Body:', body);\n } else {\n const body = await clonedResponse.text().catch(() => null);\n if (body) {\n console[logLevel]('Response Body:', body.substring(0, 500) + (body.length > 500 ? '...' : ''));\n } else {\n console[logLevel]('No response body');\n }\n }\n } catch (error) {\n console.error('Could not log response body:', error);\n }\n }\n\n return response;\n },\n };\n};\n";
1152
1230
 
1153
1231
  // packages/typescript/src/lib/http/parse-response.txt
1154
- var parse_response_default = 'import { parse } from "fast-content-type-parse";\n\nexport async function handleError(response: Response) {\n try {\n if (response.status >= 400 && response.status < 500) {\n const body = (await response.json()) as Record<string, any>;\n return {\n status: response.status,\n body: body,\n };\n }\n return new Error(\n `An error occurred while fetching the data. Status: ${response.status}`,\n );\n } catch (error) {\n return error as any;\n }\n}\n\nasync function handleChunkedResponse(response: Response, contentType: string) {\n const { type } = parse(contentType);\n\n switch (type) {\n case "application/json": {\n let buffer = "";\n const reader = response.body!.getReader();\n const decoder = new TextDecoder();\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value);\n }\n return JSON.parse(buffer);\n }\n case "text/html":\n case "text/plain": {\n let buffer = "";\n const reader = response.body!.getReader();\n const decoder = new TextDecoder();\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value);\n }\n return buffer;\n }\n default:\n return response.body;\n }\n}\n\nexport function chunked(response: Response) {\n return response.body;\n}\n\nexport async function buffered(response: Response) {\n const contentType = response.headers.get("Content-Type");\n if (!contentType) {\n throw new Error("Content-Type header is missing");\n }\n\n if (response.status === 204) {\n return null;\n }\n\n const { type } = parse(contentType);\n switch (type) {\n case "application/json":\n return response.json();\n case "text/plain":\n return response.text();\n case "text/html":\n return response.text();\n case "text/xml":\n case "application/xml":\n return response.text();\n case "application/x-www-form-urlencoded": {\n const text = await response.text();\n return Object.fromEntries(new URLSearchParams(text));\n }\n case "multipart/form-data":\n return response.formData();\n default:\n throw new Error(`Unsupported content type: ${contentType}`);\n }\n}\n';
1232
+ var parse_response_default = 'import { parse } from "fast-content-type-parse";\n\nexport async function handleError(response: Response) {\n try {\n if (response.status >= 400 && response.status < 500) {\n const body = (await response.json()) as Record<string, any>;\n return {\n status: response.status,\n body: body,\n };\n }\n return new Error(\n `An error occurred while fetching the data. Status: ${response.status}`,\n );\n } catch (error) {\n return error as any;\n }\n}\n\nasync function handleChunkedResponse(response: Response, contentType: string) {\n const { type } = parse(contentType);\n\n switch (type) {\n case "application/json": {\n let buffer = "";\n const reader = response.body!.getReader();\n const decoder = new TextDecoder();\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value);\n }\n return JSON.parse(buffer);\n }\n case "text/html":\n case "text/plain": {\n let buffer = "";\n const reader = response.body!.getReader();\n const decoder = new TextDecoder();\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value);\n }\n return buffer;\n }\n default:\n return response.body;\n }\n}\n\nexport function chunked(response: Response) {\n return response.body!;\n}\n\nexport async function buffered(response: Response) {\n const contentType = response.headers.get("Content-Type");\n if (!contentType) {\n throw new Error("Content-Type header is missing");\n }\n\n if (response.status === 204) {\n return null;\n }\n\n const { type } = parse(contentType);\n switch (type) {\n case "application/json":\n return response.json();\n case "text/plain":\n return response.text();\n case "text/html":\n return response.text();\n case "text/xml":\n case "application/xml":\n return response.text();\n case "application/x-www-form-urlencoded": {\n const text = await response.text();\n return Object.fromEntries(new URLSearchParams(text));\n }\n case "multipart/form-data":\n return response.formData();\n default:\n throw new Error(`Unsupported content type: ${contentType}`);\n }\n}\n';
1155
1233
 
1156
1234
  // packages/typescript/src/lib/http/parser.txt
1157
- var parser_default = "import { z } from 'zod';\n\nexport type ParseError<T extends z.ZodType<any, any, any>> = {\n kind: 'parse';\n} & z.inferFlattenedErrors<T>;\n\nexport function parse<T extends z.ZodType>(\n schema: T,\n input: unknown,\n) {\n const result = schema.safeParse(input);\n if (!result.success) {\n const errors = result.error.flatten((issue) => issue);\n return [null, errors];\n }\n return [result.data as z.infer<T>, null];\n}\n";
1235
+ var parser_default = 'import { z } from "zod";\n\nexport class ParseError<T extends z.ZodType<any, any, any>> {\n public data: z.typeToFlattenedError<T, z.ZodIssue>;\n constructor(data: z.typeToFlattenedError<T, z.ZodIssue>) {\n this.data = data;\n }\n}\n\nexport function parse<T extends z.ZodType<any, any, any>>(schema: T, input: unknown) {\n const result = schema.safeParse(input);\n if (!result.success) {\n const error = result.error.flatten((issue) => issue);\n return [null, new ParseError(error)];\n }\n return [result.data as z.infer<T>, null];\n}\n';
1158
1236
 
1159
1237
  // packages/typescript/src/lib/http/request.txt
1160
1238
  var request_default = "type Init = Omit<RequestInit, 'headers'> & { headers: Headers; };\nexport type RequestConfig = { init: Init; url: URL };\nexport type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\nexport type ContentType = 'xml' | 'json' | 'urlencoded' | 'multipart' | 'formdata';\nexport type Endpoint =\n | `${ContentType} ${Method} ${string}`\n | `${Method} ${string}`;\n\nexport type BodyInit =\n | ArrayBuffer\n | Blob\n | FormData\n | URLSearchParams\n | null\n | string;\n\nexport function createUrl(path: string, query: URLSearchParams) {\n const url = new URL(path, `local://`);\n url.search = query.toString();\n return url;\n}\n\nfunction template(\n templateString: string,\n templateVariables: Record<string, any>,\n): string {\n const nargs = /{([0-9a-zA-Z_]+)}/g;\n return templateString.replace(nargs, (match, key: string, index: number) => {\n // Handle escaped double braces\n if (\n templateString[index - 1] === '{' &&\n templateString[index + match.length] === '}'\n ) {\n return key;\n }\n\n const result = key in templateVariables ? templateVariables[key] : null;\n return result === null || result === undefined ? '' : String(result);\n });\n}\n\ntype Input = Record<string, any>;\ntype Props = {\n inputHeaders: string[];\n inputQuery: string[];\n inputBody: string[];\n inputParams: string[];\n};\n\nabstract class Serializer {\n protected input: Input;\n protected props: Props;\n\n constructor(\n input: Input,\n props: Props,\n ) {\n this.input = input;\n this.props = props;\n }\n\n abstract getBody(): BodyInit | null;\n abstract getHeaders(): Record<string, string>;\n serialize(): Serialized {\n const headers = new Headers({});\n for (const header of this.props.inputHeaders) {\n headers.set(header, this.input[header]);\n }\n\n const query = new URLSearchParams();\n for (const key of this.props.inputQuery) {\n const value = this.input[key];\n if (value !== undefined) {\n query.set(key, String(value));\n }\n }\n\n const params = this.props.inputParams.reduce<Record<string, any>>(\n (acc, key) => {\n acc[key] = this.input[key];\n return acc;\n },\n {},\n );\n\n return {\n body: this.getBody(),\n query,\n params,\n headers: this.getHeaders(),\n };\n }\n}\n\ninterface Serialized {\n body: BodyInit | null;\n query: URLSearchParams;\n params: Record<string, any>;\n headers: Record<string, string>;\n}\n\nclass JsonSerializer extends Serializer {\n getBody(): BodyInit | null {\n const body: Record<string, any> = {};\n for (const prop of this.props.inputBody) {\n body[prop] = this.input[prop];\n }\n return JSON.stringify(body);\n }\n getHeaders(): Record<string, string> {\n return {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n };\n }\n}\n\nclass UrlencodedSerializer extends Serializer {\n getBody(): BodyInit | null {\n const body = new URLSearchParams();\n for (const prop of this.props.inputBody) {\n body.set(prop, this.input[prop]);\n }\n return body;\n }\n getHeaders(): Record<string, string> {\n return {};\n }\n}\n\nclass NoBodySerializer extends Serializer {\n getBody(): BodyInit | null {\n return null;\n }\n getHeaders(): Record<string, string> {\n return {};\n }\n}\n\nclass FormDataSerializer extends Serializer {\n getBody(): BodyInit | null {\n const body = new FormData();\n for (const prop of this.props.inputBody) {\n body.append(prop, this.input[prop]);\n }\n return body;\n }\n getHeaders(): Record<string, string> {\n return {};\n }\n}\n\nexport function json(input: Input, props: Props) {\n return new JsonSerializer(input, props).serialize();\n}\nexport function urlencoded(input: Input, props: Props) {\n return new UrlencodedSerializer(input, props).serialize();\n}\nexport function nobody(input: Input, props: Props) {\n return new NoBodySerializer(input, props).serialize();\n}\nexport function formdata(input: Input, props: Props) {\n return new FormDataSerializer(input, props).serialize();\n}\n\nexport function toRequest<T extends Endpoint>(\n endpoint: T,\n input: Serialized,\n): RequestConfig {\n const [method, path] = endpoint.split(' ');\n const pathVariable = template(path, input.params);\n\n return {\n url: createUrl(pathVariable, input.query),\n init: {\n method: method,\n headers: new Headers(input.headers),\n body: method === 'GET' ? undefined : input.body,\n },\n }\n}\n";
1161
1239
 
1162
1240
  // packages/typescript/src/lib/http/response.txt
1163
- var response_default = "export interface ApiResponse<Status extends number, Body extends unknown> {\n kind: 'response';\n status: Status;\n body: Body;\n}\n\n// 4xx Client Errors\nexport type BadRequest = ApiResponse<400, { message: string }>;\nexport type Unauthorized = ApiResponse<401, { message: string }>;\nexport type PaymentRequired = ApiResponse<402, { message: string }>;\nexport type Forbidden = ApiResponse<403, { message: string }>;\nexport type NotFound = ApiResponse<404, { message: string }>;\nexport type MethodNotAllowed = ApiResponse<405, { message: string }>;\nexport type NotAcceptable = ApiResponse<406, { message: string }>;\nexport type Conflict = ApiResponse<409, { message: string }>;\nexport type Gone = ApiResponse<410, { message: string }>;\nexport type UnprocessableEntity = ApiResponse<422, { message: string; errors?: Record<string, string[]> }>;\nexport type TooManyRequests = ApiResponse<429, { message: string; retryAfter?: string }>;\nexport type PayloadTooLarge = ApiResponse<413, { message: string; }>;\nexport type UnsupportedMediaType = ApiResponse<415, { message: string; }>;\n\n// 5xx Server Errors\nexport type InternalServerError = ApiResponse<500, { message: string }>;\nexport type NotImplemented = ApiResponse<501, { message: string }>;\nexport type BadGateway = ApiResponse<502, { message: string }>;\nexport type ServiceUnavailable = ApiResponse<503, { message: string; retryAfter?: string }>;\nexport type GatewayTimeout = ApiResponse<504, { message: string }>;\n\nexport type ClientError =\n | BadRequest\n | Unauthorized\n | PaymentRequired\n | Forbidden\n | NotFound\n | MethodNotAllowed\n | NotAcceptable\n | Conflict\n | Gone\n | UnprocessableEntity\n | TooManyRequests;\n\nexport type ServerError =\n | InternalServerError\n | NotImplemented\n | BadGateway\n | ServiceUnavailable\n | GatewayTimeout;\n\nexport type ProblematicResponse = ClientError | ServerError;\n";
1241
+ var response_default = "export class APIResponse<Body = unknown, Status extends number = number> {\n static status: number;\n status: Status;\n data: Body;\n\n constructor(status: Status, data: Body) {\n this.status = status;\n this.data = data;\n }\n}\n\nexport class APIError<Body, Status extends number = number> extends APIResponse<\n Body,\n Status\n> {}\n\n// 2xx Success\nexport class Ok<T> extends APIResponse<T, 200> {\n static status = 200;\n}\nexport class Created<T> extends APIResponse<T, 201> {}\nexport class Accepted<T> extends APIResponse<T, 202> {}\nexport class NoContent extends APIResponse<null, 204> {}\n\n// 4xx Client Errors\nexport class BadRequest<T> extends APIError<T, 400> {}\nexport class Unauthorized<T = { message: string }> extends APIError<T, 401> {}\nexport class PaymentRequired<T = { message: string }> extends APIError<\n T,\n 402\n> {}\nexport class Forbidden<T = { message: string }> extends APIError<T, 403> {}\nexport class NotFound<T = { message: string }> extends APIError<T, 404> {}\nexport class MethodNotAllowed<T = { message: string }> extends APIError<\n T,\n 405\n> {}\nexport class NotAcceptable<T = { message: string }> extends APIError<T, 406> {}\nexport class Conflict<T = { message: string }> extends APIError<T, 409> {}\nexport class Gone<T = { message: string }> extends APIError<T, 410> {}\nexport class UnprocessableEntity<\n T = { message: string; errors?: Record<string, string[]> },\n> extends APIError<T, 422> {}\nexport class TooManyRequests<\n T = { message: string; retryAfter?: string },\n> extends APIError<T, 429> {}\nexport class PayloadTooLarge<T = { message: string }> extends APIError<\n T,\n 413\n> {}\nexport class UnsupportedMediaType<T = { message: string }> extends APIError<\n T,\n 415\n> {}\n\n// 5xx Server Errors\nexport class InternalServerError<T = { message: string }> extends APIError<\n T,\n 500\n> {}\nexport class NotImplemented<T = { message: string }> extends APIError<T, 501> {}\nexport class BadGateway<T = { message: string }> extends APIError<T, 502> {}\nexport class ServiceUnavailable<\n T = { message: string; retryAfter?: string },\n> extends APIError<T, 503> {}\nexport class GatewayTimeout<T = { message: string }> extends APIError<T, 504> {}\n\nexport type ClientError =\n | BadRequest<{ message: string }>\n | Unauthorized\n | PaymentRequired\n | Forbidden\n | NotFound\n | MethodNotAllowed\n | NotAcceptable\n | Conflict\n | Gone\n | UnprocessableEntity\n | TooManyRequests;\n\nexport type ServerError =\n | InternalServerError\n | NotImplemented\n | BadGateway\n | ServiceUnavailable\n | GatewayTimeout;\n\nexport type ProblematicResponse = ClientError | ServerError;\n";
1164
1242
 
1165
1243
  // packages/typescript/src/lib/http/send-request.txt
1166
- var send_request_default = "\nexport interface RequestSchema {\n schema: z.ZodType;\n toRequest: (input: any) => RequestConfig;\n deserializer: (response: Response) => Promise<unknown> | unknown;\n}\n\nexport const fetchType = z\n .function()\n .args(z.instanceof(Request))\n .returns(z.promise(z.instanceof(Response)))\n .optional();\n\nexport async function sendRequest(\n input: unknown,\n route: RequestSchema,\n options: {\n fetch?: z.infer<typeof fetchType>;\n interceptors?: Interceptor[];\n signal?: AbortSignal;\n },\n) {\n const { interceptors = [] } = options;\n const [parsedInput, parseError] = parse(route.schema, input);\n if (parseError) {\n return [null as never, { ...parseError, kind: 'parse' } as never] as const;\n }\n\n let config = route.toRequest(parsedInput as never);\n for (const interceptor of interceptors) {\n if (interceptor.before) {\n config = await interceptor.before(config);\n }\n }\n\n let response = await (options.fetch ?? fetch)(new Request(config.url, config.init), {\n ...config.init,\n signal: options.signal,\n });\n\n for (let i = interceptors.length - 1; i >= 0; i--) {\n const interceptor = interceptors[i];\n if (interceptor.after) {\n response = await interceptor.after(response.clone());\n }\n }\n\n if (response.ok) {\n const data = await route.deserializer(response);\n return [data as never, null] as const;\n }\n const error = await handleError(response);\n return [null as never, { ...error, kind: 'response' }] as const;\n}\n";
1244
+ var send_request_default = "export interface Type<T> {\n new (...args: any[]): T;\n}\nexport type Parser = (\n response: Response,\n) => Promise<unknown> | ReadableStream<any>;\nexport type OutputType =\n | Type<APIResponse>\n | { parser: Parser; type: Type<APIResponse> };\ntype Constructor<T> = new (...args: any[]) => T;\nexport interface RequestSchema {\n schema: z.ZodType;\n toRequest: (input: any) => RequestConfig;\n output: OutputType[];\n}\n\nexport const fetchType = z\n .function()\n .args(z.instanceof(Request))\n .returns(z.promise(z.instanceof(Response)))\n .optional();\n\nexport async function sendRequest(\n input: unknown,\n route: RequestSchema,\n options: {\n fetch?: z.infer<typeof fetchType>;\n interceptors?: Interceptor[];\n signal?: AbortSignal;\n },\n) {\n const { interceptors = [] } = options;\n const [parsedInput, parseError] = parse(route.schema, input);\n if (parseError) {\n return [null as never, { ...parseError, kind: 'parse' } as never] as const;\n }\n\n let config = route.toRequest(parsedInput as never);\n for (const interceptor of interceptors) {\n if (interceptor.before) {\n config = await interceptor.before(config);\n }\n }\n\n let response = await (options.fetch ?? fetch)(\n new Request(config.url, config.init),\n {\n ...config.init,\n signal: options.signal,\n },\n );\n\n for (let i = interceptors.length - 1; i >= 0; i--) {\n const interceptor = interceptors[i];\n if (interceptor.after) {\n response = await interceptor.after(response.clone());\n }\n }\n\n let output: Constructor<APIResponse> | null = APIResponse;\n let parser: Parser = buffered;\n for (const outputType of route.output) {\n if ('parser' in outputType) {\n parser = outputType.parser;\n if (isTypeOf(outputType.type, APIResponse)) {\n if (response.status === outputType.type.status) {\n output = outputType.type;\n break;\n }\n }\n } else if (isTypeOf(outputType, APIResponse)) {\n if (response.status === outputType.status) {\n output = outputType;\n break;\n }\n }\n }\n\n const data = new output(response.status, await parser(response));\n if (response.ok) {\n return [data as never, null] as const;\n }\n return [null as never, data as never] as const;\n}\n\nexport function isTypeOf<T extends Type<APIResponse>>(\n instance: any,\n baseType: T,\n): instance is T {\n if (instance === baseType) {\n return true;\n }\n const prototype = Object.getPrototypeOf(instance);\n if (prototype === null) {\n return false;\n }\n return isTypeOf(prototype, baseType);\n}\n";
1167
1245
 
1168
1246
  // packages/typescript/src/lib/generate.ts
1169
1247
  function security(spec) {
@@ -1191,21 +1269,14 @@ async function generate(spec, settings) {
1191
1269
  const makeImport = (moduleSpecifier) => {
1192
1270
  return settings.useTsExtension ? `${moduleSpecifier}.ts` : moduleSpecifier;
1193
1271
  };
1194
- const { commonSchemas, groups, outputs, commonZod } = generateCode({
1272
+ const { commonSchemas, endpoints, groups, outputs, commonZod, clientFiles } = generateCode({
1195
1273
  spec,
1196
1274
  style: "github",
1197
1275
  makeImport
1198
1276
  });
1199
- const output = settings.mode === "full" ? join(settings.output, "src") : settings.output;
1277
+ const output = settings.mode === "full" ? join2(settings.output, "src") : settings.output;
1200
1278
  const options = security(spec);
1201
1279
  const clientName = settings.name || "Client";
1202
- const clientFiles = generateSDK({
1203
- name: clientName,
1204
- operations: groups,
1205
- servers: spec.servers?.map((server) => server.url) || [],
1206
- options,
1207
- makeImport
1208
- });
1209
1280
  const inputFiles = generateInputs(groups, commonZod, makeImport);
1210
1281
  await writeFiles(output, {
1211
1282
  "outputs/.gitkeep": "",
@@ -1213,24 +1284,33 @@ async function generate(spec, settings) {
1213
1284
  "models/.getkeep": ""
1214
1285
  // 'README.md': readme,
1215
1286
  });
1216
- await writeFiles(join(output, "http"), {
1287
+ await writeFiles(join2(output, "http"), {
1217
1288
  "interceptors.ts": interceptors_default,
1218
1289
  "parse-response.ts": parse_response_default,
1219
1290
  "send-request.ts": `import z from 'zod';
1220
1291
  import type { Interceptor } from './${makeImport("interceptors")}';
1221
- import { handleError } from './${makeImport("parse-response")}';
1292
+ import { buffered } from './${makeImport("parse-response")}';
1222
1293
  import { parse } from './${makeImport("parser")}';
1223
- import type { RequestConfig } from './request.ts';
1294
+ import type { RequestConfig } from './${makeImport("request")}';
1295
+ import { APIResponse } from './${makeImport("response")}';
1296
+
1224
1297
  ${send_request_default}`,
1225
1298
  "response.ts": response_default,
1226
1299
  "parser.ts": parser_default,
1227
1300
  "request.ts": request_default
1228
1301
  });
1229
- await writeFiles(join(output, "outputs"), outputs);
1302
+ await writeFiles(join2(output, "outputs"), outputs);
1230
1303
  const modelsImports = Object.entries(commonSchemas).map(([name]) => name);
1231
1304
  await writeFiles(output, {
1305
+ "client.ts": client_default({
1306
+ name: clientName,
1307
+ servers: (spec.servers ?? []).map((server) => server.url) || [],
1308
+ options,
1309
+ makeImport
1310
+ }),
1232
1311
  ...clientFiles,
1233
1312
  ...inputFiles,
1313
+ ...endpoints,
1234
1314
  ...Object.fromEntries(
1235
1315
  Object.entries(commonSchemas).map(([name, schema]) => [
1236
1316
  `models/${name}.ts`,
@@ -1245,29 +1325,36 @@ ${send_request_default}`,
1245
1325
  )
1246
1326
  });
1247
1327
  const folders = [
1248
- getFolderExports(join(output, "outputs"), settings.useTsExtension),
1328
+ getFolderExports(join2(output, "outputs"), settings.useTsExtension),
1249
1329
  getFolderExports(
1250
- join(output, "inputs"),
1330
+ join2(output, "inputs"),
1251
1331
  settings.useTsExtension,
1252
1332
  ["ts"],
1253
- (dirent) => dirent.isDirectory() && dirent.name === "schemas"
1333
+ (dirent) => dirent.isDirectory() && ["schemas"].includes(dirent.name)
1254
1334
  ),
1255
- getFolderExports(join(output, "http"), settings.useTsExtension)
1335
+ getFolderExports(join2(output, "api"), settings.useTsExtension),
1336
+ getFolderExports(
1337
+ join2(output, "http"),
1338
+ settings.useTsExtension,
1339
+ ["ts"],
1340
+ (dirent) => dirent.name !== "response.ts"
1341
+ )
1256
1342
  ];
1257
1343
  if (modelsImports.length) {
1258
1344
  folders.push(
1259
- getFolderExports(join(output, "models"), settings.useTsExtension)
1345
+ getFolderExports(join2(output, "models"), settings.useTsExtension)
1260
1346
  );
1261
1347
  }
1262
- const [outputIndex, inputsIndex, httpIndex, modelsIndex] = await Promise.all(folders);
1348
+ const [outputIndex, inputsIndex, apiIndex, httpIndex, modelsIndex] = await Promise.all(folders);
1263
1349
  await writeFiles(output, {
1350
+ "api/index.ts": apiIndex,
1264
1351
  "outputs/index.ts": outputIndex,
1265
1352
  "inputs/index.ts": inputsIndex || null,
1266
1353
  "http/index.ts": httpIndex,
1267
1354
  ...modelsImports.length ? { "models/index.ts": modelsIndex } : {}
1268
1355
  });
1269
1356
  await writeFiles(output, {
1270
- "index.ts": await getFolderExports(output, settings.useTsExtension)
1357
+ "index.ts": await getFolderExports(output, settings.useTsExtension, ["ts"])
1271
1358
  });
1272
1359
  if (settings.mode === "full") {
1273
1360
  await writeFiles(settings.output, {