@sdk-it/typescript 0.12.6 → 0.12.8
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 +238 -193
- package/dist/index.js.map +4 -4
- 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
|
@@ -1,89 +1,22 @@
|
|
|
1
1
|
// packages/typescript/src/lib/generate.ts
|
|
2
|
-
import { join
|
|
2
|
+
import { join } from "node:path";
|
|
3
3
|
import { npmRunPathEnv } from "npm-run-path";
|
|
4
|
-
|
|
5
|
-
// packages/core/dist/index.js
|
|
6
|
-
import ts, { TypeFlags, symbolName } from "typescript";
|
|
7
|
-
import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
8
|
-
import { dirname, isAbsolute, join } from "node:path";
|
|
9
|
-
import debug from "debug";
|
|
10
|
-
import ts2 from "typescript";
|
|
11
|
-
var deriveSymbol = Symbol.for("serialize");
|
|
12
|
-
var $types = Symbol.for("types");
|
|
13
|
-
async function exist(file) {
|
|
14
|
-
return stat(file).then(() => true).catch(() => false);
|
|
15
|
-
}
|
|
16
|
-
async function writeFiles(dir, contents) {
|
|
17
|
-
return Promise.all(
|
|
18
|
-
Object.entries(contents).map(async ([file, content]) => {
|
|
19
|
-
const filePath = isAbsolute(file) ? file : join(dir, file);
|
|
20
|
-
await mkdir(dirname(filePath), { recursive: true });
|
|
21
|
-
if (typeof content === "string") {
|
|
22
|
-
await writeFile(filePath, content, "utf-8");
|
|
23
|
-
} else {
|
|
24
|
-
if (content.ignoreIfExists) {
|
|
25
|
-
if (!await exist(filePath)) {
|
|
26
|
-
await writeFile(filePath, content.content, "utf-8");
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
})
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
async function getFolderExports(folder, extensions = ["ts"]) {
|
|
34
|
-
const files = await readdir(folder, { withFileTypes: true });
|
|
35
|
-
const exports = [];
|
|
36
|
-
for (const file of files) {
|
|
37
|
-
if (file.isDirectory()) {
|
|
38
|
-
exports.push(`export * from './${file.name}/index.ts';`);
|
|
39
|
-
} else if (file.name !== "index.ts" && extensions.includes(getExt(file.name))) {
|
|
40
|
-
exports.push(`export * from './${file.name}';`);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return exports.join("\n");
|
|
44
|
-
}
|
|
45
|
-
var getExt = (fileName) => {
|
|
46
|
-
if (!fileName) {
|
|
47
|
-
return "";
|
|
48
|
-
}
|
|
49
|
-
const lastDot = fileName.lastIndexOf(".");
|
|
50
|
-
if (lastDot === -1) {
|
|
51
|
-
return "";
|
|
52
|
-
}
|
|
53
|
-
const ext = fileName.slice(lastDot + 1).split("/").filter(Boolean).join("");
|
|
54
|
-
if (ext === fileName) {
|
|
55
|
-
return "";
|
|
56
|
-
}
|
|
57
|
-
return ext || "txt";
|
|
58
|
-
};
|
|
59
|
-
var methods = [
|
|
60
|
-
"get",
|
|
61
|
-
"post",
|
|
62
|
-
"put",
|
|
63
|
-
"patch",
|
|
64
|
-
"delete",
|
|
65
|
-
"trace",
|
|
66
|
-
"head"
|
|
67
|
-
];
|
|
68
|
-
var logger = debug("january:client");
|
|
69
|
-
function removeDuplicates(data, accessor) {
|
|
70
|
-
return [...new Map(data.map((x) => [accessor(x), x])).values()];
|
|
71
|
-
}
|
|
72
|
-
function toLitObject(obj, accessor = (value) => value) {
|
|
73
|
-
return `{${Object.keys(obj).map((key) => `${key}: ${accessor(obj[key])}`).join(", ")}}`;
|
|
74
|
-
}
|
|
4
|
+
import { getFolderExports, methods, writeFiles } from "@sdk-it/core";
|
|
75
5
|
|
|
76
6
|
// packages/typescript/src/lib/generator.ts
|
|
77
7
|
import { get as get2, merge } from "lodash-es";
|
|
78
|
-
import { camelcase as camelcase2, pascalcase
|
|
8
|
+
import { camelcase as camelcase2, pascalcase, spinalcase as spinalcase2 } from "stringcase";
|
|
79
9
|
|
|
80
10
|
// packages/typescript/src/lib/utils.ts
|
|
81
11
|
import { get } from "lodash-es";
|
|
12
|
+
import { removeDuplicates as removeDuplicates2 } from "@sdk-it/core";
|
|
82
13
|
|
|
83
14
|
// packages/typescript/src/lib/sdk.ts
|
|
84
|
-
import { camelcase,
|
|
15
|
+
import { camelcase, spinalcase } from "stringcase";
|
|
16
|
+
import { removeDuplicates, toLitObject as toLitObject2 } from "@sdk-it/core";
|
|
85
17
|
|
|
86
18
|
// packages/typescript/src/lib/client.ts
|
|
19
|
+
import { toLitObject } from "@sdk-it/core";
|
|
87
20
|
var client_default = (spec) => {
|
|
88
21
|
const optionsEntries = Object.entries(spec.options).map(
|
|
89
22
|
([key, value]) => [`'${key}'`, value]
|
|
@@ -122,8 +55,10 @@ ${spec.servers.length ? `export type Servers = typeof servers[number];` : ""}
|
|
|
122
55
|
type ${spec.name}Options = z.infer<typeof optionsSchema>;
|
|
123
56
|
|
|
124
57
|
export class ${spec.name} {
|
|
125
|
-
|
|
126
|
-
constructor(
|
|
58
|
+
public options: ${spec.name}Options
|
|
59
|
+
constructor(options: ${spec.name}Options) {
|
|
60
|
+
this.options = options;
|
|
61
|
+
}
|
|
127
62
|
|
|
128
63
|
async request<E extends keyof Endpoints>(
|
|
129
64
|
endpoint: E,
|
|
@@ -200,37 +135,63 @@ ${this.endpoints.join("\n")}
|
|
|
200
135
|
}`;
|
|
201
136
|
}
|
|
202
137
|
};
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
}
|
|
138
|
+
function generateInputs(operationsSet, commonZod) {
|
|
139
|
+
const commonImports = commonZod.keys().toArray();
|
|
140
|
+
const inputs = {};
|
|
141
|
+
for (const [name, operations] of Object.entries(operationsSet)) {
|
|
142
|
+
const output = [];
|
|
143
|
+
const imports = /* @__PURE__ */ new Set(['import { z } from "zod";']);
|
|
144
|
+
for (const operation of operations) {
|
|
145
|
+
const schemaName = camelcase(`${operation.name} schema`);
|
|
146
|
+
const schema = `export const ${schemaName} = ${Object.keys(operation.schemas).length === 1 ? Object.values(operation.schemas)[0] : toLitObject2(operation.schemas)};`;
|
|
147
|
+
const inputContent = schema;
|
|
148
|
+
for (const schema2 of commonImports) {
|
|
149
|
+
if (inputContent.includes(schema2)) {
|
|
150
|
+
imports.add(
|
|
151
|
+
`import { ${schema2} } from './schemas/${spinalcase(schema2)}.ts';`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
output.push(inputContent);
|
|
156
|
+
}
|
|
157
|
+
inputs[`inputs/${spinalcase(name)}.ts`] = [...imports, ...output].join("\n") + "\n";
|
|
209
158
|
}
|
|
210
|
-
|
|
211
|
-
|
|
159
|
+
const schemas = commonZod.entries().reduce((acc, [name, schema]) => {
|
|
160
|
+
const output = [`import { z } from 'zod';`];
|
|
161
|
+
const content = `export const ${name} = ${schema};`;
|
|
162
|
+
for (const schema2 of commonImports) {
|
|
163
|
+
const preciseMatch = new RegExp(`\\b${schema2}\\b`);
|
|
164
|
+
if (preciseMatch.test(content) && schema2 !== name) {
|
|
165
|
+
output.push(
|
|
166
|
+
`import { ${schema2} } from './${spinalcase(schema2)}.ts';`
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
output.push(content);
|
|
171
|
+
return [
|
|
172
|
+
[`inputs/schemas/${spinalcase(name)}.ts`, output.join("\n")],
|
|
173
|
+
...acc
|
|
174
|
+
];
|
|
175
|
+
}, []);
|
|
176
|
+
return {
|
|
177
|
+
...Object.fromEntries(schemas),
|
|
178
|
+
...inputs
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function generateSDK(spec) {
|
|
212
182
|
const emitter = new Emitter();
|
|
213
|
-
const streamEmitter = new StreamEmitter();
|
|
214
|
-
const schemas = {};
|
|
215
183
|
const schemaEndpoint = new SchemaEndpoint();
|
|
216
184
|
const errors = [];
|
|
217
185
|
for (const [name, operations] of Object.entries(spec.operations)) {
|
|
218
|
-
const featureSchemaFileName = camelcase(name);
|
|
219
|
-
schemas[featureSchemaFileName] = [`import z from 'zod';`];
|
|
220
186
|
emitter.addImport(
|
|
221
|
-
`import * as ${
|
|
222
|
-
);
|
|
223
|
-
streamEmitter.addImport(
|
|
224
|
-
`import * as ${featureSchemaFileName} from './inputs/${featureSchemaFileName}.ts';`
|
|
187
|
+
`import * as ${camelcase(name)} from './inputs/${spinalcase(name)}.ts';`
|
|
225
188
|
);
|
|
226
189
|
schemaEndpoint.addImport(
|
|
227
|
-
`import * as ${
|
|
190
|
+
`import * as ${camelcase(name)} from './inputs/${spinalcase(name)}.ts';`
|
|
228
191
|
);
|
|
229
192
|
for (const operation of operations) {
|
|
230
193
|
const schemaName = camelcase(`${operation.name} schema`);
|
|
231
|
-
const
|
|
232
|
-
schemas[featureSchemaFileName].push(schema);
|
|
233
|
-
const schemaRef = `${featureSchemaFileName}.${schemaName}`;
|
|
194
|
+
const schemaRef = `${camelcase(name)}.${schemaName}`;
|
|
234
195
|
const output = operation.formatOutput();
|
|
235
196
|
const inputHeaders = [];
|
|
236
197
|
const inputQuery = [];
|
|
@@ -255,51 +216,25 @@ function generateClientSdk(spec) {
|
|
|
255
216
|
);
|
|
256
217
|
}
|
|
257
218
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
219
|
+
emitter.addImport(
|
|
220
|
+
`import type {${output.import}} from './outputs/${spinalcase(operation.name)}.ts';`
|
|
221
|
+
);
|
|
222
|
+
errors.push(...operation.errors ?? []);
|
|
223
|
+
const addTypeParser = Object.keys(operation.schemas).length > 1;
|
|
224
|
+
for (const type in operation.schemas ?? {}) {
|
|
225
|
+
let typePrefix = "";
|
|
226
|
+
if (addTypeParser && type !== "json") {
|
|
227
|
+
typePrefix = `${type} `;
|
|
228
|
+
}
|
|
229
|
+
const input = `typeof ${schemaRef}${addTypeParser ? `.${type}` : ""}`;
|
|
230
|
+
const endpoint = `${typePrefix}${operation.trigger.method.toUpperCase()} ${operation.trigger.path}`;
|
|
231
|
+
emitter.addEndpoint(
|
|
265
232
|
endpoint,
|
|
266
|
-
`{input:
|
|
233
|
+
`{input: z.infer<${input}>; output: ${output.use}; error: ${(operation.errors ?? ["ServerError"]).concat(`ParseError<${input}>`).join("|")}}`
|
|
267
234
|
);
|
|
268
235
|
schemaEndpoint.addEndpoint(
|
|
269
236
|
endpoint,
|
|
270
237
|
`{
|
|
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
238
|
schema: ${schemaRef}${addTypeParser ? `.${type}` : ""},
|
|
304
239
|
toRequest(input: Endpoints['${endpoint}']['input']) {
|
|
305
240
|
const endpoint = '${endpoint}';
|
|
@@ -311,8 +246,7 @@ function generateClientSdk(spec) {
|
|
|
311
246
|
}));
|
|
312
247
|
},
|
|
313
248
|
}`
|
|
314
|
-
|
|
315
|
-
}
|
|
249
|
+
);
|
|
316
250
|
}
|
|
317
251
|
}
|
|
318
252
|
}
|
|
@@ -320,19 +254,6 @@ function generateClientSdk(spec) {
|
|
|
320
254
|
`import type { ${removeDuplicates(errors, (it) => it).join(", ")} } from './http/response.ts';`
|
|
321
255
|
);
|
|
322
256
|
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
257
|
"client.ts": client_default(spec),
|
|
337
258
|
"schemas.ts": schemaEndpoint.complete(),
|
|
338
259
|
"endpoints.ts": emitter.complete()
|
|
@@ -416,11 +337,30 @@ function importsToString(...imports) {
|
|
|
416
337
|
return `import * as ${it.namespaceImport} from '${it.moduleSpecifier}'`;
|
|
417
338
|
}
|
|
418
339
|
if (it.namedImports) {
|
|
419
|
-
return `import {${
|
|
340
|
+
return `import {${removeDuplicates2(it.namedImports, (it2) => it2.name).map((n) => `${n.isTypeOnly ? "type" : ""} ${n.name}`).join(", ")}} from '${it.moduleSpecifier}'`;
|
|
420
341
|
}
|
|
421
342
|
throw new Error(`Invalid import ${JSON.stringify(it)}`);
|
|
422
343
|
});
|
|
423
344
|
}
|
|
345
|
+
function exclude2(list, exclude3) {
|
|
346
|
+
return list.filter((it) => !exclude3.includes(it));
|
|
347
|
+
}
|
|
348
|
+
function useImports(content, imports) {
|
|
349
|
+
const output = [];
|
|
350
|
+
for (const it of mergeImports(imports)) {
|
|
351
|
+
const singleImport = it.defaultImport ?? it.namespaceImport;
|
|
352
|
+
if (singleImport && content.includes(singleImport)) {
|
|
353
|
+
output.push(importsToString(it).join("\n"));
|
|
354
|
+
} else if (it.namedImports.length) {
|
|
355
|
+
for (const namedImport of it.namedImports) {
|
|
356
|
+
if (content.includes(namedImport.name)) {
|
|
357
|
+
output.push(importsToString(it).join("\n"));
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return output;
|
|
363
|
+
}
|
|
424
364
|
|
|
425
365
|
// packages/typescript/src/lib/emitters/interface.ts
|
|
426
366
|
var TypeScriptDeserialzer = class {
|
|
@@ -431,6 +371,70 @@ var TypeScriptDeserialzer = class {
|
|
|
431
371
|
this.#spec = spec;
|
|
432
372
|
this.#onRef = onRef;
|
|
433
373
|
}
|
|
374
|
+
#stringifyKey = (key) => {
|
|
375
|
+
const reservedWords = [
|
|
376
|
+
"constructor",
|
|
377
|
+
"prototype",
|
|
378
|
+
"break",
|
|
379
|
+
"case",
|
|
380
|
+
"catch",
|
|
381
|
+
"class",
|
|
382
|
+
"const",
|
|
383
|
+
"continue",
|
|
384
|
+
"debugger",
|
|
385
|
+
"default",
|
|
386
|
+
"delete",
|
|
387
|
+
"do",
|
|
388
|
+
"else",
|
|
389
|
+
"export",
|
|
390
|
+
"extends",
|
|
391
|
+
"false",
|
|
392
|
+
"finally",
|
|
393
|
+
"for",
|
|
394
|
+
"function",
|
|
395
|
+
"if",
|
|
396
|
+
"import",
|
|
397
|
+
"in",
|
|
398
|
+
"instanceof",
|
|
399
|
+
"new",
|
|
400
|
+
"null",
|
|
401
|
+
"return",
|
|
402
|
+
"super",
|
|
403
|
+
"switch",
|
|
404
|
+
"this",
|
|
405
|
+
"throw",
|
|
406
|
+
"true",
|
|
407
|
+
"try",
|
|
408
|
+
"typeof",
|
|
409
|
+
"var",
|
|
410
|
+
"void",
|
|
411
|
+
"while",
|
|
412
|
+
"with",
|
|
413
|
+
"yield"
|
|
414
|
+
];
|
|
415
|
+
if (reservedWords.includes(key)) {
|
|
416
|
+
return `'${key}'`;
|
|
417
|
+
}
|
|
418
|
+
if (key.trim() === "") {
|
|
419
|
+
return `'${key}'`;
|
|
420
|
+
}
|
|
421
|
+
const firstChar = key.charAt(0);
|
|
422
|
+
const validFirstChar = firstChar >= "a" && firstChar <= "z" || firstChar >= "A" && firstChar <= "Z" || firstChar === "_" || firstChar === "$";
|
|
423
|
+
if (!validFirstChar) {
|
|
424
|
+
return `'${key.replace(/'/g, "\\'")}'`;
|
|
425
|
+
}
|
|
426
|
+
for (let i = 1; i < key.length; i++) {
|
|
427
|
+
const char = key.charAt(i);
|
|
428
|
+
const validChar = char >= "a" && char <= "z" || char >= "A" && char <= "Z" || char >= "0" && char <= "9" || char === "_" || char === "$";
|
|
429
|
+
if (!validChar) {
|
|
430
|
+
return `'${key.replace(/'/g, "\\'")}'`;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
return key;
|
|
434
|
+
};
|
|
435
|
+
#stringifyKeyV2 = (value) => {
|
|
436
|
+
return `'${value}'`;
|
|
437
|
+
};
|
|
434
438
|
/**
|
|
435
439
|
* Handle objects (properties)
|
|
436
440
|
*/
|
|
@@ -439,7 +443,7 @@ var TypeScriptDeserialzer = class {
|
|
|
439
443
|
const propEntries = Object.entries(properties).map(([key, propSchema]) => {
|
|
440
444
|
const isRequired = (schema.required ?? []).includes(key);
|
|
441
445
|
const tsType = this.handle(propSchema, isRequired);
|
|
442
|
-
return `${key}: ${tsType}`;
|
|
446
|
+
return `${this.#stringifyKeyV2(key)}: ${tsType}`;
|
|
443
447
|
});
|
|
444
448
|
if (schema.additionalProperties) {
|
|
445
449
|
if (typeof schema.additionalProperties === "object") {
|
|
@@ -689,10 +693,16 @@ var ZodDeserialzer = class {
|
|
|
689
693
|
}
|
|
690
694
|
allOf(schemas) {
|
|
691
695
|
const allOfSchemas = schemas.map((sub) => this.handle(sub, true));
|
|
696
|
+
if (allOfSchemas.length === 1) {
|
|
697
|
+
return allOfSchemas[0];
|
|
698
|
+
}
|
|
692
699
|
return allOfSchemas.length ? `z.intersection(${allOfSchemas.join(", ")})` : allOfSchemas[0];
|
|
693
700
|
}
|
|
694
701
|
anyOf(schemas, required) {
|
|
695
702
|
const anyOfSchemas = schemas.map((sub) => this.handle(sub, false));
|
|
703
|
+
if (anyOfSchemas.length === 1) {
|
|
704
|
+
return anyOfSchemas[0];
|
|
705
|
+
}
|
|
696
706
|
return anyOfSchemas.length > 1 ? `z.union([${anyOfSchemas.join(", ")}])${appendOptional2(required)}` : (
|
|
697
707
|
// Handle an invalid anyOf with one schema
|
|
698
708
|
anyOfSchemas[0]
|
|
@@ -708,6 +718,9 @@ var ZodDeserialzer = class {
|
|
|
708
718
|
}
|
|
709
719
|
return this.handle(sub, false);
|
|
710
720
|
});
|
|
721
|
+
if (oneOfSchemas.length === 1) {
|
|
722
|
+
return oneOfSchemas[0];
|
|
723
|
+
}
|
|
711
724
|
return oneOfSchemas.length > 1 ? `z.union([${oneOfSchemas.join(", ")}])${appendOptional2(required)}` : (
|
|
712
725
|
// Handle an invalid oneOf with one schema
|
|
713
726
|
oneOfSchemas[0]
|
|
@@ -869,7 +882,18 @@ var defaults = {
|
|
|
869
882
|
};
|
|
870
883
|
function generateCode(config) {
|
|
871
884
|
const commonSchemas = {};
|
|
872
|
-
const
|
|
885
|
+
const commonZod = /* @__PURE__ */ new Map();
|
|
886
|
+
const commonZodImports = [];
|
|
887
|
+
const zodDeserialzer = new ZodDeserialzer(config.spec, (model, schema) => {
|
|
888
|
+
commonZod.set(model, schema);
|
|
889
|
+
commonZodImports.push({
|
|
890
|
+
defaultImport: void 0,
|
|
891
|
+
isTypeOnly: true,
|
|
892
|
+
moduleSpecifier: `./${model}.ts`,
|
|
893
|
+
namedImports: [{ isTypeOnly: true, name: model }],
|
|
894
|
+
namespaceImport: void 0
|
|
895
|
+
});
|
|
896
|
+
});
|
|
873
897
|
const groups = {};
|
|
874
898
|
const outputs = {};
|
|
875
899
|
for (const [path, methods2] of Object.entries(config.spec.paths ?? {})) {
|
|
@@ -1003,26 +1027,15 @@ function generateCode(config) {
|
|
|
1003
1027
|
responseContent["application/json"].schema,
|
|
1004
1028
|
true
|
|
1005
1029
|
) : "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
|
-
}
|
|
1030
|
+
output.push(...useImports(responseSchema, imports));
|
|
1018
1031
|
output.push(
|
|
1019
|
-
`export type ${
|
|
1032
|
+
`export type ${pascalcase(operationName + " output")} = ${responseSchema}`
|
|
1020
1033
|
);
|
|
1021
1034
|
}
|
|
1022
1035
|
}
|
|
1023
1036
|
if (!foundResponse) {
|
|
1024
1037
|
output.push(
|
|
1025
|
-
`export type ${
|
|
1038
|
+
`export type ${pascalcase(operationName + " output")} = void`
|
|
1026
1039
|
);
|
|
1027
1040
|
}
|
|
1028
1041
|
outputs[`${spinalcase2(operationName)}.ts`] = output.join("\n");
|
|
@@ -1034,8 +1047,8 @@ function generateCode(config) {
|
|
|
1034
1047
|
contentType,
|
|
1035
1048
|
schemas: types,
|
|
1036
1049
|
formatOutput: () => ({
|
|
1037
|
-
import:
|
|
1038
|
-
use:
|
|
1050
|
+
import: pascalcase(operationName + " output"),
|
|
1051
|
+
use: pascalcase(operationName + " output")
|
|
1039
1052
|
}),
|
|
1040
1053
|
trigger: {
|
|
1041
1054
|
path,
|
|
@@ -1044,7 +1057,7 @@ function generateCode(config) {
|
|
|
1044
1057
|
});
|
|
1045
1058
|
}
|
|
1046
1059
|
}
|
|
1047
|
-
return { groups, commonSchemas, outputs };
|
|
1060
|
+
return { groups, commonSchemas, commonZod, outputs };
|
|
1048
1061
|
}
|
|
1049
1062
|
|
|
1050
1063
|
// packages/typescript/src/lib/http/interceptors.txt
|
|
@@ -1057,7 +1070,7 @@ var parse_response_default = "import { parse } from 'fast-content-type-parse';\n
|
|
|
1057
1070
|
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
1071
|
|
|
1059
1072
|
// 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
|
|
1073
|
+
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
1074
|
|
|
1062
1075
|
// packages/typescript/src/lib/http/response.txt
|
|
1063
1076
|
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,26 +1100,27 @@ function security(spec) {
|
|
|
1087
1100
|
return options;
|
|
1088
1101
|
}
|
|
1089
1102
|
async function generate(spec, settings) {
|
|
1090
|
-
const { commonSchemas, groups, outputs } = generateCode({
|
|
1103
|
+
const { commonSchemas, groups, outputs, commonZod } = generateCode({
|
|
1091
1104
|
spec,
|
|
1092
1105
|
style: "github",
|
|
1093
1106
|
target: "javascript"
|
|
1094
1107
|
});
|
|
1095
|
-
const output = settings.mode === "full" ?
|
|
1108
|
+
const output = settings.mode === "full" ? join(settings.output, "src") : settings.output;
|
|
1096
1109
|
const options = security(spec);
|
|
1097
|
-
const clientFiles =
|
|
1110
|
+
const clientFiles = generateSDK({
|
|
1098
1111
|
name: settings.name || "Client",
|
|
1099
1112
|
operations: groups,
|
|
1100
1113
|
servers: spec.servers?.map((server) => server.url) || [],
|
|
1101
1114
|
options
|
|
1102
1115
|
});
|
|
1116
|
+
const inputFiles = generateInputs(groups, commonZod);
|
|
1103
1117
|
await writeFiles(output, {
|
|
1104
|
-
"outputs
|
|
1105
|
-
"inputs
|
|
1106
|
-
|
|
1118
|
+
"outputs/.gitkeep": "",
|
|
1119
|
+
"inputs/.gitkeep": "",
|
|
1120
|
+
"models/.getkeep": ""
|
|
1107
1121
|
// 'README.md': readme,
|
|
1108
1122
|
});
|
|
1109
|
-
await writeFiles(
|
|
1123
|
+
await writeFiles(join(output, "http"), {
|
|
1110
1124
|
"interceptors.ts": interceptors_default,
|
|
1111
1125
|
"parse-response.ts": parse_response_default,
|
|
1112
1126
|
"send-request.ts": send_request_default,
|
|
@@ -1114,16 +1128,17 @@ async function generate(spec, settings) {
|
|
|
1114
1128
|
"parser.ts": parser_default,
|
|
1115
1129
|
"request.ts": request_default
|
|
1116
1130
|
});
|
|
1117
|
-
await writeFiles(
|
|
1118
|
-
const
|
|
1131
|
+
await writeFiles(join(output, "outputs"), outputs);
|
|
1132
|
+
const modelsImports = Object.entries(commonSchemas).map(([name]) => name);
|
|
1119
1133
|
await writeFiles(output, {
|
|
1120
1134
|
...clientFiles,
|
|
1135
|
+
...inputFiles,
|
|
1121
1136
|
...Object.fromEntries(
|
|
1122
1137
|
Object.entries(commonSchemas).map(([name, schema]) => [
|
|
1123
1138
|
`models/${name}.ts`,
|
|
1124
1139
|
[
|
|
1125
1140
|
`import { z } from 'zod';`,
|
|
1126
|
-
...
|
|
1141
|
+
...exclude2(modelsImports, [name]).map(
|
|
1127
1142
|
(it) => `import type { ${it} } from './${it}.ts';`
|
|
1128
1143
|
),
|
|
1129
1144
|
`export type ${name} = ${schema};`
|
|
@@ -1133,26 +1148,59 @@ async function generate(spec, settings) {
|
|
|
1133
1148
|
});
|
|
1134
1149
|
const folders = [
|
|
1135
1150
|
getFolderExports(output),
|
|
1136
|
-
getFolderExports(
|
|
1137
|
-
getFolderExports(
|
|
1138
|
-
|
|
1151
|
+
getFolderExports(join(output, "outputs")),
|
|
1152
|
+
getFolderExports(
|
|
1153
|
+
join(output, "inputs"),
|
|
1154
|
+
["ts"],
|
|
1155
|
+
(dirent) => dirent.isDirectory() && dirent.name === "schemas"
|
|
1156
|
+
),
|
|
1157
|
+
getFolderExports(join(output, "http"))
|
|
1139
1158
|
];
|
|
1140
|
-
if (
|
|
1141
|
-
folders.push(getFolderExports(
|
|
1159
|
+
if (modelsImports.length) {
|
|
1160
|
+
folders.push(getFolderExports(join(output, "models")));
|
|
1142
1161
|
}
|
|
1143
1162
|
const [index, outputIndex, inputsIndex, httpIndex, modelsIndex] = await Promise.all(folders);
|
|
1144
1163
|
await writeFiles(output, {
|
|
1145
1164
|
"index.ts": index,
|
|
1146
1165
|
"outputs/index.ts": outputIndex,
|
|
1147
|
-
"inputs/index.ts": inputsIndex,
|
|
1166
|
+
"inputs/index.ts": inputsIndex || null,
|
|
1148
1167
|
"http/index.ts": httpIndex,
|
|
1149
|
-
...
|
|
1168
|
+
...modelsImports.length ? { "models/index.ts": modelsIndex } : {}
|
|
1150
1169
|
});
|
|
1151
1170
|
if (settings.mode === "full") {
|
|
1152
1171
|
await writeFiles(settings.output, {
|
|
1153
1172
|
"package.json": {
|
|
1154
1173
|
ignoreIfExists: true,
|
|
1155
|
-
content:
|
|
1174
|
+
content: JSON.stringify(
|
|
1175
|
+
{
|
|
1176
|
+
type: "module",
|
|
1177
|
+
main: "./src/index.ts",
|
|
1178
|
+
dependencies: { "fast-content-type-parse": "^3.0.0" }
|
|
1179
|
+
},
|
|
1180
|
+
null,
|
|
1181
|
+
2
|
|
1182
|
+
)
|
|
1183
|
+
},
|
|
1184
|
+
"tsconfig.json": {
|
|
1185
|
+
ignoreIfExists: false,
|
|
1186
|
+
content: JSON.stringify(
|
|
1187
|
+
{
|
|
1188
|
+
compilerOptions: {
|
|
1189
|
+
skipLibCheck: true,
|
|
1190
|
+
skipDefaultLibCheck: true,
|
|
1191
|
+
target: "ESNext",
|
|
1192
|
+
module: "ESNext",
|
|
1193
|
+
noEmit: true,
|
|
1194
|
+
allowImportingTsExtensions: true,
|
|
1195
|
+
verbatimModuleSyntax: true,
|
|
1196
|
+
baseUrl: ".",
|
|
1197
|
+
moduleResolution: "bundler"
|
|
1198
|
+
},
|
|
1199
|
+
include: ["**/*.ts"]
|
|
1200
|
+
},
|
|
1201
|
+
null,
|
|
1202
|
+
2
|
|
1203
|
+
)
|
|
1156
1204
|
}
|
|
1157
1205
|
});
|
|
1158
1206
|
}
|
|
@@ -1161,9 +1209,6 @@ async function generate(spec, settings) {
|
|
|
1161
1209
|
env: npmRunPathEnv()
|
|
1162
1210
|
});
|
|
1163
1211
|
}
|
|
1164
|
-
function exclude(list, exclude2) {
|
|
1165
|
-
return list.filter((it) => !exclude2.includes(it));
|
|
1166
|
-
}
|
|
1167
1212
|
|
|
1168
1213
|
// packages/typescript/src/lib/watcher.ts
|
|
1169
1214
|
import { watch as nodeWatch } from "node:fs/promises";
|