@sdk-it/typescript 0.12.6 → 0.12.8

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,89 +1,22 @@
1
1
  // packages/typescript/src/lib/generate.ts
2
- import { join as join2 } from "node:path";
2
+ import { join } from "node:path";
3
3
  import { npmRunPathEnv } from "npm-run-path";
4
-
5
- // packages/core/dist/index.js
6
- import ts, { TypeFlags, symbolName } from "typescript";
7
- import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
8
- import { dirname, isAbsolute, join } from "node:path";
9
- import debug from "debug";
10
- import ts2 from "typescript";
11
- var deriveSymbol = Symbol.for("serialize");
12
- var $types = Symbol.for("types");
13
- async function exist(file) {
14
- return stat(file).then(() => true).catch(() => false);
15
- }
16
- async function writeFiles(dir, contents) {
17
- return Promise.all(
18
- Object.entries(contents).map(async ([file, content]) => {
19
- const filePath = isAbsolute(file) ? file : join(dir, file);
20
- await mkdir(dirname(filePath), { recursive: true });
21
- if (typeof content === "string") {
22
- await writeFile(filePath, content, "utf-8");
23
- } else {
24
- if (content.ignoreIfExists) {
25
- if (!await exist(filePath)) {
26
- await writeFile(filePath, content.content, "utf-8");
27
- }
28
- }
29
- }
30
- })
31
- );
32
- }
33
- async function getFolderExports(folder, extensions = ["ts"]) {
34
- const files = await readdir(folder, { withFileTypes: true });
35
- const exports = [];
36
- for (const file of files) {
37
- if (file.isDirectory()) {
38
- exports.push(`export * from './${file.name}/index.ts';`);
39
- } else if (file.name !== "index.ts" && extensions.includes(getExt(file.name))) {
40
- exports.push(`export * from './${file.name}';`);
41
- }
42
- }
43
- return exports.join("\n");
44
- }
45
- var getExt = (fileName) => {
46
- if (!fileName) {
47
- return "";
48
- }
49
- const lastDot = fileName.lastIndexOf(".");
50
- if (lastDot === -1) {
51
- return "";
52
- }
53
- const ext = fileName.slice(lastDot + 1).split("/").filter(Boolean).join("");
54
- if (ext === fileName) {
55
- return "";
56
- }
57
- return ext || "txt";
58
- };
59
- var methods = [
60
- "get",
61
- "post",
62
- "put",
63
- "patch",
64
- "delete",
65
- "trace",
66
- "head"
67
- ];
68
- var logger = debug("january:client");
69
- function removeDuplicates(data, accessor) {
70
- return [...new Map(data.map((x) => [accessor(x), x])).values()];
71
- }
72
- function toLitObject(obj, accessor = (value) => value) {
73
- return `{${Object.keys(obj).map((key) => `${key}: ${accessor(obj[key])}`).join(", ")}}`;
74
- }
4
+ import { getFolderExports, methods, writeFiles } from "@sdk-it/core";
75
5
 
76
6
  // packages/typescript/src/lib/generator.ts
77
7
  import { get as get2, merge } from "lodash-es";
78
- import { camelcase as camelcase2, pascalcase as pascalcase2, spinalcase as spinalcase2 } from "stringcase";
8
+ import { camelcase as camelcase2, pascalcase, spinalcase as spinalcase2 } from "stringcase";
79
9
 
80
10
  // packages/typescript/src/lib/utils.ts
81
11
  import { get } from "lodash-es";
12
+ import { removeDuplicates as removeDuplicates2 } from "@sdk-it/core";
82
13
 
83
14
  // packages/typescript/src/lib/sdk.ts
84
- import { camelcase, pascalcase, spinalcase } from "stringcase";
15
+ import { camelcase, spinalcase } from "stringcase";
16
+ import { removeDuplicates, toLitObject as toLitObject2 } from "@sdk-it/core";
85
17
 
86
18
  // packages/typescript/src/lib/client.ts
19
+ import { toLitObject } from "@sdk-it/core";
87
20
  var client_default = (spec) => {
88
21
  const optionsEntries = Object.entries(spec.options).map(
89
22
  ([key, value]) => [`'${key}'`, value]
@@ -122,8 +55,10 @@ ${spec.servers.length ? `export type Servers = typeof servers[number];` : ""}
122
55
  type ${spec.name}Options = z.infer<typeof optionsSchema>;
123
56
 
124
57
  export class ${spec.name} {
125
-
126
- constructor(public options: ${spec.name}Options) {}
58
+ public options: ${spec.name}Options
59
+ constructor(options: ${spec.name}Options) {
60
+ this.options = options;
61
+ }
127
62
 
128
63
  async request<E extends keyof Endpoints>(
129
64
  endpoint: E,
@@ -200,37 +135,63 @@ ${this.endpoints.join("\n")}
200
135
  }`;
201
136
  }
202
137
  };
203
- var StreamEmitter = class extends Emitter {
204
- complete() {
205
- return `${this.imports.join("\n")}
206
- export interface StreamEndpoints {
207
- ${this.endpoints.join("\n")}
208
- }`;
138
+ function generateInputs(operationsSet, commonZod) {
139
+ const commonImports = commonZod.keys().toArray();
140
+ const inputs = {};
141
+ for (const [name, operations] of Object.entries(operationsSet)) {
142
+ const output = [];
143
+ const imports = /* @__PURE__ */ new Set(['import { z } from "zod";']);
144
+ for (const operation of operations) {
145
+ const schemaName = camelcase(`${operation.name} schema`);
146
+ const schema = `export const ${schemaName} = ${Object.keys(operation.schemas).length === 1 ? Object.values(operation.schemas)[0] : toLitObject2(operation.schemas)};`;
147
+ const inputContent = schema;
148
+ for (const schema2 of commonImports) {
149
+ if (inputContent.includes(schema2)) {
150
+ imports.add(
151
+ `import { ${schema2} } from './schemas/${spinalcase(schema2)}.ts';`
152
+ );
153
+ }
154
+ }
155
+ output.push(inputContent);
156
+ }
157
+ inputs[`inputs/${spinalcase(name)}.ts`] = [...imports, ...output].join("\n") + "\n";
209
158
  }
210
- };
211
- function generateClientSdk(spec) {
159
+ const schemas = commonZod.entries().reduce((acc, [name, schema]) => {
160
+ const output = [`import { z } from 'zod';`];
161
+ const content = `export const ${name} = ${schema};`;
162
+ for (const schema2 of commonImports) {
163
+ const preciseMatch = new RegExp(`\\b${schema2}\\b`);
164
+ if (preciseMatch.test(content) && schema2 !== name) {
165
+ output.push(
166
+ `import { ${schema2} } from './${spinalcase(schema2)}.ts';`
167
+ );
168
+ }
169
+ }
170
+ output.push(content);
171
+ return [
172
+ [`inputs/schemas/${spinalcase(name)}.ts`, output.join("\n")],
173
+ ...acc
174
+ ];
175
+ }, []);
176
+ return {
177
+ ...Object.fromEntries(schemas),
178
+ ...inputs
179
+ };
180
+ }
181
+ function generateSDK(spec) {
212
182
  const emitter = new Emitter();
213
- const streamEmitter = new StreamEmitter();
214
- const schemas = {};
215
183
  const schemaEndpoint = new SchemaEndpoint();
216
184
  const errors = [];
217
185
  for (const [name, operations] of Object.entries(spec.operations)) {
218
- const featureSchemaFileName = camelcase(name);
219
- schemas[featureSchemaFileName] = [`import z from 'zod';`];
220
186
  emitter.addImport(
221
- `import * as ${featureSchemaFileName} from './inputs/${featureSchemaFileName}.ts';`
222
- );
223
- streamEmitter.addImport(
224
- `import * as ${featureSchemaFileName} from './inputs/${featureSchemaFileName}.ts';`
187
+ `import * as ${camelcase(name)} from './inputs/${spinalcase(name)}.ts';`
225
188
  );
226
189
  schemaEndpoint.addImport(
227
- `import * as ${featureSchemaFileName} from './inputs/${featureSchemaFileName}.ts';`
190
+ `import * as ${camelcase(name)} from './inputs/${spinalcase(name)}.ts';`
228
191
  );
229
192
  for (const operation of operations) {
230
193
  const schemaName = camelcase(`${operation.name} schema`);
231
- const schema = `export const ${schemaName} = ${Object.keys(operation.schemas).length === 1 ? Object.values(operation.schemas)[0] : toLitObject(operation.schemas)};`;
232
- schemas[featureSchemaFileName].push(schema);
233
- const schemaRef = `${featureSchemaFileName}.${schemaName}`;
194
+ const schemaRef = `${camelcase(name)}.${schemaName}`;
234
195
  const output = operation.formatOutput();
235
196
  const inputHeaders = [];
236
197
  const inputQuery = [];
@@ -255,51 +216,25 @@ function generateClientSdk(spec) {
255
216
  );
256
217
  }
257
218
  }
258
- if (operation.type === "sse") {
259
- const input = `z.infer<typeof ${schemaRef}>`;
260
- const endpoint = `${operation.trigger.method.toUpperCase()} ${operation.trigger.path}`;
261
- streamEmitter.addImport(
262
- `import type {${pascalcase(operation.name)}} from './outputs/${spinalcase(operation.name)}.ts';`
263
- );
264
- streamEmitter.addEndpoint(
219
+ emitter.addImport(
220
+ `import type {${output.import}} from './outputs/${spinalcase(operation.name)}.ts';`
221
+ );
222
+ errors.push(...operation.errors ?? []);
223
+ const addTypeParser = Object.keys(operation.schemas).length > 1;
224
+ for (const type in operation.schemas ?? {}) {
225
+ let typePrefix = "";
226
+ if (addTypeParser && type !== "json") {
227
+ typePrefix = `${type} `;
228
+ }
229
+ const input = `typeof ${schemaRef}${addTypeParser ? `.${type}` : ""}`;
230
+ const endpoint = `${typePrefix}${operation.trigger.method.toUpperCase()} ${operation.trigger.path}`;
231
+ emitter.addEndpoint(
265
232
  endpoint,
266
- `{input: ${input}, output: ${output.use}}`
233
+ `{input: z.infer<${input}>; output: ${output.use}; error: ${(operation.errors ?? ["ServerError"]).concat(`ParseError<${input}>`).join("|")}}`
267
234
  );
268
235
  schemaEndpoint.addEndpoint(
269
236
  endpoint,
270
237
  `{
271
- schema: ${schemaRef},
272
- toRequest(input: StreamEndpoints['${endpoint}']['input']) {
273
- const endpoint = '${endpoint}';
274
- return toRequest(endpoint, json(input, {
275
- inputHeaders: [${inputHeaders}],
276
- inputQuery: [${inputQuery}],
277
- inputBody: [${inputBody}],
278
- inputParams: [${inputParams}],
279
- }));
280
- },
281
- }`
282
- );
283
- } else {
284
- emitter.addImport(
285
- `import type {${output.import}} from './outputs/${spinalcase(operation.name)}.ts';`
286
- );
287
- errors.push(...operation.errors ?? []);
288
- const addTypeParser = Object.keys(operation.schemas).length > 1;
289
- for (const type in operation.schemas ?? {}) {
290
- let typePrefix = "";
291
- if (addTypeParser && type !== "json") {
292
- typePrefix = `${type} `;
293
- }
294
- const input = `typeof ${schemaRef}${addTypeParser ? `.${type}` : ""}`;
295
- const endpoint = `${typePrefix}${operation.trigger.method.toUpperCase()} ${operation.trigger.path}`;
296
- emitter.addEndpoint(
297
- endpoint,
298
- `{input: z.infer<${input}>; output: ${output.use}; error: ${(operation.errors ?? ["ServerError"]).concat(`ParseError<${input}>`).join("|")}}`
299
- );
300
- schemaEndpoint.addEndpoint(
301
- endpoint,
302
- `{
303
238
  schema: ${schemaRef}${addTypeParser ? `.${type}` : ""},
304
239
  toRequest(input: Endpoints['${endpoint}']['input']) {
305
240
  const endpoint = '${endpoint}';
@@ -311,8 +246,7 @@ function generateClientSdk(spec) {
311
246
  }));
312
247
  },
313
248
  }`
314
- );
315
- }
249
+ );
316
250
  }
317
251
  }
318
252
  }
@@ -320,19 +254,6 @@ function generateClientSdk(spec) {
320
254
  `import type { ${removeDuplicates(errors, (it) => it).join(", ")} } from './http/response.ts';`
321
255
  );
322
256
  return {
323
- ...Object.fromEntries(
324
- Object.entries(schemas).map(([key, value]) => [
325
- `inputs/${key}.ts`,
326
- [
327
- // schemasImports.length
328
- // ? `import {${removeDuplicates(schemasImports, (it) => it)}} from '../zod';`
329
- // : '',
330
- spec.commonZod ? 'import * as commonZod from "../zod.ts";' : "",
331
- ...value
332
- ].map((it) => it.trim()).filter(Boolean).join("\n") + "\n"
333
- // add a newline at the end
334
- ])
335
- ),
336
257
  "client.ts": client_default(spec),
337
258
  "schemas.ts": schemaEndpoint.complete(),
338
259
  "endpoints.ts": emitter.complete()
@@ -416,11 +337,30 @@ function importsToString(...imports) {
416
337
  return `import * as ${it.namespaceImport} from '${it.moduleSpecifier}'`;
417
338
  }
418
339
  if (it.namedImports) {
419
- return `import {${removeDuplicates(it.namedImports, (it2) => it2.name).map((n) => `${n.isTypeOnly ? "type" : ""} ${n.name}`).join(", ")}} from '${it.moduleSpecifier}'`;
340
+ return `import {${removeDuplicates2(it.namedImports, (it2) => it2.name).map((n) => `${n.isTypeOnly ? "type" : ""} ${n.name}`).join(", ")}} from '${it.moduleSpecifier}'`;
420
341
  }
421
342
  throw new Error(`Invalid import ${JSON.stringify(it)}`);
422
343
  });
423
344
  }
345
+ function exclude2(list, exclude3) {
346
+ return list.filter((it) => !exclude3.includes(it));
347
+ }
348
+ function useImports(content, imports) {
349
+ const output = [];
350
+ for (const it of mergeImports(imports)) {
351
+ const singleImport = it.defaultImport ?? it.namespaceImport;
352
+ if (singleImport && content.includes(singleImport)) {
353
+ output.push(importsToString(it).join("\n"));
354
+ } else if (it.namedImports.length) {
355
+ for (const namedImport of it.namedImports) {
356
+ if (content.includes(namedImport.name)) {
357
+ output.push(importsToString(it).join("\n"));
358
+ }
359
+ }
360
+ }
361
+ }
362
+ return output;
363
+ }
424
364
 
425
365
  // packages/typescript/src/lib/emitters/interface.ts
426
366
  var TypeScriptDeserialzer = class {
@@ -431,6 +371,70 @@ var TypeScriptDeserialzer = class {
431
371
  this.#spec = spec;
432
372
  this.#onRef = onRef;
433
373
  }
374
+ #stringifyKey = (key) => {
375
+ const reservedWords = [
376
+ "constructor",
377
+ "prototype",
378
+ "break",
379
+ "case",
380
+ "catch",
381
+ "class",
382
+ "const",
383
+ "continue",
384
+ "debugger",
385
+ "default",
386
+ "delete",
387
+ "do",
388
+ "else",
389
+ "export",
390
+ "extends",
391
+ "false",
392
+ "finally",
393
+ "for",
394
+ "function",
395
+ "if",
396
+ "import",
397
+ "in",
398
+ "instanceof",
399
+ "new",
400
+ "null",
401
+ "return",
402
+ "super",
403
+ "switch",
404
+ "this",
405
+ "throw",
406
+ "true",
407
+ "try",
408
+ "typeof",
409
+ "var",
410
+ "void",
411
+ "while",
412
+ "with",
413
+ "yield"
414
+ ];
415
+ if (reservedWords.includes(key)) {
416
+ return `'${key}'`;
417
+ }
418
+ if (key.trim() === "") {
419
+ return `'${key}'`;
420
+ }
421
+ const firstChar = key.charAt(0);
422
+ const validFirstChar = firstChar >= "a" && firstChar <= "z" || firstChar >= "A" && firstChar <= "Z" || firstChar === "_" || firstChar === "$";
423
+ if (!validFirstChar) {
424
+ return `'${key.replace(/'/g, "\\'")}'`;
425
+ }
426
+ for (let i = 1; i < key.length; i++) {
427
+ const char = key.charAt(i);
428
+ const validChar = char >= "a" && char <= "z" || char >= "A" && char <= "Z" || char >= "0" && char <= "9" || char === "_" || char === "$";
429
+ if (!validChar) {
430
+ return `'${key.replace(/'/g, "\\'")}'`;
431
+ }
432
+ }
433
+ return key;
434
+ };
435
+ #stringifyKeyV2 = (value) => {
436
+ return `'${value}'`;
437
+ };
434
438
  /**
435
439
  * Handle objects (properties)
436
440
  */
@@ -439,7 +443,7 @@ var TypeScriptDeserialzer = class {
439
443
  const propEntries = Object.entries(properties).map(([key, propSchema]) => {
440
444
  const isRequired = (schema.required ?? []).includes(key);
441
445
  const tsType = this.handle(propSchema, isRequired);
442
- return `${key}: ${tsType}`;
446
+ return `${this.#stringifyKeyV2(key)}: ${tsType}`;
443
447
  });
444
448
  if (schema.additionalProperties) {
445
449
  if (typeof schema.additionalProperties === "object") {
@@ -689,10 +693,16 @@ var ZodDeserialzer = class {
689
693
  }
690
694
  allOf(schemas) {
691
695
  const allOfSchemas = schemas.map((sub) => this.handle(sub, true));
696
+ if (allOfSchemas.length === 1) {
697
+ return allOfSchemas[0];
698
+ }
692
699
  return allOfSchemas.length ? `z.intersection(${allOfSchemas.join(", ")})` : allOfSchemas[0];
693
700
  }
694
701
  anyOf(schemas, required) {
695
702
  const anyOfSchemas = schemas.map((sub) => this.handle(sub, false));
703
+ if (anyOfSchemas.length === 1) {
704
+ return anyOfSchemas[0];
705
+ }
696
706
  return anyOfSchemas.length > 1 ? `z.union([${anyOfSchemas.join(", ")}])${appendOptional2(required)}` : (
697
707
  // Handle an invalid anyOf with one schema
698
708
  anyOfSchemas[0]
@@ -708,6 +718,9 @@ var ZodDeserialzer = class {
708
718
  }
709
719
  return this.handle(sub, false);
710
720
  });
721
+ if (oneOfSchemas.length === 1) {
722
+ return oneOfSchemas[0];
723
+ }
711
724
  return oneOfSchemas.length > 1 ? `z.union([${oneOfSchemas.join(", ")}])${appendOptional2(required)}` : (
712
725
  // Handle an invalid oneOf with one schema
713
726
  oneOfSchemas[0]
@@ -869,7 +882,18 @@ var defaults = {
869
882
  };
870
883
  function generateCode(config) {
871
884
  const commonSchemas = {};
872
- const zodDeserialzer = new ZodDeserialzer(config.spec);
885
+ const commonZod = /* @__PURE__ */ new Map();
886
+ const commonZodImports = [];
887
+ const zodDeserialzer = new ZodDeserialzer(config.spec, (model, schema) => {
888
+ commonZod.set(model, schema);
889
+ commonZodImports.push({
890
+ defaultImport: void 0,
891
+ isTypeOnly: true,
892
+ moduleSpecifier: `./${model}.ts`,
893
+ namedImports: [{ isTypeOnly: true, name: model }],
894
+ namespaceImport: void 0
895
+ });
896
+ });
873
897
  const groups = {};
874
898
  const outputs = {};
875
899
  for (const [path, methods2] of Object.entries(config.spec.paths ?? {})) {
@@ -1003,26 +1027,15 @@ function generateCode(config) {
1003
1027
  responseContent["application/json"].schema,
1004
1028
  true
1005
1029
  ) : "ReadableStream";
1006
- for (const it of mergeImports(imports)) {
1007
- const singleImport = it.defaultImport ?? it.namespaceImport;
1008
- if (singleImport && responseSchema.includes(singleImport)) {
1009
- output.push(importsToString(it).join("\n"));
1010
- } else if (it.namedImports.length) {
1011
- for (const namedImport of it.namedImports) {
1012
- if (responseSchema.includes(namedImport.name)) {
1013
- output.push(importsToString(it).join("\n"));
1014
- }
1015
- }
1016
- }
1017
- }
1030
+ output.push(...useImports(responseSchema, imports));
1018
1031
  output.push(
1019
- `export type ${pascalcase2(operationName + " output")} = ${responseSchema}`
1032
+ `export type ${pascalcase(operationName + " output")} = ${responseSchema}`
1020
1033
  );
1021
1034
  }
1022
1035
  }
1023
1036
  if (!foundResponse) {
1024
1037
  output.push(
1025
- `export type ${pascalcase2(operationName + " output")} = void`
1038
+ `export type ${pascalcase(operationName + " output")} = void`
1026
1039
  );
1027
1040
  }
1028
1041
  outputs[`${spinalcase2(operationName)}.ts`] = output.join("\n");
@@ -1034,8 +1047,8 @@ function generateCode(config) {
1034
1047
  contentType,
1035
1048
  schemas: types,
1036
1049
  formatOutput: () => ({
1037
- import: pascalcase2(operationName + " output"),
1038
- use: pascalcase2(operationName + " output")
1050
+ import: pascalcase(operationName + " output"),
1051
+ use: pascalcase(operationName + " output")
1039
1052
  }),
1040
1053
  trigger: {
1041
1054
  path,
@@ -1044,7 +1057,7 @@ function generateCode(config) {
1044
1057
  });
1045
1058
  }
1046
1059
  }
1047
- return { groups, commonSchemas, outputs };
1060
+ return { groups, commonSchemas, commonZod, outputs };
1048
1061
  }
1049
1062
 
1050
1063
  // packages/typescript/src/lib/http/interceptors.txt
@@ -1057,7 +1070,7 @@ var parse_response_default = "import { parse } from 'fast-content-type-parse';\n
1057
1070
  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";
1058
1071
 
1059
1072
  // packages/typescript/src/lib/http/request.txt
1060
- var request_default = "export type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\nexport type ContentType = 'xml' | 'json' | 'urlencoded' | 'multipart';\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 constructor(\n protected input: Input,\n protected props: Props,\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): Request {\n const [method, path] = endpoint.split(' ');\n const pathVariable = template(path, input.params);\n\n const url = createUrl(pathVariable, input.query);\n return new Request(url, {\n method: method,\n headers: input.headers,\n body: method === 'GET' ? undefined : input.body,\n });\n}\n";
1073
+ var request_default = "export type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\nexport type ContentType = 'xml' | 'json' | 'urlencoded' | 'multipart';\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): Request {\n const [method, path] = endpoint.split(' ');\n const pathVariable = template(path, input.params);\n\n const url = createUrl(pathVariable, input.query);\n return new Request(url, {\n method: method,\n headers: input.headers,\n body: method === 'GET' ? undefined : input.body,\n });\n}\n";
1061
1074
 
1062
1075
  // packages/typescript/src/lib/http/response.txt
1063
1076
  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";
@@ -1087,26 +1100,27 @@ function security(spec) {
1087
1100
  return options;
1088
1101
  }
1089
1102
  async function generate(spec, settings) {
1090
- const { commonSchemas, groups, outputs } = generateCode({
1103
+ const { commonSchemas, groups, outputs, commonZod } = generateCode({
1091
1104
  spec,
1092
1105
  style: "github",
1093
1106
  target: "javascript"
1094
1107
  });
1095
- const output = settings.mode === "full" ? join2(settings.output, "src") : settings.output;
1108
+ const output = settings.mode === "full" ? join(settings.output, "src") : settings.output;
1096
1109
  const options = security(spec);
1097
- const clientFiles = generateClientSdk({
1110
+ const clientFiles = generateSDK({
1098
1111
  name: settings.name || "Client",
1099
1112
  operations: groups,
1100
1113
  servers: spec.servers?.map((server) => server.url) || [],
1101
1114
  options
1102
1115
  });
1116
+ const inputFiles = generateInputs(groups, commonZod);
1103
1117
  await writeFiles(output, {
1104
- "outputs/index.ts": "",
1105
- "inputs/index.ts": ""
1106
- // 'models/index.ts': '',
1118
+ "outputs/.gitkeep": "",
1119
+ "inputs/.gitkeep": "",
1120
+ "models/.getkeep": ""
1107
1121
  // 'README.md': readme,
1108
1122
  });
1109
- await writeFiles(join2(output, "http"), {
1123
+ await writeFiles(join(output, "http"), {
1110
1124
  "interceptors.ts": interceptors_default,
1111
1125
  "parse-response.ts": parse_response_default,
1112
1126
  "send-request.ts": send_request_default,
@@ -1114,16 +1128,17 @@ async function generate(spec, settings) {
1114
1128
  "parser.ts": parser_default,
1115
1129
  "request.ts": request_default
1116
1130
  });
1117
- await writeFiles(join2(output, "outputs"), outputs);
1118
- const imports = Object.entries(commonSchemas).map(([name]) => name);
1131
+ await writeFiles(join(output, "outputs"), outputs);
1132
+ const modelsImports = Object.entries(commonSchemas).map(([name]) => name);
1119
1133
  await writeFiles(output, {
1120
1134
  ...clientFiles,
1135
+ ...inputFiles,
1121
1136
  ...Object.fromEntries(
1122
1137
  Object.entries(commonSchemas).map(([name, schema]) => [
1123
1138
  `models/${name}.ts`,
1124
1139
  [
1125
1140
  `import { z } from 'zod';`,
1126
- ...exclude(imports, [name]).map(
1141
+ ...exclude2(modelsImports, [name]).map(
1127
1142
  (it) => `import type { ${it} } from './${it}.ts';`
1128
1143
  ),
1129
1144
  `export type ${name} = ${schema};`
@@ -1133,26 +1148,59 @@ async function generate(spec, settings) {
1133
1148
  });
1134
1149
  const folders = [
1135
1150
  getFolderExports(output),
1136
- getFolderExports(join2(output, "outputs")),
1137
- getFolderExports(join2(output, "inputs")),
1138
- getFolderExports(join2(output, "http"))
1151
+ getFolderExports(join(output, "outputs")),
1152
+ getFolderExports(
1153
+ join(output, "inputs"),
1154
+ ["ts"],
1155
+ (dirent) => dirent.isDirectory() && dirent.name === "schemas"
1156
+ ),
1157
+ getFolderExports(join(output, "http"))
1139
1158
  ];
1140
- if (imports.length) {
1141
- folders.push(getFolderExports(join2(output, "models")));
1159
+ if (modelsImports.length) {
1160
+ folders.push(getFolderExports(join(output, "models")));
1142
1161
  }
1143
1162
  const [index, outputIndex, inputsIndex, httpIndex, modelsIndex] = await Promise.all(folders);
1144
1163
  await writeFiles(output, {
1145
1164
  "index.ts": index,
1146
1165
  "outputs/index.ts": outputIndex,
1147
- "inputs/index.ts": inputsIndex,
1166
+ "inputs/index.ts": inputsIndex || null,
1148
1167
  "http/index.ts": httpIndex,
1149
- ...imports.length ? { "models/index.ts": modelsIndex } : {}
1168
+ ...modelsImports.length ? { "models/index.ts": modelsIndex } : {}
1150
1169
  });
1151
1170
  if (settings.mode === "full") {
1152
1171
  await writeFiles(settings.output, {
1153
1172
  "package.json": {
1154
1173
  ignoreIfExists: true,
1155
- content: `{"type":"module","main":"./src/index.ts","dependencies":{"fast-content-type-parse":"^3.0.0"}}`
1174
+ content: JSON.stringify(
1175
+ {
1176
+ type: "module",
1177
+ main: "./src/index.ts",
1178
+ dependencies: { "fast-content-type-parse": "^3.0.0" }
1179
+ },
1180
+ null,
1181
+ 2
1182
+ )
1183
+ },
1184
+ "tsconfig.json": {
1185
+ ignoreIfExists: false,
1186
+ content: JSON.stringify(
1187
+ {
1188
+ compilerOptions: {
1189
+ skipLibCheck: true,
1190
+ skipDefaultLibCheck: true,
1191
+ target: "ESNext",
1192
+ module: "ESNext",
1193
+ noEmit: true,
1194
+ allowImportingTsExtensions: true,
1195
+ verbatimModuleSyntax: true,
1196
+ baseUrl: ".",
1197
+ moduleResolution: "bundler"
1198
+ },
1199
+ include: ["**/*.ts"]
1200
+ },
1201
+ null,
1202
+ 2
1203
+ )
1156
1204
  }
1157
1205
  });
1158
1206
  }
@@ -1161,9 +1209,6 @@ async function generate(spec, settings) {
1161
1209
  env: npmRunPathEnv()
1162
1210
  });
1163
1211
  }
1164
- function exclude(list, exclude2) {
1165
- return list.filter((it) => !exclude2.includes(it));
1166
- }
1167
1212
 
1168
1213
  // packages/typescript/src/lib/watcher.ts
1169
1214
  import { watch as nodeWatch } from "node:fs/promises";