@sdk-it/typescript 0.12.10 → 0.13.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
@@ -39,14 +39,14 @@ var client_default = (spec) => {
39
39
  }
40
40
  };
41
41
  return `
42
- import { fetchType, sendRequest } from './http/send-request.ts';
42
+ import { fetchType, sendRequest } from './http/${spec.makeImport("send-request")}';
43
43
  import z from 'zod';
44
- import type { Endpoints } from './endpoints.ts';
45
- import schemas from './schemas.ts';
44
+ import type { Endpoints } from './${spec.makeImport("endpoints")}';
45
+ import schemas from './${spec.makeImport("schemas")}';
46
46
  import {
47
47
  createBaseUrlInterceptor,
48
48
  createDefaultHeadersInterceptor,
49
- } from './http/interceptors.ts';
49
+ } from './http/${spec.makeImport("interceptors")}';
50
50
 
51
51
  ${spec.servers.length ? `export const servers = ${JSON.stringify(spec.servers, null, 2)} as const` : ""}
52
52
  const optionsSchema = z.object(${toLitObject(specOptions, (x) => x.schema)});
@@ -57,7 +57,7 @@ type ${spec.name}Options = z.infer<typeof optionsSchema>;
57
57
  export class ${spec.name} {
58
58
  public options: ${spec.name}Options
59
59
  constructor(options: ${spec.name}Options) {
60
- this.options = options;
60
+ this.options = optionsSchema.parse(options);
61
61
  }
62
62
 
63
63
  async request<E extends keyof Endpoints>(
@@ -96,12 +96,18 @@ export class ${spec.name} {
96
96
 
97
97
  // packages/typescript/src/lib/sdk.ts
98
98
  var SchemaEndpoint = class {
99
- #imports = [
100
- `import z from 'zod';`,
101
- 'import type { Endpoints } from "./endpoints.ts";',
102
- `import { toRequest, json, urlencoded, nobody, formdata, createUrl } from './http/request.ts';`,
103
- `import type { ParseError } from './http/parser.ts';`
104
- ];
99
+ #makeImport;
100
+ #imports = [];
101
+ constructor(makeImport) {
102
+ this.#makeImport = makeImport;
103
+ this.#imports = [
104
+ `import z from 'zod';`,
105
+ `import type { Endpoints } from '${this.#makeImport("./endpoints")}';`,
106
+ `import { toRequest, json, urlencoded, nobody, formdata, createUrl } from '${this.#makeImport("./http/request")}';`,
107
+ `import type { ParseError } from '${this.#makeImport("./http/parser")}';`,
108
+ `import { chunked, buffered } from "${this.#makeImport("./http/parse-response")}";`
109
+ ];
110
+ }
105
111
  #endpoints = [];
106
112
  addEndpoint(endpoint, operation) {
107
113
  this.#endpoints.push(` "${endpoint}": ${operation},`);
@@ -117,10 +123,15 @@ ${this.#endpoints.join("\n")}
117
123
  }
118
124
  };
119
125
  var Emitter = class {
120
- imports = [
121
- `import z from 'zod';`,
122
- `import type { ParseError } from './http/parser.ts';`
123
- ];
126
+ #makeImport;
127
+ imports = [];
128
+ constructor(makeImport) {
129
+ this.#makeImport = makeImport;
130
+ this.imports = [
131
+ `import type z from 'zod';`,
132
+ `import type { ParseError } from '${this.#makeImport("./http/parser")}';`
133
+ ];
134
+ }
124
135
  endpoints = [];
125
136
  addEndpoint(endpoint, operation) {
126
137
  this.endpoints.push(` "${endpoint}": ${operation};`);
@@ -135,7 +146,7 @@ ${this.endpoints.join("\n")}
135
146
  }`;
136
147
  }
137
148
  };
138
- function generateInputs(operationsSet, commonZod) {
149
+ function generateInputs(operationsSet, commonZod, makeImport) {
139
150
  const commonImports = commonZod.keys().toArray();
140
151
  const inputs = {};
141
152
  for (const [name, operations] of Object.entries(operationsSet)) {
@@ -148,7 +159,7 @@ function generateInputs(operationsSet, commonZod) {
148
159
  for (const schema2 of commonImports) {
149
160
  if (inputContent.includes(schema2)) {
150
161
  imports.add(
151
- `import { ${schema2} } from './schemas/${spinalcase(schema2)}.ts';`
162
+ `import { ${schema2} } from './schemas/${makeImport(spinalcase(schema2))}';`
152
163
  );
153
164
  }
154
165
  }
@@ -163,7 +174,7 @@ function generateInputs(operationsSet, commonZod) {
163
174
  const preciseMatch = new RegExp(`\\b${schema2}\\b`);
164
175
  if (preciseMatch.test(content) && schema2 !== name) {
165
176
  output.push(
166
- `import { ${schema2} } from './${spinalcase(schema2)}.ts';`
177
+ `import { ${schema2} } from './${makeImport(spinalcase(schema2))}';`
167
178
  );
168
179
  }
169
180
  }
@@ -179,15 +190,15 @@ function generateInputs(operationsSet, commonZod) {
179
190
  };
180
191
  }
181
192
  function generateSDK(spec) {
182
- const emitter = new Emitter();
183
- const schemaEndpoint = new SchemaEndpoint();
193
+ const emitter = new Emitter(spec.makeImport);
194
+ const schemaEndpoint = new SchemaEndpoint(spec.makeImport);
184
195
  const errors = [];
185
196
  for (const [name, operations] of Object.entries(spec.operations)) {
186
197
  emitter.addImport(
187
- `import * as ${camelcase(name)} from './inputs/${spinalcase(name)}.ts';`
198
+ `import type * as ${camelcase(name)} from './inputs/${spec.makeImport(spinalcase(name))}';`
188
199
  );
189
200
  schemaEndpoint.addImport(
190
- `import * as ${camelcase(name)} from './inputs/${spinalcase(name)}.ts';`
201
+ `import * as ${camelcase(name)} from './inputs/${spec.makeImport(spinalcase(name))}';`
191
202
  );
192
203
  for (const operation of operations) {
193
204
  const schemaName = camelcase(`${operation.name} schema`);
@@ -217,7 +228,7 @@ function generateSDK(spec) {
217
228
  }
218
229
  }
219
230
  emitter.addImport(
220
- `import type {${output.import}} from './outputs/${spinalcase(operation.name)}.ts';`
231
+ `import type {${output.import}} from './outputs/${spec.makeImport(spinalcase(operation.name))}';`
221
232
  );
222
233
  errors.push(...operation.errors ?? []);
223
234
  const addTypeParser = Object.keys(operation.schemas).length > 1;
@@ -236,9 +247,10 @@ function generateSDK(spec) {
236
247
  endpoint,
237
248
  `{
238
249
  schema: ${schemaRef}${addTypeParser ? `.${type}` : ""},
250
+ deserializer: ${operation.parser === "chunked" ? "chunked" : "buffered"},
239
251
  toRequest(input: Endpoints['${endpoint}']['input']) {
240
252
  const endpoint = '${endpoint}';
241
- return toRequest(endpoint, ${operation.contentType || "nobody"}(input, {
253
+ return toRequest(endpoint, ${operation.outgoingContentType || "nobody"}(input, {
242
254
  inputHeaders: [${inputHeaders}],
243
255
  inputQuery: [${inputQuery}],
244
256
  inputBody: [${inputBody}],
@@ -251,7 +263,7 @@ function generateSDK(spec) {
251
263
  }
252
264
  }
253
265
  emitter.addImport(
254
- `import type { ${removeDuplicates(errors, (it) => it).join(", ")} } from './http/response.ts';`
266
+ `import type { ${removeDuplicates(errors, (it) => it).join(", ")} } from '${spec.makeImport("./http/response")}';`
255
267
  );
256
268
  return {
257
269
  "client.ts": client_default(spec),
@@ -342,8 +354,8 @@ function importsToString(...imports) {
342
354
  throw new Error(`Invalid import ${JSON.stringify(it)}`);
343
355
  });
344
356
  }
345
- function exclude2(list, exclude3) {
346
- return list.filter((it) => !exclude3.includes(it));
357
+ function exclude(list, exclude2) {
358
+ return list.filter((it) => !exclude2.includes(it));
347
359
  }
348
360
  function useImports(content, imports) {
349
361
  const output = [];
@@ -580,6 +592,9 @@ var TypeScriptDeserialzer = class {
580
592
  }
581
593
  const types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : [];
582
594
  if (!types.length) {
595
+ if ("properties" in schema) {
596
+ return this.object(schema, required);
597
+ }
583
598
  return appendOptional("any", required);
584
599
  }
585
600
  if (types.length > 1) {
@@ -655,19 +670,24 @@ var ZodDeserialzer = class {
655
670
  }
656
671
  // oneOf() {}
657
672
  // enum() {}
673
+ #suffixes = (defaultValue, required, nullable) => {
674
+ return `${nullable ? ".nullable()" : ""}${appendDefault(defaultValue)}${appendOptional2(required)}`;
675
+ };
658
676
  /**
659
677
  * Convert a basic type (string | number | boolean | object | array, etc.) to Zod.
660
678
  * We'll also handle .optional() if needed.
661
679
  */
662
- normal(type, schema, required = false) {
680
+ normal(type, schema, required = false, nullable = false) {
663
681
  switch (type) {
664
682
  case "string":
665
- return this.string(schema, required);
683
+ return `${this.string(schema)}${this.#suffixes(schema.default, required, nullable)}`;
666
684
  case "number":
667
- case "integer":
668
- return this.number(schema, required);
685
+ case "integer": {
686
+ const { base, defaultValue } = this.number(schema);
687
+ return `${base}${this.#suffixes(defaultValue, required, nullable)}`;
688
+ }
669
689
  case "boolean":
670
- return `z.boolean()${appendDefault(schema.default)}${appendOptional2(required)}`;
690
+ return `z.boolean()${this.#suffixes(schema.default, required, nullable)}`;
671
691
  case "object":
672
692
  return this.object(schema, required);
673
693
  case "array":
@@ -693,10 +713,20 @@ var ZodDeserialzer = class {
693
713
  }
694
714
  allOf(schemas) {
695
715
  const allOfSchemas = schemas.map((sub) => this.handle(sub, true));
716
+ if (allOfSchemas.length === 0) {
717
+ return `z.unknown()`;
718
+ }
696
719
  if (allOfSchemas.length === 1) {
697
720
  return allOfSchemas[0];
698
721
  }
699
- return allOfSchemas.length ? `z.intersection(${allOfSchemas.join(", ")})` : allOfSchemas[0];
722
+ return this.#toIntersection(allOfSchemas);
723
+ }
724
+ #toIntersection(schemas) {
725
+ const [left, ...right] = schemas;
726
+ if (!right.length) {
727
+ return left;
728
+ }
729
+ return `z.intersection(${left}, ${this.#toIntersection(right)})`;
700
730
  }
701
731
  anyOf(schemas, required) {
702
732
  const anyOfSchemas = schemas.map((sub) => this.handle(sub, false));
@@ -733,7 +763,7 @@ var ZodDeserialzer = class {
733
763
  /**
734
764
  * Handle a `string` schema with possible format keywords (JSON Schema).
735
765
  */
736
- string(schema, required) {
766
+ string(schema) {
737
767
  let base = "z.string()";
738
768
  switch (schema.format) {
739
769
  case "date-time":
@@ -775,20 +805,20 @@ var ZodDeserialzer = class {
775
805
  default:
776
806
  break;
777
807
  }
778
- return `${base}${appendDefault(schema.default)}${appendOptional2(required)}`;
808
+ return base;
779
809
  }
780
810
  /**
781
811
  * Handle number/integer constraints from OpenAPI/JSON Schema.
782
812
  * In 3.1, exclusiveMinimum/Maximum hold the actual numeric threshold,
783
813
  * rather than a boolean toggling `minimum`/`maximum`.
784
814
  */
785
- number(schema, required) {
786
- let defaultValue = schema.default !== void 0 ? `.default(${schema.default})` : ``;
815
+ number(schema) {
816
+ let defaultValue = schema.default;
787
817
  let base = "z.number()";
788
818
  if (schema.format === "int64") {
789
819
  base = "z.bigint()";
790
820
  if (schema.default !== void 0) {
791
- defaultValue = `.default(BigInt(${schema.default}))`;
821
+ defaultValue = `BigInt(${schema.default})`;
792
822
  }
793
823
  }
794
824
  if (schema.format === "int32") {
@@ -809,7 +839,7 @@ var ZodDeserialzer = class {
809
839
  if (typeof schema.multipleOf === "number") {
810
840
  base += `.refine((val) => Number.isInteger(val / ${schema.multipleOf}), "Must be a multiple of ${schema.multipleOf}")`;
811
841
  }
812
- return `${base}${defaultValue}${appendOptional2(required)}`;
842
+ return { base, defaultValue };
813
843
  }
814
844
  handle(schema, required) {
815
845
  if (isRef(schema)) {
@@ -831,16 +861,18 @@ var ZodDeserialzer = class {
831
861
  if (!types.length) {
832
862
  return `z.unknown()${appendOptional2(required)}`;
833
863
  }
864
+ if ("nullable" in schema && schema.nullable) {
865
+ types.push("null");
866
+ }
834
867
  if (types.length > 1) {
835
868
  const realTypes = types.filter((t) => t !== "null");
836
869
  if (realTypes.length === 1 && types.includes("null")) {
837
- const typeZod = this.normal(realTypes[0], schema, false);
838
- return `${typeZod}.nullable()${appendOptional2(required)}`;
870
+ return this.normal(realTypes[0], schema, false, true);
839
871
  }
840
872
  const subSchemas = types.map((t) => this.normal(t, schema, false));
841
873
  return `z.union([${subSchemas.join(", ")}])${appendOptional2(required)}`;
842
874
  }
843
- return this.normal(types[0], schema, required);
875
+ return this.normal(types[0], schema, required, false);
844
876
  }
845
877
  };
846
878
  function appendOptional2(isRequired) {
@@ -889,7 +921,7 @@ function generateCode(config) {
889
921
  commonZodImports.push({
890
922
  defaultImport: void 0,
891
923
  isTypeOnly: true,
892
- moduleSpecifier: `./${model}.ts`,
924
+ moduleSpecifier: `./${config.makeImport(model)}`,
893
925
  namedImports: [{ isTypeOnly: true, name: model }],
894
926
  namespaceImport: void 0
895
927
  });
@@ -901,7 +933,7 @@ function generateCode(config) {
901
933
  const formatOperationId = config.operationId ?? defaults.operationId;
902
934
  const operationName = formatOperationId(operation, path, method);
903
935
  console.log(`Processing ${method} ${path}`);
904
- const groupName = (operation.tags ?? ["unknown"])[0];
936
+ const [groupName] = Array.isArray(operation.tags) ? operation.tags : ["unknown"];
905
937
  groups[groupName] ??= [];
906
938
  const inputs = {};
907
939
  const additionalProperties = [];
@@ -942,7 +974,7 @@ function generateCode(config) {
942
974
  "application/xml": "xml",
943
975
  "text/plain": "text"
944
976
  };
945
- let contentType;
977
+ let outgoingContentType;
946
978
  if (operation.requestBody && Object.keys(operation.requestBody).length) {
947
979
  const content = isRef(operation.requestBody) ? get2(followRef(config.spec, operation.requestBody.$ref), ["content"]) : operation.requestBody.content;
948
980
  for (const type in content) {
@@ -961,22 +993,17 @@ function generateCode(config) {
961
993
  {}
962
994
  )
963
995
  });
964
- for (const [name] of Object.entries(ctSchema.properties ?? {})) {
965
- inputs[name] = {
966
- in: "body",
967
- schema: ""
968
- };
969
- }
996
+ Object.assign(inputs, bodyInputs(config, ctSchema));
970
997
  types[shortContenTypeMap[type]] = zodDeserialzer.handle(schema, true);
971
998
  }
972
999
  if (content["application/json"]) {
973
- contentType = "json";
1000
+ outgoingContentType = "json";
974
1001
  } else if (content["application/x-www-form-urlencoded"]) {
975
- contentType = "urlencoded";
1002
+ outgoingContentType = "urlencoded";
976
1003
  } else if (content["multipart/form-data"]) {
977
- contentType = "formdata";
1004
+ outgoingContentType = "formdata";
978
1005
  } else {
979
- contentType = "json";
1006
+ outgoingContentType = "json";
980
1007
  }
981
1008
  } else {
982
1009
  const properties = additionalProperties.reduce(
@@ -999,8 +1026,14 @@ function generateCode(config) {
999
1026
  operation.responses ??= {};
1000
1027
  let foundResponse = false;
1001
1028
  const output = [`import z from 'zod';`];
1029
+ let parser = "buffered";
1002
1030
  for (const status in operation.responses) {
1003
- const response = operation.responses[status];
1031
+ const response = isRef(
1032
+ operation.responses[status]
1033
+ ) ? followRef(
1034
+ config.spec,
1035
+ operation.responses[status].$ref
1036
+ ) : operation.responses[status];
1004
1037
  const statusCode = +status;
1005
1038
  if (statusCode >= 400) {
1006
1039
  errors.push(responses[status] ?? "ProblematicResponse");
@@ -1009,6 +1042,9 @@ function generateCode(config) {
1009
1042
  foundResponse = true;
1010
1043
  const responseContent = get2(response, ["content"]);
1011
1044
  const isJson = responseContent && responseContent["application/json"];
1045
+ if ((response.headers ?? {})["Transfer-Encoding"]) {
1046
+ parser = "chunked";
1047
+ }
1012
1048
  const imports = [];
1013
1049
  const typeScriptDeserialzer = new TypeScriptDeserialzer(
1014
1050
  config.spec,
@@ -1017,7 +1053,7 @@ function generateCode(config) {
1017
1053
  imports.push({
1018
1054
  defaultImport: void 0,
1019
1055
  isTypeOnly: true,
1020
- moduleSpecifier: `../models/${schemaName}.ts`,
1056
+ moduleSpecifier: `../models/${config.makeImport(schemaName)}`,
1021
1057
  namedImports: [{ isTypeOnly: true, name: schemaName }],
1022
1058
  namespaceImport: void 0
1023
1059
  });
@@ -1044,8 +1080,9 @@ function generateCode(config) {
1044
1080
  type: "http",
1045
1081
  inputs,
1046
1082
  errors: errors.length ? errors : ["ServerError"],
1047
- contentType,
1083
+ outgoingContentType,
1048
1084
  schemas: types,
1085
+ parser,
1049
1086
  formatOutput: () => ({
1050
1087
  import: pascalcase(operationName + " output"),
1051
1088
  use: pascalcase(operationName + " output")
@@ -1059,12 +1096,42 @@ function generateCode(config) {
1059
1096
  }
1060
1097
  return { groups, commonSchemas, commonZod, outputs };
1061
1098
  }
1099
+ function toProps(spec, schemaOrRef, aggregator = []) {
1100
+ if (isRef(schemaOrRef)) {
1101
+ const schema = followRef(spec, schemaOrRef.$ref);
1102
+ return toProps(spec, schema, aggregator);
1103
+ } else if (schemaOrRef.type === "object") {
1104
+ for (const [name] of Object.entries(schemaOrRef.properties ?? {})) {
1105
+ aggregator.push(name);
1106
+ }
1107
+ return void 0;
1108
+ } else if (schemaOrRef.allOf) {
1109
+ for (const it of schemaOrRef.allOf) {
1110
+ toProps(spec, it, aggregator);
1111
+ }
1112
+ return void 0;
1113
+ }
1114
+ }
1115
+ function bodyInputs(config, ctSchema) {
1116
+ const props = [];
1117
+ toProps(config.spec, ctSchema, props);
1118
+ return props.reduce(
1119
+ (acc, prop) => ({
1120
+ ...acc,
1121
+ [prop]: {
1122
+ in: "body",
1123
+ schema: ""
1124
+ }
1125
+ }),
1126
+ {}
1127
+ );
1128
+ }
1062
1129
 
1063
1130
  // packages/typescript/src/lib/http/interceptors.txt
1064
- var interceptors_default = "export interface Interceptor {\n before?: (request: Request) => Promise<Request> | Request;\n after?: (response: Response) => Promise<Response> | Response;\n}\n\nexport const createDefaultHeadersInterceptor = (\n getHeaders: () => Record<string, string | undefined>,\n) => {\n return {\n before(request: Request) {\n const headers = getHeaders();\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 && !request.headers.has(key)) {\n request.headers.set(key, value);\n }\n }\n\n return request;\n },\n };\n};\n\nexport const createBaseUrlInterceptor = (getBaseUrl: () => string) => {\n return {\n before(request: Request) {\n const baseUrl = getBaseUrl();\n if (request.url.startsWith('local://')) {\n return new Request(request.url.replace('local://', baseUrl), request);\n }\n return request;\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";
1131
+ var interceptors_default = "export interface Interceptor {\n before?: (request: Request) => Promise<Request> | Request;\n after?: (response: Response) => Promise<Response> | Response;\n}\n\nexport const createDefaultHeadersInterceptor = (\n getHeaders: () => Record<string, string | undefined>,\n) => {\n return {\n before(request: Request) {\n const headers = getHeaders();\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 && !request.headers.has(key)) {\n request.headers.set(key, value);\n }\n }\n\n return request;\n },\n };\n};\n\nexport const createBaseUrlInterceptor = (getBaseUrl: () => string) => {\n return {\n before(request: Request) {\n const baseUrl = getBaseUrl();\n if (request.url.startsWith('local://')) {\n return new Request(request.url.replace('local://', baseUrl), request);\n }\n return request;\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";
1065
1132
 
1066
1133
  // packages/typescript/src/lib/http/parse-response.txt
1067
- 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 async function parseResponse(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 const isChunked = response.headers.get('Transfer-Encoding') === 'chunked';\n if (isChunked) {\n return response.body!;\n // return handleChunkedResponse(response, contentType);\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";
1134
+ 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';
1068
1135
 
1069
1136
  // packages/typescript/src/lib/http/parser.txt
1070
1137
  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";
@@ -1076,7 +1143,7 @@ var request_default = "export type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | '
1076
1143
  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";
1077
1144
 
1078
1145
  // packages/typescript/src/lib/http/send-request.txt
1079
- var send_request_default = "import z from 'zod';\n\nimport type { Interceptor } from './interceptors.ts';\nimport { handleError, parseResponse } from './parse-response.ts';\nimport { parse } from './parser.ts';\n\nexport interface RequestSchema {\n schema: z.ZodType;\n toRequest: (input: any) => Request;\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: any,\n route: RequestSchema,\n options: {\n fetch?: z.infer<typeof fetchType>;\n interceptors?: Interceptor[];\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 request = route.toRequest(parsedInput as never);\n for (const interceptor of interceptors) {\n if (interceptor.before) {\n request = await interceptor.before(request);\n }\n }\n\n let response = await (options.fetch ?? fetch)(request);\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 parseResponse(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";
1146
+ var send_request_default = "\nexport interface RequestSchema {\n schema: z.ZodType;\n toRequest: (input: any) => Request;\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 },\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 request = route.toRequest(parsedInput as never);\n for (const interceptor of interceptors) {\n if (interceptor.before) {\n request = await interceptor.before(request);\n }\n }\n\n let response = await (options.fetch ?? fetch)(request);\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";
1080
1147
 
1081
1148
  // packages/typescript/src/lib/generate.ts
1082
1149
  function security(spec) {
@@ -1100,10 +1167,15 @@ function security(spec) {
1100
1167
  return options;
1101
1168
  }
1102
1169
  async function generate(spec, settings) {
1170
+ settings.useTsExtension ??= true;
1171
+ const makeImport = (moduleSpecifier) => {
1172
+ return settings.useTsExtension ? `${moduleSpecifier}.ts` : moduleSpecifier;
1173
+ };
1103
1174
  const { commonSchemas, groups, outputs, commonZod } = generateCode({
1104
1175
  spec,
1105
1176
  style: "github",
1106
- target: "javascript"
1177
+ target: "javascript",
1178
+ makeImport
1107
1179
  });
1108
1180
  const output = settings.mode === "full" ? join(settings.output, "src") : settings.output;
1109
1181
  const options = security(spec);
@@ -1111,9 +1183,10 @@ async function generate(spec, settings) {
1111
1183
  name: settings.name || "Client",
1112
1184
  operations: groups,
1113
1185
  servers: spec.servers?.map((server) => server.url) || [],
1114
- options
1186
+ options,
1187
+ makeImport
1115
1188
  });
1116
- const inputFiles = generateInputs(groups, commonZod);
1189
+ const inputFiles = generateInputs(groups, commonZod, makeImport);
1117
1190
  await writeFiles(output, {
1118
1191
  "outputs/.gitkeep": "",
1119
1192
  "inputs/.gitkeep": "",
@@ -1123,7 +1196,11 @@ async function generate(spec, settings) {
1123
1196
  await writeFiles(join(output, "http"), {
1124
1197
  "interceptors.ts": interceptors_default,
1125
1198
  "parse-response.ts": parse_response_default,
1126
- "send-request.ts": send_request_default,
1199
+ "send-request.ts": `import z from 'zod';
1200
+ import type { Interceptor } from './${makeImport("interceptors")}';
1201
+ import { handleError } from './${makeImport("parse-response")}';
1202
+ import { parse } from './${makeImport("parser")}';
1203
+ ${send_request_default}`,
1127
1204
  "response.ts": response_default,
1128
1205
  "parser.ts": parser_default,
1129
1206
  "request.ts": request_default
@@ -1138,7 +1215,7 @@ async function generate(spec, settings) {
1138
1215
  `models/${name}.ts`,
1139
1216
  [
1140
1217
  `import { z } from 'zod';`,
1141
- ...exclude2(modelsImports, [name]).map(
1218
+ ...exclude(modelsImports, [name]).map(
1142
1219
  (it) => `import type { ${it} } from './${it}.ts';`
1143
1220
  ),
1144
1221
  `export type ${name} = ${schema};`
@@ -1147,17 +1224,20 @@ async function generate(spec, settings) {
1147
1224
  )
1148
1225
  });
1149
1226
  const folders = [
1150
- getFolderExports(output),
1151
- getFolderExports(join(output, "outputs")),
1227
+ getFolderExports(output, settings.useTsExtension),
1228
+ getFolderExports(join(output, "outputs"), settings.useTsExtension),
1152
1229
  getFolderExports(
1153
1230
  join(output, "inputs"),
1231
+ settings.useTsExtension,
1154
1232
  ["ts"],
1155
1233
  (dirent) => dirent.isDirectory() && dirent.name === "schemas"
1156
1234
  ),
1157
- getFolderExports(join(output, "http"))
1235
+ getFolderExports(join(output, "http"), settings.useTsExtension)
1158
1236
  ];
1159
1237
  if (modelsImports.length) {
1160
- folders.push(getFolderExports(join(output, "models")));
1238
+ folders.push(
1239
+ getFolderExports(join(output, "models"), settings.useTsExtension)
1240
+ );
1161
1241
  }
1162
1242
  const [index, outputIndex, inputsIndex, httpIndex, modelsIndex] = await Promise.all(folders);
1163
1243
  await writeFiles(output, {
@@ -1173,6 +1253,7 @@ async function generate(spec, settings) {
1173
1253
  ignoreIfExists: true,
1174
1254
  content: JSON.stringify(
1175
1255
  {
1256
+ name: "sdk",
1176
1257
  type: "module",
1177
1258
  main: "./src/index.ts",
1178
1259
  dependencies: {
@@ -1185,7 +1266,7 @@ async function generate(spec, settings) {
1185
1266
  )
1186
1267
  },
1187
1268
  "tsconfig.json": {
1188
- ignoreIfExists: false,
1269
+ ignoreIfExists: true,
1189
1270
  content: JSON.stringify(
1190
1271
  {
1191
1272
  compilerOptions: {
@@ -1194,6 +1275,7 @@ async function generate(spec, settings) {
1194
1275
  target: "ESNext",
1195
1276
  module: "ESNext",
1196
1277
  noEmit: true,
1278
+ strict: true,
1197
1279
  allowImportingTsExtensions: true,
1198
1280
  verbatimModuleSyntax: true,
1199
1281
  baseUrl: ".",