@sdk-it/typescript 0.12.6 → 0.12.7

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
@@ -14,8 +14,11 @@ async function exist(file) {
14
14
  return stat(file).then(() => true).catch(() => false);
15
15
  }
16
16
  async function writeFiles(dir, contents) {
17
- return Promise.all(
17
+ await Promise.all(
18
18
  Object.entries(contents).map(async ([file, content]) => {
19
+ if (content === null) {
20
+ return;
21
+ }
19
22
  const filePath = isAbsolute(file) ? file : join(dir, file);
20
23
  await mkdir(dirname(filePath), { recursive: true });
21
24
  if (typeof content === "string") {
@@ -25,15 +28,20 @@ async function writeFiles(dir, contents) {
25
28
  if (!await exist(filePath)) {
26
29
  await writeFile(filePath, content.content, "utf-8");
27
30
  }
31
+ } else {
32
+ await writeFile(filePath, content.content, "utf-8");
28
33
  }
29
34
  }
30
35
  })
31
36
  );
32
37
  }
33
- async function getFolderExports(folder, extensions = ["ts"]) {
38
+ async function getFolderExports(folder, extensions = ["ts"], ignore = () => false) {
34
39
  const files = await readdir(folder, { withFileTypes: true });
35
40
  const exports = [];
36
41
  for (const file of files) {
42
+ if (ignore(file)) {
43
+ continue;
44
+ }
37
45
  if (file.isDirectory()) {
38
46
  exports.push(`export * from './${file.name}/index.ts';`);
39
47
  } else if (file.name !== "index.ts" && extensions.includes(getExt(file.name))) {
@@ -75,13 +83,13 @@ function toLitObject(obj, accessor = (value) => value) {
75
83
 
76
84
  // packages/typescript/src/lib/generator.ts
77
85
  import { get as get2, merge } from "lodash-es";
78
- import { camelcase as camelcase2, pascalcase as pascalcase2, spinalcase as spinalcase2 } from "stringcase";
86
+ import { camelcase as camelcase2, pascalcase, spinalcase as spinalcase2 } from "stringcase";
79
87
 
80
88
  // packages/typescript/src/lib/utils.ts
81
89
  import { get } from "lodash-es";
82
90
 
83
91
  // packages/typescript/src/lib/sdk.ts
84
- import { camelcase, pascalcase, spinalcase } from "stringcase";
92
+ import { camelcase, spinalcase } from "stringcase";
85
93
 
86
94
  // packages/typescript/src/lib/client.ts
87
95
  var client_default = (spec) => {
@@ -122,8 +130,10 @@ ${spec.servers.length ? `export type Servers = typeof servers[number];` : ""}
122
130
  type ${spec.name}Options = z.infer<typeof optionsSchema>;
123
131
 
124
132
  export class ${spec.name} {
125
-
126
- constructor(public options: ${spec.name}Options) {}
133
+ public options: ${spec.name}Options
134
+ constructor(options: ${spec.name}Options) {
135
+ this.options = options;
136
+ }
127
137
 
128
138
  async request<E extends keyof Endpoints>(
129
139
  endpoint: E,
@@ -200,37 +210,63 @@ ${this.endpoints.join("\n")}
200
210
  }`;
201
211
  }
202
212
  };
203
- var StreamEmitter = class extends Emitter {
204
- complete() {
205
- return `${this.imports.join("\n")}
206
- export interface StreamEndpoints {
207
- ${this.endpoints.join("\n")}
208
- }`;
213
+ function generateInputs(operationsSet, commonZod) {
214
+ const commonImports = commonZod.keys().toArray();
215
+ const inputs = {};
216
+ for (const [name, operations] of Object.entries(operationsSet)) {
217
+ const output = [];
218
+ const imports = /* @__PURE__ */ new Set(['import { z } from "zod";']);
219
+ for (const operation of operations) {
220
+ const schemaName = camelcase(`${operation.name} schema`);
221
+ const schema = `export const ${schemaName} = ${Object.keys(operation.schemas).length === 1 ? Object.values(operation.schemas)[0] : toLitObject(operation.schemas)};`;
222
+ const inputContent = schema;
223
+ for (const schema2 of commonImports) {
224
+ if (inputContent.includes(schema2)) {
225
+ imports.add(
226
+ `import { ${schema2} } from './schemas/${spinalcase(schema2)}.ts';`
227
+ );
228
+ }
229
+ }
230
+ output.push(inputContent);
231
+ }
232
+ inputs[`inputs/${spinalcase(name)}.ts`] = [...imports, ...output].join("\n") + "\n";
209
233
  }
210
- };
211
- function generateClientSdk(spec) {
234
+ const schemas = commonZod.entries().reduce((acc, [name, schema]) => {
235
+ const output = [`import { z } from 'zod';`];
236
+ const content = `export const ${name} = ${schema};`;
237
+ for (const schema2 of commonImports) {
238
+ const preciseMatch = new RegExp(`\\b${schema2}\\b`);
239
+ if (preciseMatch.test(content) && schema2 !== name) {
240
+ output.push(
241
+ `import { ${schema2} } from './${spinalcase(schema2)}.ts';`
242
+ );
243
+ }
244
+ }
245
+ output.push(content);
246
+ return [
247
+ [`inputs/schemas/${spinalcase(name)}.ts`, output.join("\n")],
248
+ ...acc
249
+ ];
250
+ }, []);
251
+ return {
252
+ ...Object.fromEntries(schemas),
253
+ ...inputs
254
+ };
255
+ }
256
+ function generateSDK(spec) {
212
257
  const emitter = new Emitter();
213
- const streamEmitter = new StreamEmitter();
214
- const schemas = {};
215
258
  const schemaEndpoint = new SchemaEndpoint();
216
259
  const errors = [];
217
260
  for (const [name, operations] of Object.entries(spec.operations)) {
218
- const featureSchemaFileName = camelcase(name);
219
- schemas[featureSchemaFileName] = [`import z from 'zod';`];
220
261
  emitter.addImport(
221
- `import * as ${featureSchemaFileName} from './inputs/${featureSchemaFileName}.ts';`
222
- );
223
- streamEmitter.addImport(
224
- `import * as ${featureSchemaFileName} from './inputs/${featureSchemaFileName}.ts';`
262
+ `import * as ${camelcase(name)} from './inputs/${spinalcase(name)}.ts';`
225
263
  );
226
264
  schemaEndpoint.addImport(
227
- `import * as ${featureSchemaFileName} from './inputs/${featureSchemaFileName}.ts';`
265
+ `import * as ${camelcase(name)} from './inputs/${spinalcase(name)}.ts';`
228
266
  );
229
267
  for (const operation of operations) {
230
268
  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}`;
269
+ const schemaRef = `${camelcase(name)}.${schemaName}`;
234
270
  const output = operation.formatOutput();
235
271
  const inputHeaders = [];
236
272
  const inputQuery = [];
@@ -255,51 +291,25 @@ function generateClientSdk(spec) {
255
291
  );
256
292
  }
257
293
  }
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(
294
+ emitter.addImport(
295
+ `import type {${output.import}} from './outputs/${spinalcase(operation.name)}.ts';`
296
+ );
297
+ errors.push(...operation.errors ?? []);
298
+ const addTypeParser = Object.keys(operation.schemas).length > 1;
299
+ for (const type in operation.schemas ?? {}) {
300
+ let typePrefix = "";
301
+ if (addTypeParser && type !== "json") {
302
+ typePrefix = `${type} `;
303
+ }
304
+ const input = `typeof ${schemaRef}${addTypeParser ? `.${type}` : ""}`;
305
+ const endpoint = `${typePrefix}${operation.trigger.method.toUpperCase()} ${operation.trigger.path}`;
306
+ emitter.addEndpoint(
265
307
  endpoint,
266
- `{input: ${input}, output: ${output.use}}`
308
+ `{input: z.infer<${input}>; output: ${output.use}; error: ${(operation.errors ?? ["ServerError"]).concat(`ParseError<${input}>`).join("|")}}`
267
309
  );
268
310
  schemaEndpoint.addEndpoint(
269
311
  endpoint,
270
312
  `{
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
313
  schema: ${schemaRef}${addTypeParser ? `.${type}` : ""},
304
314
  toRequest(input: Endpoints['${endpoint}']['input']) {
305
315
  const endpoint = '${endpoint}';
@@ -311,8 +321,7 @@ function generateClientSdk(spec) {
311
321
  }));
312
322
  },
313
323
  }`
314
- );
315
- }
324
+ );
316
325
  }
317
326
  }
318
327
  }
@@ -320,19 +329,6 @@ function generateClientSdk(spec) {
320
329
  `import type { ${removeDuplicates(errors, (it) => it).join(", ")} } from './http/response.ts';`
321
330
  );
322
331
  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
332
  "client.ts": client_default(spec),
337
333
  "schemas.ts": schemaEndpoint.complete(),
338
334
  "endpoints.ts": emitter.complete()
@@ -421,6 +417,25 @@ function importsToString(...imports) {
421
417
  throw new Error(`Invalid import ${JSON.stringify(it)}`);
422
418
  });
423
419
  }
420
+ function exclude2(list, exclude3) {
421
+ return list.filter((it) => !exclude3.includes(it));
422
+ }
423
+ function useImports(content, imports) {
424
+ const output = [];
425
+ for (const it of mergeImports(imports)) {
426
+ const singleImport = it.defaultImport ?? it.namespaceImport;
427
+ if (singleImport && content.includes(singleImport)) {
428
+ output.push(importsToString(it).join("\n"));
429
+ } else if (it.namedImports.length) {
430
+ for (const namedImport of it.namedImports) {
431
+ if (content.includes(namedImport.name)) {
432
+ output.push(importsToString(it).join("\n"));
433
+ }
434
+ }
435
+ }
436
+ }
437
+ return output;
438
+ }
424
439
 
425
440
  // packages/typescript/src/lib/emitters/interface.ts
426
441
  var TypeScriptDeserialzer = class {
@@ -431,6 +446,70 @@ var TypeScriptDeserialzer = class {
431
446
  this.#spec = spec;
432
447
  this.#onRef = onRef;
433
448
  }
449
+ #stringifyKey = (key) => {
450
+ const reservedWords = [
451
+ "constructor",
452
+ "prototype",
453
+ "break",
454
+ "case",
455
+ "catch",
456
+ "class",
457
+ "const",
458
+ "continue",
459
+ "debugger",
460
+ "default",
461
+ "delete",
462
+ "do",
463
+ "else",
464
+ "export",
465
+ "extends",
466
+ "false",
467
+ "finally",
468
+ "for",
469
+ "function",
470
+ "if",
471
+ "import",
472
+ "in",
473
+ "instanceof",
474
+ "new",
475
+ "null",
476
+ "return",
477
+ "super",
478
+ "switch",
479
+ "this",
480
+ "throw",
481
+ "true",
482
+ "try",
483
+ "typeof",
484
+ "var",
485
+ "void",
486
+ "while",
487
+ "with",
488
+ "yield"
489
+ ];
490
+ if (reservedWords.includes(key)) {
491
+ return `'${key}'`;
492
+ }
493
+ if (key.trim() === "") {
494
+ return `'${key}'`;
495
+ }
496
+ const firstChar = key.charAt(0);
497
+ const validFirstChar = firstChar >= "a" && firstChar <= "z" || firstChar >= "A" && firstChar <= "Z" || firstChar === "_" || firstChar === "$";
498
+ if (!validFirstChar) {
499
+ return `'${key.replace(/'/g, "\\'")}'`;
500
+ }
501
+ for (let i = 1; i < key.length; i++) {
502
+ const char = key.charAt(i);
503
+ const validChar = char >= "a" && char <= "z" || char >= "A" && char <= "Z" || char >= "0" && char <= "9" || char === "_" || char === "$";
504
+ if (!validChar) {
505
+ return `'${key.replace(/'/g, "\\'")}'`;
506
+ }
507
+ }
508
+ return key;
509
+ };
510
+ #stringifyKeyV2 = (value) => {
511
+ return `'${value}'`;
512
+ };
434
513
  /**
435
514
  * Handle objects (properties)
436
515
  */
@@ -439,7 +518,7 @@ var TypeScriptDeserialzer = class {
439
518
  const propEntries = Object.entries(properties).map(([key, propSchema]) => {
440
519
  const isRequired = (schema.required ?? []).includes(key);
441
520
  const tsType = this.handle(propSchema, isRequired);
442
- return `${key}: ${tsType}`;
521
+ return `${this.#stringifyKeyV2(key)}: ${tsType}`;
443
522
  });
444
523
  if (schema.additionalProperties) {
445
524
  if (typeof schema.additionalProperties === "object") {
@@ -689,10 +768,16 @@ var ZodDeserialzer = class {
689
768
  }
690
769
  allOf(schemas) {
691
770
  const allOfSchemas = schemas.map((sub) => this.handle(sub, true));
771
+ if (allOfSchemas.length === 1) {
772
+ return allOfSchemas[0];
773
+ }
692
774
  return allOfSchemas.length ? `z.intersection(${allOfSchemas.join(", ")})` : allOfSchemas[0];
693
775
  }
694
776
  anyOf(schemas, required) {
695
777
  const anyOfSchemas = schemas.map((sub) => this.handle(sub, false));
778
+ if (anyOfSchemas.length === 1) {
779
+ return anyOfSchemas[0];
780
+ }
696
781
  return anyOfSchemas.length > 1 ? `z.union([${anyOfSchemas.join(", ")}])${appendOptional2(required)}` : (
697
782
  // Handle an invalid anyOf with one schema
698
783
  anyOfSchemas[0]
@@ -708,6 +793,9 @@ var ZodDeserialzer = class {
708
793
  }
709
794
  return this.handle(sub, false);
710
795
  });
796
+ if (oneOfSchemas.length === 1) {
797
+ return oneOfSchemas[0];
798
+ }
711
799
  return oneOfSchemas.length > 1 ? `z.union([${oneOfSchemas.join(", ")}])${appendOptional2(required)}` : (
712
800
  // Handle an invalid oneOf with one schema
713
801
  oneOfSchemas[0]
@@ -869,7 +957,18 @@ var defaults = {
869
957
  };
870
958
  function generateCode(config) {
871
959
  const commonSchemas = {};
872
- const zodDeserialzer = new ZodDeserialzer(config.spec);
960
+ const commonZod = /* @__PURE__ */ new Map();
961
+ const commonZodImports = [];
962
+ const zodDeserialzer = new ZodDeserialzer(config.spec, (model, schema) => {
963
+ commonZod.set(model, schema);
964
+ commonZodImports.push({
965
+ defaultImport: void 0,
966
+ isTypeOnly: true,
967
+ moduleSpecifier: `./${model}.ts`,
968
+ namedImports: [{ isTypeOnly: true, name: model }],
969
+ namespaceImport: void 0
970
+ });
971
+ });
873
972
  const groups = {};
874
973
  const outputs = {};
875
974
  for (const [path, methods2] of Object.entries(config.spec.paths ?? {})) {
@@ -1003,26 +1102,15 @@ function generateCode(config) {
1003
1102
  responseContent["application/json"].schema,
1004
1103
  true
1005
1104
  ) : "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
- }
1105
+ output.push(...useImports(responseSchema, imports));
1018
1106
  output.push(
1019
- `export type ${pascalcase2(operationName + " output")} = ${responseSchema}`
1107
+ `export type ${pascalcase(operationName + " output")} = ${responseSchema}`
1020
1108
  );
1021
1109
  }
1022
1110
  }
1023
1111
  if (!foundResponse) {
1024
1112
  output.push(
1025
- `export type ${pascalcase2(operationName + " output")} = void`
1113
+ `export type ${pascalcase(operationName + " output")} = void`
1026
1114
  );
1027
1115
  }
1028
1116
  outputs[`${spinalcase2(operationName)}.ts`] = output.join("\n");
@@ -1034,8 +1122,8 @@ function generateCode(config) {
1034
1122
  contentType,
1035
1123
  schemas: types,
1036
1124
  formatOutput: () => ({
1037
- import: pascalcase2(operationName + " output"),
1038
- use: pascalcase2(operationName + " output")
1125
+ import: pascalcase(operationName + " output"),
1126
+ use: pascalcase(operationName + " output")
1039
1127
  }),
1040
1128
  trigger: {
1041
1129
  path,
@@ -1044,7 +1132,7 @@ function generateCode(config) {
1044
1132
  });
1045
1133
  }
1046
1134
  }
1047
- return { groups, commonSchemas, outputs };
1135
+ return { groups, commonSchemas, commonZod, outputs };
1048
1136
  }
1049
1137
 
1050
1138
  // packages/typescript/src/lib/http/interceptors.txt
@@ -1057,7 +1145,7 @@ var parse_response_default = "import { parse } from 'fast-content-type-parse';\n
1057
1145
  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
1146
 
1059
1147
  // 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";
1148
+ 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
1149
 
1062
1150
  // packages/typescript/src/lib/http/response.txt
1063
1151
  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,23 +1175,24 @@ function security(spec) {
1087
1175
  return options;
1088
1176
  }
1089
1177
  async function generate(spec, settings) {
1090
- const { commonSchemas, groups, outputs } = generateCode({
1178
+ const { commonSchemas, groups, outputs, commonZod } = generateCode({
1091
1179
  spec,
1092
1180
  style: "github",
1093
1181
  target: "javascript"
1094
1182
  });
1095
1183
  const output = settings.mode === "full" ? join2(settings.output, "src") : settings.output;
1096
1184
  const options = security(spec);
1097
- const clientFiles = generateClientSdk({
1185
+ const clientFiles = generateSDK({
1098
1186
  name: settings.name || "Client",
1099
1187
  operations: groups,
1100
1188
  servers: spec.servers?.map((server) => server.url) || [],
1101
1189
  options
1102
1190
  });
1191
+ const inputFiles = generateInputs(groups, commonZod);
1103
1192
  await writeFiles(output, {
1104
- "outputs/index.ts": "",
1105
- "inputs/index.ts": ""
1106
- // 'models/index.ts': '',
1193
+ "outputs/.gitkeep": "",
1194
+ "inputs/.gitkeep": "",
1195
+ "models/.getkeep": ""
1107
1196
  // 'README.md': readme,
1108
1197
  });
1109
1198
  await writeFiles(join2(output, "http"), {
@@ -1115,15 +1204,16 @@ async function generate(spec, settings) {
1115
1204
  "request.ts": request_default
1116
1205
  });
1117
1206
  await writeFiles(join2(output, "outputs"), outputs);
1118
- const imports = Object.entries(commonSchemas).map(([name]) => name);
1207
+ const modelsImports = Object.entries(commonSchemas).map(([name]) => name);
1119
1208
  await writeFiles(output, {
1120
1209
  ...clientFiles,
1210
+ ...inputFiles,
1121
1211
  ...Object.fromEntries(
1122
1212
  Object.entries(commonSchemas).map(([name, schema]) => [
1123
1213
  `models/${name}.ts`,
1124
1214
  [
1125
1215
  `import { z } from 'zod';`,
1126
- ...exclude(imports, [name]).map(
1216
+ ...exclude2(modelsImports, [name]).map(
1127
1217
  (it) => `import type { ${it} } from './${it}.ts';`
1128
1218
  ),
1129
1219
  `export type ${name} = ${schema};`
@@ -1134,25 +1224,58 @@ async function generate(spec, settings) {
1134
1224
  const folders = [
1135
1225
  getFolderExports(output),
1136
1226
  getFolderExports(join2(output, "outputs")),
1137
- getFolderExports(join2(output, "inputs")),
1227
+ getFolderExports(
1228
+ join2(output, "inputs"),
1229
+ ["ts"],
1230
+ (dirent) => dirent.isDirectory() && dirent.name === "schemas"
1231
+ ),
1138
1232
  getFolderExports(join2(output, "http"))
1139
1233
  ];
1140
- if (imports.length) {
1234
+ if (modelsImports.length) {
1141
1235
  folders.push(getFolderExports(join2(output, "models")));
1142
1236
  }
1143
1237
  const [index, outputIndex, inputsIndex, httpIndex, modelsIndex] = await Promise.all(folders);
1144
1238
  await writeFiles(output, {
1145
1239
  "index.ts": index,
1146
1240
  "outputs/index.ts": outputIndex,
1147
- "inputs/index.ts": inputsIndex,
1241
+ "inputs/index.ts": inputsIndex || null,
1148
1242
  "http/index.ts": httpIndex,
1149
- ...imports.length ? { "models/index.ts": modelsIndex } : {}
1243
+ ...modelsImports.length ? { "models/index.ts": modelsIndex } : {}
1150
1244
  });
1151
1245
  if (settings.mode === "full") {
1152
1246
  await writeFiles(settings.output, {
1153
1247
  "package.json": {
1154
1248
  ignoreIfExists: true,
1155
- content: `{"type":"module","main":"./src/index.ts","dependencies":{"fast-content-type-parse":"^3.0.0"}}`
1249
+ content: JSON.stringify(
1250
+ {
1251
+ type: "module",
1252
+ main: "./src/index.ts",
1253
+ dependencies: { "fast-content-type-parse": "^3.0.0" }
1254
+ },
1255
+ null,
1256
+ 2
1257
+ )
1258
+ },
1259
+ "tsconfig.json": {
1260
+ ignoreIfExists: false,
1261
+ content: JSON.stringify(
1262
+ {
1263
+ compilerOptions: {
1264
+ skipLibCheck: true,
1265
+ skipDefaultLibCheck: true,
1266
+ target: "ESNext",
1267
+ module: "ESNext",
1268
+ noEmit: true,
1269
+ allowImportingTsExtensions: true,
1270
+ verbatimModuleSyntax: true,
1271
+ baseUrl: ".",
1272
+ moduleResolution: "bundler"
1273
+ },
1274
+ include: ["**/*.ts"]
1275
+ },
1276
+ null,
1277
+ 2
1278
+ )
1156
1279
  }
1157
1280
  });
1158
1281
  }
@@ -1161,9 +1284,6 @@ async function generate(spec, settings) {
1161
1284
  env: npmRunPathEnv()
1162
1285
  });
1163
1286
  }
1164
- function exclude(list, exclude2) {
1165
- return list.filter((it) => !exclude2.includes(it));
1166
- }
1167
1287
 
1168
1288
  // packages/typescript/src/lib/watcher.ts
1169
1289
  import { watch as nodeWatch } from "node:fs/promises";