@sdk-it/typescript 0.19.1 → 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
@@ -3,7 +3,11 @@ import { template as template2 } from "lodash-es";
3
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
- import { getFolderExports, methods, writeFiles } from "@sdk-it/core";
6
+ import { methods, pascalcase as pascalcase3 } from "@sdk-it/core";
7
+ import {
8
+ getFolderExports,
9
+ writeFiles
10
+ } from "@sdk-it/core/file-system.js";
7
11
 
8
12
  // packages/typescript/src/lib/client.ts
9
13
  import { toLitObject } from "@sdk-it/core";
@@ -132,10 +136,11 @@ export class ${spec.name} {
132
136
  import { merge, template } from "lodash-es";
133
137
  import { join } from "node:path";
134
138
  import { camelcase as camelcase3, pascalcase as pascalcase2, spinalcase as spinalcase2 } from "stringcase";
135
- import { followRef as followRef4, isEmpty, isRef as isRef5 } from "@sdk-it/core";
139
+ import { followRef as followRef5, isEmpty, isRef as isRef6 } from "@sdk-it/core";
136
140
 
137
141
  // packages/spec/dist/lib/operation.js
138
142
  import { camelcase } from "stringcase";
143
+ import { followRef, isRef } from "@sdk-it/core";
139
144
  var defaults = {
140
145
  operationId: (operation, path, method) => {
141
146
  if (operation.operationId) {
@@ -150,9 +155,18 @@ var defaults = {
150
155
  );
151
156
  },
152
157
  tag: (operation, path) => {
153
- return operation.tags?.[0] || determineGenericTag(path, operation);
158
+ return operation.tags?.[0] ? sanitizeTag(operation.tags?.[0]) : determineGenericTag(path, operation);
154
159
  }
155
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
+ }
156
170
  function forEachOperation(config, callback) {
157
171
  const result = [];
158
172
  for (const [path, pathItem] of Object.entries(config.spec.paths ?? {})) {
@@ -176,7 +190,8 @@ function forEachOperation(config, callback) {
176
190
  {
177
191
  ...operation,
178
192
  parameters: [...parameters, ...operation.parameters ?? []],
179
- operationId: operationName
193
+ operationId: operationName,
194
+ responses: resolveResponses(config.spec, operation)
180
195
  }
181
196
  )
182
197
  );
@@ -185,15 +200,11 @@ function forEachOperation(config, callback) {
185
200
  return result;
186
201
  }
187
202
  var reservedKeywords = /* @__PURE__ */ new Set([
188
- "abstract",
189
- "arguments",
190
203
  "await",
191
- "boolean",
204
+ // Reserved in async functions
192
205
  "break",
193
- "byte",
194
206
  "case",
195
207
  "catch",
196
- "char",
197
208
  "class",
198
209
  "const",
199
210
  "continue",
@@ -201,85 +212,59 @@ var reservedKeywords = /* @__PURE__ */ new Set([
201
212
  "default",
202
213
  "delete",
203
214
  "do",
204
- "double",
205
215
  "else",
206
216
  "enum",
207
- "eval",
208
217
  "export",
209
218
  "extends",
210
219
  "false",
211
- "final",
212
220
  "finally",
213
- "float",
214
221
  "for",
215
222
  "function",
216
- "goto",
217
223
  "if",
218
224
  "implements",
225
+ // Strict mode
219
226
  "import",
220
227
  "in",
221
228
  "instanceof",
222
- "int",
223
229
  "interface",
230
+ // Strict mode
224
231
  "let",
225
- "long",
226
- "native",
232
+ // Strict mode
227
233
  "new",
228
234
  "null",
229
235
  "package",
236
+ // Strict mode
230
237
  "private",
238
+ // Strict mode
231
239
  "protected",
240
+ // Strict mode
232
241
  "public",
242
+ // Strict mode
233
243
  "return",
234
- "short",
235
244
  "static",
245
+ // Strict mode
236
246
  "super",
237
247
  "switch",
238
- "synchronized",
239
248
  "this",
240
249
  "throw",
241
- "throws",
242
- "transient",
243
250
  "true",
244
251
  "try",
245
252
  "typeof",
246
253
  "var",
247
254
  "void",
248
- "volatile",
249
255
  "while",
250
256
  "with",
251
257
  "yield",
252
- // Potentially problematic identifiers / Common Verbs used as tags
253
- "object",
254
- "string",
255
- "number",
256
- "any",
257
- "unknown",
258
- "never",
259
- "get",
260
- "list",
261
- "create",
262
- "update",
263
- "delete",
264
- "post",
265
- "put",
266
- "patch",
267
- "do",
268
- "send",
269
- "add",
270
- "remove",
271
- "set",
272
- "find",
273
- "search",
274
- "check",
275
- "make"
276
- // 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"
277
262
  ]);
278
263
  function sanitizeTag(camelCasedTag) {
279
264
  if (/^\d/.test(camelCasedTag)) {
280
265
  return `_${camelCasedTag}`;
281
266
  }
282
- return reservedKeywords.has(camelCasedTag) ? `${camelCasedTag}_` : camelCasedTag;
267
+ return reservedKeywords.has(camelcase(camelCasedTag)) ? `${camelCasedTag}_` : camelCasedTag;
283
268
  }
284
269
  function determineGenericTag(pathString, operation) {
285
270
  const operationId = operation.operationId || "";
@@ -303,7 +288,6 @@ function determineGenericTag(pathString, operation) {
303
288
  "search",
304
289
  "check",
305
290
  "make"
306
- // Added make
307
291
  ]);
308
292
  const segments = pathString.split("/").filter(Boolean);
309
293
  const potentialCandidates = segments.filter(
@@ -397,9 +381,9 @@ function determineGenericTag(pathString, operation) {
397
381
  }
398
382
 
399
383
  // packages/typescript/src/lib/emitters/zod.ts
400
- import { cleanRef, followRef, isRef, parseRef } from "@sdk-it/core";
384
+ import { cleanRef, followRef as followRef2, isRef as isRef2 } from "@sdk-it/core";
401
385
  var ZodDeserialzer = class {
402
- circularRefTracker = /* @__PURE__ */ new Set();
386
+ generatedRefs = /* @__PURE__ */ new Set();
403
387
  #spec;
404
388
  #onRef;
405
389
  constructor(spec, onRef) {
@@ -473,15 +457,14 @@ var ZodDeserialzer = class {
473
457
  }
474
458
  ref($ref, required) {
475
459
  const schemaName = cleanRef($ref).split("/").pop();
476
- if (this.circularRefTracker.has(schemaName)) {
460
+ if (this.generatedRefs.has(schemaName)) {
477
461
  return schemaName;
478
462
  }
479
- this.circularRefTracker.add(schemaName);
463
+ this.generatedRefs.add(schemaName);
480
464
  this.#onRef?.(
481
465
  schemaName,
482
- this.handle(followRef(this.#spec, $ref), required)
466
+ this.handle(followRef2(this.#spec, $ref), required)
483
467
  );
484
- this.circularRefTracker.delete(schemaName);
485
468
  return schemaName;
486
469
  }
487
470
  allOf(schemas, required) {
@@ -509,15 +492,7 @@ var ZodDeserialzer = class {
509
492
  return `z.union([${anyOfSchemas.join(", ")}])${appendOptional(required)}`;
510
493
  }
511
494
  oneOf(schemas, required) {
512
- const oneOfSchemas = schemas.map((sub) => {
513
- if (isRef(sub)) {
514
- const { model } = parseRef(sub.$ref);
515
- if (this.circularRefTracker.has(model)) {
516
- return `${model}${appendOptional(required)}`;
517
- }
518
- }
519
- return this.handle(sub, true);
520
- });
495
+ const oneOfSchemas = schemas.map((sub) => this.handle(sub, true));
521
496
  if (oneOfSchemas.length === 1) {
522
497
  return `${oneOfSchemas[0]}${appendOptional(required)}`;
523
498
  }
@@ -537,6 +512,10 @@ var ZodDeserialzer = class {
537
512
  */
538
513
  string(schema) {
539
514
  let base = "z.string()";
515
+ if (schema.contentEncoding === "binary") {
516
+ base = "z.instanceof(Blob)";
517
+ return base;
518
+ }
540
519
  switch (schema.format) {
541
520
  case "date-time":
542
521
  case "datetime":
@@ -614,7 +593,7 @@ var ZodDeserialzer = class {
614
593
  return { base, defaultValue };
615
594
  }
616
595
  handle(schema, required) {
617
- if (isRef(schema)) {
596
+ if (isRef2(schema)) {
618
597
  return `${this.ref(schema.$ref, true)}${appendOptional(required)}`;
619
598
  }
620
599
  if (schema.allOf && Array.isArray(schema.allOf)) {
@@ -661,12 +640,12 @@ function appendDefault(defaultValue) {
661
640
  // packages/typescript/src/lib/sdk.ts
662
641
  import { get } from "lodash-es";
663
642
  import { camelcase as camelcase2, pascalcase, spinalcase } from "stringcase";
664
- import { followRef as followRef3, isRef as isRef4, toLitObject as toLitObject2 } from "@sdk-it/core";
643
+ import { followRef as followRef4, isRef as isRef5, toLitObject as toLitObject2 } from "@sdk-it/core";
665
644
 
666
645
  // packages/typescript/src/lib/emitters/interface.ts
667
- import { cleanRef as cleanRef2, followRef as followRef2, isRef as isRef2, parseRef as parseRef2 } from "@sdk-it/core";
646
+ import { cleanRef as cleanRef2, followRef as followRef3, isRef as isRef3 } from "@sdk-it/core";
668
647
  var TypeScriptDeserialzer = class {
669
- circularRefTracker = /* @__PURE__ */ new Set();
648
+ generatedRefs = /* @__PURE__ */ new Set();
670
649
  #spec;
671
650
  #onRef;
672
651
  constructor(spec, onRef) {
@@ -677,7 +656,7 @@ var TypeScriptDeserialzer = class {
677
656
  return `'${value}'`;
678
657
  };
679
658
  #isInternal = (schema) => {
680
- return isRef2(schema) ? false : !!schema["x-internal"];
659
+ return isRef3(schema) ? false : !!schema["x-internal"];
681
660
  };
682
661
  /**
683
662
  * Handle objects (properties)
@@ -739,15 +718,14 @@ var TypeScriptDeserialzer = class {
739
718
  }
740
719
  ref($ref, required) {
741
720
  const schemaName = cleanRef2($ref).split("/").pop();
742
- if (this.circularRefTracker.has(schemaName)) {
721
+ if (this.generatedRefs.has(schemaName)) {
743
722
  return schemaName;
744
723
  }
745
- this.circularRefTracker.add(schemaName);
724
+ this.generatedRefs.add(schemaName);
746
725
  this.#onRef?.(
747
726
  schemaName,
748
- this.handle(followRef2(this.#spec, $ref), required)
727
+ this.handle(followRef3(this.#spec, $ref), required)
749
728
  );
750
- this.circularRefTracker.delete(schemaName);
751
729
  return appendOptional2(schemaName, required);
752
730
  }
753
731
  allOf(schemas) {
@@ -763,12 +741,6 @@ var TypeScriptDeserialzer = class {
763
741
  }
764
742
  oneOf(schemas, required) {
765
743
  const oneOfTypes = schemas.map((sub) => {
766
- if (isRef2(sub)) {
767
- const { model } = parseRef2(sub.$ref);
768
- if (this.circularRefTracker.has(model)) {
769
- return model;
770
- }
771
- }
772
744
  return this.handle(sub, false);
773
745
  });
774
746
  return appendOptional2(
@@ -785,6 +757,9 @@ var TypeScriptDeserialzer = class {
785
757
  */
786
758
  string(schema, required) {
787
759
  let type;
760
+ if (schema.contentEncoding === "binary") {
761
+ return appendOptional2("Blob", required);
762
+ }
788
763
  switch (schema.format) {
789
764
  case "date-time":
790
765
  case "datetime":
@@ -811,7 +786,7 @@ var TypeScriptDeserialzer = class {
811
786
  return appendOptional2(type, required);
812
787
  }
813
788
  handle(schema, required) {
814
- if (isRef2(schema)) {
789
+ if (isRef3(schema)) {
815
790
  return this.ref(schema.$ref, required);
816
791
  }
817
792
  if (schema.allOf && Array.isArray(schema.allOf)) {
@@ -856,7 +831,7 @@ function appendOptional2(type, isRequired) {
856
831
  }
857
832
 
858
833
  // packages/typescript/src/lib/utils.ts
859
- import { isRef as isRef3, removeDuplicates } from "@sdk-it/core";
834
+ import { isRef as isRef4, removeDuplicates } from "@sdk-it/core";
860
835
  function securityToOptions(security2, securitySchemes, staticIn) {
861
836
  securitySchemes ??= {};
862
837
  const options = {};
@@ -866,7 +841,7 @@ function securityToOptions(security2, securitySchemes, staticIn) {
866
841
  continue;
867
842
  }
868
843
  const schema = securitySchemes[name];
869
- if (isRef3(schema)) {
844
+ if (isRef4(schema)) {
870
845
  throw new Error(`Ref security schemas are not supported`);
871
846
  }
872
847
  if (schema.type === "http") {
@@ -1025,7 +1000,7 @@ function toEndpoint(groupName, spec, specOperation, operation, utils) {
1025
1000
  return statusCode >= 200 && statusCode < 300;
1026
1001
  }).length > 1;
1027
1002
  for (const status in specOperation.responses) {
1028
- const response = isRef4(specOperation.responses[status]) ? followRef3(spec, specOperation.responses[status].$ref) : specOperation.responses[status];
1003
+ const response = isRef5(specOperation.responses[status]) ? followRef4(spec, specOperation.responses[status].$ref) : specOperation.responses[status];
1029
1004
  const handled = handleResponse(
1030
1005
  spec,
1031
1006
  operation.name,
@@ -1135,7 +1110,7 @@ function handleResponse(spec, operationName, status, response, utils, numbered)
1135
1110
  let responseSchema = parser === "chunked" ? "ReadableStream" : "void";
1136
1111
  if (isJson) {
1137
1112
  const schema = responseContent["application/json"].schema;
1138
- const isObject = !isRef4(schema) && schema.type === "object";
1113
+ const isObject = !isRef5(schema) && schema.type === "object";
1139
1114
  if (isObject && schema.properties) {
1140
1115
  schema.properties["[http.KIND]"] = {
1141
1116
  "x-internal": true,
@@ -1143,7 +1118,7 @@ function handleResponse(spec, operationName, status, response, utils, numbered)
1143
1118
  type: "string"
1144
1119
  };
1145
1120
  schema.required ??= [];
1146
- schema.required.push("[KIND]");
1121
+ schema.required.push("[http.KIND]");
1147
1122
  }
1148
1123
  responseSchema = typeScriptDeserialzer.handle(schema, true);
1149
1124
  }
@@ -1200,9 +1175,9 @@ function generateCode(config) {
1200
1175
  groups[entry.groupName] ??= [];
1201
1176
  endpoints[entry.groupName] ??= [];
1202
1177
  const inputs = {};
1203
- const additionalProperties = [];
1178
+ const additionalProperties = {};
1204
1179
  for (const param of operation.parameters ?? []) {
1205
- if (isRef5(param)) {
1180
+ if (isRef6(param)) {
1206
1181
  throw new Error(`Found reference in parameter ${param.$ref}`);
1207
1182
  }
1208
1183
  if (!param.schema) {
@@ -1212,24 +1187,22 @@ function generateCode(config) {
1212
1187
  in: param.in,
1213
1188
  schema: ""
1214
1189
  };
1215
- additionalProperties.push(param);
1190
+ additionalProperties[param.name] = param;
1216
1191
  }
1217
1192
  const security2 = operation.security ?? [];
1218
1193
  const securitySchemes = config.spec.components?.securitySchemes ?? {};
1219
1194
  const securityOptions = securityToOptions(security2, securitySchemes);
1220
1195
  Object.assign(inputs, securityOptions);
1221
- additionalProperties.push(
1222
- ...Object.entries(securityOptions).map(
1223
- ([name, value]) => ({
1224
- name,
1225
- required: false,
1226
- schema: {
1227
- type: "string"
1228
- },
1229
- in: value.in
1230
- })
1231
- )
1232
- );
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
+ });
1233
1206
  const schemas = {};
1234
1207
  const shortContenTypeMap = {
1235
1208
  "application/json": "json",
@@ -1244,9 +1217,9 @@ function generateCode(config) {
1244
1217
  };
1245
1218
  let outgoingContentType;
1246
1219
  if (!isEmpty(operation.requestBody)) {
1247
- const requestBody = isRef5(operation.requestBody) ? followRef4(config.spec, operation.requestBody.$ref) : operation.requestBody;
1220
+ const requestBody = isRef6(operation.requestBody) ? followRef5(config.spec, operation.requestBody.$ref) : operation.requestBody;
1248
1221
  for (const type in requestBody.content) {
1249
- const ctSchema = isRef5(requestBody.content[type].schema) ? followRef4(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;
1250
1223
  if (!ctSchema) {
1251
1224
  console.warn(
1252
1225
  `Schema not found for ${type} in ${entry.method} ${entry.path}`
@@ -1264,9 +1237,9 @@ function generateCode(config) {
1264
1237
  };
1265
1238
  }
1266
1239
  const schema = merge({}, objectSchema, {
1267
- required: additionalProperties.filter((p) => p.required).map((p) => p.name),
1268
- properties: additionalProperties.reduce(
1269
- (acc, p) => ({
1240
+ required: Object.values(additionalProperties).filter((p) => p.required).map((p) => p.name),
1241
+ properties: Object.entries(additionalProperties).reduce(
1242
+ (acc, [, p]) => ({
1270
1243
  ...acc,
1271
1244
  [p.name]: p.schema
1272
1245
  }),
@@ -1286,8 +1259,8 @@ function generateCode(config) {
1286
1259
  outgoingContentType = "json";
1287
1260
  }
1288
1261
  } else {
1289
- const properties = additionalProperties.reduce(
1290
- (acc, p) => ({
1262
+ const properties = Object.entries(additionalProperties).reduce(
1263
+ (acc, [, p]) => ({
1291
1264
  ...acc,
1292
1265
  [p.name]: p.schema
1293
1266
  }),
@@ -1296,7 +1269,7 @@ function generateCode(config) {
1296
1269
  schemas[shortContenTypeMap["application/json"]] = zodDeserialzer.handle(
1297
1270
  {
1298
1271
  type: "object",
1299
- required: additionalProperties.filter((p) => p.required).map((p) => p.name),
1272
+ required: Object.values(additionalProperties).filter((p) => p.required).map((p) => p.name),
1300
1273
  properties
1301
1274
  },
1302
1275
  true
@@ -1421,7 +1394,6 @@ ${allSchemas.map((it) => it.use).join(",\n")}
1421
1394
  join("api", `${spinalcase2(name)}.ts`),
1422
1395
  `${[
1423
1396
  ...imps,
1424
- // ...imports,
1425
1397
  `import z from 'zod';`,
1426
1398
  `import * as http from '${config.makeImport("../http/response")}';`,
1427
1399
  `import { toRequest, json, urlencoded, nobody, formdata, createUrl } from '${config.makeImport("../http/request")}';`,
@@ -1441,8 +1413,8 @@ ${endpoint.flatMap((it) => it.schemas).join(",\n")}
1441
1413
  };
1442
1414
  }
1443
1415
  function toProps(spec, schemaOrRef, aggregator = []) {
1444
- if (isRef5(schemaOrRef)) {
1445
- const schema = followRef4(spec, schemaOrRef.$ref);
1416
+ if (isRef6(schemaOrRef)) {
1417
+ const schema = followRef5(spec, schemaOrRef.$ref);
1446
1418
  return toProps(spec, schema, aggregator);
1447
1419
  } else if (schemaOrRef.type === "object") {
1448
1420
  for (const [name] of Object.entries(schemaOrRef.properties ?? {})) {
@@ -1912,6 +1884,311 @@ export type SuccessfulResponse =
1912
1884
  // packages/typescript/src/lib/http/send-request.txt
1913
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";
1914
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
+
1915
2192
  // packages/typescript/src/lib/generate.ts
1916
2193
  function security(spec) {
1917
2194
  const security2 = spec.security || [];
@@ -1956,13 +2233,14 @@ async function generate(spec, settings) {
1956
2233
  );
1957
2234
  const output = settings.mode === "full" ? join2(settings.output, "src") : settings.output;
1958
2235
  const options = security(spec);
1959
- const clientName = settings.name || "Client";
2236
+ const clientName = settings.name?.trim() ? pascalcase3(settings.name) : "Client";
2237
+ const readme = settings.readme ? toReadme(spec) : "";
1960
2238
  const inputFiles = generateInputs(groups, commonZod, makeImport);
2239
+ console.log("Writing to", output);
1961
2240
  await writeFiles(output, {
1962
2241
  "outputs/.gitkeep": "",
1963
2242
  "inputs/.gitkeep": "",
1964
2243
  "models/.getkeep": ""
1965
- // 'README.md': readme,
1966
2244
  });
1967
2245
  await writeFiles(join2(output, "http"), {
1968
2246
  "interceptors.ts": `
@@ -2041,7 +2319,7 @@ ${template2(send_request_default, {})({ throwError: !style.errorAsValue, outputT
2041
2319
  "index.ts": await getFolderExports(output, settings.useTsExtension, ["ts"])
2042
2320
  });
2043
2321
  if (settings.mode === "full") {
2044
- await writeFiles(settings.output, {
2322
+ const configFiles = {
2045
2323
  "package.json": {
2046
2324
  ignoreIfExists: true,
2047
2325
  content: JSON.stringify(
@@ -2080,7 +2358,14 @@ ${template2(send_request_default, {})({ throwError: !style.errorAsValue, outputT
2080
2358
  2
2081
2359
  )
2082
2360
  }
2083
- });
2361
+ };
2362
+ if (readme) {
2363
+ configFiles["README.md"] = {
2364
+ ignoreIfExists: true,
2365
+ content: readme
2366
+ };
2367
+ }
2368
+ await writeFiles(settings.output, configFiles);
2084
2369
  }
2085
2370
  await settings.formatCode?.({
2086
2371
  output,