@sdk-it/typescript 0.12.6 → 0.12.7
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 +236 -116
- package/dist/index.js.map +3 -3
- package/dist/lib/client.d.ts.map +1 -1
- package/dist/lib/emitters/interface.d.ts.map +1 -1
- package/dist/lib/emitters/zod.d.ts.map +1 -1
- package/dist/lib/generate.d.ts.map +1 -1
- package/dist/lib/generator.d.ts +1 -0
- package/dist/lib/generator.d.ts.map +1 -1
- package/dist/lib/sdk.d.ts +2 -2
- package/dist/lib/sdk.d.ts.map +1 -1
- package/dist/lib/utils.d.ts +2 -0
- package/dist/lib/utils.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -14,8 +14,11 @@ async function exist(file) {
|
|
|
14
14
|
return stat(file).then(() => true).catch(() => false);
|
|
15
15
|
}
|
|
16
16
|
async function writeFiles(dir, contents) {
|
|
17
|
-
|
|
17
|
+
await Promise.all(
|
|
18
18
|
Object.entries(contents).map(async ([file, content]) => {
|
|
19
|
+
if (content === null) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
19
22
|
const filePath = isAbsolute(file) ? file : join(dir, file);
|
|
20
23
|
await mkdir(dirname(filePath), { recursive: true });
|
|
21
24
|
if (typeof content === "string") {
|
|
@@ -25,15 +28,20 @@ async function writeFiles(dir, contents) {
|
|
|
25
28
|
if (!await exist(filePath)) {
|
|
26
29
|
await writeFile(filePath, content.content, "utf-8");
|
|
27
30
|
}
|
|
31
|
+
} else {
|
|
32
|
+
await writeFile(filePath, content.content, "utf-8");
|
|
28
33
|
}
|
|
29
34
|
}
|
|
30
35
|
})
|
|
31
36
|
);
|
|
32
37
|
}
|
|
33
|
-
async function getFolderExports(folder, extensions = ["ts"]) {
|
|
38
|
+
async function getFolderExports(folder, extensions = ["ts"], ignore = () => false) {
|
|
34
39
|
const files = await readdir(folder, { withFileTypes: true });
|
|
35
40
|
const exports = [];
|
|
36
41
|
for (const file of files) {
|
|
42
|
+
if (ignore(file)) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
37
45
|
if (file.isDirectory()) {
|
|
38
46
|
exports.push(`export * from './${file.name}/index.ts';`);
|
|
39
47
|
} else if (file.name !== "index.ts" && extensions.includes(getExt(file.name))) {
|
|
@@ -75,13 +83,13 @@ function toLitObject(obj, accessor = (value) => value) {
|
|
|
75
83
|
|
|
76
84
|
// packages/typescript/src/lib/generator.ts
|
|
77
85
|
import { get as get2, merge } from "lodash-es";
|
|
78
|
-
import { camelcase as camelcase2, pascalcase
|
|
86
|
+
import { camelcase as camelcase2, pascalcase, spinalcase as spinalcase2 } from "stringcase";
|
|
79
87
|
|
|
80
88
|
// packages/typescript/src/lib/utils.ts
|
|
81
89
|
import { get } from "lodash-es";
|
|
82
90
|
|
|
83
91
|
// packages/typescript/src/lib/sdk.ts
|
|
84
|
-
import { camelcase,
|
|
92
|
+
import { camelcase, spinalcase } from "stringcase";
|
|
85
93
|
|
|
86
94
|
// packages/typescript/src/lib/client.ts
|
|
87
95
|
var client_default = (spec) => {
|
|
@@ -122,8 +130,10 @@ ${spec.servers.length ? `export type Servers = typeof servers[number];` : ""}
|
|
|
122
130
|
type ${spec.name}Options = z.infer<typeof optionsSchema>;
|
|
123
131
|
|
|
124
132
|
export class ${spec.name} {
|
|
125
|
-
|
|
126
|
-
constructor(
|
|
133
|
+
public options: ${spec.name}Options
|
|
134
|
+
constructor(options: ${spec.name}Options) {
|
|
135
|
+
this.options = options;
|
|
136
|
+
}
|
|
127
137
|
|
|
128
138
|
async request<E extends keyof Endpoints>(
|
|
129
139
|
endpoint: E,
|
|
@@ -200,37 +210,63 @@ ${this.endpoints.join("\n")}
|
|
|
200
210
|
}`;
|
|
201
211
|
}
|
|
202
212
|
};
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
}
|
|
213
|
+
function generateInputs(operationsSet, commonZod) {
|
|
214
|
+
const commonImports = commonZod.keys().toArray();
|
|
215
|
+
const inputs = {};
|
|
216
|
+
for (const [name, operations] of Object.entries(operationsSet)) {
|
|
217
|
+
const output = [];
|
|
218
|
+
const imports = /* @__PURE__ */ new Set(['import { z } from "zod";']);
|
|
219
|
+
for (const operation of operations) {
|
|
220
|
+
const schemaName = camelcase(`${operation.name} schema`);
|
|
221
|
+
const schema = `export const ${schemaName} = ${Object.keys(operation.schemas).length === 1 ? Object.values(operation.schemas)[0] : toLitObject(operation.schemas)};`;
|
|
222
|
+
const inputContent = schema;
|
|
223
|
+
for (const schema2 of commonImports) {
|
|
224
|
+
if (inputContent.includes(schema2)) {
|
|
225
|
+
imports.add(
|
|
226
|
+
`import { ${schema2} } from './schemas/${spinalcase(schema2)}.ts';`
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
output.push(inputContent);
|
|
231
|
+
}
|
|
232
|
+
inputs[`inputs/${spinalcase(name)}.ts`] = [...imports, ...output].join("\n") + "\n";
|
|
209
233
|
}
|
|
210
|
-
|
|
211
|
-
|
|
234
|
+
const schemas = commonZod.entries().reduce((acc, [name, schema]) => {
|
|
235
|
+
const output = [`import { z } from 'zod';`];
|
|
236
|
+
const content = `export const ${name} = ${schema};`;
|
|
237
|
+
for (const schema2 of commonImports) {
|
|
238
|
+
const preciseMatch = new RegExp(`\\b${schema2}\\b`);
|
|
239
|
+
if (preciseMatch.test(content) && schema2 !== name) {
|
|
240
|
+
output.push(
|
|
241
|
+
`import { ${schema2} } from './${spinalcase(schema2)}.ts';`
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
output.push(content);
|
|
246
|
+
return [
|
|
247
|
+
[`inputs/schemas/${spinalcase(name)}.ts`, output.join("\n")],
|
|
248
|
+
...acc
|
|
249
|
+
];
|
|
250
|
+
}, []);
|
|
251
|
+
return {
|
|
252
|
+
...Object.fromEntries(schemas),
|
|
253
|
+
...inputs
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function generateSDK(spec) {
|
|
212
257
|
const emitter = new Emitter();
|
|
213
|
-
const streamEmitter = new StreamEmitter();
|
|
214
|
-
const schemas = {};
|
|
215
258
|
const schemaEndpoint = new SchemaEndpoint();
|
|
216
259
|
const errors = [];
|
|
217
260
|
for (const [name, operations] of Object.entries(spec.operations)) {
|
|
218
|
-
const featureSchemaFileName = camelcase(name);
|
|
219
|
-
schemas[featureSchemaFileName] = [`import z from 'zod';`];
|
|
220
261
|
emitter.addImport(
|
|
221
|
-
`import * as ${
|
|
222
|
-
);
|
|
223
|
-
streamEmitter.addImport(
|
|
224
|
-
`import * as ${featureSchemaFileName} from './inputs/${featureSchemaFileName}.ts';`
|
|
262
|
+
`import * as ${camelcase(name)} from './inputs/${spinalcase(name)}.ts';`
|
|
225
263
|
);
|
|
226
264
|
schemaEndpoint.addImport(
|
|
227
|
-
`import * as ${
|
|
265
|
+
`import * as ${camelcase(name)} from './inputs/${spinalcase(name)}.ts';`
|
|
228
266
|
);
|
|
229
267
|
for (const operation of operations) {
|
|
230
268
|
const schemaName = camelcase(`${operation.name} schema`);
|
|
231
|
-
const
|
|
232
|
-
schemas[featureSchemaFileName].push(schema);
|
|
233
|
-
const schemaRef = `${featureSchemaFileName}.${schemaName}`;
|
|
269
|
+
const schemaRef = `${camelcase(name)}.${schemaName}`;
|
|
234
270
|
const output = operation.formatOutput();
|
|
235
271
|
const inputHeaders = [];
|
|
236
272
|
const inputQuery = [];
|
|
@@ -255,51 +291,25 @@ function generateClientSdk(spec) {
|
|
|
255
291
|
);
|
|
256
292
|
}
|
|
257
293
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
294
|
+
emitter.addImport(
|
|
295
|
+
`import type {${output.import}} from './outputs/${spinalcase(operation.name)}.ts';`
|
|
296
|
+
);
|
|
297
|
+
errors.push(...operation.errors ?? []);
|
|
298
|
+
const addTypeParser = Object.keys(operation.schemas).length > 1;
|
|
299
|
+
for (const type in operation.schemas ?? {}) {
|
|
300
|
+
let typePrefix = "";
|
|
301
|
+
if (addTypeParser && type !== "json") {
|
|
302
|
+
typePrefix = `${type} `;
|
|
303
|
+
}
|
|
304
|
+
const input = `typeof ${schemaRef}${addTypeParser ? `.${type}` : ""}`;
|
|
305
|
+
const endpoint = `${typePrefix}${operation.trigger.method.toUpperCase()} ${operation.trigger.path}`;
|
|
306
|
+
emitter.addEndpoint(
|
|
265
307
|
endpoint,
|
|
266
|
-
`{input:
|
|
308
|
+
`{input: z.infer<${input}>; output: ${output.use}; error: ${(operation.errors ?? ["ServerError"]).concat(`ParseError<${input}>`).join("|")}}`
|
|
267
309
|
);
|
|
268
310
|
schemaEndpoint.addEndpoint(
|
|
269
311
|
endpoint,
|
|
270
312
|
`{
|
|
271
|
-
schema: ${schemaRef},
|
|
272
|
-
toRequest(input: StreamEndpoints['${endpoint}']['input']) {
|
|
273
|
-
const endpoint = '${endpoint}';
|
|
274
|
-
return toRequest(endpoint, json(input, {
|
|
275
|
-
inputHeaders: [${inputHeaders}],
|
|
276
|
-
inputQuery: [${inputQuery}],
|
|
277
|
-
inputBody: [${inputBody}],
|
|
278
|
-
inputParams: [${inputParams}],
|
|
279
|
-
}));
|
|
280
|
-
},
|
|
281
|
-
}`
|
|
282
|
-
);
|
|
283
|
-
} else {
|
|
284
|
-
emitter.addImport(
|
|
285
|
-
`import type {${output.import}} from './outputs/${spinalcase(operation.name)}.ts';`
|
|
286
|
-
);
|
|
287
|
-
errors.push(...operation.errors ?? []);
|
|
288
|
-
const addTypeParser = Object.keys(operation.schemas).length > 1;
|
|
289
|
-
for (const type in operation.schemas ?? {}) {
|
|
290
|
-
let typePrefix = "";
|
|
291
|
-
if (addTypeParser && type !== "json") {
|
|
292
|
-
typePrefix = `${type} `;
|
|
293
|
-
}
|
|
294
|
-
const input = `typeof ${schemaRef}${addTypeParser ? `.${type}` : ""}`;
|
|
295
|
-
const endpoint = `${typePrefix}${operation.trigger.method.toUpperCase()} ${operation.trigger.path}`;
|
|
296
|
-
emitter.addEndpoint(
|
|
297
|
-
endpoint,
|
|
298
|
-
`{input: z.infer<${input}>; output: ${output.use}; error: ${(operation.errors ?? ["ServerError"]).concat(`ParseError<${input}>`).join("|")}}`
|
|
299
|
-
);
|
|
300
|
-
schemaEndpoint.addEndpoint(
|
|
301
|
-
endpoint,
|
|
302
|
-
`{
|
|
303
313
|
schema: ${schemaRef}${addTypeParser ? `.${type}` : ""},
|
|
304
314
|
toRequest(input: Endpoints['${endpoint}']['input']) {
|
|
305
315
|
const endpoint = '${endpoint}';
|
|
@@ -311,8 +321,7 @@ function generateClientSdk(spec) {
|
|
|
311
321
|
}));
|
|
312
322
|
},
|
|
313
323
|
}`
|
|
314
|
-
|
|
315
|
-
}
|
|
324
|
+
);
|
|
316
325
|
}
|
|
317
326
|
}
|
|
318
327
|
}
|
|
@@ -320,19 +329,6 @@ function generateClientSdk(spec) {
|
|
|
320
329
|
`import type { ${removeDuplicates(errors, (it) => it).join(", ")} } from './http/response.ts';`
|
|
321
330
|
);
|
|
322
331
|
return {
|
|
323
|
-
...Object.fromEntries(
|
|
324
|
-
Object.entries(schemas).map(([key, value]) => [
|
|
325
|
-
`inputs/${key}.ts`,
|
|
326
|
-
[
|
|
327
|
-
// schemasImports.length
|
|
328
|
-
// ? `import {${removeDuplicates(schemasImports, (it) => it)}} from '../zod';`
|
|
329
|
-
// : '',
|
|
330
|
-
spec.commonZod ? 'import * as commonZod from "../zod.ts";' : "",
|
|
331
|
-
...value
|
|
332
|
-
].map((it) => it.trim()).filter(Boolean).join("\n") + "\n"
|
|
333
|
-
// add a newline at the end
|
|
334
|
-
])
|
|
335
|
-
),
|
|
336
332
|
"client.ts": client_default(spec),
|
|
337
333
|
"schemas.ts": schemaEndpoint.complete(),
|
|
338
334
|
"endpoints.ts": emitter.complete()
|
|
@@ -421,6 +417,25 @@ function importsToString(...imports) {
|
|
|
421
417
|
throw new Error(`Invalid import ${JSON.stringify(it)}`);
|
|
422
418
|
});
|
|
423
419
|
}
|
|
420
|
+
function exclude2(list, exclude3) {
|
|
421
|
+
return list.filter((it) => !exclude3.includes(it));
|
|
422
|
+
}
|
|
423
|
+
function useImports(content, imports) {
|
|
424
|
+
const output = [];
|
|
425
|
+
for (const it of mergeImports(imports)) {
|
|
426
|
+
const singleImport = it.defaultImport ?? it.namespaceImport;
|
|
427
|
+
if (singleImport && content.includes(singleImport)) {
|
|
428
|
+
output.push(importsToString(it).join("\n"));
|
|
429
|
+
} else if (it.namedImports.length) {
|
|
430
|
+
for (const namedImport of it.namedImports) {
|
|
431
|
+
if (content.includes(namedImport.name)) {
|
|
432
|
+
output.push(importsToString(it).join("\n"));
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return output;
|
|
438
|
+
}
|
|
424
439
|
|
|
425
440
|
// packages/typescript/src/lib/emitters/interface.ts
|
|
426
441
|
var TypeScriptDeserialzer = class {
|
|
@@ -431,6 +446,70 @@ var TypeScriptDeserialzer = class {
|
|
|
431
446
|
this.#spec = spec;
|
|
432
447
|
this.#onRef = onRef;
|
|
433
448
|
}
|
|
449
|
+
#stringifyKey = (key) => {
|
|
450
|
+
const reservedWords = [
|
|
451
|
+
"constructor",
|
|
452
|
+
"prototype",
|
|
453
|
+
"break",
|
|
454
|
+
"case",
|
|
455
|
+
"catch",
|
|
456
|
+
"class",
|
|
457
|
+
"const",
|
|
458
|
+
"continue",
|
|
459
|
+
"debugger",
|
|
460
|
+
"default",
|
|
461
|
+
"delete",
|
|
462
|
+
"do",
|
|
463
|
+
"else",
|
|
464
|
+
"export",
|
|
465
|
+
"extends",
|
|
466
|
+
"false",
|
|
467
|
+
"finally",
|
|
468
|
+
"for",
|
|
469
|
+
"function",
|
|
470
|
+
"if",
|
|
471
|
+
"import",
|
|
472
|
+
"in",
|
|
473
|
+
"instanceof",
|
|
474
|
+
"new",
|
|
475
|
+
"null",
|
|
476
|
+
"return",
|
|
477
|
+
"super",
|
|
478
|
+
"switch",
|
|
479
|
+
"this",
|
|
480
|
+
"throw",
|
|
481
|
+
"true",
|
|
482
|
+
"try",
|
|
483
|
+
"typeof",
|
|
484
|
+
"var",
|
|
485
|
+
"void",
|
|
486
|
+
"while",
|
|
487
|
+
"with",
|
|
488
|
+
"yield"
|
|
489
|
+
];
|
|
490
|
+
if (reservedWords.includes(key)) {
|
|
491
|
+
return `'${key}'`;
|
|
492
|
+
}
|
|
493
|
+
if (key.trim() === "") {
|
|
494
|
+
return `'${key}'`;
|
|
495
|
+
}
|
|
496
|
+
const firstChar = key.charAt(0);
|
|
497
|
+
const validFirstChar = firstChar >= "a" && firstChar <= "z" || firstChar >= "A" && firstChar <= "Z" || firstChar === "_" || firstChar === "$";
|
|
498
|
+
if (!validFirstChar) {
|
|
499
|
+
return `'${key.replace(/'/g, "\\'")}'`;
|
|
500
|
+
}
|
|
501
|
+
for (let i = 1; i < key.length; i++) {
|
|
502
|
+
const char = key.charAt(i);
|
|
503
|
+
const validChar = char >= "a" && char <= "z" || char >= "A" && char <= "Z" || char >= "0" && char <= "9" || char === "_" || char === "$";
|
|
504
|
+
if (!validChar) {
|
|
505
|
+
return `'${key.replace(/'/g, "\\'")}'`;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
return key;
|
|
509
|
+
};
|
|
510
|
+
#stringifyKeyV2 = (value) => {
|
|
511
|
+
return `'${value}'`;
|
|
512
|
+
};
|
|
434
513
|
/**
|
|
435
514
|
* Handle objects (properties)
|
|
436
515
|
*/
|
|
@@ -439,7 +518,7 @@ var TypeScriptDeserialzer = class {
|
|
|
439
518
|
const propEntries = Object.entries(properties).map(([key, propSchema]) => {
|
|
440
519
|
const isRequired = (schema.required ?? []).includes(key);
|
|
441
520
|
const tsType = this.handle(propSchema, isRequired);
|
|
442
|
-
return `${key}: ${tsType}`;
|
|
521
|
+
return `${this.#stringifyKeyV2(key)}: ${tsType}`;
|
|
443
522
|
});
|
|
444
523
|
if (schema.additionalProperties) {
|
|
445
524
|
if (typeof schema.additionalProperties === "object") {
|
|
@@ -689,10 +768,16 @@ var ZodDeserialzer = class {
|
|
|
689
768
|
}
|
|
690
769
|
allOf(schemas) {
|
|
691
770
|
const allOfSchemas = schemas.map((sub) => this.handle(sub, true));
|
|
771
|
+
if (allOfSchemas.length === 1) {
|
|
772
|
+
return allOfSchemas[0];
|
|
773
|
+
}
|
|
692
774
|
return allOfSchemas.length ? `z.intersection(${allOfSchemas.join(", ")})` : allOfSchemas[0];
|
|
693
775
|
}
|
|
694
776
|
anyOf(schemas, required) {
|
|
695
777
|
const anyOfSchemas = schemas.map((sub) => this.handle(sub, false));
|
|
778
|
+
if (anyOfSchemas.length === 1) {
|
|
779
|
+
return anyOfSchemas[0];
|
|
780
|
+
}
|
|
696
781
|
return anyOfSchemas.length > 1 ? `z.union([${anyOfSchemas.join(", ")}])${appendOptional2(required)}` : (
|
|
697
782
|
// Handle an invalid anyOf with one schema
|
|
698
783
|
anyOfSchemas[0]
|
|
@@ -708,6 +793,9 @@ var ZodDeserialzer = class {
|
|
|
708
793
|
}
|
|
709
794
|
return this.handle(sub, false);
|
|
710
795
|
});
|
|
796
|
+
if (oneOfSchemas.length === 1) {
|
|
797
|
+
return oneOfSchemas[0];
|
|
798
|
+
}
|
|
711
799
|
return oneOfSchemas.length > 1 ? `z.union([${oneOfSchemas.join(", ")}])${appendOptional2(required)}` : (
|
|
712
800
|
// Handle an invalid oneOf with one schema
|
|
713
801
|
oneOfSchemas[0]
|
|
@@ -869,7 +957,18 @@ var defaults = {
|
|
|
869
957
|
};
|
|
870
958
|
function generateCode(config) {
|
|
871
959
|
const commonSchemas = {};
|
|
872
|
-
const
|
|
960
|
+
const commonZod = /* @__PURE__ */ new Map();
|
|
961
|
+
const commonZodImports = [];
|
|
962
|
+
const zodDeserialzer = new ZodDeserialzer(config.spec, (model, schema) => {
|
|
963
|
+
commonZod.set(model, schema);
|
|
964
|
+
commonZodImports.push({
|
|
965
|
+
defaultImport: void 0,
|
|
966
|
+
isTypeOnly: true,
|
|
967
|
+
moduleSpecifier: `./${model}.ts`,
|
|
968
|
+
namedImports: [{ isTypeOnly: true, name: model }],
|
|
969
|
+
namespaceImport: void 0
|
|
970
|
+
});
|
|
971
|
+
});
|
|
873
972
|
const groups = {};
|
|
874
973
|
const outputs = {};
|
|
875
974
|
for (const [path, methods2] of Object.entries(config.spec.paths ?? {})) {
|
|
@@ -1003,26 +1102,15 @@ function generateCode(config) {
|
|
|
1003
1102
|
responseContent["application/json"].schema,
|
|
1004
1103
|
true
|
|
1005
1104
|
) : "ReadableStream";
|
|
1006
|
-
|
|
1007
|
-
const singleImport = it.defaultImport ?? it.namespaceImport;
|
|
1008
|
-
if (singleImport && responseSchema.includes(singleImport)) {
|
|
1009
|
-
output.push(importsToString(it).join("\n"));
|
|
1010
|
-
} else if (it.namedImports.length) {
|
|
1011
|
-
for (const namedImport of it.namedImports) {
|
|
1012
|
-
if (responseSchema.includes(namedImport.name)) {
|
|
1013
|
-
output.push(importsToString(it).join("\n"));
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1105
|
+
output.push(...useImports(responseSchema, imports));
|
|
1018
1106
|
output.push(
|
|
1019
|
-
`export type ${
|
|
1107
|
+
`export type ${pascalcase(operationName + " output")} = ${responseSchema}`
|
|
1020
1108
|
);
|
|
1021
1109
|
}
|
|
1022
1110
|
}
|
|
1023
1111
|
if (!foundResponse) {
|
|
1024
1112
|
output.push(
|
|
1025
|
-
`export type ${
|
|
1113
|
+
`export type ${pascalcase(operationName + " output")} = void`
|
|
1026
1114
|
);
|
|
1027
1115
|
}
|
|
1028
1116
|
outputs[`${spinalcase2(operationName)}.ts`] = output.join("\n");
|
|
@@ -1034,8 +1122,8 @@ function generateCode(config) {
|
|
|
1034
1122
|
contentType,
|
|
1035
1123
|
schemas: types,
|
|
1036
1124
|
formatOutput: () => ({
|
|
1037
|
-
import:
|
|
1038
|
-
use:
|
|
1125
|
+
import: pascalcase(operationName + " output"),
|
|
1126
|
+
use: pascalcase(operationName + " output")
|
|
1039
1127
|
}),
|
|
1040
1128
|
trigger: {
|
|
1041
1129
|
path,
|
|
@@ -1044,7 +1132,7 @@ function generateCode(config) {
|
|
|
1044
1132
|
});
|
|
1045
1133
|
}
|
|
1046
1134
|
}
|
|
1047
|
-
return { groups, commonSchemas, outputs };
|
|
1135
|
+
return { groups, commonSchemas, commonZod, outputs };
|
|
1048
1136
|
}
|
|
1049
1137
|
|
|
1050
1138
|
// packages/typescript/src/lib/http/interceptors.txt
|
|
@@ -1057,7 +1145,7 @@ var parse_response_default = "import { parse } from 'fast-content-type-parse';\n
|
|
|
1057
1145
|
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";
|
|
1058
1146
|
|
|
1059
1147
|
// packages/typescript/src/lib/http/request.txt
|
|
1060
|
-
var request_default = "export type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\nexport type ContentType = 'xml' | 'json' | 'urlencoded' | 'multipart';\nexport type Endpoint =\n | `${ContentType} ${Method} ${string}`\n | `${Method} ${string}`;\n\nexport type BodyInit =\n | ArrayBuffer\n | Blob\n | FormData\n | URLSearchParams\n | null\n | string;\n\nexport function createUrl(path: string, query: URLSearchParams) {\n const url = new URL(path, `local://`);\n url.search = query.toString();\n return url;\n}\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\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
|
|
1148
|
+
var request_default = "export type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\nexport type ContentType = 'xml' | 'json' | 'urlencoded' | 'multipart';\nexport type Endpoint =\n | `${ContentType} ${Method} ${string}`\n | `${Method} ${string}`;\n\nexport type BodyInit =\n | ArrayBuffer\n | Blob\n | FormData\n | URLSearchParams\n | null\n | string;\n\nexport function createUrl(path: string, query: URLSearchParams) {\n const url = new URL(path, `local://`);\n url.search = query.toString();\n return url;\n}\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\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 protected input: Input;\n protected props: Props;\n\n constructor(\n input: Input,\n props: Props,\n ) {\n this.input = input;\n this.props = props;\n }\n\n abstract getBody(): BodyInit | null;\n abstract getHeaders(): 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: 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 NoBodySerializer extends Serializer {\n getBody(): BodyInit | null {\n return null;\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 nobody(input: Input, props: Props) {\n return new NoBodySerializer(input, props).serialize();\n}\nexport function formdata(input: Input, props: Props) {\n return new FormDataSerializer(input, props).serialize();\n}\n\nexport function toRequest<T extends Endpoint>(\n endpoint: T,\n input: Serialized,\n): Request {\n const [method, path] = endpoint.split(' ');\n const pathVariable = template(path, input.params);\n\n const url = createUrl(pathVariable, input.query);\n return new Request(url, {\n method: method,\n headers: input.headers,\n body: method === 'GET' ? undefined : input.body,\n });\n}\n";
|
|
1061
1149
|
|
|
1062
1150
|
// packages/typescript/src/lib/http/response.txt
|
|
1063
1151
|
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";
|
|
@@ -1087,23 +1175,24 @@ function security(spec) {
|
|
|
1087
1175
|
return options;
|
|
1088
1176
|
}
|
|
1089
1177
|
async function generate(spec, settings) {
|
|
1090
|
-
const { commonSchemas, groups, outputs } = generateCode({
|
|
1178
|
+
const { commonSchemas, groups, outputs, commonZod } = generateCode({
|
|
1091
1179
|
spec,
|
|
1092
1180
|
style: "github",
|
|
1093
1181
|
target: "javascript"
|
|
1094
1182
|
});
|
|
1095
1183
|
const output = settings.mode === "full" ? join2(settings.output, "src") : settings.output;
|
|
1096
1184
|
const options = security(spec);
|
|
1097
|
-
const clientFiles =
|
|
1185
|
+
const clientFiles = generateSDK({
|
|
1098
1186
|
name: settings.name || "Client",
|
|
1099
1187
|
operations: groups,
|
|
1100
1188
|
servers: spec.servers?.map((server) => server.url) || [],
|
|
1101
1189
|
options
|
|
1102
1190
|
});
|
|
1191
|
+
const inputFiles = generateInputs(groups, commonZod);
|
|
1103
1192
|
await writeFiles(output, {
|
|
1104
|
-
"outputs
|
|
1105
|
-
"inputs
|
|
1106
|
-
|
|
1193
|
+
"outputs/.gitkeep": "",
|
|
1194
|
+
"inputs/.gitkeep": "",
|
|
1195
|
+
"models/.getkeep": ""
|
|
1107
1196
|
// 'README.md': readme,
|
|
1108
1197
|
});
|
|
1109
1198
|
await writeFiles(join2(output, "http"), {
|
|
@@ -1115,15 +1204,16 @@ async function generate(spec, settings) {
|
|
|
1115
1204
|
"request.ts": request_default
|
|
1116
1205
|
});
|
|
1117
1206
|
await writeFiles(join2(output, "outputs"), outputs);
|
|
1118
|
-
const
|
|
1207
|
+
const modelsImports = Object.entries(commonSchemas).map(([name]) => name);
|
|
1119
1208
|
await writeFiles(output, {
|
|
1120
1209
|
...clientFiles,
|
|
1210
|
+
...inputFiles,
|
|
1121
1211
|
...Object.fromEntries(
|
|
1122
1212
|
Object.entries(commonSchemas).map(([name, schema]) => [
|
|
1123
1213
|
`models/${name}.ts`,
|
|
1124
1214
|
[
|
|
1125
1215
|
`import { z } from 'zod';`,
|
|
1126
|
-
...
|
|
1216
|
+
...exclude2(modelsImports, [name]).map(
|
|
1127
1217
|
(it) => `import type { ${it} } from './${it}.ts';`
|
|
1128
1218
|
),
|
|
1129
1219
|
`export type ${name} = ${schema};`
|
|
@@ -1134,25 +1224,58 @@ async function generate(spec, settings) {
|
|
|
1134
1224
|
const folders = [
|
|
1135
1225
|
getFolderExports(output),
|
|
1136
1226
|
getFolderExports(join2(output, "outputs")),
|
|
1137
|
-
getFolderExports(
|
|
1227
|
+
getFolderExports(
|
|
1228
|
+
join2(output, "inputs"),
|
|
1229
|
+
["ts"],
|
|
1230
|
+
(dirent) => dirent.isDirectory() && dirent.name === "schemas"
|
|
1231
|
+
),
|
|
1138
1232
|
getFolderExports(join2(output, "http"))
|
|
1139
1233
|
];
|
|
1140
|
-
if (
|
|
1234
|
+
if (modelsImports.length) {
|
|
1141
1235
|
folders.push(getFolderExports(join2(output, "models")));
|
|
1142
1236
|
}
|
|
1143
1237
|
const [index, outputIndex, inputsIndex, httpIndex, modelsIndex] = await Promise.all(folders);
|
|
1144
1238
|
await writeFiles(output, {
|
|
1145
1239
|
"index.ts": index,
|
|
1146
1240
|
"outputs/index.ts": outputIndex,
|
|
1147
|
-
"inputs/index.ts": inputsIndex,
|
|
1241
|
+
"inputs/index.ts": inputsIndex || null,
|
|
1148
1242
|
"http/index.ts": httpIndex,
|
|
1149
|
-
...
|
|
1243
|
+
...modelsImports.length ? { "models/index.ts": modelsIndex } : {}
|
|
1150
1244
|
});
|
|
1151
1245
|
if (settings.mode === "full") {
|
|
1152
1246
|
await writeFiles(settings.output, {
|
|
1153
1247
|
"package.json": {
|
|
1154
1248
|
ignoreIfExists: true,
|
|
1155
|
-
content:
|
|
1249
|
+
content: JSON.stringify(
|
|
1250
|
+
{
|
|
1251
|
+
type: "module",
|
|
1252
|
+
main: "./src/index.ts",
|
|
1253
|
+
dependencies: { "fast-content-type-parse": "^3.0.0" }
|
|
1254
|
+
},
|
|
1255
|
+
null,
|
|
1256
|
+
2
|
|
1257
|
+
)
|
|
1258
|
+
},
|
|
1259
|
+
"tsconfig.json": {
|
|
1260
|
+
ignoreIfExists: false,
|
|
1261
|
+
content: JSON.stringify(
|
|
1262
|
+
{
|
|
1263
|
+
compilerOptions: {
|
|
1264
|
+
skipLibCheck: true,
|
|
1265
|
+
skipDefaultLibCheck: true,
|
|
1266
|
+
target: "ESNext",
|
|
1267
|
+
module: "ESNext",
|
|
1268
|
+
noEmit: true,
|
|
1269
|
+
allowImportingTsExtensions: true,
|
|
1270
|
+
verbatimModuleSyntax: true,
|
|
1271
|
+
baseUrl: ".",
|
|
1272
|
+
moduleResolution: "bundler"
|
|
1273
|
+
},
|
|
1274
|
+
include: ["**/*.ts"]
|
|
1275
|
+
},
|
|
1276
|
+
null,
|
|
1277
|
+
2
|
|
1278
|
+
)
|
|
1156
1279
|
}
|
|
1157
1280
|
});
|
|
1158
1281
|
}
|
|
@@ -1161,9 +1284,6 @@ async function generate(spec, settings) {
|
|
|
1161
1284
|
env: npmRunPathEnv()
|
|
1162
1285
|
});
|
|
1163
1286
|
}
|
|
1164
|
-
function exclude(list, exclude2) {
|
|
1165
|
-
return list.filter((it) => !exclude2.includes(it));
|
|
1166
|
-
}
|
|
1167
1287
|
|
|
1168
1288
|
// packages/typescript/src/lib/watcher.ts
|
|
1169
1289
|
import { watch as nodeWatch } from "node:fs/promises";
|