@sdk-it/typescript 0.6.0 → 0.8.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
@@ -6,6 +6,7 @@ import { npmRunPathEnv } from "npm-run-path";
6
6
  import ts, { TypeFlags, symbolName } from "typescript";
7
7
  import debug from "debug";
8
8
  import ts2 from "typescript";
9
+ import "lodash-es";
9
10
  import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
10
11
  import { dirname as dirname2, isAbsolute, join as join2 } from "node:path";
11
12
  var deriveSymbol = Symbol.for("serialize");
@@ -45,7 +46,7 @@ async function getFolderExports(folder, extensions = ["ts"]) {
45
46
  const exports = [];
46
47
  for (const file of files) {
47
48
  if (file.isDirectory()) {
48
- exports.push(`export * from './${file.name}';`);
49
+ exports.push(`export * from './${file.name}/index.ts';`);
49
50
  } else if (file.name !== "index.ts" && extensions.includes(getExt(file.name))) {
50
51
  exports.push(`export * from './${file.name}';`);
51
52
  }
@@ -77,259 +78,8 @@ function toLitObject(obj, accessor = (value) => value) {
77
78
  import { get as get2, merge } from "lodash-es";
78
79
  import { camelcase as camelcase2, pascalcase as pascalcase2, spinalcase as spinalcase2 } from "stringcase";
79
80
 
80
- // packages/typescript/src/lib/json-zod.ts
81
+ // packages/typescript/src/lib/utils.ts
81
82
  import { get } from "lodash-es";
82
- function cleanRef(ref) {
83
- return ref.replace(/^#\//, "");
84
- }
85
- function parseRef(ref) {
86
- const parts = ref.split(ref);
87
- const [model] = parts.splice(-1);
88
- return { model, path: parts.join("/") };
89
- }
90
- function followRef(spec, ref) {
91
- const pathParts = cleanRef(ref).split("/");
92
- const entry = get(spec, pathParts);
93
- if (entry && "$ref" in entry) {
94
- return followRef(spec, entry.$ref);
95
- }
96
- return entry;
97
- }
98
- function jsonSchemaToZod(spec, schema, required = false, onRef, circularRefTracker = /* @__PURE__ */ new Set()) {
99
- if ("$ref" in schema) {
100
- const schemaName = cleanRef(schema.$ref).split("/").pop();
101
- if (circularRefTracker.has(schemaName)) {
102
- return schemaName;
103
- }
104
- circularRefTracker.add(schemaName);
105
- onRef(
106
- schemaName,
107
- jsonSchemaToZod(
108
- spec,
109
- followRef(spec, schema.$ref),
110
- required,
111
- onRef,
112
- circularRefTracker
113
- )
114
- );
115
- circularRefTracker.delete(schemaName);
116
- return schemaName;
117
- }
118
- if (schema.allOf && Array.isArray(schema.allOf)) {
119
- const allOfSchemas = schema.allOf.map(
120
- (sub) => jsonSchemaToZod(spec, sub, true, onRef, circularRefTracker)
121
- );
122
- return allOfSchemas.length ? `z.intersection(${allOfSchemas.join(", ")})` : allOfSchemas[0];
123
- }
124
- if (schema.anyOf && Array.isArray(schema.anyOf)) {
125
- const anyOfSchemas = schema.anyOf.map(
126
- (sub) => jsonSchemaToZod(spec, sub, false, onRef, circularRefTracker)
127
- );
128
- return anyOfSchemas.length > 1 ? `z.union([${anyOfSchemas.join(", ")}])${appendOptional(required)}` : (
129
- // Handle an invalid anyOf with one schema
130
- anyOfSchemas[0]
131
- );
132
- }
133
- if (schema.oneOf && Array.isArray(schema.oneOf)) {
134
- const oneOfSchemas = schema.oneOf.map((sub) => {
135
- if ("$ref" in sub) {
136
- const { model } = parseRef(sub.$ref);
137
- if (circularRefTracker.has(model)) {
138
- return model;
139
- }
140
- }
141
- return jsonSchemaToZod(spec, sub, false, onRef, circularRefTracker);
142
- });
143
- return oneOfSchemas.length > 1 ? `z.union([${oneOfSchemas.join(", ")}])${appendOptional(required)}` : (
144
- // Handle an invalid oneOf with one schema
145
- oneOfSchemas[0]
146
- );
147
- }
148
- if (schema.enum && Array.isArray(schema.enum)) {
149
- const enumVals = schema.enum.map((val) => JSON.stringify(val)).join(", ");
150
- return `z.enum([${enumVals}])${appendOptional(required)}`;
151
- }
152
- const types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : [];
153
- if (!types.length) {
154
- return `z.unknown()${appendOptional(required)}`;
155
- }
156
- if (types.length > 1) {
157
- const realTypes = types.filter((t) => t !== "null");
158
- if (realTypes.length === 1 && types.includes("null")) {
159
- const typeZod = basicTypeToZod(
160
- realTypes[0],
161
- schema,
162
- spec,
163
- false,
164
- onRef,
165
- circularRefTracker
166
- );
167
- return `${typeZod}.nullable()${appendOptional(required)}`;
168
- }
169
- const subSchemas = types.map(
170
- (t) => basicTypeToZod(t, schema, spec, false, onRef, circularRefTracker)
171
- );
172
- return `z.union([${subSchemas.join(", ")}])${appendOptional(required)}`;
173
- }
174
- return basicTypeToZod(
175
- types[0],
176
- schema,
177
- spec,
178
- required,
179
- onRef,
180
- circularRefTracker
181
- );
182
- }
183
- function basicTypeToZod(type, schema, spec, required = false, onRef, refProcessingStack) {
184
- switch (type) {
185
- case "string":
186
- return handleString(schema, required);
187
- case "number":
188
- case "integer":
189
- return handleNumber(schema, required);
190
- case "boolean":
191
- return `z.boolean()${appendDefault(schema.default)}${appendOptional(required)}`;
192
- case "object":
193
- return handleObject(schema, spec, required, onRef, refProcessingStack);
194
- case "array":
195
- return handleArray(schema, spec, required, onRef, refProcessingStack);
196
- case "null":
197
- return `z.null()${appendOptional(required)}`;
198
- default:
199
- return `z.unknown()${appendOptional(required)}`;
200
- }
201
- }
202
- function handleString(schema, required) {
203
- let base = "z.string()";
204
- switch (schema.format) {
205
- case "date-time":
206
- case "datetime":
207
- base = "z.coerce.date()";
208
- break;
209
- case "date":
210
- base = "z.coerce.date() /* or z.string() if you want raw date strings */";
211
- break;
212
- case "time":
213
- base = "z.string() /* optionally add .regex(...) for HH:MM:SS format */";
214
- break;
215
- case "email":
216
- base = "z.string().email()";
217
- break;
218
- case "uuid":
219
- base = "z.string().uuid()";
220
- break;
221
- case "url":
222
- case "uri":
223
- base = "z.string().url()";
224
- break;
225
- case "ipv4":
226
- base = 'z.string().ip({version: "v4"})';
227
- break;
228
- case "ipv6":
229
- base = 'z.string().ip({version: "v6"})';
230
- break;
231
- case "phone":
232
- base = "z.string() /* or add .regex(...) for phone formats */";
233
- break;
234
- case "byte":
235
- case "binary":
236
- base = "z.instanceof(Blob) /* consider base64 check if needed */";
237
- break;
238
- case "int64":
239
- base = "z.string() /* or z.bigint() if your app can handle it */";
240
- break;
241
- default:
242
- break;
243
- }
244
- return `${base}${appendDefault(schema.default)}${appendOptional(required)}`;
245
- }
246
- function handleNumber(schema, required) {
247
- let defaultValue = schema.default !== void 0 ? `.default(${schema.default})` : ``;
248
- let base = "z.number()";
249
- if (schema.format === "int64") {
250
- base = "z.bigint()";
251
- if (schema.default !== void 0) {
252
- defaultValue = `.default(BigInt(${schema.default}))`;
253
- }
254
- }
255
- if (schema.format === "int32") {
256
- base += ".int()";
257
- }
258
- if (typeof schema.exclusiveMinimum === "number") {
259
- base += `.gt(${schema.exclusiveMinimum})`;
260
- }
261
- if (typeof schema.exclusiveMaximum === "number") {
262
- base += `.lt(${schema.exclusiveMaximum})`;
263
- }
264
- if (typeof schema.minimum === "number") {
265
- base += schema.format === "int64" ? `.min(BigInt(${schema.minimum}))` : `.min(${schema.minimum})`;
266
- }
267
- if (typeof schema.maximum === "number") {
268
- base += schema.format === "int64" ? `.max(BigInt(${schema.maximum}))` : `.max(${schema.maximum})`;
269
- }
270
- if (typeof schema.multipleOf === "number") {
271
- base += `.refine((val) => Number.isInteger(val / ${schema.multipleOf}), "Must be a multiple of ${schema.multipleOf}")`;
272
- }
273
- return `${base}${defaultValue}${appendOptional(required)}`;
274
- }
275
- function handleObject(schema, spec, required = false, onRef, refProcessingStack) {
276
- const properties = schema.properties || {};
277
- const propEntries = Object.entries(properties).map(([key, propSchema]) => {
278
- const isRequired = (schema.required ?? []).includes(key);
279
- const zodPart = jsonSchemaToZod(
280
- spec,
281
- propSchema,
282
- isRequired,
283
- onRef,
284
- refProcessingStack
285
- );
286
- return `'${key}': ${zodPart}`;
287
- });
288
- let additionalProps = "";
289
- if (schema.additionalProperties) {
290
- if (typeof schema.additionalProperties === "object") {
291
- const addPropZod = jsonSchemaToZod(
292
- spec,
293
- schema.additionalProperties,
294
- true,
295
- onRef,
296
- refProcessingStack
297
- );
298
- additionalProps = `.catchall(${addPropZod})`;
299
- } else if (schema.additionalProperties === true) {
300
- additionalProps = `.catchall(z.unknown())`;
301
- }
302
- }
303
- const objectSchema = `z.object({${propEntries.join(", ")}})${additionalProps}`;
304
- return `${objectSchema}${appendOptional(required)}`;
305
- }
306
- function handleArray(schema, spec, required = false, onRef, refProcessingStack) {
307
- const { items } = schema;
308
- if (!items) {
309
- return `z.array(z.unknown())${appendOptional(required)}`;
310
- }
311
- if (Array.isArray(items)) {
312
- const tupleItems = items.map(
313
- (sub) => jsonSchemaToZod(spec, sub, true, onRef, refProcessingStack)
314
- );
315
- const base = `z.tuple([${tupleItems.join(", ")}])`;
316
- return `${base}${appendOptional(required)}`;
317
- }
318
- const itemsSchema = jsonSchemaToZod(
319
- spec,
320
- items,
321
- true,
322
- onRef,
323
- refProcessingStack
324
- );
325
- return `z.array(${itemsSchema})${appendOptional(required)}`;
326
- }
327
- function appendOptional(isRequired) {
328
- return isRequired ? "" : ".optional()";
329
- }
330
- function appendDefault(defaultValue) {
331
- return defaultValue !== void 0 ? `.default(${JSON.stringify(defaultValue)})` : "";
332
- }
333
83
 
334
84
  // packages/typescript/src/lib/sdk.ts
335
85
  import { camelcase, pascalcase, spinalcase } from "stringcase";
@@ -506,7 +256,7 @@ function generateClientSdk(spec) {
506
256
  const input = `z.infer<typeof ${schemaRef}>`;
507
257
  const endpoint = `${operation.trigger.method.toUpperCase()} ${operation.trigger.path}`;
508
258
  streamEmitter.addImport(
509
- `import type {${pascalcase(operation.name)}} from './outputs/${spinalcase(operation.name)}';`
259
+ `import type {${pascalcase(operation.name)}} from './outputs/${spinalcase(operation.name)}.ts';`
510
260
  );
511
261
  streamEmitter.addEndpoint(
512
262
  endpoint,
@@ -529,7 +279,7 @@ function generateClientSdk(spec) {
529
279
  );
530
280
  } else {
531
281
  emitter.addImport(
532
- `import type {${output.import}} from './outputs/${spinalcase(operation.name)}';`
282
+ `import type {${output.import}} from './outputs/${spinalcase(operation.name)}.ts';`
533
283
  );
534
284
  errors.push(...operation.errors ?? []);
535
285
  const addTypeParser = Object.keys(operation.schemas).length > 1;
@@ -571,7 +321,9 @@ function generateClientSdk(spec) {
571
321
  Object.entries(schemas).map(([key, value]) => [
572
322
  `inputs/${key}.ts`,
573
323
  [
574
- schemasImports.length ? `import {${removeDuplicates(schemasImports, (it) => it)}} from '../zod';` : "",
324
+ // schemasImports.length
325
+ // ? `import {${removeDuplicates(schemasImports, (it) => it)}} from '../zod';`
326
+ // : '',
575
327
  spec.commonZod ? 'import * as commonZod from "../zod.ts";' : "",
576
328
  ...value
577
329
  ].map((it) => it.trim()).filter(Boolean).join("\n") + "\n"
@@ -588,6 +340,22 @@ function generateClientSdk(spec) {
588
340
  function isRef(obj) {
589
341
  return "$ref" in obj;
590
342
  }
343
+ function cleanRef(ref) {
344
+ return ref.replace(/^#\//, "");
345
+ }
346
+ function parseRef(ref) {
347
+ const parts = ref.split("/");
348
+ const [model] = parts.splice(-1);
349
+ return { model, path: parts.join("/") };
350
+ }
351
+ function followRef(spec, ref) {
352
+ const pathParts = cleanRef(ref).split("/");
353
+ const entry = get(spec, pathParts);
354
+ if (entry && "$ref" in entry) {
355
+ return followRef(spec, entry.$ref);
356
+ }
357
+ return entry;
358
+ }
591
359
  function securityToOptions(security2, securitySchemas, staticIn) {
592
360
  securitySchemas ??= {};
593
361
  const options = {};
@@ -622,6 +390,418 @@ function securityToOptions(security2, securitySchemas, staticIn) {
622
390
  return options;
623
391
  }
624
392
 
393
+ // packages/typescript/src/lib/emitters/interface.ts
394
+ var TypeScriptDeserialzer = class {
395
+ circularRefTracker = /* @__PURE__ */ new Set();
396
+ #spec;
397
+ #onRef;
398
+ constructor(spec, onRef) {
399
+ this.#spec = spec;
400
+ this.#onRef = onRef;
401
+ }
402
+ /**
403
+ * Handle objects (properties)
404
+ */
405
+ object(schema, required = false) {
406
+ const properties = schema.properties || {};
407
+ const propEntries = Object.entries(properties).map(([key, propSchema]) => {
408
+ const isRequired = (schema.required ?? []).includes(key);
409
+ const tsType = this.handle(propSchema, isRequired);
410
+ return `${key}: ${tsType}`;
411
+ });
412
+ if (schema.additionalProperties) {
413
+ if (typeof schema.additionalProperties === "object") {
414
+ const indexType = this.handle(schema.additionalProperties, true);
415
+ propEntries.push(`[key: string]: ${indexType}`);
416
+ } else if (schema.additionalProperties === true) {
417
+ propEntries.push("[key: string]: any");
418
+ }
419
+ }
420
+ return `{ ${propEntries.join("; ")} }`;
421
+ }
422
+ /**
423
+ * Handle arrays (items could be a single schema or a tuple)
424
+ */
425
+ array(schema, required = false) {
426
+ const { items } = schema;
427
+ if (!items) {
428
+ return "any[]";
429
+ }
430
+ if (Array.isArray(items)) {
431
+ const tupleItems = items.map((sub) => this.handle(sub, true));
432
+ return `[${tupleItems.join(", ")}]`;
433
+ }
434
+ const itemsType = this.handle(items, true);
435
+ return `${itemsType}[]`;
436
+ }
437
+ /**
438
+ * Convert a basic type (string | number | boolean | object | array, etc.) to TypeScript
439
+ */
440
+ normal(type, schema, required = false) {
441
+ switch (type) {
442
+ case "string":
443
+ return this.string(schema, required);
444
+ case "number":
445
+ case "integer":
446
+ return this.number(schema, required);
447
+ case "boolean":
448
+ return appendOptional("boolean", required);
449
+ case "object":
450
+ return this.object(schema, required);
451
+ case "array":
452
+ return this.array(schema, required);
453
+ case "null":
454
+ return "null";
455
+ default:
456
+ return appendOptional("any", required);
457
+ }
458
+ }
459
+ ref($ref, required) {
460
+ const schemaName = cleanRef($ref).split("/").pop();
461
+ if (this.circularRefTracker.has(schemaName)) {
462
+ return schemaName;
463
+ }
464
+ this.circularRefTracker.add(schemaName);
465
+ this.#onRef(schemaName, this.handle(followRef(this.#spec, $ref), true));
466
+ this.circularRefTracker.delete(schemaName);
467
+ return appendOptional(schemaName, required);
468
+ }
469
+ allOf(schemas) {
470
+ const allOfTypes = schemas.map((sub) => this.handle(sub, true));
471
+ return allOfTypes.length > 1 ? `${allOfTypes.join(" & ")}` : allOfTypes[0];
472
+ }
473
+ anyOf(schemas, required) {
474
+ const anyOfTypes = schemas.map((sub) => this.handle(sub, true));
475
+ return appendOptional(
476
+ anyOfTypes.length > 1 ? `${anyOfTypes.join(" | ")}` : anyOfTypes[0],
477
+ required
478
+ );
479
+ }
480
+ oneOf(schemas, required) {
481
+ const oneOfTypes = schemas.map((sub) => {
482
+ if (isRef(sub)) {
483
+ const { model } = parseRef(sub.$ref);
484
+ if (this.circularRefTracker.has(model)) {
485
+ return model;
486
+ }
487
+ }
488
+ return this.handle(sub, false);
489
+ });
490
+ return appendOptional(
491
+ oneOfTypes.length > 1 ? `${oneOfTypes.join(" | ")}` : oneOfTypes[0],
492
+ required
493
+ );
494
+ }
495
+ enum(values, required) {
496
+ const enumValues = values.map((val) => typeof val === "string" ? `'${val}'` : `${val}`).join(" | ");
497
+ return appendOptional(enumValues, required);
498
+ }
499
+ /**
500
+ * Handle string type with formats
501
+ */
502
+ string(schema, required) {
503
+ let type;
504
+ switch (schema.format) {
505
+ case "date-time":
506
+ case "datetime":
507
+ case "date":
508
+ type = "Date";
509
+ break;
510
+ case "binary":
511
+ case "byte":
512
+ type = "Blob";
513
+ break;
514
+ case "int64":
515
+ type = "bigint";
516
+ break;
517
+ default:
518
+ type = "string";
519
+ }
520
+ return appendOptional(type, required);
521
+ }
522
+ /**
523
+ * Handle number/integer types with formats
524
+ */
525
+ number(schema, required) {
526
+ const type = schema.format === "int64" ? "bigint" : "number";
527
+ return appendOptional(type, required);
528
+ }
529
+ handle(schema, required) {
530
+ if (isRef(schema)) {
531
+ return this.ref(schema.$ref, required);
532
+ }
533
+ if (schema.allOf && Array.isArray(schema.allOf)) {
534
+ return this.allOf(schema.allOf);
535
+ }
536
+ if (schema.anyOf && Array.isArray(schema.anyOf)) {
537
+ return this.anyOf(schema.anyOf, required);
538
+ }
539
+ if (schema.oneOf && Array.isArray(schema.oneOf)) {
540
+ return this.oneOf(schema.oneOf, required);
541
+ }
542
+ if (schema.enum && Array.isArray(schema.enum)) {
543
+ return this.enum(schema.enum, required);
544
+ }
545
+ const types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : [];
546
+ if (!types.length) {
547
+ return appendOptional("any", required);
548
+ }
549
+ if (types.length > 1) {
550
+ const realTypes = types.filter((t) => t !== "null");
551
+ if (realTypes.length === 1 && types.includes("null")) {
552
+ const tsType = this.normal(realTypes[0], schema, false);
553
+ return appendOptional(`${tsType} | null`, required);
554
+ }
555
+ const typeResults = types.map((t) => this.normal(t, schema, false));
556
+ return appendOptional(typeResults.join(" | "), required);
557
+ }
558
+ return this.normal(types[0], schema, required);
559
+ }
560
+ /**
561
+ * Generate an interface declaration
562
+ */
563
+ generateInterface(name, schema) {
564
+ const content = this.handle(schema, true);
565
+ return `interface ${name} ${content}`;
566
+ }
567
+ };
568
+ function appendOptional(type, isRequired) {
569
+ return isRequired ? type : `${type} | undefined`;
570
+ }
571
+
572
+ // packages/typescript/src/lib/emitters/zod.ts
573
+ var ZodDeserialzer = class {
574
+ circularRefTracker = /* @__PURE__ */ new Set();
575
+ #spec;
576
+ #onRef;
577
+ constructor(spec, onRef) {
578
+ this.#spec = spec;
579
+ this.#onRef = onRef;
580
+ }
581
+ /**
582
+ * Handle objects (properties, additionalProperties).
583
+ */
584
+ object(schema, required = false) {
585
+ const properties = schema.properties || {};
586
+ const propEntries = Object.entries(properties).map(([key, propSchema]) => {
587
+ const isRequired = (schema.required ?? []).includes(key);
588
+ const zodPart = this.handle(propSchema, isRequired);
589
+ return `'${key}': ${zodPart}`;
590
+ });
591
+ let additionalProps = "";
592
+ if (schema.additionalProperties) {
593
+ if (typeof schema.additionalProperties === "object") {
594
+ const addPropZod = this.handle(schema.additionalProperties, true);
595
+ additionalProps = `.catchall(${addPropZod})`;
596
+ } else if (schema.additionalProperties === true) {
597
+ additionalProps = `.catchall(z.unknown())`;
598
+ }
599
+ }
600
+ const objectSchema = `z.object({${propEntries.join(", ")}})${additionalProps}`;
601
+ return `${objectSchema}${appendOptional2(required)}`;
602
+ }
603
+ /**
604
+ * Handle arrays (items could be a single schema or a tuple (array of schemas)).
605
+ * In JSON Schema 2020-12, `items` can be an array → tuple style.
606
+ */
607
+ array(schema, required = false) {
608
+ const { items } = schema;
609
+ if (!items) {
610
+ return `z.array(z.unknown())${appendOptional2(required)}`;
611
+ }
612
+ if (Array.isArray(items)) {
613
+ const tupleItems = items.map((sub) => this.handle(sub, true));
614
+ const base = `z.tuple([${tupleItems.join(", ")}])`;
615
+ return `${base}${appendOptional2(required)}`;
616
+ }
617
+ const itemsSchema = this.handle(items, true);
618
+ return `z.array(${itemsSchema})${appendOptional2(required)}`;
619
+ }
620
+ // oneOf() {}
621
+ // enum() {}
622
+ /**
623
+ * Convert a basic type (string | number | boolean | object | array, etc.) to Zod.
624
+ * We'll also handle .optional() if needed.
625
+ */
626
+ normal(type, schema, required = false) {
627
+ switch (type) {
628
+ case "string":
629
+ return this.string(schema, required);
630
+ case "number":
631
+ case "integer":
632
+ return this.number(schema, required);
633
+ case "boolean":
634
+ return `z.boolean()${appendDefault(schema.default)}${appendOptional2(required)}`;
635
+ case "object":
636
+ return this.object(schema, required);
637
+ case "array":
638
+ return this.array(schema, required);
639
+ case "null":
640
+ return `z.null()${appendOptional2(required)}`;
641
+ default:
642
+ return `z.unknown()${appendOptional2(required)}`;
643
+ }
644
+ }
645
+ ref($ref, required) {
646
+ const schemaName = cleanRef($ref).split("/").pop();
647
+ if (this.circularRefTracker.has(schemaName)) {
648
+ return schemaName;
649
+ }
650
+ this.circularRefTracker.add(schemaName);
651
+ this.#onRef(schemaName, this.handle(followRef(this.#spec, $ref), required));
652
+ this.circularRefTracker.delete(schemaName);
653
+ return schemaName;
654
+ }
655
+ allOf(schemas) {
656
+ const allOfSchemas = schemas.map((sub) => this.handle(sub, true));
657
+ return allOfSchemas.length ? `z.intersection(${allOfSchemas.join(", ")})` : allOfSchemas[0];
658
+ }
659
+ anyOf(schemas, required) {
660
+ const anyOfSchemas = schemas.map((sub) => this.handle(sub, false));
661
+ return anyOfSchemas.length > 1 ? `z.union([${anyOfSchemas.join(", ")}])${appendOptional2(required)}` : (
662
+ // Handle an invalid anyOf with one schema
663
+ anyOfSchemas[0]
664
+ );
665
+ }
666
+ oneOf(schemas, required) {
667
+ const oneOfSchemas = schemas.map((sub) => {
668
+ if ("$ref" in sub) {
669
+ const { model } = parseRef(sub.$ref);
670
+ if (this.circularRefTracker.has(model)) {
671
+ return model;
672
+ }
673
+ }
674
+ return this.handle(sub, false);
675
+ });
676
+ return oneOfSchemas.length > 1 ? `z.union([${oneOfSchemas.join(", ")}])${appendOptional2(required)}` : (
677
+ // Handle an invalid oneOf with one schema
678
+ oneOfSchemas[0]
679
+ );
680
+ }
681
+ enum(values, required) {
682
+ const enumVals = values.map((val) => JSON.stringify(val)).join(", ");
683
+ return `z.enum([${enumVals}])${appendOptional2(required)}`;
684
+ }
685
+ /**
686
+ * Handle a `string` schema with possible format keywords (JSON Schema).
687
+ */
688
+ string(schema, required) {
689
+ let base = "z.string()";
690
+ switch (schema.format) {
691
+ case "date-time":
692
+ case "datetime":
693
+ base = "z.coerce.date()";
694
+ break;
695
+ case "date":
696
+ base = "z.coerce.date() /* or z.string() if you want raw date strings */";
697
+ break;
698
+ case "time":
699
+ base = "z.string() /* optionally add .regex(...) for HH:MM:SS format */";
700
+ break;
701
+ case "email":
702
+ base = "z.string().email()";
703
+ break;
704
+ case "uuid":
705
+ base = "z.string().uuid()";
706
+ break;
707
+ case "url":
708
+ case "uri":
709
+ base = "z.string().url()";
710
+ break;
711
+ case "ipv4":
712
+ base = 'z.string().ip({version: "v4"})';
713
+ break;
714
+ case "ipv6":
715
+ base = 'z.string().ip({version: "v6"})';
716
+ break;
717
+ case "phone":
718
+ base = "z.string() /* or add .regex(...) for phone formats */";
719
+ break;
720
+ case "byte":
721
+ case "binary":
722
+ base = "z.instanceof(Blob) /* consider base64 check if needed */";
723
+ break;
724
+ case "int64":
725
+ base = "z.string() /* or z.bigint() if your app can handle it */";
726
+ break;
727
+ default:
728
+ break;
729
+ }
730
+ return `${base}${appendDefault(schema.default)}${appendOptional2(required)}`;
731
+ }
732
+ /**
733
+ * Handle number/integer constraints from OpenAPI/JSON Schema.
734
+ * In 3.1, exclusiveMinimum/Maximum hold the actual numeric threshold,
735
+ * rather than a boolean toggling `minimum`/`maximum`.
736
+ */
737
+ number(schema, required) {
738
+ let defaultValue = schema.default !== void 0 ? `.default(${schema.default})` : ``;
739
+ let base = "z.number()";
740
+ if (schema.format === "int64") {
741
+ base = "z.bigint()";
742
+ if (schema.default !== void 0) {
743
+ defaultValue = `.default(BigInt(${schema.default}))`;
744
+ }
745
+ }
746
+ if (schema.format === "int32") {
747
+ base += ".int()";
748
+ }
749
+ if (typeof schema.exclusiveMinimum === "number") {
750
+ base += `.gt(${schema.exclusiveMinimum})`;
751
+ }
752
+ if (typeof schema.exclusiveMaximum === "number") {
753
+ base += `.lt(${schema.exclusiveMaximum})`;
754
+ }
755
+ if (typeof schema.minimum === "number") {
756
+ base += schema.format === "int64" ? `.min(BigInt(${schema.minimum}))` : `.min(${schema.minimum})`;
757
+ }
758
+ if (typeof schema.maximum === "number") {
759
+ base += schema.format === "int64" ? `.max(BigInt(${schema.maximum}))` : `.max(${schema.maximum})`;
760
+ }
761
+ if (typeof schema.multipleOf === "number") {
762
+ base += `.refine((val) => Number.isInteger(val / ${schema.multipleOf}), "Must be a multiple of ${schema.multipleOf}")`;
763
+ }
764
+ return `${base}${defaultValue}${appendOptional2(required)}`;
765
+ }
766
+ handle(schema, required) {
767
+ if (isRef(schema)) {
768
+ return this.ref(schema.$ref, required);
769
+ }
770
+ if (schema.allOf && Array.isArray(schema.allOf)) {
771
+ return this.allOf(schema.allOf ?? []);
772
+ }
773
+ if (schema.anyOf && Array.isArray(schema.anyOf)) {
774
+ return this.anyOf(schema.anyOf ?? [], required);
775
+ }
776
+ if (schema.oneOf && Array.isArray(schema.oneOf)) {
777
+ return this.oneOf(schema.oneOf ?? [], required);
778
+ }
779
+ if (schema.enum && Array.isArray(schema.enum)) {
780
+ return this.enum(schema.enum, required);
781
+ }
782
+ const types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : [];
783
+ if (!types.length) {
784
+ return `z.unknown()${appendOptional2(required)}`;
785
+ }
786
+ if (types.length > 1) {
787
+ const realTypes = types.filter((t) => t !== "null");
788
+ if (realTypes.length === 1 && types.includes("null")) {
789
+ const typeZod = this.normal(realTypes[0], schema, false);
790
+ return `${typeZod}.nullable()${appendOptional2(required)}`;
791
+ }
792
+ const subSchemas = types.map((t) => this.normal(t, schema, false));
793
+ return `z.union([${subSchemas.join(", ")}])${appendOptional2(required)}`;
794
+ }
795
+ return this.normal(types[0], schema, required);
796
+ }
797
+ };
798
+ function appendOptional2(isRequired) {
799
+ return isRequired ? "" : ".optional()";
800
+ }
801
+ function appendDefault(defaultValue) {
802
+ return defaultValue !== void 0 ? `.default(${JSON.stringify(defaultValue)})` : "";
803
+ }
804
+
625
805
  // packages/typescript/src/lib/generator.ts
626
806
  var responses = {
627
807
  "400": "BadRequest",
@@ -653,6 +833,30 @@ var defaults = {
653
833
  }
654
834
  };
655
835
  function generateCode(config) {
836
+ const imports = [];
837
+ const zodDeserialzer = new ZodDeserialzer(config.spec, (schemaName, zod) => {
838
+ commonSchemas[schemaName] = zod;
839
+ imports.push({
840
+ defaultImport: void 0,
841
+ isTypeOnly: false,
842
+ moduleSpecifier: `../models/${schemaName}.ts`,
843
+ namedImports: [{ isTypeOnly: false, name: schemaName }],
844
+ namespaceImport: void 0
845
+ });
846
+ });
847
+ const typeScriptDeserialzer = new TypeScriptDeserialzer(
848
+ config.spec,
849
+ (schemaName, zod) => {
850
+ commonSchemas[schemaName] = zod;
851
+ imports.push({
852
+ defaultImport: void 0,
853
+ isTypeOnly: true,
854
+ moduleSpecifier: `../models/${schemaName}.ts`,
855
+ namedImports: [{ isTypeOnly: true, name: schemaName }],
856
+ namespaceImport: void 0
857
+ });
858
+ }
859
+ );
656
860
  const groups = {};
657
861
  const commonSchemas = {};
658
862
  const outputs = {};
@@ -664,7 +868,6 @@ function generateCode(config) {
664
868
  const groupName = (operation.tags ?? ["unknown"])[0];
665
869
  groups[groupName] ??= [];
666
870
  const inputs = {};
667
- const imports = [];
668
871
  const additionalProperties = [];
669
872
  for (const param of operation.parameters ?? []) {
670
873
  if (isRef(param)) {
@@ -708,8 +911,7 @@ function generateCode(config) {
708
911
  const content = isRef(operation.requestBody) ? get2(followRef(config.spec, operation.requestBody.$ref), ["content"]) : operation.requestBody.content;
709
912
  for (const type in content) {
710
913
  const schema = isRef(content[type].schema) ? followRef(config.spec, content[type].schema.$ref) : content[type].schema;
711
- types[shortContenTypeMap[type]] = jsonSchemaToZod(
712
- config.spec,
914
+ types[shortContenTypeMap[type]] = zodDeserialzer.handle(
713
915
  merge(schema, {
714
916
  required: additionalProperties.filter((p) => p.required).map((p) => p.name),
715
917
  properties: additionalProperties.reduce(
@@ -720,17 +922,7 @@ function generateCode(config) {
720
922
  {}
721
923
  )
722
924
  }),
723
- true,
724
- (schemaName, zod) => {
725
- commonSchemas[schemaName] = zod;
726
- imports.push({
727
- defaultImport: void 0,
728
- isTypeOnly: false,
729
- moduleSpecifier: "../zod",
730
- namedImports: [{ isTypeOnly: false, name: schemaName }],
731
- namespaceImport: void 0
732
- });
733
- }
925
+ true
734
926
  );
735
927
  }
736
928
  if (content["application/json"]) {
@@ -743,30 +935,20 @@ function generateCode(config) {
743
935
  contentType = "json";
744
936
  }
745
937
  } else {
746
- types[shortContenTypeMap["application/json"]] = jsonSchemaToZod(
747
- config.spec,
938
+ const properties = additionalProperties.reduce(
939
+ (acc, p) => ({
940
+ ...acc,
941
+ [p.name]: p.schema
942
+ }),
943
+ {}
944
+ );
945
+ types[shortContenTypeMap["application/json"]] = zodDeserialzer.handle(
748
946
  {
749
947
  type: "object",
750
948
  required: additionalProperties.filter((p) => p.required).map((p) => p.name),
751
- properties: additionalProperties.reduce(
752
- (acc, p) => ({
753
- ...acc,
754
- [p.name]: p.schema
755
- }),
756
- {}
757
- )
949
+ properties
758
950
  },
759
- true,
760
- (schemaName, zod) => {
761
- commonSchemas[schemaName] = zod;
762
- imports.push({
763
- defaultImport: void 0,
764
- isTypeOnly: false,
765
- moduleSpecifier: "./zod",
766
- namedImports: [{ isTypeOnly: false, name: schemaName }],
767
- namespaceImport: void 0
768
- });
769
- }
951
+ true
770
952
  );
771
953
  }
772
954
  const errors = [];
@@ -783,34 +965,23 @@ function generateCode(config) {
783
965
  foundResponse = true;
784
966
  const responseContent = get2(response, ["content"]);
785
967
  const isJson = responseContent && responseContent["application/json"];
786
- const responseSchema = isJson ? jsonSchemaToZod(
787
- config.spec,
968
+ const responseSchema = isJson ? typeScriptDeserialzer.handle(
788
969
  responseContent["application/json"].schema,
789
- true,
790
- (schemaName, zod) => {
791
- commonSchemas[schemaName] = zod;
792
- imports.push({
793
- defaultImport: void 0,
794
- isTypeOnly: false,
795
- moduleSpecifier: "../zod",
796
- namedImports: [{ isTypeOnly: false, name: schemaName }],
797
- namespaceImport: void 0
798
- });
799
- }
800
- ) : "z.instanceof(ReadableStream)";
970
+ true
971
+ ) : "ReadableStream";
801
972
  output.push(
802
973
  importsToString(mergeImports(Object.values(imports).flat())).join(
803
974
  "\n"
804
975
  )
805
976
  );
806
977
  output.push(
807
- `export const ${pascalcase2(operationName + " output")} = ${responseSchema}`
978
+ `export type ${pascalcase2(operationName + " output")} = ${responseSchema}`
808
979
  );
809
980
  }
810
981
  }
811
982
  if (!foundResponse) {
812
983
  output.push(
813
- `export const ${pascalcase2(operationName + " output")} = z.void()`
984
+ `export type ${pascalcase2(operationName + " output")} = void`
814
985
  );
815
986
  }
816
987
  outputs[`${spinalcase2(operationName)}.ts`] = output.join("\n");
@@ -824,7 +995,7 @@ function generateCode(config) {
824
995
  schemas: types,
825
996
  formatOutput: () => ({
826
997
  import: pascalcase2(operationName + " output"),
827
- use: `z.infer<typeof ${pascalcase2(operationName + " output")}>`
998
+ use: pascalcase2(operationName + " output")
828
999
  }),
829
1000
  trigger: {
830
1001
  path,
@@ -851,17 +1022,17 @@ function mergeImports(imports) {
851
1022
  return Object.values(merged);
852
1023
  }
853
1024
  function importsToString(imports) {
854
- return imports.map((i) => {
855
- if (i.defaultImport) {
856
- return `import ${i.defaultImport} from '${i.moduleSpecifier}'`;
1025
+ return imports.map((it) => {
1026
+ if (it.defaultImport) {
1027
+ return `import ${it.defaultImport} from '${it.moduleSpecifier}'`;
857
1028
  }
858
- if (i.namespaceImport) {
859
- return `import * as ${i.namespaceImport} from '${i.moduleSpecifier}'`;
1029
+ if (it.namespaceImport) {
1030
+ return `import * as ${it.namespaceImport} from '${it.moduleSpecifier}'`;
860
1031
  }
861
- if (i.namedImports) {
862
- return `import {${removeDuplicates(i.namedImports, (it) => it.name).map((n) => n.name).join(", ")}} from '${i.moduleSpecifier}'`;
1032
+ if (it.namedImports) {
1033
+ return `import {${removeDuplicates(it.namedImports, (it2) => it2.name).map((n) => `${n.isTypeOnly ? "type" : ""} ${n.name}`).join(", ")}} from '${it.moduleSpecifier}'`;
863
1034
  }
864
- throw new Error(`Invalid import ${JSON.stringify(i)}`);
1035
+ throw new Error(`Invalid import ${JSON.stringify(it)}`);
865
1036
  });
866
1037
  }
867
1038
 
@@ -907,6 +1078,7 @@ async function generate(spec, settings) {
907
1078
  style: "github",
908
1079
  target: "javascript"
909
1080
  });
1081
+ const output = settings.mode === "full" ? join(settings.output, "src") : settings.output;
910
1082
  const options = security(spec);
911
1083
  const clientFiles = generateClientSdk({
912
1084
  name: settings.name || "Client",
@@ -914,41 +1086,65 @@ async function generate(spec, settings) {
914
1086
  servers: spec.servers?.map((server) => server.url) || [],
915
1087
  options
916
1088
  });
917
- await writeFiles(settings.output, {
1089
+ await writeFiles(output, {
918
1090
  "outputs/index.ts": "",
919
1091
  "inputs/index.ts": ""
920
1092
  // 'README.md': readme,
921
1093
  });
922
- await writeFiles(join(settings.output, "http"), {
1094
+ await writeFiles(join(output, "http"), {
923
1095
  "parse-response.ts": client_default2,
924
1096
  "send-request.ts": send_request_default,
925
1097
  "response.ts": response_default,
926
1098
  "parser.ts": parser_default,
927
1099
  "request.ts": request_default
928
1100
  });
929
- await writeFiles(join(settings.output, "outputs"), outputs);
930
- await writeFiles(settings.output, {
1101
+ await writeFiles(join(output, "outputs"), outputs);
1102
+ const imports = Object.entries(commonSchemas).map(([name]) => name);
1103
+ await writeFiles(output, {
931
1104
  ...clientFiles,
932
- "zod.ts": `import z from 'zod';
933
- ${Object.entries(commonSchemas).map(([name, schema]) => `export const ${name} = ${schema};`).join("\n")}`
1105
+ ...Object.fromEntries(
1106
+ Object.entries(commonSchemas).map(([name, schema]) => [
1107
+ `models/${name}.ts`,
1108
+ [
1109
+ `import { z } from 'zod';`,
1110
+ ...exclude(imports, [name]).map(
1111
+ (it) => `import type { ${it} } from './${it}.ts';`
1112
+ ),
1113
+ `export type ${name} = ${schema};`
1114
+ ].join("\n")
1115
+ ])
1116
+ )
934
1117
  });
935
- const [index, outputIndex, inputsIndex, httpIndex] = await Promise.all([
936
- getFolderExports(settings.output),
937
- getFolderExports(join(settings.output, "outputs")),
938
- getFolderExports(join(settings.output, "inputs")),
939
- getFolderExports(join(settings.output, "http"))
1118
+ const [index, outputIndex, inputsIndex, httpIndex, modelsIndex] = await Promise.all([
1119
+ getFolderExports(output),
1120
+ getFolderExports(join(output, "outputs")),
1121
+ getFolderExports(join(output, "inputs")),
1122
+ getFolderExports(join(output, "http")),
1123
+ getFolderExports(join(output, "models"))
940
1124
  ]);
941
- await writeFiles(settings.output, {
1125
+ await writeFiles(output, {
942
1126
  "index.ts": index,
943
1127
  "outputs/index.ts": outputIndex,
944
1128
  "inputs/index.ts": inputsIndex,
945
- "http/index.ts": httpIndex
1129
+ "http/index.ts": httpIndex,
1130
+ "models/index.ts": modelsIndex
946
1131
  });
1132
+ if (settings.mode === "full") {
1133
+ await writeFiles(settings.output, {
1134
+ "package.json": {
1135
+ ignoreIfExists: true,
1136
+ content: `{"type":"module","main":"./src/index.ts","dependencies":{"fast-content-type-parse":"^2.0.1"}}`
1137
+ }
1138
+ });
1139
+ }
947
1140
  await settings.formatCode?.({
948
- output: settings.output,
1141
+ output,
949
1142
  env: npmRunPathEnv()
950
1143
  });
951
1144
  }
1145
+ function exclude(list, exclude2) {
1146
+ return list.filter((it) => !exclude2.includes(it));
1147
+ }
952
1148
 
953
1149
  // packages/typescript/src/lib/watcher.ts
954
1150
  import { watch as nodeWatch } from "node:fs/promises";