@sdk-it/typescript 0.19.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,137 +1,16 @@
1
1
  // packages/typescript/src/lib/generate.ts
2
2
  import { template as template2 } from "lodash-es";
3
- import { join as join3 } from "node:path";
3
+ import { join as join2 } from "node:path";
4
4
  import { npmRunPathEnv } from "npm-run-path";
5
5
  import { spinalcase as spinalcase3 } from "stringcase";
6
-
7
- // packages/core/dist/index.js
6
+ import { methods, pascalcase as pascalcase3 } from "@sdk-it/core";
8
7
  import {
9
- pascalcase as _pascalcase,
10
- snakecase as _snakecase,
11
- spinalcase as _spinalcase
12
- } from "stringcase";
13
- import ts, { TypeFlags, symbolName } from "typescript";
14
- import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
15
- import { dirname, extname, isAbsolute, join } from "node:path";
16
- import debug from "debug";
17
- import ts2 from "typescript";
18
- import { get } from "lodash-es";
19
- var deriveSymbol = Symbol.for("serialize");
20
- var $types = Symbol.for("types");
21
- async function exist(file) {
22
- return stat(file).then(() => true).catch(() => false);
23
- }
24
- async function writeFiles(dir, contents) {
25
- await Promise.all(
26
- Object.entries(contents).map(async ([file, content]) => {
27
- if (content === null) {
28
- return;
29
- }
30
- const filePath = isAbsolute(file) ? file : join(dir, file);
31
- await mkdir(dirname(filePath), { recursive: true });
32
- if (typeof content === "string") {
33
- await writeFile(filePath, content, "utf-8");
34
- } else {
35
- if (content.ignoreIfExists) {
36
- if (!await exist(filePath)) {
37
- await writeFile(filePath, content.content, "utf-8");
38
- }
39
- } else {
40
- await writeFile(filePath, content.content, "utf-8");
41
- }
42
- }
43
- })
44
- );
45
- }
46
- async function getFolderExports(folder, includeExtension = true, extensions = ["ts"], ignore = () => false) {
47
- const files = await readdir(folder, { withFileTypes: true });
48
- const exports = [];
49
- for (const file of files) {
50
- if (ignore(file)) {
51
- continue;
52
- }
53
- if (file.isDirectory()) {
54
- if (await exist(`${file.parentPath}/${file.name}/index.ts`)) {
55
- exports.push(
56
- `export * from './${file.name}/index${includeExtension ? ".ts" : ""}';`
57
- );
58
- }
59
- } else if (file.name !== "index.ts" && extensions.includes(getExt(file.name))) {
60
- exports.push(
61
- `export * from './${includeExtension ? file.name : file.name.replace(extname(file.name), "")}';`
62
- );
63
- }
64
- }
65
- return exports.join("\n");
66
- }
67
- var getExt = (fileName) => {
68
- if (!fileName) {
69
- return "";
70
- }
71
- const lastDot = fileName.lastIndexOf(".");
72
- if (lastDot === -1) {
73
- return "";
74
- }
75
- const ext = fileName.slice(lastDot + 1).split("/").filter(Boolean).join("");
76
- if (ext === fileName) {
77
- return "";
78
- }
79
- return ext || "txt";
80
- };
81
- var methods = [
82
- "get",
83
- "post",
84
- "put",
85
- "patch",
86
- "delete",
87
- "trace",
88
- "head"
89
- ];
90
- var logger = debug("january:client");
91
- function isRef(obj) {
92
- return obj && "$ref" in obj;
93
- }
94
- function cleanRef(ref) {
95
- return ref.replace(/^#\//, "");
96
- }
97
- function parseRef(ref) {
98
- const parts = ref.split("/");
99
- const [model] = parts.splice(-1);
100
- const [namespace] = parts.splice(-1);
101
- return {
102
- model,
103
- namespace,
104
- path: cleanRef(parts.join("/"))
105
- };
106
- }
107
- function followRef(spec, ref) {
108
- const pathParts = cleanRef(ref).split("/");
109
- const entry = get(spec, pathParts);
110
- if (entry && "$ref" in entry) {
111
- return followRef(spec, entry.$ref);
112
- }
113
- return entry;
114
- }
115
- function removeDuplicates(data, accessor = (item) => item) {
116
- return [...new Map(data.map((x) => [accessor(x), x])).values()];
117
- }
118
- function toLitObject(obj, accessor = (value) => value) {
119
- return `{${Object.keys(obj).map((key) => `${key}: ${accessor(obj[key])}`).join(", ")}}`;
120
- }
121
- function isEmpty(value) {
122
- if (value === null || value === void 0 || value === "") {
123
- return true;
124
- }
125
- if (Array.isArray(value) && value.length === 0) {
126
- return true;
127
- }
128
- if (typeof value === "object" && Object.keys(value).length === 0) {
129
- return true;
130
- }
131
- return false;
132
- }
8
+ getFolderExports,
9
+ writeFiles
10
+ } from "@sdk-it/core/file-system.js";
133
11
 
134
12
  // packages/typescript/src/lib/client.ts
13
+ import { toLitObject } from "@sdk-it/core";
135
14
  var client_default = (spec, style) => {
136
15
  const optionsEntries = Object.entries(spec.options).map(
137
16
  ([key, value]) => [`'${key}'`, value]
@@ -255,11 +134,13 @@ export class ${spec.name} {
255
134
 
256
135
  // packages/typescript/src/lib/generator.ts
257
136
  import { merge, template } from "lodash-es";
258
- import { join as join2 } from "node:path";
137
+ import { join } from "node:path";
259
138
  import { camelcase as camelcase3, pascalcase as pascalcase2, spinalcase as spinalcase2 } from "stringcase";
139
+ import { followRef as followRef5, isEmpty, isRef as isRef6 } from "@sdk-it/core";
260
140
 
261
141
  // packages/spec/dist/lib/operation.js
262
142
  import { camelcase } from "stringcase";
143
+ import { followRef, isRef } from "@sdk-it/core";
263
144
  var defaults = {
264
145
  operationId: (operation, path, method) => {
265
146
  if (operation.operationId) {
@@ -274,9 +155,18 @@ var defaults = {
274
155
  );
275
156
  },
276
157
  tag: (operation, path) => {
277
- return operation.tags?.[0] || determineGenericTag(path, operation);
158
+ return operation.tags?.[0] ? sanitizeTag(operation.tags?.[0]) : determineGenericTag(path, operation);
278
159
  }
279
160
  };
161
+ function resolveResponses(spec, operation) {
162
+ const responses = operation.responses ?? {};
163
+ const resolved = {};
164
+ for (const status in responses) {
165
+ const response = isRef(responses[status]) ? followRef(spec, responses[status].$ref) : responses[status];
166
+ resolved[status] = response;
167
+ }
168
+ return resolved;
169
+ }
280
170
  function forEachOperation(config, callback) {
281
171
  const result = [];
282
172
  for (const [path, pathItem] of Object.entries(config.spec.paths ?? {})) {
@@ -300,7 +190,8 @@ function forEachOperation(config, callback) {
300
190
  {
301
191
  ...operation,
302
192
  parameters: [...parameters, ...operation.parameters ?? []],
303
- operationId: operationName
193
+ operationId: operationName,
194
+ responses: resolveResponses(config.spec, operation)
304
195
  }
305
196
  )
306
197
  );
@@ -309,15 +200,11 @@ function forEachOperation(config, callback) {
309
200
  return result;
310
201
  }
311
202
  var reservedKeywords = /* @__PURE__ */ new Set([
312
- "abstract",
313
- "arguments",
314
203
  "await",
315
- "boolean",
204
+ // Reserved in async functions
316
205
  "break",
317
- "byte",
318
206
  "case",
319
207
  "catch",
320
- "char",
321
208
  "class",
322
209
  "const",
323
210
  "continue",
@@ -325,85 +212,59 @@ var reservedKeywords = /* @__PURE__ */ new Set([
325
212
  "default",
326
213
  "delete",
327
214
  "do",
328
- "double",
329
215
  "else",
330
216
  "enum",
331
- "eval",
332
217
  "export",
333
218
  "extends",
334
219
  "false",
335
- "final",
336
220
  "finally",
337
- "float",
338
221
  "for",
339
222
  "function",
340
- "goto",
341
223
  "if",
342
224
  "implements",
225
+ // Strict mode
343
226
  "import",
344
227
  "in",
345
228
  "instanceof",
346
- "int",
347
229
  "interface",
230
+ // Strict mode
348
231
  "let",
349
- "long",
350
- "native",
232
+ // Strict mode
351
233
  "new",
352
234
  "null",
353
235
  "package",
236
+ // Strict mode
354
237
  "private",
238
+ // Strict mode
355
239
  "protected",
240
+ // Strict mode
356
241
  "public",
242
+ // Strict mode
357
243
  "return",
358
- "short",
359
244
  "static",
245
+ // Strict mode
360
246
  "super",
361
247
  "switch",
362
- "synchronized",
363
248
  "this",
364
249
  "throw",
365
- "throws",
366
- "transient",
367
250
  "true",
368
251
  "try",
369
252
  "typeof",
370
253
  "var",
371
254
  "void",
372
- "volatile",
373
255
  "while",
374
256
  "with",
375
257
  "yield",
376
- // Potentially problematic identifiers / Common Verbs used as tags
377
- "object",
378
- "string",
379
- "number",
380
- "any",
381
- "unknown",
382
- "never",
383
- "get",
384
- "list",
385
- "create",
386
- "update",
387
- "delete",
388
- "post",
389
- "put",
390
- "patch",
391
- "do",
392
- "send",
393
- "add",
394
- "remove",
395
- "set",
396
- "find",
397
- "search",
398
- "check",
399
- "make"
400
- // Added make, check
258
+ // Strict mode / Generator functions
259
+ // 'arguments' is not technically a reserved word, but it's a special identifier within functions
260
+ // and assigning to it or declaring it can cause issues or unexpected behavior.
261
+ "arguments"
401
262
  ]);
402
263
  function sanitizeTag(camelCasedTag) {
403
264
  if (/^\d/.test(camelCasedTag)) {
404
265
  return `_${camelCasedTag}`;
405
266
  }
406
- return reservedKeywords.has(camelCasedTag) ? `${camelCasedTag}_` : camelCasedTag;
267
+ return reservedKeywords.has(camelcase(camelCasedTag)) ? `${camelCasedTag}_` : camelCasedTag;
407
268
  }
408
269
  function determineGenericTag(pathString, operation) {
409
270
  const operationId = operation.operationId || "";
@@ -427,7 +288,6 @@ function determineGenericTag(pathString, operation) {
427
288
  "search",
428
289
  "check",
429
290
  "make"
430
- // Added make
431
291
  ]);
432
292
  const segments = pathString.split("/").filter(Boolean);
433
293
  const potentialCandidates = segments.filter(
@@ -521,8 +381,9 @@ function determineGenericTag(pathString, operation) {
521
381
  }
522
382
 
523
383
  // packages/typescript/src/lib/emitters/zod.ts
384
+ import { cleanRef, followRef as followRef2, isRef as isRef2 } from "@sdk-it/core";
524
385
  var ZodDeserialzer = class {
525
- circularRefTracker = /* @__PURE__ */ new Set();
386
+ generatedRefs = /* @__PURE__ */ new Set();
526
387
  #spec;
527
388
  #onRef;
528
389
  constructor(spec, onRef) {
@@ -596,15 +457,14 @@ var ZodDeserialzer = class {
596
457
  }
597
458
  ref($ref, required) {
598
459
  const schemaName = cleanRef($ref).split("/").pop();
599
- if (this.circularRefTracker.has(schemaName)) {
460
+ if (this.generatedRefs.has(schemaName)) {
600
461
  return schemaName;
601
462
  }
602
- this.circularRefTracker.add(schemaName);
463
+ this.generatedRefs.add(schemaName);
603
464
  this.#onRef?.(
604
465
  schemaName,
605
- this.handle(followRef(this.#spec, $ref), required)
466
+ this.handle(followRef2(this.#spec, $ref), required)
606
467
  );
607
- this.circularRefTracker.delete(schemaName);
608
468
  return schemaName;
609
469
  }
610
470
  allOf(schemas, required) {
@@ -632,15 +492,7 @@ var ZodDeserialzer = class {
632
492
  return `z.union([${anyOfSchemas.join(", ")}])${appendOptional(required)}`;
633
493
  }
634
494
  oneOf(schemas, required) {
635
- const oneOfSchemas = schemas.map((sub) => {
636
- if (isRef(sub)) {
637
- const { model } = parseRef(sub.$ref);
638
- if (this.circularRefTracker.has(model)) {
639
- return `${model}${appendOptional(required)}`;
640
- }
641
- }
642
- return this.handle(sub, true);
643
- });
495
+ const oneOfSchemas = schemas.map((sub) => this.handle(sub, true));
644
496
  if (oneOfSchemas.length === 1) {
645
497
  return `${oneOfSchemas[0]}${appendOptional(required)}`;
646
498
  }
@@ -660,6 +512,10 @@ var ZodDeserialzer = class {
660
512
  */
661
513
  string(schema) {
662
514
  let base = "z.string()";
515
+ if (schema.contentEncoding === "binary") {
516
+ base = "z.instanceof(Blob)";
517
+ return base;
518
+ }
663
519
  switch (schema.format) {
664
520
  case "date-time":
665
521
  case "datetime":
@@ -737,7 +593,7 @@ var ZodDeserialzer = class {
737
593
  return { base, defaultValue };
738
594
  }
739
595
  handle(schema, required) {
740
- if (isRef(schema)) {
596
+ if (isRef2(schema)) {
741
597
  return `${this.ref(schema.$ref, true)}${appendOptional(required)}`;
742
598
  }
743
599
  if (schema.allOf && Array.isArray(schema.allOf)) {
@@ -782,12 +638,14 @@ function appendDefault(defaultValue) {
782
638
  }
783
639
 
784
640
  // packages/typescript/src/lib/sdk.ts
785
- import { get as get2 } from "lodash-es";
641
+ import { get } from "lodash-es";
786
642
  import { camelcase as camelcase2, pascalcase, spinalcase } from "stringcase";
643
+ import { followRef as followRef4, isRef as isRef5, toLitObject as toLitObject2 } from "@sdk-it/core";
787
644
 
788
645
  // packages/typescript/src/lib/emitters/interface.ts
646
+ import { cleanRef as cleanRef2, followRef as followRef3, isRef as isRef3 } from "@sdk-it/core";
789
647
  var TypeScriptDeserialzer = class {
790
- circularRefTracker = /* @__PURE__ */ new Set();
648
+ generatedRefs = /* @__PURE__ */ new Set();
791
649
  #spec;
792
650
  #onRef;
793
651
  constructor(spec, onRef) {
@@ -798,7 +656,7 @@ var TypeScriptDeserialzer = class {
798
656
  return `'${value}'`;
799
657
  };
800
658
  #isInternal = (schema) => {
801
- return isRef(schema) ? false : !!schema["x-internal"];
659
+ return isRef3(schema) ? false : !!schema["x-internal"];
802
660
  };
803
661
  /**
804
662
  * Handle objects (properties)
@@ -859,16 +717,15 @@ var TypeScriptDeserialzer = class {
859
717
  }
860
718
  }
861
719
  ref($ref, required) {
862
- const schemaName = cleanRef($ref).split("/").pop();
863
- if (this.circularRefTracker.has(schemaName)) {
720
+ const schemaName = cleanRef2($ref).split("/").pop();
721
+ if (this.generatedRefs.has(schemaName)) {
864
722
  return schemaName;
865
723
  }
866
- this.circularRefTracker.add(schemaName);
724
+ this.generatedRefs.add(schemaName);
867
725
  this.#onRef?.(
868
726
  schemaName,
869
- this.handle(followRef(this.#spec, $ref), required)
727
+ this.handle(followRef3(this.#spec, $ref), required)
870
728
  );
871
- this.circularRefTracker.delete(schemaName);
872
729
  return appendOptional2(schemaName, required);
873
730
  }
874
731
  allOf(schemas) {
@@ -884,12 +741,6 @@ var TypeScriptDeserialzer = class {
884
741
  }
885
742
  oneOf(schemas, required) {
886
743
  const oneOfTypes = schemas.map((sub) => {
887
- if (isRef(sub)) {
888
- const { model } = parseRef(sub.$ref);
889
- if (this.circularRefTracker.has(model)) {
890
- return model;
891
- }
892
- }
893
744
  return this.handle(sub, false);
894
745
  });
895
746
  return appendOptional2(
@@ -906,6 +757,9 @@ var TypeScriptDeserialzer = class {
906
757
  */
907
758
  string(schema, required) {
908
759
  let type;
760
+ if (schema.contentEncoding === "binary") {
761
+ return appendOptional2("Blob", required);
762
+ }
909
763
  switch (schema.format) {
910
764
  case "date-time":
911
765
  case "datetime":
@@ -932,7 +786,7 @@ var TypeScriptDeserialzer = class {
932
786
  return appendOptional2(type, required);
933
787
  }
934
788
  handle(schema, required) {
935
- if (isRef(schema)) {
789
+ if (isRef3(schema)) {
936
790
  return this.ref(schema.$ref, required);
937
791
  }
938
792
  if (schema.allOf && Array.isArray(schema.allOf)) {
@@ -977,6 +831,7 @@ function appendOptional2(type, isRequired) {
977
831
  }
978
832
 
979
833
  // packages/typescript/src/lib/utils.ts
834
+ import { isRef as isRef4, removeDuplicates } from "@sdk-it/core";
980
835
  function securityToOptions(security2, securitySchemes, staticIn) {
981
836
  securitySchemes ??= {};
982
837
  const options = {};
@@ -986,7 +841,7 @@ function securityToOptions(security2, securitySchemes, staticIn) {
986
841
  continue;
987
842
  }
988
843
  const schema = securitySchemes[name];
989
- if (isRef(schema)) {
844
+ if (isRef4(schema)) {
990
845
  throw new Error(`Ref security schemas are not supported`);
991
846
  }
992
847
  if (schema.type === "http") {
@@ -1075,7 +930,7 @@ function generateInputs(operationsSet, commonZod, makeImport) {
1075
930
  const imports = /* @__PURE__ */ new Set(['import { z } from "zod";']);
1076
931
  for (const operation of operations) {
1077
932
  const schemaName = camelcase2(`${operation.name} schema`);
1078
- const schema = `export const ${schemaName} = ${Object.keys(operation.schemas).length === 1 ? Object.values(operation.schemas)[0] : toLitObject(operation.schemas)};`;
933
+ const schema = `export const ${schemaName} = ${Object.keys(operation.schemas).length === 1 ? Object.values(operation.schemas)[0] : toLitObject2(operation.schemas)};`;
1079
934
  const inputContent = schema;
1080
935
  for (const schema2 of commonImports) {
1081
936
  if (inputContent.includes(schema2)) {
@@ -1145,7 +1000,7 @@ function toEndpoint(groupName, spec, specOperation, operation, utils) {
1145
1000
  return statusCode >= 200 && statusCode < 300;
1146
1001
  }).length > 1;
1147
1002
  for (const status in specOperation.responses) {
1148
- const response = isRef(specOperation.responses[status]) ? followRef(spec, specOperation.responses[status].$ref) : specOperation.responses[status];
1003
+ const response = isRef5(specOperation.responses[status]) ? followRef4(spec, specOperation.responses[status].$ref) : specOperation.responses[status];
1149
1004
  const handled = handleResponse(
1150
1005
  spec,
1151
1006
  operation.name,
@@ -1250,12 +1105,12 @@ function handleResponse(spec, operationName, status, response, utils, numbered)
1250
1105
  );
1251
1106
  }
1252
1107
  }
1253
- const responseContent = get2(response, ["content"]);
1108
+ const responseContent = get(response, ["content"]);
1254
1109
  const isJson = responseContent && responseContent["application/json"];
1255
1110
  let responseSchema = parser === "chunked" ? "ReadableStream" : "void";
1256
1111
  if (isJson) {
1257
1112
  const schema = responseContent["application/json"].schema;
1258
- const isObject = !isRef(schema) && schema.type === "object";
1113
+ const isObject = !isRef5(schema) && schema.type === "object";
1259
1114
  if (isObject && schema.properties) {
1260
1115
  schema.properties["[http.KIND]"] = {
1261
1116
  "x-internal": true,
@@ -1263,7 +1118,7 @@ function handleResponse(spec, operationName, status, response, utils, numbered)
1263
1118
  type: "string"
1264
1119
  };
1265
1120
  schema.required ??= [];
1266
- schema.required.push("[KIND]");
1121
+ schema.required.push("[http.KIND]");
1267
1122
  }
1268
1123
  responseSchema = typeScriptDeserialzer.handle(schema, true);
1269
1124
  }
@@ -1320,9 +1175,9 @@ function generateCode(config) {
1320
1175
  groups[entry.groupName] ??= [];
1321
1176
  endpoints[entry.groupName] ??= [];
1322
1177
  const inputs = {};
1323
- const additionalProperties = [];
1178
+ const additionalProperties = {};
1324
1179
  for (const param of operation.parameters ?? []) {
1325
- if (isRef(param)) {
1180
+ if (isRef6(param)) {
1326
1181
  throw new Error(`Found reference in parameter ${param.$ref}`);
1327
1182
  }
1328
1183
  if (!param.schema) {
@@ -1332,24 +1187,22 @@ function generateCode(config) {
1332
1187
  in: param.in,
1333
1188
  schema: ""
1334
1189
  };
1335
- additionalProperties.push(param);
1190
+ additionalProperties[param.name] = param;
1336
1191
  }
1337
1192
  const security2 = operation.security ?? [];
1338
1193
  const securitySchemes = config.spec.components?.securitySchemes ?? {};
1339
1194
  const securityOptions = securityToOptions(security2, securitySchemes);
1340
1195
  Object.assign(inputs, securityOptions);
1341
- additionalProperties.push(
1342
- ...Object.entries(securityOptions).map(
1343
- ([name, value]) => ({
1344
- name,
1345
- required: false,
1346
- schema: {
1347
- type: "string"
1348
- },
1349
- in: value.in
1350
- })
1351
- )
1352
- );
1196
+ Object.entries(securityOptions).forEach(([name, value]) => {
1197
+ additionalProperties[name] = {
1198
+ name,
1199
+ required: false,
1200
+ schema: {
1201
+ type: "string"
1202
+ },
1203
+ in: value.in
1204
+ };
1205
+ });
1353
1206
  const schemas = {};
1354
1207
  const shortContenTypeMap = {
1355
1208
  "application/json": "json",
@@ -1364,9 +1217,9 @@ function generateCode(config) {
1364
1217
  };
1365
1218
  let outgoingContentType;
1366
1219
  if (!isEmpty(operation.requestBody)) {
1367
- const requestBody = isRef(operation.requestBody) ? followRef(config.spec, operation.requestBody.$ref) : operation.requestBody;
1220
+ const requestBody = isRef6(operation.requestBody) ? followRef5(config.spec, operation.requestBody.$ref) : operation.requestBody;
1368
1221
  for (const type in requestBody.content) {
1369
- const ctSchema = isRef(requestBody.content[type].schema) ? followRef(config.spec, requestBody.content[type].schema.$ref) : requestBody.content[type].schema;
1222
+ const ctSchema = isRef6(requestBody.content[type].schema) ? followRef5(config.spec, requestBody.content[type].schema.$ref) : requestBody.content[type].schema;
1370
1223
  if (!ctSchema) {
1371
1224
  console.warn(
1372
1225
  `Schema not found for ${type} in ${entry.method} ${entry.path}`
@@ -1384,9 +1237,9 @@ function generateCode(config) {
1384
1237
  };
1385
1238
  }
1386
1239
  const schema = merge({}, objectSchema, {
1387
- required: additionalProperties.filter((p) => p.required).map((p) => p.name),
1388
- properties: additionalProperties.reduce(
1389
- (acc, p) => ({
1240
+ required: Object.values(additionalProperties).filter((p) => p.required).map((p) => p.name),
1241
+ properties: Object.entries(additionalProperties).reduce(
1242
+ (acc, [, p]) => ({
1390
1243
  ...acc,
1391
1244
  [p.name]: p.schema
1392
1245
  }),
@@ -1406,8 +1259,8 @@ function generateCode(config) {
1406
1259
  outgoingContentType = "json";
1407
1260
  }
1408
1261
  } else {
1409
- const properties = additionalProperties.reduce(
1410
- (acc, p) => ({
1262
+ const properties = Object.entries(additionalProperties).reduce(
1263
+ (acc, [, p]) => ({
1411
1264
  ...acc,
1412
1265
  [p.name]: p.schema
1413
1266
  }),
@@ -1416,7 +1269,7 @@ function generateCode(config) {
1416
1269
  schemas[shortContenTypeMap["application/json"]] = zodDeserialzer.handle(
1417
1270
  {
1418
1271
  type: "object",
1419
- required: additionalProperties.filter((p) => p.required).map((p) => p.name),
1272
+ required: Object.values(additionalProperties).filter((p) => p.required).map((p) => p.name),
1420
1273
  properties
1421
1274
  },
1422
1275
  true
@@ -1503,7 +1356,7 @@ function generateCode(config) {
1503
1356
  commonZod,
1504
1357
  outputs,
1505
1358
  endpoints: {
1506
- [join2("api", "endpoints.ts")]: `
1359
+ [join("api", "endpoints.ts")]: `
1507
1360
 
1508
1361
 
1509
1362
  import type z from 'zod';
@@ -1518,7 +1371,7 @@ import type { OutputType, Parser, Type } from '${config.makeImport(
1518
1371
  import schemas from '${config.makeImport("./schemas")}';
1519
1372
 
1520
1373
  ${template(endpoints_default)({ outputType: config.style?.outputType })}`,
1521
- [`${join2("api", "schemas.ts")}`]: `${allSchemas.map((it) => it.import).join("\n")}
1374
+ [`${join("api", "schemas.ts")}`]: `${allSchemas.map((it) => it.import).join("\n")}
1522
1375
  import { KIND } from "${config.makeImport("../http/index")}";
1523
1376
  export default {
1524
1377
  ${allSchemas.map((it) => it.use).join(",\n")}
@@ -1538,10 +1391,9 @@ ${allSchemas.map((it) => it.use).join(",\n")}
1538
1391
  );
1539
1392
  return [
1540
1393
  [
1541
- join2("api", `${spinalcase2(name)}.ts`),
1394
+ join("api", `${spinalcase2(name)}.ts`),
1542
1395
  `${[
1543
1396
  ...imps,
1544
- // ...imports,
1545
1397
  `import z from 'zod';`,
1546
1398
  `import * as http from '${config.makeImport("../http/response")}';`,
1547
1399
  `import { toRequest, json, urlencoded, nobody, formdata, createUrl } from '${config.makeImport("../http/request")}';`,
@@ -1561,8 +1413,8 @@ ${endpoint.flatMap((it) => it.schemas).join(",\n")}
1561
1413
  };
1562
1414
  }
1563
1415
  function toProps(spec, schemaOrRef, aggregator = []) {
1564
- if (isRef(schemaOrRef)) {
1565
- const schema = followRef(spec, schemaOrRef.$ref);
1416
+ if (isRef6(schemaOrRef)) {
1417
+ const schema = followRef5(spec, schemaOrRef.$ref);
1566
1418
  return toProps(spec, schema, aggregator);
1567
1419
  } else if (schemaOrRef.type === "object") {
1568
1420
  for (const [name] of Object.entries(schemaOrRef.properties ?? {})) {
@@ -2032,6 +1884,311 @@ export type SuccessfulResponse =
2032
1884
  // packages/typescript/src/lib/http/send-request.txt
2033
1885
  var send_request_default = "export interface Type<T> {\n new (...args: any[]): T;\n}\nexport type Parser = (\n response: Response,\n) => Promise<unknown> | ReadableStream<any>;\nexport type OutputType =\n | Type<APIResponse>\n | { parser: Parser; type: Type<APIResponse> };\n\nexport interface RequestSchema {\n schema: z.ZodType;\n toRequest: (input: any) => RequestConfig;\n output: OutputType[];\n}\n\nexport const fetchType = z\n .function()\n .args(z.instanceof(Request))\n .returns(z.promise(z.instanceof(Response)))\n .optional();\n\nexport async function dispatch(\n input: unknown,\n route: RequestSchema,\n options: {\n fetch?: z.infer<typeof fetchType>;\n interceptors?: Interceptor[];\n signal?: AbortSignal;\n },\n) {\n const { interceptors = [] } = options;\n const [parsedInput, parseError] = parseInput(route.schema, input);\n if (parseError) {\n <% if(throwError) { %>\n throw parseError;\n <% } else { %>\n return [null as never, parseError as never] as const;\n <% } %>\n }\n\n let config = route.toRequest(parsedInput as never);\n for (const interceptor of interceptors) {\n if (interceptor.before) {\n config = await interceptor.before(config);\n }\n }\n\n let response = await (options.fetch ?? fetch)(\n new Request(config.url, config.init),\n {\n ...config.init,\n signal: options.signal,\n },\n );\n\n for (let i = interceptors.length - 1; i >= 0; i--) {\n const interceptor = interceptors[i];\n if (interceptor.after) {\n response = await interceptor.after(response.clone());\n }\n }\n return await parse(route, response);\n}\n\nexport async function parse(route: RequestSchema, response: Response) {\n let output: typeof APIResponse | null = null;\n let parser: Parser = buffered;\n for (const outputType of route.output) {\n if ('parser' in outputType) {\n parser = outputType.parser;\n if (isTypeOf(outputType.type, APIResponse)) {\n if (response.status === outputType.type.status) {\n output = outputType.type;\n break;\n }\n }\n } else if (isTypeOf(outputType, APIResponse)) {\n if (response.status === outputType.status) {\n output = outputType;\n break;\n }\n }\n }\n\n if (response.ok) {\n const apiresponse = (output || APIResponse).create(\n response.status,\n await parser(response),\n );\n <% if(throwError) { %>\n return <% if (outputType === 'default') { %>apiresponse.data<% } else { %>apiresponse<% } %>;\n <% } else { %>\n return [<% if (outputType === 'default') { %>apiresponse.data<% } else { %>apiresponse<% } %> , null] as const;\n <% } %>\n }\n<% if(throwError) { %>\n throw (output || APIError).create(\n response.status,\n await parser(response),\n );\n<% } else { %>\n const data = (output || APIError).create(\n response.status,\n await parser(response),\n );\n return [null as never, data as never] as const;\n<% } %>\n}\n\nexport function isTypeOf<T extends Type<APIResponse>>(\n instance: any,\n baseType: T,\n): instance is T {\n if (instance === baseType) {\n return true;\n }\n const prototype = Object.getPrototypeOf(instance);\n if (prototype === null) {\n return false;\n }\n return isTypeOf(prototype, baseType);\n}\n";
2034
1886
 
1887
+ // packages/typescript/src/lib/readme.ts
1888
+ import { followRef as followRef6, isRef as isRef7 } from "@sdk-it/core";
1889
+ var PropEmitter = class {
1890
+ #spec;
1891
+ constructor(spec) {
1892
+ this.#spec = spec;
1893
+ }
1894
+ /**
1895
+ * Handle objects (properties)
1896
+ */
1897
+ #object(schema) {
1898
+ const lines = [];
1899
+ const properties = schema.properties || {};
1900
+ if (Object.keys(properties).length > 0) {
1901
+ lines.push(`**Properties:**`);
1902
+ for (const [propName, propSchema] of Object.entries(properties)) {
1903
+ const isRequired = (schema.required ?? []).includes(propName);
1904
+ lines.push(...this.#property(propName, propSchema, isRequired));
1905
+ }
1906
+ }
1907
+ if (schema.additionalProperties) {
1908
+ lines.push(`**Additional Properties:**`);
1909
+ if (typeof schema.additionalProperties === "boolean") {
1910
+ lines.push(`- Allowed: ${schema.additionalProperties}`);
1911
+ } else {
1912
+ lines.push(
1913
+ ...this.handle(schema.additionalProperties).map((l) => ` ${l}`)
1914
+ );
1915
+ }
1916
+ }
1917
+ return lines;
1918
+ }
1919
+ /**
1920
+ * Format a property with its type and description
1921
+ */
1922
+ #property(name, schema, required) {
1923
+ const requiredMark = required ? " (required)" : "";
1924
+ const propNameLine = `- \`${name}\`${requiredMark}:`;
1925
+ const lines = [propNameLine];
1926
+ const schemaDocs = this.handle(schema);
1927
+ lines.push(...schemaDocs.map((line) => ` ${line}`));
1928
+ return lines;
1929
+ }
1930
+ /**
1931
+ * Handle array schemas
1932
+ */
1933
+ #array(schema) {
1934
+ const lines = [];
1935
+ lines.push(`**Array items:**`);
1936
+ if (schema.items) {
1937
+ const itemDocs = this.handle(schema.items);
1938
+ lines.push(...itemDocs.map((line) => ` ${line}`));
1939
+ } else {
1940
+ lines.push(` **Type:** \`unknown\``);
1941
+ }
1942
+ if (schema.minItems !== void 0)
1943
+ lines.push(`- Minimum items: ${schema.minItems}`);
1944
+ if (schema.maxItems !== void 0)
1945
+ lines.push(`- Maximum items: ${schema.maxItems}`);
1946
+ if (schema.uniqueItems)
1947
+ lines.push(`- Items must be unique.`);
1948
+ return lines;
1949
+ }
1950
+ #ref($ref) {
1951
+ const schemaName = $ref.split("/").pop() || "object";
1952
+ const resolved = followRef6(this.#spec, $ref);
1953
+ const lines = [
1954
+ `**Type:** [\`${schemaName}\`](#${schemaName.toLowerCase()})`
1955
+ ];
1956
+ if (resolved.description) {
1957
+ lines.push(resolved.description);
1958
+ }
1959
+ return lines;
1960
+ }
1961
+ #allOf(schemas) {
1962
+ const lines = ["**All of (Intersection):**"];
1963
+ schemas.forEach((subSchema, index) => {
1964
+ lines.push(`- **Constraint ${index + 1}:**`);
1965
+ const subLines = this.handle(subSchema);
1966
+ lines.push(...subLines.map((l) => ` ${l}`));
1967
+ });
1968
+ return lines;
1969
+ }
1970
+ #anyOf(schemas) {
1971
+ const lines = ["**Any of (Union):**"];
1972
+ schemas.forEach((subSchema, index) => {
1973
+ lines.push(`- **Option ${index + 1}:**`);
1974
+ const subLines = this.handle(subSchema);
1975
+ lines.push(...subLines.map((l) => ` ${l}`));
1976
+ });
1977
+ return lines;
1978
+ }
1979
+ #oneOf(schemas) {
1980
+ const lines = ["**One of (Exclusive Union):**"];
1981
+ schemas.forEach((subSchema, index) => {
1982
+ lines.push(`- **Option ${index + 1}:**`);
1983
+ const subLines = this.handle(subSchema);
1984
+ lines.push(...subLines.map((l) => ` ${l}`));
1985
+ });
1986
+ return lines;
1987
+ }
1988
+ #enum(schema) {
1989
+ const lines = [`**Type:** \`${schema.type || "unknown"}\` (enum)`];
1990
+ if (schema.description)
1991
+ lines.push(schema.description);
1992
+ lines.push("**Allowed values:**");
1993
+ lines.push(
1994
+ ...(schema.enum || []).map((val) => `- \`${JSON.stringify(val)}\``)
1995
+ );
1996
+ if (schema.default !== void 0) {
1997
+ lines.push(`**Default:** \`${JSON.stringify(schema.default)}\``);
1998
+ }
1999
+ return lines;
2000
+ }
2001
+ #normal(type, schema, nullable) {
2002
+ const lines = [];
2003
+ const nullableSuffix = nullable ? " (nullable)" : "";
2004
+ const description = schema.description ? [schema.description] : [];
2005
+ switch (type) {
2006
+ case "string":
2007
+ lines.push(
2008
+ `**Type:** \`string\`${schema.format ? ` (format: ${schema.format})` : ""}${nullableSuffix}`
2009
+ );
2010
+ lines.push(...description);
2011
+ if (schema.minLength !== void 0)
2012
+ lines.push(`- Minimum length: ${schema.minLength}`);
2013
+ if (schema.maxLength !== void 0)
2014
+ lines.push(`- Maximum length: ${schema.maxLength}`);
2015
+ if (schema.pattern !== void 0)
2016
+ lines.push(`- Pattern: \`${schema.pattern}\``);
2017
+ break;
2018
+ case "number":
2019
+ case "integer":
2020
+ lines.push(
2021
+ `**Type:** \`${type}\`${schema.format ? ` (format: ${schema.format})` : ""}${nullableSuffix}`
2022
+ );
2023
+ lines.push(...description);
2024
+ if (schema.minimum !== void 0) {
2025
+ const exclusiveMin = typeof schema.exclusiveMinimum === "number";
2026
+ lines.push(
2027
+ `- Minimum: ${schema.minimum}${exclusiveMin ? " (exclusive)" : ""}`
2028
+ );
2029
+ if (exclusiveMin) {
2030
+ lines.push(
2031
+ `- Must be strictly greater than: ${schema.exclusiveMinimum}`
2032
+ );
2033
+ }
2034
+ } else if (typeof schema.exclusiveMinimum === "number") {
2035
+ lines.push(
2036
+ `- Must be strictly greater than: ${schema.exclusiveMinimum}`
2037
+ );
2038
+ }
2039
+ if (schema.maximum !== void 0) {
2040
+ const exclusiveMax = typeof schema.exclusiveMaximum === "number";
2041
+ lines.push(
2042
+ `- Maximum: ${schema.maximum}${exclusiveMax ? " (exclusive)" : ""}`
2043
+ );
2044
+ if (exclusiveMax) {
2045
+ lines.push(
2046
+ `- Must be strictly less than: ${schema.exclusiveMaximum}`
2047
+ );
2048
+ }
2049
+ } else if (typeof schema.exclusiveMaximum === "number") {
2050
+ lines.push(
2051
+ `- Must be strictly less than: ${schema.exclusiveMaximum}`
2052
+ );
2053
+ }
2054
+ if (schema.multipleOf !== void 0)
2055
+ lines.push(`- Must be a multiple of: ${schema.multipleOf}`);
2056
+ break;
2057
+ case "boolean":
2058
+ lines.push(`**Type:** \`boolean\`${nullableSuffix}`);
2059
+ lines.push(...description);
2060
+ break;
2061
+ case "object":
2062
+ lines.push(`**Type:** \`object\`${nullableSuffix}`);
2063
+ lines.push(...description);
2064
+ lines.push(...this.#object(schema));
2065
+ break;
2066
+ case "array":
2067
+ lines.push(`**Type:** \`array\`${nullableSuffix}`);
2068
+ lines.push(...description);
2069
+ lines.push(...this.#array(schema));
2070
+ break;
2071
+ case "null":
2072
+ lines.push(`**Type:** \`null\``);
2073
+ lines.push(...description);
2074
+ break;
2075
+ default:
2076
+ lines.push(`**Type:** \`${type}\`${nullableSuffix}`);
2077
+ lines.push(...description);
2078
+ }
2079
+ if (schema.default !== void 0) {
2080
+ lines.push(`**Default:** \`${JSON.stringify(schema.default)}\``);
2081
+ }
2082
+ return lines.filter((l) => l);
2083
+ }
2084
+ /**
2085
+ * Handle schemas by resolving references and delegating to appropriate handler
2086
+ */
2087
+ handle(schemaOrRef) {
2088
+ if (isRef7(schemaOrRef)) {
2089
+ return this.#ref(schemaOrRef.$ref);
2090
+ }
2091
+ const schema = schemaOrRef;
2092
+ if (schema.allOf && Array.isArray(schema.allOf)) {
2093
+ return this.#allOf(schema.allOf);
2094
+ }
2095
+ if (schema.anyOf && Array.isArray(schema.anyOf)) {
2096
+ return this.#anyOf(schema.anyOf);
2097
+ }
2098
+ if (schema.oneOf && Array.isArray(schema.oneOf)) {
2099
+ return this.#oneOf(schema.oneOf);
2100
+ }
2101
+ if (schema.enum && Array.isArray(schema.enum)) {
2102
+ return this.#enum(schema);
2103
+ }
2104
+ let types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : [];
2105
+ let nullable = false;
2106
+ if (types.includes("null")) {
2107
+ nullable = true;
2108
+ types = types.filter((t) => t !== "null");
2109
+ }
2110
+ if (types.length === 0) {
2111
+ if (schema.properties || schema.additionalProperties) {
2112
+ types = ["object"];
2113
+ } else if (schema.items) {
2114
+ types = ["array"];
2115
+ }
2116
+ }
2117
+ if (types.length === 0) {
2118
+ const lines2 = ["**Type:** `unknown`"];
2119
+ if (schema.description)
2120
+ lines2.push(schema.description);
2121
+ if (schema.default !== void 0)
2122
+ lines2.push(`**Default:** \`${JSON.stringify(schema.default)}\``);
2123
+ return lines2;
2124
+ }
2125
+ if (types.length === 1) {
2126
+ return this.#normal(types[0], schema, nullable);
2127
+ }
2128
+ const typeString = types.join(" | ");
2129
+ const nullableSuffix = nullable ? " (nullable)" : "";
2130
+ const lines = [`**Type:** \`${typeString}\`${nullableSuffix}`];
2131
+ if (schema.description)
2132
+ lines.push(schema.description);
2133
+ if (schema.default !== void 0)
2134
+ lines.push(`**Default:** \`${JSON.stringify(schema.default)}\``);
2135
+ return lines;
2136
+ }
2137
+ /**
2138
+ * Process a request body and return markdown documentation
2139
+ */
2140
+ requestBody(requestBody) {
2141
+ if (!requestBody)
2142
+ return [];
2143
+ const resolvedBody = isRef7(requestBody) ? followRef6(this.#spec, requestBody.$ref) : requestBody;
2144
+ const lines = [];
2145
+ lines.push(`##### Request Body`);
2146
+ if (resolvedBody.description) {
2147
+ lines.push(resolvedBody.description);
2148
+ }
2149
+ if (resolvedBody.required) {
2150
+ lines.push(`*This request body is required.*`);
2151
+ }
2152
+ if (resolvedBody.content) {
2153
+ for (const [contentType, mediaType] of Object.entries(
2154
+ resolvedBody.content
2155
+ )) {
2156
+ lines.push(`**Content Type:** \`${contentType}\``);
2157
+ if (mediaType.schema) {
2158
+ const schemaDocs = this.handle(mediaType.schema);
2159
+ lines.push(...schemaDocs);
2160
+ }
2161
+ }
2162
+ }
2163
+ return lines;
2164
+ }
2165
+ };
2166
+ function toReadme(spec) {
2167
+ const markdown = [];
2168
+ const propEmitter = new PropEmitter(spec);
2169
+ forEachOperation({ spec }, ({ method, path, name }, operation) => {
2170
+ spec.components ??= {};
2171
+ spec.components.schemas ??= {};
2172
+ const statuses = [];
2173
+ markdown.push(
2174
+ `#### ${name || operation.operationId} | ${`_${method.toUpperCase()} ${path}_`}`
2175
+ );
2176
+ markdown.push(operation.summary || "");
2177
+ const requestBodyContent = propEmitter.requestBody(operation.requestBody);
2178
+ if (requestBodyContent.length > 1) {
2179
+ markdown.push(requestBodyContent.join("\n\n"));
2180
+ }
2181
+ markdown.push(`##### Responses`);
2182
+ for (const status in operation.responses) {
2183
+ const response = operation.responses[status];
2184
+ const resolvedResponse = isRef7(response) ? followRef6(spec, response.$ref) : response;
2185
+ statuses.push(`**${status}** _${resolvedResponse.description}_`);
2186
+ }
2187
+ markdown.push(`<small>${statuses.join("\n\n")}</small>`);
2188
+ });
2189
+ return markdown.join("\n\n");
2190
+ }
2191
+
2035
2192
  // packages/typescript/src/lib/generate.ts
2036
2193
  function security(spec) {
2037
2194
  const security2 = spec.security || [];
@@ -2074,17 +2231,18 @@ async function generate(spec, settings) {
2074
2231
  makeImport
2075
2232
  }
2076
2233
  );
2077
- const output = settings.mode === "full" ? join3(settings.output, "src") : settings.output;
2234
+ const output = settings.mode === "full" ? join2(settings.output, "src") : settings.output;
2078
2235
  const options = security(spec);
2079
- const clientName = settings.name || "Client";
2236
+ const clientName = settings.name?.trim() ? pascalcase3(settings.name) : "Client";
2237
+ const readme = settings.readme ? toReadme(spec) : "";
2080
2238
  const inputFiles = generateInputs(groups, commonZod, makeImport);
2239
+ console.log("Writing to", output);
2081
2240
  await writeFiles(output, {
2082
2241
  "outputs/.gitkeep": "",
2083
2242
  "inputs/.gitkeep": "",
2084
2243
  "models/.getkeep": ""
2085
- // 'README.md': readme,
2086
2244
  });
2087
- await writeFiles(join3(output, "http"), {
2245
+ await writeFiles(join2(output, "http"), {
2088
2246
  "interceptors.ts": `
2089
2247
  import type { RequestConfig, HeadersInit } from './${makeImport("request")}';
2090
2248
  ${interceptors_default}`,
@@ -2101,7 +2259,7 @@ ${template2(send_request_default, {})({ throwError: !style.errorAsValue, outputT
2101
2259
  "parser.ts": parser_default,
2102
2260
  "request.ts": request_default
2103
2261
  });
2104
- await writeFiles(join3(output, "outputs"), outputs);
2262
+ await writeFiles(join2(output, "outputs"), outputs);
2105
2263
  const modelsImports = Object.entries(commonSchemas).map(([name]) => name);
2106
2264
  await writeFiles(output, {
2107
2265
  "client.ts": client_default(
@@ -2129,16 +2287,16 @@ ${template2(send_request_default, {})({ throwError: !style.errorAsValue, outputT
2129
2287
  )
2130
2288
  });
2131
2289
  const folders = [
2132
- getFolderExports(join3(output, "outputs"), settings.useTsExtension),
2290
+ getFolderExports(join2(output, "outputs"), settings.useTsExtension),
2133
2291
  getFolderExports(
2134
- join3(output, "inputs"),
2292
+ join2(output, "inputs"),
2135
2293
  settings.useTsExtension,
2136
2294
  ["ts"],
2137
2295
  (dirent) => dirent.isDirectory() && ["schemas"].includes(dirent.name)
2138
2296
  ),
2139
- getFolderExports(join3(output, "api"), settings.useTsExtension),
2297
+ getFolderExports(join2(output, "api"), settings.useTsExtension),
2140
2298
  getFolderExports(
2141
- join3(output, "http"),
2299
+ join2(output, "http"),
2142
2300
  settings.useTsExtension,
2143
2301
  ["ts"],
2144
2302
  (dirent) => !["response.ts", "parser.ts"].includes(dirent.name)
@@ -2146,7 +2304,7 @@ ${template2(send_request_default, {})({ throwError: !style.errorAsValue, outputT
2146
2304
  ];
2147
2305
  if (modelsImports.length) {
2148
2306
  folders.push(
2149
- getFolderExports(join3(output, "models"), settings.useTsExtension)
2307
+ getFolderExports(join2(output, "models"), settings.useTsExtension)
2150
2308
  );
2151
2309
  }
2152
2310
  const [outputIndex, inputsIndex, apiIndex, httpIndex, modelsIndex] = await Promise.all(folders);
@@ -2161,7 +2319,7 @@ ${template2(send_request_default, {})({ throwError: !style.errorAsValue, outputT
2161
2319
  "index.ts": await getFolderExports(output, settings.useTsExtension, ["ts"])
2162
2320
  });
2163
2321
  if (settings.mode === "full") {
2164
- await writeFiles(settings.output, {
2322
+ const configFiles = {
2165
2323
  "package.json": {
2166
2324
  ignoreIfExists: true,
2167
2325
  content: JSON.stringify(
@@ -2200,7 +2358,14 @@ ${template2(send_request_default, {})({ throwError: !style.errorAsValue, outputT
2200
2358
  2
2201
2359
  )
2202
2360
  }
2203
- });
2361
+ };
2362
+ if (readme) {
2363
+ configFiles["README.md"] = {
2364
+ ignoreIfExists: true,
2365
+ content: readme
2366
+ };
2367
+ }
2368
+ await writeFiles(settings.output, configFiles);
2204
2369
  }
2205
2370
  await settings.formatCode?.({
2206
2371
  output,