@sdk-it/typescript 0.14.5 → 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,21 +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
+ import { spinalcase as spinalcase3 } from "stringcase";
4
5
  import { getFolderExports, methods, writeFiles } from "@sdk-it/core";
5
6
 
6
- // packages/typescript/src/lib/generator.ts
7
- import { get as get2, merge } from "lodash-es";
8
- import { camelcase as camelcase2, pascalcase, spinalcase as spinalcase2 } from "stringcase";
9
- import { removeDuplicates as removeDuplicates3 } from "@sdk-it/core";
10
-
11
- // packages/typescript/src/lib/utils.ts
12
- import { get } from "lodash-es";
13
- import { removeDuplicates as removeDuplicates2 } from "@sdk-it/core";
14
-
15
- // packages/typescript/src/lib/sdk.ts
16
- import { camelcase, spinalcase } from "stringcase";
17
- import { removeDuplicates, toLitObject as toLitObject2 } from "@sdk-it/core";
18
-
19
7
  // packages/typescript/src/lib/client.ts
20
8
  import { toLitObject } from "@sdk-it/core";
21
9
  var client_default = (spec) => {
@@ -42,8 +30,8 @@ var client_default = (spec) => {
42
30
  return `
43
31
  import { fetchType, sendRequest } from './http/${spec.makeImport("send-request")}';
44
32
  import z from 'zod';
45
- import type { Endpoints } from './${spec.makeImport("endpoints")}';
46
- import schemas from './${spec.makeImport("schemas")}';
33
+ import type { Endpoints } from './api/${spec.makeImport("schemas")}';
34
+ import schemas from './api/${spec.makeImport("schemas")}';
47
35
  import {
48
36
  createBaseUrlInterceptor,
49
37
  createHeadersInterceptor,
@@ -97,290 +85,278 @@ export class ${spec.name} {
97
85
  }`;
98
86
  };
99
87
 
100
- // packages/typescript/src/lib/sdk.ts
101
- var SchemaEndpoint = class {
102
- #makeImport;
103
- #imports = [];
104
- constructor(makeImport) {
105
- this.#makeImport = makeImport;
106
- this.#imports = [
107
- `import z from 'zod';`,
108
- `import type { Endpoints } from '${this.#makeImport("./endpoints")}';`,
109
- `import { toRequest, json, urlencoded, nobody, formdata, createUrl } from '${this.#makeImport("./http/request")}';`,
110
- `import type { ParseError } from '${this.#makeImport("./http/parser")}';`,
111
- `import { chunked, buffered } from "${this.#makeImport("./http/parse-response")}";`
112
- ];
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";
93
+
94
+ // packages/typescript/src/lib/emitters/zod.ts
95
+ import { cleanRef, followRef, isRef, parseRef } from "@sdk-it/core";
96
+ var ZodDeserialzer = class {
97
+ circularRefTracker = /* @__PURE__ */ new Set();
98
+ #spec;
99
+ #onRef;
100
+ constructor(spec, onRef) {
101
+ this.#spec = spec;
102
+ this.#onRef = onRef;
113
103
  }
114
- #endpoints = [];
115
- addEndpoint(endpoint, operation) {
116
- this.#endpoints.push(` "${endpoint}": ${operation},`);
104
+ /**
105
+ * Handle objects (properties, additionalProperties).
106
+ */
107
+ object(schema) {
108
+ const properties = schema.properties || {};
109
+ const propEntries = Object.entries(properties).map(([key, propSchema]) => {
110
+ const isRequired = (schema.required ?? []).includes(key);
111
+ return `'${key}': ${this.handle(propSchema, isRequired)}`;
112
+ });
113
+ let additionalProps = "";
114
+ if (schema.additionalProperties) {
115
+ if (typeof schema.additionalProperties === "object") {
116
+ const addPropZod = this.handle(schema.additionalProperties, true);
117
+ additionalProps = `.catchall(${addPropZod})`;
118
+ } else if (schema.additionalProperties === true) {
119
+ additionalProps = `.catchall(z.unknown())`;
120
+ }
121
+ }
122
+ return `z.object({${propEntries.join(", ")}})${additionalProps}`;
117
123
  }
118
- addImport(value) {
119
- this.#imports.push(value);
124
+ /**
125
+ * Handle arrays (items could be a single schema or a tuple (array of schemas)).
126
+ * In JSON Schema 2020-12, `items` can be an array → tuple style.
127
+ */
128
+ array(schema, required = false) {
129
+ const { items } = schema;
130
+ if (!items) {
131
+ return `z.array(z.unknown())${appendOptional(required)}`;
132
+ }
133
+ if (Array.isArray(items)) {
134
+ const tupleItems = items.map((sub) => this.handle(sub, true));
135
+ const base = `z.tuple([${tupleItems.join(", ")}])`;
136
+ return `${base}${appendOptional(required)}`;
137
+ }
138
+ const itemsSchema = this.handle(items, true);
139
+ return `z.array(${itemsSchema})${appendOptional(required)}`;
120
140
  }
121
- complete() {
122
- return `${this.#imports.join("\n")}
123
- export default {
124
- ${this.#endpoints.join("\n")}
125
- }`;
141
+ #suffixes = (defaultValue, required, nullable) => {
142
+ return `${nullable ? ".nullable()" : ""}${appendDefault(defaultValue)}${appendOptional(required)}`;
143
+ };
144
+ /**
145
+ * Convert a basic type (string | number | boolean | object | array, etc.) to Zod.
146
+ * We'll also handle .optional() if needed.
147
+ */
148
+ normal(type, schema, required = false, nullable = false) {
149
+ switch (type) {
150
+ case "string":
151
+ return `${this.string(schema)}${this.#suffixes(JSON.stringify(schema.default), required, nullable)}`;
152
+ case "number":
153
+ case "integer": {
154
+ const { base, defaultValue } = this.number(schema);
155
+ return `${base}${this.#suffixes(defaultValue, required, nullable)}`;
156
+ }
157
+ case "boolean":
158
+ return `z.boolean()${this.#suffixes(schema.default, required, nullable)}`;
159
+ case "object":
160
+ return `${this.object(schema)}${this.#suffixes(JSON.stringify(schema.default), required, nullable)}`;
161
+ case "array":
162
+ return this.array(schema, required);
163
+ case "null":
164
+ return `z.null()${appendOptional(required)}`;
165
+ default:
166
+ return `z.unknown()${appendOptional(required)}`;
167
+ }
126
168
  }
127
- };
128
- var Emitter = class {
129
- #makeImport;
130
- imports = [];
131
- constructor(makeImport) {
132
- this.#makeImport = makeImport;
133
- this.imports = [
134
- `import type z from 'zod';`,
135
- `import type { ParseError } from '${this.#makeImport("./http/parser")}';`
136
- ];
169
+ ref($ref, required) {
170
+ const schemaName = cleanRef($ref).split("/").pop();
171
+ if (this.circularRefTracker.has(schemaName)) {
172
+ return schemaName;
173
+ }
174
+ this.circularRefTracker.add(schemaName);
175
+ this.#onRef?.(
176
+ schemaName,
177
+ this.handle(followRef(this.#spec, $ref), required)
178
+ );
179
+ this.circularRefTracker.delete(schemaName);
180
+ return schemaName;
137
181
  }
138
- endpoints = [];
139
- addEndpoint(endpoint, operation) {
140
- this.endpoints.push(` "${endpoint}": ${operation};`);
182
+ allOf(schemas, required) {
183
+ const allOfSchemas = schemas.map((sub) => this.handle(sub, true));
184
+ if (allOfSchemas.length === 0) {
185
+ return `z.unknown()`;
186
+ }
187
+ if (allOfSchemas.length === 1) {
188
+ return `${allOfSchemas[0]}${appendOptional(required)}`;
189
+ }
190
+ return `${this.#toIntersection(allOfSchemas)}${appendOptional(required)}`;
141
191
  }
142
- addImport(value) {
143
- this.imports.push(value);
192
+ #toIntersection(schemas) {
193
+ const [left, ...right] = schemas;
194
+ if (!right.length) {
195
+ return left;
196
+ }
197
+ return `z.intersection(${left}, ${this.#toIntersection(right)})`;
144
198
  }
145
- complete() {
146
- return `${this.imports.join("\n")}
147
- export interface Endpoints {
148
- ${this.endpoints.join("\n")}
149
- }`;
199
+ anyOf(schemas, required) {
200
+ const anyOfSchemas = schemas.map((sub) => this.handle(sub, true));
201
+ if (anyOfSchemas.length === 1) {
202
+ return `${anyOfSchemas[0]}${appendOptional(required)}`;
203
+ }
204
+ return `z.union([${anyOfSchemas.join(", ")}])${appendOptional(required)}`;
150
205
  }
151
- };
152
- function generateInputs(operationsSet, commonZod, makeImport) {
153
- const commonImports = commonZod.keys().toArray();
154
- const inputs = {};
155
- for (const [name, operations] of Object.entries(operationsSet)) {
156
- const output = [];
157
- const imports = /* @__PURE__ */ new Set(['import { z } from "zod";']);
158
- for (const operation of operations) {
159
- const schemaName = camelcase(`${operation.name} schema`);
160
- const schema = `export const ${schemaName} = ${Object.keys(operation.schemas).length === 1 ? Object.values(operation.schemas)[0] : toLitObject2(operation.schemas)};`;
161
- const inputContent = schema;
162
- for (const schema2 of commonImports) {
163
- if (inputContent.includes(schema2)) {
164
- imports.add(
165
- `import { ${schema2} } from './schemas/${makeImport(spinalcase(schema2))}';`
166
- );
206
+ oneOf(schemas, required) {
207
+ const oneOfSchemas = schemas.map((sub) => {
208
+ if (isRef(sub)) {
209
+ const { model } = parseRef(sub.$ref);
210
+ if (this.circularRefTracker.has(model)) {
211
+ return `${model}${appendOptional(required)}`;
167
212
  }
168
213
  }
169
- output.push(inputContent);
214
+ return this.handle(sub, true);
215
+ });
216
+ if (oneOfSchemas.length === 1) {
217
+ return `${oneOfSchemas[0]}${appendOptional(required)}`;
170
218
  }
171
- inputs[`inputs/${spinalcase(name)}.ts`] = [...imports, ...output].join("\n") + "\n";
219
+ return `z.union([${oneOfSchemas.join(", ")}])${appendOptional(required)}`;
172
220
  }
173
- const schemas = commonZod.entries().reduce((acc, [name, schema]) => {
174
- const output = [`import { z } from 'zod';`];
175
- const content = `export const ${name} = ${schema};`;
176
- for (const schema2 of commonImports) {
177
- const preciseMatch = new RegExp(`\\b${schema2}\\b`);
178
- if (preciseMatch.test(content) && schema2 !== name) {
179
- output.push(
180
- `import { ${schema2} } from './${makeImport(spinalcase(schema2))}';`
181
- );
182
- }
183
- }
184
- output.push(content);
185
- return [
186
- [`inputs/schemas/${spinalcase(name)}.ts`, output.join("\n")],
187
- ...acc
188
- ];
189
- }, []);
190
- return {
191
- ...Object.fromEntries(schemas),
192
- ...inputs
193
- };
194
- }
195
- function generateSDK(spec) {
196
- const emitter = new Emitter(spec.makeImport);
197
- const schemaEndpoint = new SchemaEndpoint(spec.makeImport);
198
- const errors = [];
199
- for (const [name, operations] of Object.entries(spec.operations)) {
200
- emitter.addImport(
201
- `import type * as ${camelcase(name)} from './inputs/${spec.makeImport(spinalcase(name))}';`
202
- );
203
- schemaEndpoint.addImport(
204
- `import * as ${camelcase(name)} from './inputs/${spec.makeImport(spinalcase(name))}';`
205
- );
206
- for (const operation of operations) {
207
- const schemaName = camelcase(`${operation.name} schema`);
208
- const schemaRef = `${camelcase(name)}.${schemaName}`;
209
- const output = operation.formatOutput();
210
- const inputHeaders = [];
211
- const inputQuery = [];
212
- const inputBody = [];
213
- const inputParams = [];
214
- for (const [name2, prop] of Object.entries(operation.inputs)) {
215
- if (prop.in === "headers" || prop.in === "header") {
216
- inputHeaders.push(`"${name2}"`);
217
- } else if (prop.in === "query") {
218
- inputQuery.push(`"${name2}"`);
219
- } else if (prop.in === "body") {
220
- inputBody.push(`"${name2}"`);
221
- } else if (prop.in === "path") {
222
- inputParams.push(`"${name2}"`);
223
- } else if (prop.in === "internal") {
224
- continue;
225
- } else {
226
- throw new Error(
227
- `Unknown source ${prop.in} in ${name2} ${JSON.stringify(
228
- prop
229
- )} in ${operation.name}`
230
- );
231
- }
232
- }
233
- emitter.addImport(
234
- `import type {${output.import}} from './outputs/${spec.makeImport(spinalcase(operation.name))}';`
235
- );
236
- errors.push(...operation.errors ?? []);
237
- const addTypeParser = Object.keys(operation.schemas).length > 1;
238
- for (const type in operation.schemas ?? {}) {
239
- let typePrefix = "";
240
- if (addTypeParser && type !== "json") {
241
- typePrefix = `${type} `;
242
- }
243
- const input = `typeof ${schemaRef}${addTypeParser ? `.${type}` : ""}`;
244
- const endpoint = `${typePrefix}${operation.trigger.method.toUpperCase()} ${operation.trigger.path}`;
245
- emitter.addEndpoint(
246
- endpoint,
247
- `{input: z.infer<${input}>; output: ${output.use}; error: ${(operation.errors ?? ["ServerError"]).concat(`ParseError<${input}>`).join("|")}}`
248
- );
249
- schemaEndpoint.addEndpoint(
250
- endpoint,
251
- `{
252
- schema: ${schemaRef}${addTypeParser ? `.${type}` : ""},
253
- deserializer: ${operation.parser === "chunked" ? "chunked" : "buffered"},
254
- toRequest(input: Endpoints['${endpoint}']['input']) {
255
- const endpoint = '${endpoint}';
256
- return toRequest(endpoint, ${operation.outgoingContentType || "nobody"}(input, {
257
- inputHeaders: [${inputHeaders}],
258
- inputQuery: [${inputQuery}],
259
- inputBody: [${inputBody}],
260
- inputParams: [${inputParams}],
261
- }));
262
- },
263
- }`
264
- );
265
- }
221
+ enum(values) {
222
+ if (values.length === 1) {
223
+ return `z.literal(${values.join(", ")})`;
266
224
  }
225
+ return `z.enum([${values.join(", ")}])`;
267
226
  }
268
- emitter.addImport(
269
- `import type { ${removeDuplicates(errors, (it) => it).join(", ")} } from '${spec.makeImport("./http/response")}';`
270
- );
271
- return {
272
- "client.ts": client_default(spec),
273
- "schemas.ts": schemaEndpoint.complete(),
274
- "endpoints.ts": emitter.complete()
275
- };
276
- }
277
-
278
- // packages/typescript/src/lib/utils.ts
279
- function isRef(obj) {
280
- return obj && "$ref" in obj;
281
- }
282
- function cleanRef(ref) {
283
- return ref.replace(/^#\//, "");
284
- }
285
- function parseRef(ref) {
286
- const parts = ref.split("/");
287
- const [model] = parts.splice(-1);
288
- return { model, path: parts.join("/") };
289
- }
290
- function followRef(spec, ref) {
291
- const pathParts = cleanRef(ref).split("/");
292
- const entry = get(spec, pathParts);
293
- if (entry && "$ref" in entry) {
294
- return followRef(spec, entry.$ref);
227
+ /**
228
+ * Handle a `string` schema with possible format keywords (JSON Schema).
229
+ */
230
+ string(schema) {
231
+ let base = "z.string()";
232
+ switch (schema.format) {
233
+ case "date-time":
234
+ case "datetime":
235
+ base = "z.coerce.date()";
236
+ break;
237
+ case "date":
238
+ base = "z.coerce.date() /* or z.string() if you want raw date strings */";
239
+ break;
240
+ case "time":
241
+ base = "z.string() /* optionally add .regex(...) for HH:MM:SS format */";
242
+ break;
243
+ case "email":
244
+ base = "z.string().email()";
245
+ break;
246
+ case "uuid":
247
+ base = "z.string().uuid()";
248
+ break;
249
+ case "url":
250
+ case "uri":
251
+ base = "z.string().url()";
252
+ break;
253
+ case "ipv4":
254
+ base = 'z.string().ip({version: "v4"})';
255
+ break;
256
+ case "ipv6":
257
+ base = 'z.string().ip({version: "v6"})';
258
+ break;
259
+ case "phone":
260
+ base = "z.string() /* or add .regex(...) for phone formats */";
261
+ break;
262
+ case "byte":
263
+ case "binary":
264
+ base = "z.instanceof(Blob) /* consider base64 check if needed */";
265
+ break;
266
+ case "int64":
267
+ base = "z.string() /* or z.bigint() if your app can handle it */";
268
+ break;
269
+ default:
270
+ break;
271
+ }
272
+ return base;
295
273
  }
296
- return entry;
297
- }
298
- function securityToOptions(security2, securitySchemes, staticIn) {
299
- securitySchemes ??= {};
300
- const options = {};
301
- for (const it of security2) {
302
- const [name] = Object.keys(it);
303
- if (!name) {
304
- continue;
274
+ /**
275
+ * Handle number/integer constraints from OpenAPI/JSON Schema.
276
+ * In 3.1, exclusiveMinimum/Maximum hold the actual numeric threshold,
277
+ * rather than a boolean toggling `minimum`/`maximum`.
278
+ */
279
+ number(schema) {
280
+ let defaultValue = schema.default;
281
+ let base = "z.number()";
282
+ if (schema.format === "int64") {
283
+ base = "z.bigint()";
284
+ if (schema.default !== void 0) {
285
+ defaultValue = `BigInt(${schema.default})`;
286
+ }
305
287
  }
306
- const schema = securitySchemes[name];
307
- if (isRef(schema)) {
308
- throw new Error(`Ref security schemas are not supported`);
288
+ if (schema.format === "int32") {
289
+ base += ".int()";
309
290
  }
310
- if (schema.type === "http") {
311
- options["authorization"] = {
312
- in: staticIn ?? "header",
313
- schema: "z.string().optional().transform((val) => (val ? `Bearer ${val}` : undefined))",
314
- optionName: "token"
315
- };
316
- continue;
291
+ if (typeof schema.exclusiveMinimum === "number") {
292
+ base += `.gt(${schema.exclusiveMinimum})`;
317
293
  }
318
- if (schema.type === "apiKey") {
319
- if (!schema.in) {
320
- throw new Error(`apiKey security schema must have an "in" field`);
321
- }
322
- if (!schema.name) {
323
- throw new Error(`apiKey security schema must have a "name" field`);
324
- }
325
- options[schema.name] = {
326
- in: staticIn ?? schema.in,
327
- schema: "z.string().optional()"
328
- };
329
- continue;
294
+ if (typeof schema.exclusiveMaximum === "number") {
295
+ base += `.lt(${schema.exclusiveMaximum})`;
296
+ }
297
+ if (typeof schema.minimum === "number") {
298
+ base += schema.format === "int64" ? `.min(BigInt(${schema.minimum}))` : `.min(${schema.minimum})`;
299
+ }
300
+ if (typeof schema.maximum === "number") {
301
+ base += schema.format === "int64" ? `.max(BigInt(${schema.maximum}))` : `.max(${schema.maximum})`;
302
+ }
303
+ if (typeof schema.multipleOf === "number") {
304
+ base += `.refine((val) => Number.isInteger(val / ${schema.multipleOf}), "Must be a multiple of ${schema.multipleOf}")`;
330
305
  }
306
+ return { base, defaultValue };
331
307
  }
332
- return options;
333
- }
334
- function mergeImports(imports) {
335
- const merged = {};
336
- for (const i of imports) {
337
- merged[i.moduleSpecifier] = merged[i.moduleSpecifier] ?? {
338
- moduleSpecifier: i.moduleSpecifier,
339
- defaultImport: i.defaultImport,
340
- namespaceImport: i.namespaceImport,
341
- namedImports: []
342
- };
343
- if (i.namedImports) {
344
- merged[i.moduleSpecifier].namedImports.push(...i.namedImports);
308
+ handle(schema, required) {
309
+ if (isRef(schema)) {
310
+ return `${this.ref(schema.$ref, true)}${appendOptional(required)}`;
345
311
  }
346
- }
347
- return Object.values(merged);
348
- }
349
- function importsToString(...imports) {
350
- return imports.map((it) => {
351
- if (it.defaultImport) {
352
- return `import ${it.defaultImport} from '${it.moduleSpecifier}'`;
312
+ if (schema.allOf && Array.isArray(schema.allOf)) {
313
+ return this.allOf(schema.allOf ?? [], required);
353
314
  }
354
- if (it.namespaceImport) {
355
- return `import * as ${it.namespaceImport} from '${it.moduleSpecifier}'`;
315
+ if (schema.anyOf && Array.isArray(schema.anyOf)) {
316
+ return this.anyOf(schema.anyOf ?? [], required);
356
317
  }
357
- if (it.namedImports) {
358
- return `import {${removeDuplicates2(it.namedImports, (it2) => it2.name).map((n) => `${n.isTypeOnly ? "type" : ""} ${n.name}`).join(", ")}} from '${it.moduleSpecifier}'`;
318
+ if (schema.oneOf && Array.isArray(schema.oneOf) && schema.oneOf.length) {
319
+ return this.oneOf(schema.oneOf ?? [], required);
359
320
  }
360
- throw new Error(`Invalid import ${JSON.stringify(it)}`);
361
- });
362
- }
363
- function exclude(list, exclude2) {
364
- return list.filter((it) => !exclude2.includes(it));
365
- }
366
- function useImports(content, imports) {
367
- const output = [];
368
- for (const it of mergeImports(imports)) {
369
- const singleImport = it.defaultImport ?? it.namespaceImport;
370
- if (singleImport && content.includes(singleImport)) {
371
- output.push(importsToString(it).join("\n"));
372
- } else if (it.namedImports.length) {
373
- for (const namedImport of it.namedImports) {
374
- if (content.includes(namedImport.name)) {
375
- output.push(importsToString(it).join("\n"));
376
- }
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);
377
339
  }
340
+ const subSchemas = types.map((t) => this.normal(t, schema, false));
341
+ return `z.union([${subSchemas.join(", ")}])${appendOptional(required)}`;
378
342
  }
343
+ return this.normal(types[0], schema, required, false);
379
344
  }
380
- return output;
345
+ };
346
+ function appendOptional(isRequired) {
347
+ return isRequired ? "" : ".optional()";
348
+ }
349
+ function appendDefault(defaultValue) {
350
+ return defaultValue !== void 0 || typeof defaultValue !== "undefined" ? `.default(${defaultValue})` : "";
381
351
  }
382
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
+
383
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";
384
360
  var TypeScriptDeserialzer = class {
385
361
  circularRefTracker = /* @__PURE__ */ new Set();
386
362
  #spec;
@@ -499,7 +475,7 @@ var TypeScriptDeserialzer = class {
499
475
  case "integer":
500
476
  return this.number(schema, required);
501
477
  case "boolean":
502
- return appendOptional("boolean", required);
478
+ return appendOptional2("boolean", required);
503
479
  case "object":
504
480
  return this.object(schema, required);
505
481
  case "array":
@@ -507,18 +483,22 @@ var TypeScriptDeserialzer = class {
507
483
  case "null":
508
484
  return "null";
509
485
  default:
510
- return appendOptional("any", required);
486
+ console.warn(`Unknown type: ${type}`);
487
+ return appendOptional2("any", required);
511
488
  }
512
489
  }
513
490
  ref($ref, required) {
514
- const schemaName = cleanRef($ref).split("/").pop();
491
+ const schemaName = cleanRef2($ref).split("/").pop();
515
492
  if (this.circularRefTracker.has(schemaName)) {
516
493
  return schemaName;
517
494
  }
518
495
  this.circularRefTracker.add(schemaName);
519
- this.#onRef(schemaName, this.handle(followRef(this.#spec, $ref), true));
496
+ this.#onRef?.(
497
+ schemaName,
498
+ this.handle(followRef2(this.#spec, $ref), required)
499
+ );
520
500
  this.circularRefTracker.delete(schemaName);
521
- return appendOptional(schemaName, required);
501
+ return appendOptional2(schemaName, required);
522
502
  }
523
503
  allOf(schemas) {
524
504
  const allOfTypes = schemas.map((sub) => this.handle(sub, true));
@@ -526,29 +506,29 @@ var TypeScriptDeserialzer = class {
526
506
  }
527
507
  anyOf(schemas, required) {
528
508
  const anyOfTypes = schemas.map((sub) => this.handle(sub, true));
529
- return appendOptional(
509
+ return appendOptional2(
530
510
  anyOfTypes.length > 1 ? `${anyOfTypes.join(" | ")}` : anyOfTypes[0],
531
511
  required
532
512
  );
533
513
  }
534
514
  oneOf(schemas, required) {
535
515
  const oneOfTypes = schemas.map((sub) => {
536
- if (isRef(sub)) {
537
- const { model } = parseRef(sub.$ref);
516
+ if (isRef2(sub)) {
517
+ const { model } = parseRef2(sub.$ref);
538
518
  if (this.circularRefTracker.has(model)) {
539
519
  return model;
540
520
  }
541
521
  }
542
522
  return this.handle(sub, false);
543
523
  });
544
- return appendOptional(
524
+ return appendOptional2(
545
525
  oneOfTypes.length > 1 ? `${oneOfTypes.join(" | ")}` : oneOfTypes[0],
546
526
  required
547
527
  );
548
528
  }
549
529
  enum(values, required) {
550
530
  const enumValues = values.map((val) => typeof val === "string" ? `'${val}'` : `${val}`).join(" | ");
551
- return appendOptional(enumValues, required);
531
+ return appendOptional2(enumValues, required);
552
532
  }
553
533
  /**
554
534
  * Handle string type with formats
@@ -571,17 +551,17 @@ var TypeScriptDeserialzer = class {
571
551
  default:
572
552
  type = "string";
573
553
  }
574
- return appendOptional(type, required);
554
+ return appendOptional2(type, required);
575
555
  }
576
556
  /**
577
557
  * Handle number/integer types with formats
578
558
  */
579
559
  number(schema, required) {
580
560
  const type = schema.format === "int64" ? "bigint" : "number";
581
- return appendOptional(type, required);
561
+ return appendOptional2(type, required);
582
562
  }
583
563
  handle(schema, required) {
584
- if (isRef(schema)) {
564
+ if (isRef2(schema)) {
585
565
  return this.ref(schema.$ref, required);
586
566
  }
587
567
  if (schema.allOf && Array.isArray(schema.allOf)) {
@@ -601,16 +581,16 @@ var TypeScriptDeserialzer = class {
601
581
  if ("properties" in schema) {
602
582
  return this.object(schema, required);
603
583
  }
604
- return appendOptional("any", required);
584
+ return appendOptional2("any", required);
605
585
  }
606
586
  if (types.length > 1) {
607
587
  const realTypes = types.filter((t) => t !== "null");
608
588
  if (realTypes.length === 1 && types.includes("null")) {
609
589
  const tsType = this.normal(realTypes[0], schema, false);
610
- return appendOptional(`${tsType} | null`, required);
590
+ return appendOptional2(`${tsType} | null`, required);
611
591
  }
612
592
  const typeResults = types.map((t) => this.normal(t, schema, false));
613
- return appendOptional(typeResults.join(" | "), required);
593
+ return appendOptional2(typeResults.join(" | "), required);
614
594
  }
615
595
  return this.normal(types[0], schema, required);
616
596
  }
@@ -622,275 +602,228 @@ var TypeScriptDeserialzer = class {
622
602
  return `interface ${name} ${content}`;
623
603
  }
624
604
  };
625
- function appendOptional(type, isRequired) {
605
+ function appendOptional2(type, isRequired) {
626
606
  return isRequired ? type : `${type} | undefined`;
627
607
  }
628
-
629
- // packages/typescript/src/lib/emitters/zod.ts
630
- var ZodDeserialzer = class {
631
- circularRefTracker = /* @__PURE__ */ new Set();
632
- #spec;
633
- #onRef;
634
- constructor(spec, onRef) {
635
- this.#spec = spec;
636
- this.#onRef = onRef;
637
- }
638
- /**
639
- * Handle objects (properties, additionalProperties).
640
- */
641
- object(schema, required = false) {
642
- const properties = schema.properties || {};
643
- const propEntries = Object.entries(properties).map(([key, propSchema]) => {
644
- const isRequired = (schema.required ?? []).includes(key);
645
- const zodPart = this.handle(propSchema, isRequired);
646
- return `'${key}': ${zodPart}`;
647
- });
648
- let additionalProps = "";
649
- if (schema.additionalProperties) {
650
- if (typeof schema.additionalProperties === "object") {
651
- const addPropZod = this.handle(schema.additionalProperties, true);
652
- additionalProps = `.catchall(${addPropZod})`;
653
- } else if (schema.additionalProperties === true) {
654
- additionalProps = `.catchall(z.unknown())`;
655
- }
656
- }
657
- const objectSchema = `z.object({${propEntries.join(", ")}})${additionalProps}`;
658
- return `${objectSchema}${appendOptional2(required)}`;
659
- }
660
- /**
661
- * Handle arrays (items could be a single schema or a tuple (array of schemas)).
662
- * In JSON Schema 2020-12, `items` can be an array → tuple style.
663
- */
664
- array(schema, required = false) {
665
- const { items } = schema;
666
- if (!items) {
667
- return `z.array(z.unknown())${appendOptional2(required)}`;
668
- }
669
- if (Array.isArray(items)) {
670
- const tupleItems = items.map((sub) => this.handle(sub, true));
671
- const base = `z.tuple([${tupleItems.join(", ")}])`;
672
- return `${base}${appendOptional2(required)}`;
673
- }
674
- const itemsSchema = this.handle(items, true);
675
- return `z.array(${itemsSchema})${appendOptional2(required)}`;
676
- }
677
- #suffixes = (defaultValue, required, nullable) => {
678
- return `${nullable ? ".nullable()" : ""}${appendDefault(defaultValue)}${appendOptional2(required)}`;
679
- };
680
- /**
681
- * Convert a basic type (string | number | boolean | object | array, etc.) to Zod.
682
- * We'll also handle .optional() if needed.
683
- */
684
- normal(type, schema, required = false, nullable = false) {
685
- switch (type) {
686
- case "string":
687
- return `${this.string(schema)}${this.#suffixes(JSON.stringify(schema.default), required, nullable)}`;
688
- case "number":
689
- case "integer": {
690
- const { base, defaultValue } = this.number(schema);
691
- return `${base}${this.#suffixes(defaultValue, required, nullable)}`;
692
- }
693
- case "boolean":
694
- return `z.boolean()${this.#suffixes(schema.default, required, nullable)}`;
695
- case "object":
696
- return this.object(schema, true);
697
- case "array":
698
- return this.array(schema, required);
699
- case "null":
700
- return `z.null()${appendOptional2(required)}`;
701
- default:
702
- return `z.unknown()${appendOptional2(required)}`;
703
- }
704
- }
705
- ref($ref, required) {
706
- const schemaName = cleanRef($ref).split("/").pop();
707
- if (this.circularRefTracker.has(schemaName)) {
708
- return schemaName;
709
- }
710
- this.circularRefTracker.add(schemaName);
711
- this.#onRef?.(
712
- schemaName,
713
- this.handle(followRef(this.#spec, $ref), required)
714
- );
715
- this.circularRefTracker.delete(schemaName);
716
- return schemaName;
717
- }
718
- allOf(schemas) {
719
- const allOfSchemas = schemas.map((sub) => this.handle(sub, true));
720
- if (allOfSchemas.length === 0) {
721
- return `z.unknown()`;
722
- }
723
- if (allOfSchemas.length === 1) {
724
- return allOfSchemas[0];
725
- }
726
- return this.#toIntersection(allOfSchemas);
727
- }
728
- #toIntersection(schemas) {
729
- const [left, ...right] = schemas;
730
- if (!right.length) {
731
- return left;
732
- }
733
- return `z.intersection(${left}, ${this.#toIntersection(right)})`;
734
- }
735
- anyOf(schemas, required) {
736
- const anyOfSchemas = schemas.map((sub) => this.handle(sub, false));
737
- if (anyOfSchemas.length === 1) {
738
- return anyOfSchemas[0];
739
- }
740
- return anyOfSchemas.length > 1 ? `z.union([${anyOfSchemas.join(", ")}])${appendOptional2(required)}` : (
741
- // Handle an invalid anyOf with one schema
742
- anyOfSchemas[0]
743
- );
744
- }
745
- oneOf(schemas, required) {
746
- const oneOfSchemas = schemas.map((sub) => {
747
- if (isRef(sub)) {
748
- const { model } = parseRef(sub.$ref);
749
- if (this.circularRefTracker.has(model)) {
750
- return model;
751
- }
752
- }
753
- return this.handle(sub, true);
754
- });
755
- if (oneOfSchemas.length === 1) {
756
- return oneOfSchemas[0];
757
- }
758
- return oneOfSchemas.length > 1 ? `z.union([${oneOfSchemas.join(", ")}])${appendOptional2(required)}` : (
759
- // Handle an invalid oneOf with one schema
760
- oneOfSchemas[0]
761
- );
762
- }
763
- enum(values, required) {
764
- const enumVals = values.map((val) => JSON.stringify(val)).join(", ");
765
- if (values.length === 1) {
766
- return `z.literal(${enumVals})${appendOptional2(required)}`;
767
- }
768
- return `z.enum([${enumVals}])${appendOptional2(required)}`;
769
- }
770
- /**
771
- * Handle a `string` schema with possible format keywords (JSON Schema).
772
- */
773
- string(schema) {
774
- let base = "z.string()";
775
- switch (schema.format) {
776
- case "date-time":
777
- case "datetime":
778
- base = "z.coerce.date()";
779
- break;
780
- case "date":
781
- base = "z.coerce.date() /* or z.string() if you want raw date strings */";
782
- break;
783
- case "time":
784
- base = "z.string() /* optionally add .regex(...) for HH:MM:SS format */";
785
- break;
786
- case "email":
787
- base = "z.string().email()";
788
- break;
789
- case "uuid":
790
- base = "z.string().uuid()";
791
- break;
792
- case "url":
793
- case "uri":
794
- base = "z.string().url()";
795
- break;
796
- case "ipv4":
797
- base = 'z.string().ip({version: "v4"})';
798
- break;
799
- case "ipv6":
800
- base = 'z.string().ip({version: "v6"})';
801
- break;
802
- case "phone":
803
- base = "z.string() /* or add .regex(...) for phone formats */";
804
- break;
805
- case "byte":
806
- case "binary":
807
- base = "z.instanceof(Blob) /* consider base64 check if needed */";
808
- break;
809
- case "int64":
810
- base = "z.string() /* or z.bigint() if your app can handle it */";
811
- break;
812
- default:
813
- break;
814
- }
815
- return base;
816
- }
817
- /**
818
- * Handle number/integer constraints from OpenAPI/JSON Schema.
819
- * In 3.1, exclusiveMinimum/Maximum hold the actual numeric threshold,
820
- * rather than a boolean toggling `minimum`/`maximum`.
821
- */
822
- number(schema) {
823
- let defaultValue = schema.default;
824
- let base = "z.number()";
825
- if (schema.format === "int64") {
826
- base = "z.bigint()";
827
- if (schema.default !== void 0) {
828
- defaultValue = `BigInt(${schema.default})`;
829
- }
830
- }
831
- if (schema.format === "int32") {
832
- base += ".int()";
833
- }
834
- if (typeof schema.exclusiveMinimum === "number") {
835
- base += `.gt(${schema.exclusiveMinimum})`;
836
- }
837
- if (typeof schema.exclusiveMaximum === "number") {
838
- base += `.lt(${schema.exclusiveMaximum})`;
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;
839
618
  }
840
- if (typeof schema.minimum === "number") {
841
- base += schema.format === "int64" ? `.min(BigInt(${schema.minimum}))` : `.min(${schema.minimum})`;
619
+ const schema = securitySchemes[name];
620
+ if (isRef3(schema)) {
621
+ throw new Error(`Ref security schemas are not supported`);
842
622
  }
843
- if (typeof schema.maximum === "number") {
844
- base += schema.format === "int64" ? `.max(BigInt(${schema.maximum}))` : `.max(${schema.maximum})`;
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;
845
630
  }
846
- if (typeof schema.multipleOf === "number") {
847
- base += `.refine((val) => Number.isInteger(val / ${schema.multipleOf}), "Must be a multiple of ${schema.multipleOf}")`;
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;
848
643
  }
849
- return { base, defaultValue };
850
644
  }
851
- handle(schema, required) {
852
- if (isRef(schema)) {
853
- return this.ref(schema.$ref, required);
854
- }
855
- if (schema.allOf && Array.isArray(schema.allOf)) {
856
- return this.allOf(schema.allOf ?? []);
857
- }
858
- if (schema.anyOf && Array.isArray(schema.anyOf)) {
859
- return this.anyOf(schema.anyOf ?? [], required);
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
+ }
860
662
  }
861
- if (schema.oneOf && Array.isArray(schema.oneOf) && schema.oneOf.length) {
862
- return this.oneOf(schema.oneOf ?? [], required);
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}'`;
863
670
  }
864
- if (schema.enum && Array.isArray(schema.enum)) {
865
- return this.enum(schema.enum, required);
671
+ if (it.namespaceImport) {
672
+ return `import * as ${it.namespaceImport} from '${it.moduleSpecifier}'`;
866
673
  }
867
- const types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : [];
868
- if (!types.length) {
869
- return `z.unknown()${appendOptional2(required)}`;
674
+ if (it.namedImports) {
675
+ return `import {${removeDuplicates(it.namedImports, (it2) => it2.name).map((n) => `${n.isTypeOnly ? "type" : ""} ${n.name}`).join(", ")}} from '${it.moduleSpecifier}'`;
870
676
  }
871
- if ("nullable" in schema && schema.nullable) {
872
- types.push("null");
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
+ }
873
695
  }
874
- if (types.length > 1) {
875
- const realTypes = types.filter((t) => t !== "null");
876
- if (realTypes.length === 1 && types.includes("null")) {
877
- return this.normal(realTypes[0], schema, required, true);
696
+ }
697
+ return output;
698
+ }
699
+
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
+ }
878
717
  }
879
- const subSchemas = types.map((t) => this.normal(t, schema, false));
880
- return `z.union([${subSchemas.join(", ")}])${appendOptional2(required)}`;
718
+ output.push(inputContent);
881
719
  }
882
- return this.normal(types[0], schema, required, false);
720
+ inputs[`inputs/${spinalcase(name)}.ts`] = [...imports, ...output].join("\n") + "\n";
883
721
  }
884
- };
885
- function appendOptional2(isRequired) {
886
- return isRequired ? "" : ".optional()";
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
+ };
887
743
  }
888
- function appendDefault(defaultValue) {
889
- return defaultValue !== void 0 ? `.default(${defaultValue})` : "";
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 };
890
821
  }
891
-
892
- // packages/typescript/src/lib/generator.ts
893
- var statusCdeToMessageMap = {
822
+ var statusCodeToResponseMap = {
823
+ "200": "Ok",
824
+ "201": "Created",
825
+ "202": "Accepted",
826
+ "204": "NoContent",
894
827
  "400": "BadRequest",
895
828
  "401": "Unauthorized",
896
829
  "402": "PaymentRequired",
@@ -909,18 +842,94 @@ var statusCdeToMessageMap = {
909
842
  "503": "ServiceUnavailable",
910
843
  "504": "GatewayTimeout"
911
844
  };
912
- var defaults = {
913
- target: "javascript",
914
- style: "github",
915
- operationId: (operation, path, method) => {
916
- if (operation.operationId) {
917
- return spinalcase2(operation.operationId);
918
- }
919
- return camelcase2(`${method} ${path.replace(/[\\/\\{\\}]/g, " ").trim()}`);
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
+ }
920
888
  }
921
- };
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
922
932
  function generateCode(config) {
923
- const commonSchemas = {};
924
933
  const commonZod = /* @__PURE__ */ new Map();
925
934
  const commonZodImports = [];
926
935
  const zodDeserialzer = new ZodDeserialzer(config.spec, (model, schema) => {
@@ -935,191 +944,244 @@ function generateCode(config) {
935
944
  });
936
945
  const groups = {};
937
946
  const outputs = {};
938
- for (const [path, pathItem] of Object.entries(config.spec.paths ?? {})) {
939
- const { parameters = [], ...methods2 } = pathItem;
940
- for (const [method, operation] of Object.entries(methods2)) {
941
- const formatOperationId = config.operationId ?? defaults.operationId;
942
- const operationName = formatOperationId(operation, path, method);
943
- console.log(`Processing ${method} ${path}`);
944
- const [groupName] = Array.isArray(operation.tags) ? operation.tags : ["unknown"];
945
- groups[groupName] ??= [];
946
- const inputs = {};
947
- const additionalProperties = [];
948
- for (const param of [...parameters, ...operation.parameters ?? []]) {
949
- if (isRef(param)) {
950
- throw new Error(`Found reference in parameter ${param.$ref}`);
951
- }
952
- if (!param.schema) {
953
- throw new Error(`Schema not found for parameter ${param.name}`);
954
- }
955
- inputs[param.name] = {
956
- in: param.in,
957
- schema: ""
958
- };
959
- additionalProperties.push(param);
947
+ const endpoints = {};
948
+ forEachOperation(config, (entry, operation) => {
949
+ console.log(`Processing ${entry.method} ${entry.path}`);
950
+ groups[entry.groupName] ??= [];
951
+ endpoints[entry.groupName] ??= [];
952
+ const inputs = {};
953
+ const additionalProperties = [];
954
+ for (const param of operation.parameters ?? []) {
955
+ if (isRef5(param)) {
956
+ throw new Error(`Found reference in parameter ${param.$ref}`);
960
957
  }
961
- const security2 = operation.security ?? [];
962
- const securitySchemes = config.spec.components?.securitySchemes ?? {};
963
- const securityOptions = securityToOptions(security2, securitySchemes);
964
- Object.assign(inputs, securityOptions);
965
- additionalProperties.push(
966
- ...Object.entries(securityOptions).map(
967
- ([name, value]) => ({
968
- name,
969
- required: false,
970
- schema: {
971
- type: "string"
972
- },
973
- in: value.in
974
- })
975
- )
976
- );
977
- const types = {};
978
- const shortContenTypeMap = {
979
- "application/json": "json",
980
- "application/x-www-form-urlencoded": "urlencoded",
981
- "multipart/form-data": "formdata",
982
- "application/xml": "xml",
983
- "text/plain": "text"
958
+ if (!param.schema) {
959
+ throw new Error(`Schema not found for parameter ${param.name}`);
960
+ }
961
+ inputs[param.name] = {
962
+ in: param.in,
963
+ schema: ""
984
964
  };
985
- let outgoingContentType;
986
- if (operation.requestBody && Object.keys(operation.requestBody).length) {
987
- const content = isRef(operation.requestBody) ? get2(followRef(config.spec, operation.requestBody.$ref), ["content"]) : operation.requestBody.content;
988
- for (const type in content) {
989
- const ctSchema = isRef(content[type].schema) ? followRef(config.spec, content[type].schema.$ref) : content[type].schema;
990
- if (!ctSchema) {
991
- console.warn(`Schema not found for ${type}`);
992
- continue;
993
- }
994
- const schema = merge({}, ctSchema, {
995
- required: additionalProperties.filter((p) => p.required).map((p) => p.name),
996
- properties: additionalProperties.reduce(
997
- (acc, p) => ({
998
- ...acc,
999
- [p.name]: p.schema
1000
- }),
1001
- {}
1002
- )
1003
- });
1004
- Object.assign(inputs, bodyInputs(config, ctSchema));
1005
- types[shortContenTypeMap[type]] = zodDeserialzer.handle(schema, true);
1006
- }
1007
- if (content["application/json"]) {
1008
- outgoingContentType = "json";
1009
- } else if (content["application/x-www-form-urlencoded"]) {
1010
- outgoingContentType = "urlencoded";
1011
- } else if (content["multipart/form-data"]) {
1012
- outgoingContentType = "formdata";
1013
- } else {
1014
- outgoingContentType = "json";
1015
- }
1016
- } else {
1017
- const properties = additionalProperties.reduce(
1018
- (acc, p) => ({
1019
- ...acc,
1020
- [p.name]: p.schema
1021
- }),
1022
- {}
1023
- );
1024
- types[shortContenTypeMap["application/json"]] = zodDeserialzer.handle(
1025
- {
1026
- type: "object",
1027
- required: additionalProperties.filter((p) => p.required).map((p) => p.name),
1028
- properties
965
+ additionalProperties.push(param);
966
+ }
967
+ const security2 = operation.security ?? [];
968
+ const securitySchemes = config.spec.components?.securitySchemes ?? {};
969
+ const securityOptions = securityToOptions(security2, securitySchemes);
970
+ Object.assign(inputs, securityOptions);
971
+ additionalProperties.push(
972
+ ...Object.entries(securityOptions).map(
973
+ ([name, value]) => ({
974
+ name,
975
+ required: false,
976
+ schema: {
977
+ type: "string"
1029
978
  },
1030
- true
1031
- );
1032
- }
1033
- const errors = [];
1034
- operation.responses ??= {};
1035
- let foundResponse = false;
1036
- const output = [`import z from 'zod';`];
1037
- let parser = "buffered";
1038
- const responses = [];
1039
- const responsesImports = {};
1040
- for (const status in operation.responses) {
1041
- const response = isRef(
1042
- operation.responses[status]
1043
- ) ? followRef(
1044
- config.spec,
1045
- operation.responses[status].$ref
1046
- ) : operation.responses[status];
1047
- const statusCode = +status;
1048
- if (statusCode >= 400) {
1049
- errors.push(statusCdeToMessageMap[status] ?? "ProblematicResponse");
1050
- }
1051
- if (statusCode >= 200 && statusCode < 300) {
1052
- foundResponse = true;
1053
- const responseContent = get2(response, ["content"]);
1054
- const isJson = responseContent && responseContent["application/json"];
1055
- if ((response.headers ?? {})["Transfer-Encoding"]) {
1056
- parser = "chunked";
1057
- }
1058
- const typeScriptDeserialzer = new TypeScriptDeserialzer(
1059
- config.spec,
1060
- (schemaName, zod) => {
1061
- commonSchemas[schemaName] = zod;
1062
- responsesImports[schemaName] = {
1063
- defaultImport: void 0,
1064
- isTypeOnly: true,
1065
- moduleSpecifier: `../models/${config.makeImport(schemaName)}`,
1066
- namedImports: [{ isTypeOnly: true, name: schemaName }],
1067
- namespaceImport: void 0
1068
- };
1069
- }
1070
- );
1071
- const responseSchema = isJson ? typeScriptDeserialzer.handle(
1072
- responseContent["application/json"].schema,
1073
- true
1074
- ) : statusCode === 204 ? "void" : "ReadableStream";
1075
- responses.push(responseSchema);
979
+ in: value.in
980
+ })
981
+ )
982
+ );
983
+ const schemas = {};
984
+ const shortContenTypeMap = {
985
+ "application/json": "json",
986
+ "application/x-www-form-urlencoded": "urlencoded",
987
+ "multipart/form-data": "formdata",
988
+ "application/xml": "xml",
989
+ "text/plain": "text"
990
+ };
991
+ let outgoingContentType;
992
+ if (operation.requestBody && Object.keys(operation.requestBody).length) {
993
+ const content = isRef5(operation.requestBody) ? get2(followRef4(config.spec, operation.requestBody.$ref), ["content"]) : operation.requestBody.content;
994
+ for (const type in content) {
995
+ const ctSchema = isRef5(content[type].schema) ? followRef4(config.spec, content[type].schema.$ref) : content[type].schema;
996
+ if (!ctSchema) {
997
+ console.warn(`Schema not found for ${type}`);
998
+ continue;
1076
999
  }
1000
+ const schema = merge({}, ctSchema, {
1001
+ required: additionalProperties.filter((p) => p.required).map((p) => p.name),
1002
+ properties: additionalProperties.reduce(
1003
+ (acc, p) => ({
1004
+ ...acc,
1005
+ [p.name]: p.schema
1006
+ }),
1007
+ {}
1008
+ )
1009
+ });
1010
+ Object.assign(inputs, bodyInputs(config, ctSchema));
1011
+ schemas[shortContenTypeMap[type]] = zodDeserialzer.handle(schema, true);
1077
1012
  }
1078
- if (responses.length > 1) {
1079
- output.push(
1080
- `export type ${pascalcase(operationName + " output")} = ${removeDuplicates3(
1081
- responses,
1082
- (it) => it
1083
- ).join(" | ")};`
1084
- );
1013
+ if (content["application/json"]) {
1014
+ outgoingContentType = "json";
1015
+ } else if (content["application/x-www-form-urlencoded"]) {
1016
+ outgoingContentType = "urlencoded";
1017
+ } else if (content["multipart/form-data"]) {
1018
+ outgoingContentType = "formdata";
1085
1019
  } else {
1086
- output.push(
1087
- `export type ${pascalcase(operationName + " output")} = ${responses[0]};`
1088
- );
1020
+ outgoingContentType = "json";
1089
1021
  }
1090
- output.push(
1091
- ...useImports(output.join(""), Object.values(responsesImports))
1022
+ } else {
1023
+ const properties = additionalProperties.reduce(
1024
+ (acc, p) => ({
1025
+ ...acc,
1026
+ [p.name]: p.schema
1027
+ }),
1028
+ {}
1092
1029
  );
1093
- if (!foundResponse) {
1094
- output.push(
1095
- `export type ${pascalcase(operationName + " output")} = void`
1096
- );
1097
- }
1098
- outputs[`${spinalcase2(operationName)}.ts`] = output.join("\n");
1099
- groups[groupName].push({
1100
- name: operationName,
1101
- type: "http",
1102
- inputs,
1103
- errors: errors.length ? errors : ["ServerError"],
1030
+ schemas[shortContenTypeMap["application/json"]] = zodDeserialzer.handle(
1031
+ {
1032
+ type: "object",
1033
+ required: additionalProperties.filter((p) => p.required).map((p) => p.name),
1034
+ properties
1035
+ },
1036
+ true
1037
+ );
1038
+ }
1039
+ const endpoint = toEndpoint(
1040
+ entry.groupName,
1041
+ config.spec,
1042
+ operation,
1043
+ {
1104
1044
  outgoingContentType,
1105
- schemas: types,
1106
- parser,
1107
- formatOutput: () => ({
1108
- import: pascalcase(operationName + " output"),
1109
- use: pascalcase(operationName + " output")
1045
+ name: entry.name,
1046
+ type: "http",
1047
+ trigger: entry,
1048
+ schemas,
1049
+ inputs
1050
+ },
1051
+ { makeImport: config.makeImport }
1052
+ );
1053
+ const output = [`import z from 'zod';`];
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) {
1059
+ output.push(
1060
+ ...responses.map((it) => `export type ${it.name} = ${it.schema};`)
1061
+ );
1062
+ } else {
1063
+ output.push(`export type ${pascalcase2(entry.name + " output")} = void;`);
1064
+ }
1065
+ output.unshift(...useImports(output.join(""), ...responsesImports));
1066
+ outputs[`${spinalcase2(entry.name)}.ts`] = output.join("\n");
1067
+ endpoints[entry.groupName].push(endpoint);
1068
+ groups[entry.groupName].push({
1069
+ name: entry.name,
1070
+ type: "http",
1071
+ inputs,
1072
+ outgoingContentType,
1073
+ schemas,
1074
+ trigger: entry
1075
+ });
1076
+ });
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
+ )
1110
1087
  }),
1111
- trigger: {
1112
- path,
1113
- method
1114
- }
1115
- });
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
+ )
1116
1179
  }
1117
- }
1118
- return { groups, commonSchemas, commonZod, outputs };
1180
+ };
1119
1181
  }
1120
1182
  function toProps(spec, schemaOrRef, aggregator = []) {
1121
- if (isRef(schemaOrRef)) {
1122
- const schema = followRef(spec, schemaOrRef.$ref);
1183
+ if (isRef5(schemaOrRef)) {
1184
+ const schema = followRef4(spec, schemaOrRef.$ref);
1123
1185
  return toProps(spec, schema, aggregator);
1124
1186
  } else if (schemaOrRef.type === "object") {
1125
1187
  for (const [name] of Object.entries(schemaOrRef.properties ?? {})) {
@@ -1164,22 +1226,22 @@ function bodyInputs(config, ctSchema) {
1164
1226
  }
1165
1227
 
1166
1228
  // packages/typescript/src/lib/http/interceptors.txt
1167
- 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";
1168
1230
 
1169
1231
  // packages/typescript/src/lib/http/parse-response.txt
1170
- 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';
1171
1233
 
1172
1234
  // packages/typescript/src/lib/http/parser.txt
1173
- 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';
1174
1236
 
1175
1237
  // packages/typescript/src/lib/http/request.txt
1176
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";
1177
1239
 
1178
1240
  // packages/typescript/src/lib/http/response.txt
1179
- 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";
1180
1242
 
1181
1243
  // packages/typescript/src/lib/http/send-request.txt
1182
- 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";
1183
1245
 
1184
1246
  // packages/typescript/src/lib/generate.ts
1185
1247
  function security(spec) {
@@ -1207,21 +1269,14 @@ async function generate(spec, settings) {
1207
1269
  const makeImport = (moduleSpecifier) => {
1208
1270
  return settings.useTsExtension ? `${moduleSpecifier}.ts` : moduleSpecifier;
1209
1271
  };
1210
- const { commonSchemas, groups, outputs, commonZod } = generateCode({
1272
+ const { commonSchemas, endpoints, groups, outputs, commonZod, clientFiles } = generateCode({
1211
1273
  spec,
1212
1274
  style: "github",
1213
- target: "javascript",
1214
1275
  makeImport
1215
1276
  });
1216
- const output = settings.mode === "full" ? join(settings.output, "src") : settings.output;
1277
+ const output = settings.mode === "full" ? join2(settings.output, "src") : settings.output;
1217
1278
  const options = security(spec);
1218
- const clientFiles = generateSDK({
1219
- name: settings.name || "Client",
1220
- operations: groups,
1221
- servers: spec.servers?.map((server) => server.url) || [],
1222
- options,
1223
- makeImport
1224
- });
1279
+ const clientName = settings.name || "Client";
1225
1280
  const inputFiles = generateInputs(groups, commonZod, makeImport);
1226
1281
  await writeFiles(output, {
1227
1282
  "outputs/.gitkeep": "",
@@ -1229,24 +1284,33 @@ async function generate(spec, settings) {
1229
1284
  "models/.getkeep": ""
1230
1285
  // 'README.md': readme,
1231
1286
  });
1232
- await writeFiles(join(output, "http"), {
1287
+ await writeFiles(join2(output, "http"), {
1233
1288
  "interceptors.ts": interceptors_default,
1234
1289
  "parse-response.ts": parse_response_default,
1235
1290
  "send-request.ts": `import z from 'zod';
1236
1291
  import type { Interceptor } from './${makeImport("interceptors")}';
1237
- import { handleError } from './${makeImport("parse-response")}';
1292
+ import { buffered } from './${makeImport("parse-response")}';
1238
1293
  import { parse } from './${makeImport("parser")}';
1239
- import type { RequestConfig } from './request.ts';
1294
+ import type { RequestConfig } from './${makeImport("request")}';
1295
+ import { APIResponse } from './${makeImport("response")}';
1296
+
1240
1297
  ${send_request_default}`,
1241
1298
  "response.ts": response_default,
1242
1299
  "parser.ts": parser_default,
1243
1300
  "request.ts": request_default
1244
1301
  });
1245
- await writeFiles(join(output, "outputs"), outputs);
1302
+ await writeFiles(join2(output, "outputs"), outputs);
1246
1303
  const modelsImports = Object.entries(commonSchemas).map(([name]) => name);
1247
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
+ }),
1248
1311
  ...clientFiles,
1249
1312
  ...inputFiles,
1313
+ ...endpoints,
1250
1314
  ...Object.fromEntries(
1251
1315
  Object.entries(commonSchemas).map(([name, schema]) => [
1252
1316
  `models/${name}.ts`,
@@ -1261,29 +1325,36 @@ ${send_request_default}`,
1261
1325
  )
1262
1326
  });
1263
1327
  const folders = [
1264
- getFolderExports(join(output, "outputs"), settings.useTsExtension),
1328
+ getFolderExports(join2(output, "outputs"), settings.useTsExtension),
1265
1329
  getFolderExports(
1266
- join(output, "inputs"),
1330
+ join2(output, "inputs"),
1267
1331
  settings.useTsExtension,
1268
1332
  ["ts"],
1269
- (dirent) => dirent.isDirectory() && dirent.name === "schemas"
1333
+ (dirent) => dirent.isDirectory() && ["schemas"].includes(dirent.name)
1270
1334
  ),
1271
- 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
+ )
1272
1342
  ];
1273
1343
  if (modelsImports.length) {
1274
1344
  folders.push(
1275
- getFolderExports(join(output, "models"), settings.useTsExtension)
1345
+ getFolderExports(join2(output, "models"), settings.useTsExtension)
1276
1346
  );
1277
1347
  }
1278
- const [outputIndex, inputsIndex, httpIndex, modelsIndex] = await Promise.all(folders);
1348
+ const [outputIndex, inputsIndex, apiIndex, httpIndex, modelsIndex] = await Promise.all(folders);
1279
1349
  await writeFiles(output, {
1350
+ "api/index.ts": apiIndex,
1280
1351
  "outputs/index.ts": outputIndex,
1281
1352
  "inputs/index.ts": inputsIndex || null,
1282
1353
  "http/index.ts": httpIndex,
1283
1354
  ...modelsImports.length ? { "models/index.ts": modelsIndex } : {}
1284
1355
  });
1285
1356
  await writeFiles(output, {
1286
- "index.ts": await getFolderExports(output, settings.useTsExtension)
1357
+ "index.ts": await getFolderExports(output, settings.useTsExtension, ["ts"])
1287
1358
  });
1288
1359
  if (settings.mode === "full") {
1289
1360
  await writeFiles(settings.output, {
@@ -1291,7 +1362,7 @@ ${send_request_default}`,
1291
1362
  ignoreIfExists: true,
1292
1363
  content: JSON.stringify(
1293
1364
  {
1294
- name: "sdk",
1365
+ name: settings.name ? `@${spinalcase3(clientName.toLowerCase())}/sdk` : "sdk",
1295
1366
  type: "module",
1296
1367
  main: "./src/index.ts",
1297
1368
  dependencies: {