@sdk-it/typescript 0.7.0 → 0.8.1
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 +538 -359
- package/dist/index.js.map +4 -4
- package/dist/lib/emitters/interface.d.ts +43 -0
- package/dist/lib/emitters/interface.d.ts.map +1 -0
- package/dist/lib/emitters/json-zod.d.ts +10 -0
- package/dist/lib/emitters/json-zod.d.ts.map +1 -0
- package/dist/lib/emitters/zod.d.ts +43 -0
- package/dist/lib/emitters/zod.d.ts.map +1 -0
- package/dist/lib/generate.d.ts.map +1 -1
- package/dist/lib/generator.d.ts.map +1 -1
- package/dist/lib/interface.d.ts +2 -0
- package/dist/lib/interface.d.ts.map +1 -0
- package/dist/lib/readme-generator.d.ts.map +1 -1
- package/dist/lib/sdk.d.ts +0 -13
- package/dist/lib/sdk.d.ts.map +1 -1
- package/dist/lib/utils.d.ts +21 -1
- package/dist/lib/utils.d.ts.map +1 -1
- package/package.json +2 -2
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/
|
|
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";
|
|
@@ -455,7 +205,6 @@ function generateClientSdk(spec) {
|
|
|
455
205
|
const emitter = new Emitter();
|
|
456
206
|
const streamEmitter = new StreamEmitter();
|
|
457
207
|
const schemas = {};
|
|
458
|
-
const schemasImports = [];
|
|
459
208
|
const schemaEndpoint = new SchemaEndpoint();
|
|
460
209
|
const errors = [];
|
|
461
210
|
for (const [name, operations] of Object.entries(spec.operations)) {
|
|
@@ -474,9 +223,6 @@ function generateClientSdk(spec) {
|
|
|
474
223
|
const schemaName = camelcase(`${operation.name} schema`);
|
|
475
224
|
const schema = `export const ${schemaName} = ${Object.keys(operation.schemas).length === 1 ? Object.values(operation.schemas)[0] : toLitObject(operation.schemas)};`;
|
|
476
225
|
schemas[featureSchemaFileName].push(schema);
|
|
477
|
-
schemasImports.push(
|
|
478
|
-
...operation.imports.map((it) => (it.namedImports ?? []).map((it2) => it2.name)).flat()
|
|
479
|
-
);
|
|
480
226
|
const schemaRef = `${featureSchemaFileName}.${schemaName}`;
|
|
481
227
|
const output = operation.formatOutput();
|
|
482
228
|
const inputHeaders = [];
|
|
@@ -506,7 +252,7 @@ function generateClientSdk(spec) {
|
|
|
506
252
|
const input = `z.infer<typeof ${schemaRef}>`;
|
|
507
253
|
const endpoint = `${operation.trigger.method.toUpperCase()} ${operation.trigger.path}`;
|
|
508
254
|
streamEmitter.addImport(
|
|
509
|
-
`import type {${pascalcase(operation.name)}} from './outputs/${spinalcase(operation.name)}';`
|
|
255
|
+
`import type {${pascalcase(operation.name)}} from './outputs/${spinalcase(operation.name)}.ts';`
|
|
510
256
|
);
|
|
511
257
|
streamEmitter.addEndpoint(
|
|
512
258
|
endpoint,
|
|
@@ -529,7 +275,7 @@ function generateClientSdk(spec) {
|
|
|
529
275
|
);
|
|
530
276
|
} else {
|
|
531
277
|
emitter.addImport(
|
|
532
|
-
`import type {${output.import}} from './outputs/${spinalcase(operation.name)}';`
|
|
278
|
+
`import type {${output.import}} from './outputs/${spinalcase(operation.name)}.ts';`
|
|
533
279
|
);
|
|
534
280
|
errors.push(...operation.errors ?? []);
|
|
535
281
|
const addTypeParser = Object.keys(operation.schemas).length > 1;
|
|
@@ -571,7 +317,9 @@ function generateClientSdk(spec) {
|
|
|
571
317
|
Object.entries(schemas).map(([key, value]) => [
|
|
572
318
|
`inputs/${key}.ts`,
|
|
573
319
|
[
|
|
574
|
-
schemasImports.length
|
|
320
|
+
// schemasImports.length
|
|
321
|
+
// ? `import {${removeDuplicates(schemasImports, (it) => it)}} from '../zod';`
|
|
322
|
+
// : '',
|
|
575
323
|
spec.commonZod ? 'import * as commonZod from "../zod.ts";' : "",
|
|
576
324
|
...value
|
|
577
325
|
].map((it) => it.trim()).filter(Boolean).join("\n") + "\n"
|
|
@@ -588,6 +336,22 @@ function generateClientSdk(spec) {
|
|
|
588
336
|
function isRef(obj) {
|
|
589
337
|
return "$ref" in obj;
|
|
590
338
|
}
|
|
339
|
+
function cleanRef(ref) {
|
|
340
|
+
return ref.replace(/^#\//, "");
|
|
341
|
+
}
|
|
342
|
+
function parseRef(ref) {
|
|
343
|
+
const parts = ref.split("/");
|
|
344
|
+
const [model] = parts.splice(-1);
|
|
345
|
+
return { model, path: parts.join("/") };
|
|
346
|
+
}
|
|
347
|
+
function followRef(spec, ref) {
|
|
348
|
+
const pathParts = cleanRef(ref).split("/");
|
|
349
|
+
const entry = get(spec, pathParts);
|
|
350
|
+
if (entry && "$ref" in entry) {
|
|
351
|
+
return followRef(spec, entry.$ref);
|
|
352
|
+
}
|
|
353
|
+
return entry;
|
|
354
|
+
}
|
|
591
355
|
function securityToOptions(security2, securitySchemas, staticIn) {
|
|
592
356
|
securitySchemas ??= {};
|
|
593
357
|
const options = {};
|
|
@@ -621,6 +385,447 @@ function securityToOptions(security2, securitySchemas, staticIn) {
|
|
|
621
385
|
}
|
|
622
386
|
return options;
|
|
623
387
|
}
|
|
388
|
+
function mergeImports(imports) {
|
|
389
|
+
const merged = {};
|
|
390
|
+
for (const i of imports) {
|
|
391
|
+
merged[i.moduleSpecifier] = merged[i.moduleSpecifier] ?? {
|
|
392
|
+
moduleSpecifier: i.moduleSpecifier,
|
|
393
|
+
defaultImport: i.defaultImport,
|
|
394
|
+
namespaceImport: i.namespaceImport,
|
|
395
|
+
namedImports: []
|
|
396
|
+
};
|
|
397
|
+
if (i.namedImports) {
|
|
398
|
+
merged[i.moduleSpecifier].namedImports.push(...i.namedImports);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return Object.values(merged);
|
|
402
|
+
}
|
|
403
|
+
function importsToString(...imports) {
|
|
404
|
+
return imports.map((it) => {
|
|
405
|
+
if (it.defaultImport) {
|
|
406
|
+
return `import ${it.defaultImport} from '${it.moduleSpecifier}'`;
|
|
407
|
+
}
|
|
408
|
+
if (it.namespaceImport) {
|
|
409
|
+
return `import * as ${it.namespaceImport} from '${it.moduleSpecifier}'`;
|
|
410
|
+
}
|
|
411
|
+
if (it.namedImports) {
|
|
412
|
+
return `import {${removeDuplicates(it.namedImports, (it2) => it2.name).map((n) => `${n.isTypeOnly ? "type" : ""} ${n.name}`).join(", ")}} from '${it.moduleSpecifier}'`;
|
|
413
|
+
}
|
|
414
|
+
throw new Error(`Invalid import ${JSON.stringify(it)}`);
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// packages/typescript/src/lib/emitters/interface.ts
|
|
419
|
+
var TypeScriptDeserialzer = class {
|
|
420
|
+
circularRefTracker = /* @__PURE__ */ new Set();
|
|
421
|
+
#spec;
|
|
422
|
+
#onRef;
|
|
423
|
+
constructor(spec, onRef) {
|
|
424
|
+
this.#spec = spec;
|
|
425
|
+
this.#onRef = onRef;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Handle objects (properties)
|
|
429
|
+
*/
|
|
430
|
+
object(schema, required = false) {
|
|
431
|
+
const properties = schema.properties || {};
|
|
432
|
+
const propEntries = Object.entries(properties).map(([key, propSchema]) => {
|
|
433
|
+
const isRequired = (schema.required ?? []).includes(key);
|
|
434
|
+
const tsType = this.handle(propSchema, isRequired);
|
|
435
|
+
return `${key}: ${tsType}`;
|
|
436
|
+
});
|
|
437
|
+
if (schema.additionalProperties) {
|
|
438
|
+
if (typeof schema.additionalProperties === "object") {
|
|
439
|
+
const indexType = this.handle(schema.additionalProperties, true);
|
|
440
|
+
propEntries.push(`[key: string]: ${indexType}`);
|
|
441
|
+
} else if (schema.additionalProperties === true) {
|
|
442
|
+
propEntries.push("[key: string]: any");
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return `{ ${propEntries.join("; ")} }`;
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Handle arrays (items could be a single schema or a tuple)
|
|
449
|
+
*/
|
|
450
|
+
array(schema, required = false) {
|
|
451
|
+
const { items } = schema;
|
|
452
|
+
if (!items) {
|
|
453
|
+
return "any[]";
|
|
454
|
+
}
|
|
455
|
+
if (Array.isArray(items)) {
|
|
456
|
+
const tupleItems = items.map((sub) => this.handle(sub, true));
|
|
457
|
+
return `[${tupleItems.join(", ")}]`;
|
|
458
|
+
}
|
|
459
|
+
const itemsType = this.handle(items, true);
|
|
460
|
+
return `${itemsType}[]`;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Convert a basic type (string | number | boolean | object | array, etc.) to TypeScript
|
|
464
|
+
*/
|
|
465
|
+
normal(type, schema, required = false) {
|
|
466
|
+
switch (type) {
|
|
467
|
+
case "string":
|
|
468
|
+
return this.string(schema, required);
|
|
469
|
+
case "number":
|
|
470
|
+
case "integer":
|
|
471
|
+
return this.number(schema, required);
|
|
472
|
+
case "boolean":
|
|
473
|
+
return appendOptional("boolean", required);
|
|
474
|
+
case "object":
|
|
475
|
+
return this.object(schema, required);
|
|
476
|
+
case "array":
|
|
477
|
+
return this.array(schema, required);
|
|
478
|
+
case "null":
|
|
479
|
+
return "null";
|
|
480
|
+
default:
|
|
481
|
+
return appendOptional("any", required);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
ref($ref, required) {
|
|
485
|
+
const schemaName = cleanRef($ref).split("/").pop();
|
|
486
|
+
if (this.circularRefTracker.has(schemaName)) {
|
|
487
|
+
return schemaName;
|
|
488
|
+
}
|
|
489
|
+
this.circularRefTracker.add(schemaName);
|
|
490
|
+
this.#onRef(schemaName, this.handle(followRef(this.#spec, $ref), true));
|
|
491
|
+
this.circularRefTracker.delete(schemaName);
|
|
492
|
+
return appendOptional(schemaName, required);
|
|
493
|
+
}
|
|
494
|
+
allOf(schemas) {
|
|
495
|
+
const allOfTypes = schemas.map((sub) => this.handle(sub, true));
|
|
496
|
+
return allOfTypes.length > 1 ? `${allOfTypes.join(" & ")}` : allOfTypes[0];
|
|
497
|
+
}
|
|
498
|
+
anyOf(schemas, required) {
|
|
499
|
+
const anyOfTypes = schemas.map((sub) => this.handle(sub, true));
|
|
500
|
+
return appendOptional(
|
|
501
|
+
anyOfTypes.length > 1 ? `${anyOfTypes.join(" | ")}` : anyOfTypes[0],
|
|
502
|
+
required
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
oneOf(schemas, required) {
|
|
506
|
+
const oneOfTypes = schemas.map((sub) => {
|
|
507
|
+
if (isRef(sub)) {
|
|
508
|
+
const { model } = parseRef(sub.$ref);
|
|
509
|
+
if (this.circularRefTracker.has(model)) {
|
|
510
|
+
return model;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
return this.handle(sub, false);
|
|
514
|
+
});
|
|
515
|
+
return appendOptional(
|
|
516
|
+
oneOfTypes.length > 1 ? `${oneOfTypes.join(" | ")}` : oneOfTypes[0],
|
|
517
|
+
required
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
enum(values, required) {
|
|
521
|
+
const enumValues = values.map((val) => typeof val === "string" ? `'${val}'` : `${val}`).join(" | ");
|
|
522
|
+
return appendOptional(enumValues, required);
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Handle string type with formats
|
|
526
|
+
*/
|
|
527
|
+
string(schema, required) {
|
|
528
|
+
let type;
|
|
529
|
+
switch (schema.format) {
|
|
530
|
+
case "date-time":
|
|
531
|
+
case "datetime":
|
|
532
|
+
case "date":
|
|
533
|
+
type = "Date";
|
|
534
|
+
break;
|
|
535
|
+
case "binary":
|
|
536
|
+
case "byte":
|
|
537
|
+
type = "Blob";
|
|
538
|
+
break;
|
|
539
|
+
case "int64":
|
|
540
|
+
type = "bigint";
|
|
541
|
+
break;
|
|
542
|
+
default:
|
|
543
|
+
type = "string";
|
|
544
|
+
}
|
|
545
|
+
return appendOptional(type, required);
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Handle number/integer types with formats
|
|
549
|
+
*/
|
|
550
|
+
number(schema, required) {
|
|
551
|
+
const type = schema.format === "int64" ? "bigint" : "number";
|
|
552
|
+
return appendOptional(type, required);
|
|
553
|
+
}
|
|
554
|
+
handle(schema, required) {
|
|
555
|
+
if (isRef(schema)) {
|
|
556
|
+
return this.ref(schema.$ref, required);
|
|
557
|
+
}
|
|
558
|
+
if (schema.allOf && Array.isArray(schema.allOf)) {
|
|
559
|
+
return this.allOf(schema.allOf);
|
|
560
|
+
}
|
|
561
|
+
if (schema.anyOf && Array.isArray(schema.anyOf)) {
|
|
562
|
+
return this.anyOf(schema.anyOf, required);
|
|
563
|
+
}
|
|
564
|
+
if (schema.oneOf && Array.isArray(schema.oneOf)) {
|
|
565
|
+
return this.oneOf(schema.oneOf, required);
|
|
566
|
+
}
|
|
567
|
+
if (schema.enum && Array.isArray(schema.enum)) {
|
|
568
|
+
return this.enum(schema.enum, required);
|
|
569
|
+
}
|
|
570
|
+
const types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : [];
|
|
571
|
+
if (!types.length) {
|
|
572
|
+
return appendOptional("any", required);
|
|
573
|
+
}
|
|
574
|
+
if (types.length > 1) {
|
|
575
|
+
const realTypes = types.filter((t) => t !== "null");
|
|
576
|
+
if (realTypes.length === 1 && types.includes("null")) {
|
|
577
|
+
const tsType = this.normal(realTypes[0], schema, false);
|
|
578
|
+
return appendOptional(`${tsType} | null`, required);
|
|
579
|
+
}
|
|
580
|
+
const typeResults = types.map((t) => this.normal(t, schema, false));
|
|
581
|
+
return appendOptional(typeResults.join(" | "), required);
|
|
582
|
+
}
|
|
583
|
+
return this.normal(types[0], schema, required);
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Generate an interface declaration
|
|
587
|
+
*/
|
|
588
|
+
generateInterface(name, schema) {
|
|
589
|
+
const content = this.handle(schema, true);
|
|
590
|
+
return `interface ${name} ${content}`;
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
function appendOptional(type, isRequired) {
|
|
594
|
+
return isRequired ? type : `${type} | undefined`;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// packages/typescript/src/lib/emitters/zod.ts
|
|
598
|
+
var ZodDeserialzer = class {
|
|
599
|
+
circularRefTracker = /* @__PURE__ */ new Set();
|
|
600
|
+
#spec;
|
|
601
|
+
#onRef;
|
|
602
|
+
constructor(spec, onRef) {
|
|
603
|
+
this.#spec = spec;
|
|
604
|
+
this.#onRef = onRef;
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Handle objects (properties, additionalProperties).
|
|
608
|
+
*/
|
|
609
|
+
object(schema, required = false) {
|
|
610
|
+
const properties = schema.properties || {};
|
|
611
|
+
const propEntries = Object.entries(properties).map(([key, propSchema]) => {
|
|
612
|
+
const isRequired = (schema.required ?? []).includes(key);
|
|
613
|
+
const zodPart = this.handle(propSchema, isRequired);
|
|
614
|
+
return `'${key}': ${zodPart}`;
|
|
615
|
+
});
|
|
616
|
+
let additionalProps = "";
|
|
617
|
+
if (schema.additionalProperties) {
|
|
618
|
+
if (typeof schema.additionalProperties === "object") {
|
|
619
|
+
const addPropZod = this.handle(schema.additionalProperties, true);
|
|
620
|
+
additionalProps = `.catchall(${addPropZod})`;
|
|
621
|
+
} else if (schema.additionalProperties === true) {
|
|
622
|
+
additionalProps = `.catchall(z.unknown())`;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
const objectSchema = `z.object({${propEntries.join(", ")}})${additionalProps}`;
|
|
626
|
+
return `${objectSchema}${appendOptional2(required)}`;
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Handle arrays (items could be a single schema or a tuple (array of schemas)).
|
|
630
|
+
* In JSON Schema 2020-12, `items` can be an array → tuple style.
|
|
631
|
+
*/
|
|
632
|
+
array(schema, required = false) {
|
|
633
|
+
const { items } = schema;
|
|
634
|
+
if (!items) {
|
|
635
|
+
return `z.array(z.unknown())${appendOptional2(required)}`;
|
|
636
|
+
}
|
|
637
|
+
if (Array.isArray(items)) {
|
|
638
|
+
const tupleItems = items.map((sub) => this.handle(sub, true));
|
|
639
|
+
const base = `z.tuple([${tupleItems.join(", ")}])`;
|
|
640
|
+
return `${base}${appendOptional2(required)}`;
|
|
641
|
+
}
|
|
642
|
+
const itemsSchema = this.handle(items, true);
|
|
643
|
+
return `z.array(${itemsSchema})${appendOptional2(required)}`;
|
|
644
|
+
}
|
|
645
|
+
// oneOf() {}
|
|
646
|
+
// enum() {}
|
|
647
|
+
/**
|
|
648
|
+
* Convert a basic type (string | number | boolean | object | array, etc.) to Zod.
|
|
649
|
+
* We'll also handle .optional() if needed.
|
|
650
|
+
*/
|
|
651
|
+
normal(type, schema, required = false) {
|
|
652
|
+
switch (type) {
|
|
653
|
+
case "string":
|
|
654
|
+
return this.string(schema, required);
|
|
655
|
+
case "number":
|
|
656
|
+
case "integer":
|
|
657
|
+
return this.number(schema, required);
|
|
658
|
+
case "boolean":
|
|
659
|
+
return `z.boolean()${appendDefault(schema.default)}${appendOptional2(required)}`;
|
|
660
|
+
case "object":
|
|
661
|
+
return this.object(schema, required);
|
|
662
|
+
case "array":
|
|
663
|
+
return this.array(schema, required);
|
|
664
|
+
case "null":
|
|
665
|
+
return `z.null()${appendOptional2(required)}`;
|
|
666
|
+
default:
|
|
667
|
+
return `z.unknown()${appendOptional2(required)}`;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
ref($ref, required) {
|
|
671
|
+
const schemaName = cleanRef($ref).split("/").pop();
|
|
672
|
+
if (this.circularRefTracker.has(schemaName)) {
|
|
673
|
+
return schemaName;
|
|
674
|
+
}
|
|
675
|
+
this.circularRefTracker.add(schemaName);
|
|
676
|
+
this.#onRef?.(schemaName, this.handle(followRef(this.#spec, $ref), required));
|
|
677
|
+
this.circularRefTracker.delete(schemaName);
|
|
678
|
+
return schemaName;
|
|
679
|
+
}
|
|
680
|
+
allOf(schemas) {
|
|
681
|
+
const allOfSchemas = schemas.map((sub) => this.handle(sub, true));
|
|
682
|
+
return allOfSchemas.length ? `z.intersection(${allOfSchemas.join(", ")})` : allOfSchemas[0];
|
|
683
|
+
}
|
|
684
|
+
anyOf(schemas, required) {
|
|
685
|
+
const anyOfSchemas = schemas.map((sub) => this.handle(sub, false));
|
|
686
|
+
return anyOfSchemas.length > 1 ? `z.union([${anyOfSchemas.join(", ")}])${appendOptional2(required)}` : (
|
|
687
|
+
// Handle an invalid anyOf with one schema
|
|
688
|
+
anyOfSchemas[0]
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
oneOf(schemas, required) {
|
|
692
|
+
const oneOfSchemas = schemas.map((sub) => {
|
|
693
|
+
if ("$ref" in sub) {
|
|
694
|
+
const { model } = parseRef(sub.$ref);
|
|
695
|
+
if (this.circularRefTracker.has(model)) {
|
|
696
|
+
return model;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
return this.handle(sub, false);
|
|
700
|
+
});
|
|
701
|
+
return oneOfSchemas.length > 1 ? `z.union([${oneOfSchemas.join(", ")}])${appendOptional2(required)}` : (
|
|
702
|
+
// Handle an invalid oneOf with one schema
|
|
703
|
+
oneOfSchemas[0]
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
enum(values, required) {
|
|
707
|
+
const enumVals = values.map((val) => JSON.stringify(val)).join(", ");
|
|
708
|
+
return `z.enum([${enumVals}])${appendOptional2(required)}`;
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Handle a `string` schema with possible format keywords (JSON Schema).
|
|
712
|
+
*/
|
|
713
|
+
string(schema, required) {
|
|
714
|
+
let base = "z.string()";
|
|
715
|
+
switch (schema.format) {
|
|
716
|
+
case "date-time":
|
|
717
|
+
case "datetime":
|
|
718
|
+
base = "z.coerce.date()";
|
|
719
|
+
break;
|
|
720
|
+
case "date":
|
|
721
|
+
base = "z.coerce.date() /* or z.string() if you want raw date strings */";
|
|
722
|
+
break;
|
|
723
|
+
case "time":
|
|
724
|
+
base = "z.string() /* optionally add .regex(...) for HH:MM:SS format */";
|
|
725
|
+
break;
|
|
726
|
+
case "email":
|
|
727
|
+
base = "z.string().email()";
|
|
728
|
+
break;
|
|
729
|
+
case "uuid":
|
|
730
|
+
base = "z.string().uuid()";
|
|
731
|
+
break;
|
|
732
|
+
case "url":
|
|
733
|
+
case "uri":
|
|
734
|
+
base = "z.string().url()";
|
|
735
|
+
break;
|
|
736
|
+
case "ipv4":
|
|
737
|
+
base = 'z.string().ip({version: "v4"})';
|
|
738
|
+
break;
|
|
739
|
+
case "ipv6":
|
|
740
|
+
base = 'z.string().ip({version: "v6"})';
|
|
741
|
+
break;
|
|
742
|
+
case "phone":
|
|
743
|
+
base = "z.string() /* or add .regex(...) for phone formats */";
|
|
744
|
+
break;
|
|
745
|
+
case "byte":
|
|
746
|
+
case "binary":
|
|
747
|
+
base = "z.instanceof(Blob) /* consider base64 check if needed */";
|
|
748
|
+
break;
|
|
749
|
+
case "int64":
|
|
750
|
+
base = "z.string() /* or z.bigint() if your app can handle it */";
|
|
751
|
+
break;
|
|
752
|
+
default:
|
|
753
|
+
break;
|
|
754
|
+
}
|
|
755
|
+
return `${base}${appendDefault(schema.default)}${appendOptional2(required)}`;
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* Handle number/integer constraints from OpenAPI/JSON Schema.
|
|
759
|
+
* In 3.1, exclusiveMinimum/Maximum hold the actual numeric threshold,
|
|
760
|
+
* rather than a boolean toggling `minimum`/`maximum`.
|
|
761
|
+
*/
|
|
762
|
+
number(schema, required) {
|
|
763
|
+
let defaultValue = schema.default !== void 0 ? `.default(${schema.default})` : ``;
|
|
764
|
+
let base = "z.number()";
|
|
765
|
+
if (schema.format === "int64") {
|
|
766
|
+
base = "z.bigint()";
|
|
767
|
+
if (schema.default !== void 0) {
|
|
768
|
+
defaultValue = `.default(BigInt(${schema.default}))`;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
if (schema.format === "int32") {
|
|
772
|
+
base += ".int()";
|
|
773
|
+
}
|
|
774
|
+
if (typeof schema.exclusiveMinimum === "number") {
|
|
775
|
+
base += `.gt(${schema.exclusiveMinimum})`;
|
|
776
|
+
}
|
|
777
|
+
if (typeof schema.exclusiveMaximum === "number") {
|
|
778
|
+
base += `.lt(${schema.exclusiveMaximum})`;
|
|
779
|
+
}
|
|
780
|
+
if (typeof schema.minimum === "number") {
|
|
781
|
+
base += schema.format === "int64" ? `.min(BigInt(${schema.minimum}))` : `.min(${schema.minimum})`;
|
|
782
|
+
}
|
|
783
|
+
if (typeof schema.maximum === "number") {
|
|
784
|
+
base += schema.format === "int64" ? `.max(BigInt(${schema.maximum}))` : `.max(${schema.maximum})`;
|
|
785
|
+
}
|
|
786
|
+
if (typeof schema.multipleOf === "number") {
|
|
787
|
+
base += `.refine((val) => Number.isInteger(val / ${schema.multipleOf}), "Must be a multiple of ${schema.multipleOf}")`;
|
|
788
|
+
}
|
|
789
|
+
return `${base}${defaultValue}${appendOptional2(required)}`;
|
|
790
|
+
}
|
|
791
|
+
handle(schema, required) {
|
|
792
|
+
if (isRef(schema)) {
|
|
793
|
+
return this.ref(schema.$ref, required);
|
|
794
|
+
}
|
|
795
|
+
if (schema.allOf && Array.isArray(schema.allOf)) {
|
|
796
|
+
return this.allOf(schema.allOf ?? []);
|
|
797
|
+
}
|
|
798
|
+
if (schema.anyOf && Array.isArray(schema.anyOf)) {
|
|
799
|
+
return this.anyOf(schema.anyOf ?? [], required);
|
|
800
|
+
}
|
|
801
|
+
if (schema.oneOf && Array.isArray(schema.oneOf)) {
|
|
802
|
+
return this.oneOf(schema.oneOf ?? [], required);
|
|
803
|
+
}
|
|
804
|
+
if (schema.enum && Array.isArray(schema.enum)) {
|
|
805
|
+
return this.enum(schema.enum, required);
|
|
806
|
+
}
|
|
807
|
+
const types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : [];
|
|
808
|
+
if (!types.length) {
|
|
809
|
+
return `z.unknown()${appendOptional2(required)}`;
|
|
810
|
+
}
|
|
811
|
+
if (types.length > 1) {
|
|
812
|
+
const realTypes = types.filter((t) => t !== "null");
|
|
813
|
+
if (realTypes.length === 1 && types.includes("null")) {
|
|
814
|
+
const typeZod = this.normal(realTypes[0], schema, false);
|
|
815
|
+
return `${typeZod}.nullable()${appendOptional2(required)}`;
|
|
816
|
+
}
|
|
817
|
+
const subSchemas = types.map((t) => this.normal(t, schema, false));
|
|
818
|
+
return `z.union([${subSchemas.join(", ")}])${appendOptional2(required)}`;
|
|
819
|
+
}
|
|
820
|
+
return this.normal(types[0], schema, required);
|
|
821
|
+
}
|
|
822
|
+
};
|
|
823
|
+
function appendOptional2(isRequired) {
|
|
824
|
+
return isRequired ? "" : ".optional()";
|
|
825
|
+
}
|
|
826
|
+
function appendDefault(defaultValue) {
|
|
827
|
+
return defaultValue !== void 0 ? `.default(${JSON.stringify(defaultValue)})` : "";
|
|
828
|
+
}
|
|
624
829
|
|
|
625
830
|
// packages/typescript/src/lib/generator.ts
|
|
626
831
|
var responses = {
|
|
@@ -653,8 +858,10 @@ var defaults = {
|
|
|
653
858
|
}
|
|
654
859
|
};
|
|
655
860
|
function generateCode(config) {
|
|
656
|
-
const
|
|
861
|
+
const imports = [];
|
|
657
862
|
const commonSchemas = {};
|
|
863
|
+
const zodDeserialzer = new ZodDeserialzer(config.spec);
|
|
864
|
+
const groups = {};
|
|
658
865
|
const outputs = {};
|
|
659
866
|
for (const [path, methods2] of Object.entries(config.spec.paths ?? {})) {
|
|
660
867
|
for (const [method, operation] of Object.entries(methods2)) {
|
|
@@ -664,7 +871,6 @@ function generateCode(config) {
|
|
|
664
871
|
const groupName = (operation.tags ?? ["unknown"])[0];
|
|
665
872
|
groups[groupName] ??= [];
|
|
666
873
|
const inputs = {};
|
|
667
|
-
const imports = [];
|
|
668
874
|
const additionalProperties = [];
|
|
669
875
|
for (const param of operation.parameters ?? []) {
|
|
670
876
|
if (isRef(param)) {
|
|
@@ -707,31 +913,18 @@ function generateCode(config) {
|
|
|
707
913
|
if (operation.requestBody && Object.keys(operation.requestBody).length) {
|
|
708
914
|
const content = isRef(operation.requestBody) ? get2(followRef(config.spec, operation.requestBody.$ref), ["content"]) : operation.requestBody.content;
|
|
709
915
|
for (const type in content) {
|
|
710
|
-
const
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
}),
|
|
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
|
-
}
|
|
734
|
-
);
|
|
916
|
+
const ctSchema = isRef(content[type].schema) ? followRef(config.spec, content[type].schema.$ref) : content[type].schema;
|
|
917
|
+
const schema = merge(ctSchema, {
|
|
918
|
+
required: additionalProperties.filter((p) => p.required).map((p) => p.name),
|
|
919
|
+
properties: additionalProperties.reduce(
|
|
920
|
+
(acc, p) => ({
|
|
921
|
+
...acc,
|
|
922
|
+
[p.name]: p.schema
|
|
923
|
+
}),
|
|
924
|
+
{}
|
|
925
|
+
)
|
|
926
|
+
});
|
|
927
|
+
types[shortContenTypeMap[type]] = zodDeserialzer.handle(schema, true);
|
|
735
928
|
}
|
|
736
929
|
if (content["application/json"]) {
|
|
737
930
|
contentType = "json";
|
|
@@ -743,30 +936,20 @@ function generateCode(config) {
|
|
|
743
936
|
contentType = "json";
|
|
744
937
|
}
|
|
745
938
|
} else {
|
|
746
|
-
|
|
747
|
-
|
|
939
|
+
const properties = additionalProperties.reduce(
|
|
940
|
+
(acc, p) => ({
|
|
941
|
+
...acc,
|
|
942
|
+
[p.name]: p.schema
|
|
943
|
+
}),
|
|
944
|
+
{}
|
|
945
|
+
);
|
|
946
|
+
types[shortContenTypeMap["application/json"]] = zodDeserialzer.handle(
|
|
748
947
|
{
|
|
749
948
|
type: "object",
|
|
750
949
|
required: additionalProperties.filter((p) => p.required).map((p) => p.name),
|
|
751
|
-
properties
|
|
752
|
-
(acc, p) => ({
|
|
753
|
-
...acc,
|
|
754
|
-
[p.name]: p.schema
|
|
755
|
-
}),
|
|
756
|
-
{}
|
|
757
|
-
)
|
|
950
|
+
properties
|
|
758
951
|
},
|
|
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
|
-
}
|
|
952
|
+
true
|
|
770
953
|
);
|
|
771
954
|
}
|
|
772
955
|
const errors = [];
|
|
@@ -783,48 +966,57 @@ function generateCode(config) {
|
|
|
783
966
|
foundResponse = true;
|
|
784
967
|
const responseContent = get2(response, ["content"]);
|
|
785
968
|
const isJson = responseContent && responseContent["application/json"];
|
|
786
|
-
const
|
|
969
|
+
const imports2 = [];
|
|
970
|
+
const typeScriptDeserialzer = new TypeScriptDeserialzer(
|
|
787
971
|
config.spec,
|
|
788
|
-
responseContent["application/json"].schema,
|
|
789
|
-
true,
|
|
790
972
|
(schemaName, zod) => {
|
|
791
973
|
commonSchemas[schemaName] = zod;
|
|
792
|
-
|
|
974
|
+
imports2.push({
|
|
793
975
|
defaultImport: void 0,
|
|
794
|
-
isTypeOnly:
|
|
795
|
-
moduleSpecifier:
|
|
796
|
-
namedImports: [{ isTypeOnly:
|
|
976
|
+
isTypeOnly: true,
|
|
977
|
+
moduleSpecifier: `../models/${schemaName}.ts`,
|
|
978
|
+
namedImports: [{ isTypeOnly: true, name: schemaName }],
|
|
797
979
|
namespaceImport: void 0
|
|
798
980
|
});
|
|
799
981
|
}
|
|
800
|
-
) : "z.instanceof(ReadableStream)";
|
|
801
|
-
output.push(
|
|
802
|
-
importsToString(mergeImports(Object.values(imports).flat())).join(
|
|
803
|
-
"\n"
|
|
804
|
-
)
|
|
805
982
|
);
|
|
983
|
+
const responseSchema = isJson ? typeScriptDeserialzer.handle(
|
|
984
|
+
responseContent["application/json"].schema,
|
|
985
|
+
true
|
|
986
|
+
) : "ReadableStream";
|
|
987
|
+
for (const it of mergeImports(imports2)) {
|
|
988
|
+
const singleImport = it.defaultImport ?? it.namespaceImport;
|
|
989
|
+
if (singleImport && responseSchema.includes(singleImport)) {
|
|
990
|
+
output.push(importsToString(it).join("\n"));
|
|
991
|
+
} else if (it.namedImports.length) {
|
|
992
|
+
for (const namedImport of it.namedImports) {
|
|
993
|
+
if (responseSchema.includes(namedImport.name)) {
|
|
994
|
+
output.push(importsToString(it).join("\n"));
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
}
|
|
806
999
|
output.push(
|
|
807
|
-
`export
|
|
1000
|
+
`export type ${pascalcase2(operationName + " output")} = ${responseSchema}`
|
|
808
1001
|
);
|
|
809
1002
|
}
|
|
810
1003
|
}
|
|
811
1004
|
if (!foundResponse) {
|
|
812
1005
|
output.push(
|
|
813
|
-
`export
|
|
1006
|
+
`export type ${pascalcase2(operationName + " output")} = void`
|
|
814
1007
|
);
|
|
815
1008
|
}
|
|
816
1009
|
outputs[`${spinalcase2(operationName)}.ts`] = output.join("\n");
|
|
817
1010
|
groups[groupName].push({
|
|
818
1011
|
name: operationName,
|
|
819
1012
|
type: "http",
|
|
820
|
-
imports: mergeImports(Object.values(imports).flat()),
|
|
821
1013
|
inputs,
|
|
822
1014
|
errors: errors.length ? errors : ["ServerError"],
|
|
823
1015
|
contentType,
|
|
824
1016
|
schemas: types,
|
|
825
1017
|
formatOutput: () => ({
|
|
826
1018
|
import: pascalcase2(operationName + " output"),
|
|
827
|
-
use:
|
|
1019
|
+
use: pascalcase2(operationName + " output")
|
|
828
1020
|
}),
|
|
829
1021
|
trigger: {
|
|
830
1022
|
path,
|
|
@@ -835,35 +1027,6 @@ function generateCode(config) {
|
|
|
835
1027
|
}
|
|
836
1028
|
return { groups, commonSchemas, outputs };
|
|
837
1029
|
}
|
|
838
|
-
function mergeImports(imports) {
|
|
839
|
-
const merged = {};
|
|
840
|
-
for (const i of imports) {
|
|
841
|
-
merged[i.moduleSpecifier] = merged[i.moduleSpecifier] ?? {
|
|
842
|
-
moduleSpecifier: i.moduleSpecifier,
|
|
843
|
-
defaultImport: i.defaultImport,
|
|
844
|
-
namespaceImport: i.namespaceImport,
|
|
845
|
-
namedImports: []
|
|
846
|
-
};
|
|
847
|
-
if (i.namedImports) {
|
|
848
|
-
merged[i.moduleSpecifier].namedImports.push(...i.namedImports);
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
return Object.values(merged);
|
|
852
|
-
}
|
|
853
|
-
function importsToString(imports) {
|
|
854
|
-
return imports.map((i) => {
|
|
855
|
-
if (i.defaultImport) {
|
|
856
|
-
return `import ${i.defaultImport} from '${i.moduleSpecifier}'`;
|
|
857
|
-
}
|
|
858
|
-
if (i.namespaceImport) {
|
|
859
|
-
return `import * as ${i.namespaceImport} from '${i.moduleSpecifier}'`;
|
|
860
|
-
}
|
|
861
|
-
if (i.namedImports) {
|
|
862
|
-
return `import {${removeDuplicates(i.namedImports, (it) => it.name).map((n) => n.name).join(", ")}} from '${i.moduleSpecifier}'`;
|
|
863
|
-
}
|
|
864
|
-
throw new Error(`Invalid import ${JSON.stringify(i)}`);
|
|
865
|
-
});
|
|
866
|
-
}
|
|
867
1030
|
|
|
868
1031
|
// packages/typescript/src/lib/http/client.txt
|
|
869
1032
|
var client_default2 = "import { parse } from 'fast-content-type-parse';\n\nexport async function handleError(response: Response) {\n try {\n if (response.status >= 400 && response.status < 500) {\n const body = (await response.json()) as Record<string, any>;\n return {\n status: response.status,\n body: body,\n };\n }\n return new Error(\n `An error occurred while fetching the data. Status: ${response.status}`,\n );\n } catch (error) {\n return error as any;\n }\n}\n\nasync function handleChunkedResponse(response: Response, contentType: string) {\n const { type } = parse(contentType);\n\n switch (type) {\n case 'application/json': {\n let buffer = '';\n const reader = response.body!.getReader();\n const decoder = new TextDecoder();\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value);\n }\n return JSON.parse(buffer);\n }\n case 'text/html':\n case 'text/plain': {\n let buffer = '';\n const reader = response.body!.getReader();\n const decoder = new TextDecoder();\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value);\n }\n return buffer;\n }\n default:\n return response.body;\n }\n}\n\nexport async function parseResponse(response: Response) {\n const contentType = response.headers.get('Content-Type');\n if (!contentType) {\n throw new Error('Content-Type header is missing');\n }\n\n if (response.status === 204) {\n return null;\n }\n const isChunked = response.headers.get('Transfer-Encoding') === 'chunked';\n if (isChunked) {\n return response.body!;\n // return handleChunkedResponse(response, contentType);\n }\n\n const { type } = parse(contentType);\n switch (type) {\n case 'application/json':\n return response.json();\n case 'text/plain':\n return response.text();\n case 'text/html':\n return response.text();\n case 'text/xml':\n case 'application/xml':\n return response.text();\n case 'application/x-www-form-urlencoded': {\n const text = await response.text();\n return Object.fromEntries(new URLSearchParams(text));\n }\n case 'multipart/form-data':\n return response.formData();\n default:\n throw new Error(`Unsupported content type: ${contentType}`);\n }\n}\n";
|
|
@@ -872,13 +1035,13 @@ var client_default2 = "import { parse } from 'fast-content-type-parse';\n\nexpor
|
|
|
872
1035
|
var parser_default = "import { z } from 'zod';\n\nexport type ParseError<T extends z.ZodType<any, any, any>> = {\n kind: 'parse';\n} & z.inferFlattenedErrors<T>;\n\nexport function parse<T extends z.ZodType>(\n schema: T,\n input: unknown,\n) {\n const result = schema.safeParse(input);\n if (!result.success) {\n const errors = result.error.flatten((issue) => issue);\n return [null, errors];\n }\n return [result.data as z.infer<T>, null];\n}\n";
|
|
873
1036
|
|
|
874
1037
|
// packages/typescript/src/lib/http/request.txt
|
|
875
|
-
var request_default = "type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\ntype ContentType = 'xml' | 'json' | 'urlencoded' | 'multipart';\ntype Endpoint = `${ContentType} ${Method} ${string}` | `${Method} ${string}`;\n\nexport function createUrl(base: string, path: string, query: URLSearchParams) {\n const url = new URL(path, base);\n url.search = query.toString();\n return url;\n}\nfunction template(\n templateString: string,\n templateVariables: Record<string, any>,\n): string {\n const nargs = /{([0-9a-zA-Z_]+)}/g;\n return templateString.replace(nargs, (match, key: string, index: number) => {\n // Handle escaped double braces\n if (\n templateString[index - 1] === '{' &&\n templateString[index + match.length] === '}'\n ) {\n return key;\n }\n\n const result = key in templateVariables ? templateVariables[key] : null;\n return result === null || result === undefined ? '' : String(result);\n });\n}\n\ninterface ToRequest {\n <T extends Endpoint>(\n endpoint: T,\n input: Record<string, any>,\n props: {\n inputHeaders: string[];\n inputQuery: string[];\n inputBody: string[];\n inputParams: string[];\n },\n defaults: {\n baseUrl: string;\n headers?: Partial<Record<string, string>>;\n },\n ): Request;\n urlencoded: <T extends Endpoint>(\n endpoint: T,\n input: Record<string, any>,\n props: {\n inputHeaders: string[];\n inputQuery: string[];\n inputBody: string[];\n inputParams: string[];\n },\n defaults: {\n baseUrl: string;\n headers?: Partial<Record<string, string>>;\n },\n ) => Request;\n}\n\nfunction _json(\n input: Record<string, any>,\n props: {\n inputHeaders: string[];\n inputQuery: string[];\n inputBody: string[];\n inputParams: string[];\n },\n) {\n const headers = new Headers({});\n for (const header of props.inputHeaders) {\n headers.set(header, input[header]);\n }\n\n const body: Record<string, any> = {};\n for (const prop of props.inputBody) {\n body[prop] = input[prop];\n }\n\n const query = new URLSearchParams();\n for (const key of props.inputQuery) {\n const value = input[key];\n if (value !== undefined) {\n query.set(key, String(value));\n }\n }\n\n const params = props.inputParams.reduce<Record<string, any>>((acc, key) => {\n acc[key] = input[key];\n return acc;\n }, {});\n\n return {\n body: JSON.stringify(body),\n query,\n params,\n headers: { 'Content-Type': 'application/json', Accept: 'application/json' },\n };\n}\n\ntype Input = Record<string, any>;\ntype Props = {\n inputHeaders: string[];\n inputQuery: string[];\n inputBody: string[];\n inputParams: string[];\n};\n\nabstract class Serializer {\n constructor(\n protected input: Input,\n protected props: Props,\n ) {}\n abstract getBody(): BodyInit | null;\n abstract getHeaders(): Partial<Record<string, string>>;\n serialize(): Serialized {\n const headers = new Headers({});\n for (const header of this.props.inputHeaders) {\n headers.set(header, this.input[header]);\n }\n\n const query = new URLSearchParams();\n for (const key of this.props.inputQuery) {\n const value = this.input[key];\n if (value !== undefined) {\n query.set(key, String(value));\n }\n }\n\n const params = this.props.inputParams.reduce<Record<string, any>>(\n (acc, key) => {\n acc[key] = this.input[key];\n return acc;\n },\n {},\n );\n\n return {\n body: this.getBody(),\n query,\n params,\n headers: this.getHeaders(),\n };\n }\n}\n\ninterface Serialized {\n body: BodyInit | null;\n query: URLSearchParams;\n params: Record<string, any>;\n headers: Partial<Record<string, string>>;\n}\n\nclass JsonSerializer extends Serializer {\n getBody(): BodyInit | null {\n const body: Record<string, any> = {};\n for (const prop of this.props.inputBody) {\n body[prop] = this.input[prop];\n }\n return JSON.stringify(body);\n }\n getHeaders(): Record<string, string> {\n return {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n };\n }\n}\n\nclass UrlencodedSerializer extends Serializer {\n getBody(): BodyInit | null {\n const body = new URLSearchParams();\n for (const prop of this.props.inputBody) {\n body.set(prop, this.input[prop]);\n }\n return body;\n }\n getHeaders(): Record<string, string> {\n return {};\n }\n}\n\nclass FormDataSerializer extends Serializer {\n getBody(): BodyInit | null {\n const body = new FormData();\n for (const prop of this.props.inputBody) {\n body.append(prop, this.input[prop]);\n }\n return body;\n }\n getHeaders(): Record<string, string> {\n return {};\n }\n}\n\nexport function json(input: Input, props: Props) {\n return new JsonSerializer(input, props).serialize();\n}\nexport function urlencoded(input: Input, props: Props) {\n return new UrlencodedSerializer(input, props).serialize();\n}\nexport function formdata(input: Input, props: Props) {\n return new FormDataSerializer(input, props).serialize();\n}\n\nexport function _urlencoded(\n input: Record<string, any>,\n props: {\n inputHeaders: string[];\n inputQuery: string[];\n inputBody: string[];\n inputParams: string[];\n },\n) {\n const headers = new Headers({});\n for (const header of props.inputHeaders) {\n headers.set(header, input[header]);\n }\n\n const body = new URLSearchParams();\n for (const prop of props.inputBody) {\n body.set(prop, input[prop]);\n }\n\n const query = new URLSearchParams();\n for (const key of props.inputQuery) {\n const value = input[key];\n if (value !== undefined) {\n query.set(key, String(value));\n }\n }\n\n const params = props.inputParams.reduce<Record<string, any>>((acc, key) => {\n acc[key] = input[key];\n return acc;\n }, {});\n\n return {\n body,\n query,\n params,\n headers: {},\n };\n}\n\nexport function toRequest<T extends Endpoint>(\n endpoint: T,\n input: Serialized,\n defaults: {\n baseUrl: string;\n headers?: Partial<Record<string, string>>;\n },\n): Request {\n const [method, path] = endpoint.split(' ');\n\n const headers = new Headers(\n Object.entries({\n ...defaults?.headers,\n ...input.headers,\n }).filter(truthyEntry),\n );\n const pathVariable = template(path, input.params);\n\n const url = createUrl(defaults.baseUrl, pathVariable, input.query);\n return new Request(url, {\n method: method,\n headers: headers,\n body: method === 'GET' ? undefined : input.body,\n });\n}\n\nfunction truthyEntry(entry: [string, unknown]): entry is [string, string] {\n return entry[1] !== undefined;\n}\n";
|
|
1038
|
+
var request_default = "type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\ntype ContentType = 'xml' | 'json' | 'urlencoded' | 'multipart';\ntype Endpoint = `${ContentType} ${Method} ${string}` | `${Method} ${string}`;\nexport type BodyInit =\n | ArrayBuffer\n | AsyncIterable<Uint8Array>\n | Blob\n | FormData\n | Iterable<Uint8Array>\n | NodeJS.ArrayBufferView\n | URLSearchParams\n | null\n | string;\n\nexport function createUrl(base: string, path: string, query: URLSearchParams) {\n const url = new URL(path, base);\n url.search = query.toString();\n return url;\n}\nfunction template(\n templateString: string,\n templateVariables: Record<string, any>,\n): string {\n const nargs = /{([0-9a-zA-Z_]+)}/g;\n return templateString.replace(nargs, (match, key: string, index: number) => {\n // Handle escaped double braces\n if (\n templateString[index - 1] === '{' &&\n templateString[index + match.length] === '}'\n ) {\n return key;\n }\n\n const result = key in templateVariables ? templateVariables[key] : null;\n return result === null || result === undefined ? '' : String(result);\n });\n}\n\ninterface ToRequest {\n <T extends Endpoint>(\n endpoint: T,\n input: Record<string, any>,\n props: {\n inputHeaders: string[];\n inputQuery: string[];\n inputBody: string[];\n inputParams: string[];\n },\n defaults: {\n baseUrl: string;\n headers?: Partial<Record<string, string>>;\n },\n ): Request;\n urlencoded: <T extends Endpoint>(\n endpoint: T,\n input: Record<string, any>,\n props: {\n inputHeaders: string[];\n inputQuery: string[];\n inputBody: string[];\n inputParams: string[];\n },\n defaults: {\n baseUrl: string;\n headers?: Partial<Record<string, string>>;\n },\n ) => Request;\n}\n\nfunction _json(\n input: Record<string, any>,\n props: {\n inputHeaders: string[];\n inputQuery: string[];\n inputBody: string[];\n inputParams: string[];\n },\n) {\n const headers = new Headers({});\n for (const header of props.inputHeaders) {\n headers.set(header, input[header]);\n }\n\n const body: Record<string, any> = {};\n for (const prop of props.inputBody) {\n body[prop] = input[prop];\n }\n\n const query = new URLSearchParams();\n for (const key of props.inputQuery) {\n const value = input[key];\n if (value !== undefined) {\n query.set(key, String(value));\n }\n }\n\n const params = props.inputParams.reduce<Record<string, any>>((acc, key) => {\n acc[key] = input[key];\n return acc;\n }, {});\n\n return {\n body: JSON.stringify(body),\n query,\n params,\n headers: { 'Content-Type': 'application/json', Accept: 'application/json' },\n };\n}\n\ntype Input = Record<string, any>;\ntype Props = {\n inputHeaders: string[];\n inputQuery: string[];\n inputBody: string[];\n inputParams: string[];\n};\n\nabstract class Serializer {\n constructor(\n protected input: Input,\n protected props: Props,\n ) {}\n abstract getBody(): BodyInit | null;\n abstract getHeaders(): Partial<Record<string, string>>;\n serialize(): Serialized {\n const headers = new Headers({});\n for (const header of this.props.inputHeaders) {\n headers.set(header, this.input[header]);\n }\n\n const query = new URLSearchParams();\n for (const key of this.props.inputQuery) {\n const value = this.input[key];\n if (value !== undefined) {\n query.set(key, String(value));\n }\n }\n\n const params = this.props.inputParams.reduce<Record<string, any>>(\n (acc, key) => {\n acc[key] = this.input[key];\n return acc;\n },\n {},\n );\n\n return {\n body: this.getBody(),\n query,\n params,\n headers: this.getHeaders(),\n };\n }\n}\n\ninterface Serialized {\n body: BodyInit | null;\n query: URLSearchParams;\n params: Record<string, any>;\n headers: Partial<Record<string, string>>;\n}\n\nclass JsonSerializer extends Serializer {\n getBody(): BodyInit | null {\n const body: Record<string, any> = {};\n for (const prop of this.props.inputBody) {\n body[prop] = this.input[prop];\n }\n return JSON.stringify(body);\n }\n getHeaders(): Record<string, string> {\n return {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n };\n }\n}\n\nclass UrlencodedSerializer extends Serializer {\n getBody(): BodyInit | null {\n const body = new URLSearchParams();\n for (const prop of this.props.inputBody) {\n body.set(prop, this.input[prop]);\n }\n return body;\n }\n getHeaders(): Record<string, string> {\n return {};\n }\n}\n\nclass FormDataSerializer extends Serializer {\n getBody(): BodyInit | null {\n const body = new FormData();\n for (const prop of this.props.inputBody) {\n body.append(prop, this.input[prop]);\n }\n return body;\n }\n getHeaders(): Record<string, string> {\n return {};\n }\n}\n\nexport function json(input: Input, props: Props) {\n return new JsonSerializer(input, props).serialize();\n}\nexport function urlencoded(input: Input, props: Props) {\n return new UrlencodedSerializer(input, props).serialize();\n}\nexport function formdata(input: Input, props: Props) {\n return new FormDataSerializer(input, props).serialize();\n}\n\nexport function _urlencoded(\n input: Record<string, any>,\n props: {\n inputHeaders: string[];\n inputQuery: string[];\n inputBody: string[];\n inputParams: string[];\n },\n) {\n const headers = new Headers({});\n for (const header of props.inputHeaders) {\n headers.set(header, input[header]);\n }\n\n const body = new URLSearchParams();\n for (const prop of props.inputBody) {\n body.set(prop, input[prop]);\n }\n\n const query = new URLSearchParams();\n for (const key of props.inputQuery) {\n const value = input[key];\n if (value !== undefined) {\n query.set(key, String(value));\n }\n }\n\n const params = props.inputParams.reduce<Record<string, any>>((acc, key) => {\n acc[key] = input[key];\n return acc;\n }, {});\n\n return {\n body,\n query,\n params,\n headers: {},\n };\n}\n\nexport function toRequest<T extends Endpoint>(\n endpoint: T,\n input: Serialized,\n defaults: {\n baseUrl: string;\n headers?: Partial<Record<string, string>>;\n },\n): Request {\n const [method, path] = endpoint.split(' ');\n\n const headers = new Headers(\n Object.entries({\n ...defaults?.headers,\n ...input.headers,\n }).filter(truthyEntry),\n );\n const pathVariable = template(path, input.params);\n\n const url = createUrl(defaults.baseUrl, pathVariable, input.query);\n return new Request(url, {\n method: method,\n headers: headers,\n body: method === 'GET' ? undefined : input.body,\n });\n}\n\nfunction truthyEntry(entry: [string, unknown]): entry is [string, string] {\n return entry[1] !== undefined;\n}\n";
|
|
876
1039
|
|
|
877
1040
|
// packages/typescript/src/lib/http/response.txt
|
|
878
1041
|
var response_default = "export interface ApiResponse<Status extends number, Body extends unknown> {\n kind: 'response';\n status: Status;\n body: Body;\n}\n\n// 4xx Client Errors\nexport type BadRequest = ApiResponse<400, { message: string }>;\nexport type Unauthorized = ApiResponse<401, { message: string }>;\nexport type PaymentRequired = ApiResponse<402, { message: string }>;\nexport type Forbidden = ApiResponse<403, { message: string }>;\nexport type NotFound = ApiResponse<404, { message: string }>;\nexport type MethodNotAllowed = ApiResponse<405, { message: string }>;\nexport type NotAcceptable = ApiResponse<406, { message: string }>;\nexport type Conflict = ApiResponse<409, { message: string }>;\nexport type Gone = ApiResponse<410, { message: string }>;\nexport type UnprocessableEntity = ApiResponse<422, { message: string; errors?: Record<string, string[]> }>;\nexport type TooManyRequests = ApiResponse<429, { message: string; retryAfter?: string }>;\nexport type PayloadTooLarge = ApiResponse<413, { message: string; }>;\nexport type UnsupportedMediaType = ApiResponse<415, { message: string; }>;\n\n// 5xx Server Errors\nexport type InternalServerError = ApiResponse<500, { message: string }>;\nexport type NotImplemented = ApiResponse<501, { message: string }>;\nexport type BadGateway = ApiResponse<502, { message: string }>;\nexport type ServiceUnavailable = ApiResponse<503, { message: string; retryAfter?: string }>;\nexport type GatewayTimeout = ApiResponse<504, { message: string }>;\n\nexport type ClientError =\n | BadRequest\n | Unauthorized\n | PaymentRequired\n | Forbidden\n | NotFound\n | MethodNotAllowed\n | NotAcceptable\n | Conflict\n | Gone\n | UnprocessableEntity\n | TooManyRequests;\n\nexport type ServerError =\n | InternalServerError\n | NotImplemented\n | BadGateway\n | ServiceUnavailable\n | GatewayTimeout;\n\nexport type ProblematicResponse = ClientError | ServerError;\n";
|
|
879
1042
|
|
|
880
1043
|
// packages/typescript/src/lib/http/send-request.txt
|
|
881
|
-
var send_request_default = "import z from 'zod';\n\nimport { handleError, parseResponse } from './parse-response.ts';\nimport { parse } from './parser.ts';\n\nexport interface RequestSchema {\n schema: z.ZodType;\n toRequest: (\n input: any,\n init: { baseUrl: string; headers?: Partial<Record<string, string>> },\n ) => any;\n}\n\nexport const fetchType = z\n .function()\n .args(z.instanceof(Request))\n .returns(z.promise(z.instanceof(Response)))\n .optional();\n\nexport async function sendRequest(\n input: any,\n route: RequestSchema,\n options: {\n baseUrl: string;\n fetch?: z.infer<typeof fetchType>;\n headers?: Partial<Record<string, string>>;\n },\n) {\n const [parsedInput, parseError] = parse(route.schema, input);\n if (parseError) {\n return [null as never, { ...parseError, kind: 'parse' } as never] as const;\n }\n const request = route.toRequest(parsedInput as never, {\n headers: options.headers,\n baseUrl: options.baseUrl,\n });\n const response = await (options.fetch ?? fetch)(request);\n if (response.ok) {\n const data = await parseResponse(response);\n return [data, null] as const;\n }\n const error = await handleError(response);\n return [null as never, { ...error, kind: 'response' }] as const;\n}\n";
|
|
1044
|
+
var send_request_default = "import z from 'zod';\n\nimport { handleError, parseResponse } from './parse-response.ts';\nimport { parse } from './parser.ts';\n\nexport interface RequestSchema {\n schema: z.ZodType;\n toRequest: (\n input: any,\n init: { baseUrl: string; headers?: Partial<Record<string, string>> },\n ) => any;\n}\n\nexport const fetchType = z\n .function()\n .args(z.instanceof(Request))\n .returns(z.promise(z.instanceof(Response)))\n .optional();\n\nexport async function sendRequest(\n input: any,\n route: RequestSchema,\n options: {\n baseUrl: string;\n fetch?: z.infer<typeof fetchType>;\n headers?: Partial<Record<string, string>>;\n },\n) {\n const [parsedInput, parseError] = parse(route.schema, input);\n if (parseError) {\n return [null as never, { ...parseError, kind: 'parse' } as never] as const;\n }\n const request = route.toRequest(parsedInput as never, {\n headers: options.headers,\n baseUrl: options.baseUrl,\n });\n const response = await (options.fetch ?? fetch)(request);\n if (response.ok) {\n const data = await parseResponse(response);\n return [data as never, null] as const;\n }\n const error = await handleError(response);\n return [null as never, { ...error, kind: 'response' }] as const;\n}\n";
|
|
882
1045
|
|
|
883
1046
|
// packages/typescript/src/lib/generate.ts
|
|
884
1047
|
function security(spec) {
|
|
@@ -928,22 +1091,35 @@ async function generate(spec, settings) {
|
|
|
928
1091
|
"request.ts": request_default
|
|
929
1092
|
});
|
|
930
1093
|
await writeFiles(join(output, "outputs"), outputs);
|
|
1094
|
+
const imports = Object.entries(commonSchemas).map(([name]) => name);
|
|
931
1095
|
await writeFiles(output, {
|
|
932
1096
|
...clientFiles,
|
|
933
|
-
|
|
934
|
-
|
|
1097
|
+
...Object.fromEntries(
|
|
1098
|
+
Object.entries(commonSchemas).map(([name, schema]) => [
|
|
1099
|
+
`models/${name}.ts`,
|
|
1100
|
+
[
|
|
1101
|
+
`import { z } from 'zod';`,
|
|
1102
|
+
...exclude(imports, [name]).map(
|
|
1103
|
+
(it) => `import type { ${it} } from './${it}.ts';`
|
|
1104
|
+
),
|
|
1105
|
+
`export type ${name} = ${schema};`
|
|
1106
|
+
].join("\n")
|
|
1107
|
+
])
|
|
1108
|
+
)
|
|
935
1109
|
});
|
|
936
|
-
const [index, outputIndex, inputsIndex, httpIndex] = await Promise.all([
|
|
1110
|
+
const [index, outputIndex, inputsIndex, httpIndex, modelsIndex] = await Promise.all([
|
|
937
1111
|
getFolderExports(output),
|
|
938
1112
|
getFolderExports(join(output, "outputs")),
|
|
939
1113
|
getFolderExports(join(output, "inputs")),
|
|
940
|
-
getFolderExports(join(output, "http"))
|
|
1114
|
+
getFolderExports(join(output, "http")),
|
|
1115
|
+
getFolderExports(join(output, "models"))
|
|
941
1116
|
]);
|
|
942
1117
|
await writeFiles(output, {
|
|
943
1118
|
"index.ts": index,
|
|
944
1119
|
"outputs/index.ts": outputIndex,
|
|
945
1120
|
"inputs/index.ts": inputsIndex,
|
|
946
|
-
"http/index.ts": httpIndex
|
|
1121
|
+
"http/index.ts": httpIndex,
|
|
1122
|
+
"models/index.ts": modelsIndex
|
|
947
1123
|
});
|
|
948
1124
|
if (settings.mode === "full") {
|
|
949
1125
|
await writeFiles(settings.output, {
|
|
@@ -958,6 +1134,9 @@ ${Object.entries(commonSchemas).map(([name, schema]) => `export const ${name} =
|
|
|
958
1134
|
env: npmRunPathEnv()
|
|
959
1135
|
});
|
|
960
1136
|
}
|
|
1137
|
+
function exclude(list, exclude2) {
|
|
1138
|
+
return list.filter((it) => !exclude2.includes(it));
|
|
1139
|
+
}
|
|
961
1140
|
|
|
962
1141
|
// packages/typescript/src/lib/watcher.ts
|
|
963
1142
|
import { watch as nodeWatch } from "node:fs/promises";
|