@askrjs/cli 0.0.4 → 0.0.5
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/generate.js +26 -16
- package/package.json +1 -1
package/dist/generate.js
CHANGED
|
@@ -29,10 +29,12 @@ const pascal = (name) => {
|
|
|
29
29
|
const result = name.replace(/(^|[^A-Za-z0-9]+)([A-Za-z0-9])/g, (_, _s, c) => c.toUpperCase()).replace(/[^A-Za-z0-9_$]/g, "");
|
|
30
30
|
return /^\d/.test(result) ? `_${result}` : result;
|
|
31
31
|
};
|
|
32
|
-
function schemaType(schema, at, direction = "response") {
|
|
32
|
+
function schemaType(schema, at, direction = "response", references) {
|
|
33
33
|
if (schema.$ref) {
|
|
34
34
|
const name = decodeURIComponent(String(schema.$ref).split("/").at(-1));
|
|
35
|
-
|
|
35
|
+
const typeName = pascal(name);
|
|
36
|
+
references?.add(typeName);
|
|
37
|
+
return typeName;
|
|
36
38
|
}
|
|
37
39
|
if (Array.isArray(schema.type)) {
|
|
38
40
|
const includesNull = schema.type.includes("null");
|
|
@@ -40,25 +42,25 @@ function schemaType(schema, at, direction = "response") {
|
|
|
40
42
|
...schema,
|
|
41
43
|
type: variant,
|
|
42
44
|
nullable: false
|
|
43
|
-
}, at, direction));
|
|
45
|
+
}, at, direction, references));
|
|
44
46
|
if (includesNull) rendered.push("null");
|
|
45
47
|
if (rendered.length === 0) fail("Schema type array must not be empty", at);
|
|
46
48
|
return [...new Set(rendered)].join(" | ");
|
|
47
49
|
}
|
|
48
50
|
if (schema.const !== void 0) return JSON.stringify(schema.const);
|
|
49
51
|
if (schema.enum) return schema.enum.map((value) => JSON.stringify(value)).join(" | ") || "never";
|
|
50
|
-
if (schema.oneOf || schema.anyOf) return (schema.oneOf ?? schema.anyOf).map((item, i) => schemaType(item, `${at}/${schema.oneOf ? "oneOf" : "anyOf"}/${i}`, direction)).join(" | ");
|
|
51
|
-
if (schema.allOf) return schema.allOf.map((item, i) => schemaType(item, `${at}/allOf/${i}`, direction)).join(" & ");
|
|
52
|
+
if (schema.oneOf || schema.anyOf) return (schema.oneOf ?? schema.anyOf).map((item, i) => schemaType(item, `${at}/${schema.oneOf ? "oneOf" : "anyOf"}/${i}`, direction, references)).join(" | ");
|
|
53
|
+
if (schema.allOf) return schema.allOf.map((item, i) => schemaType(item, `${at}/allOf/${i}`, direction, references)).join(" & ");
|
|
52
54
|
let type = "never";
|
|
53
55
|
if (schema.type === "string") type = "string";
|
|
54
56
|
else if (schema.type === "number" || schema.type === "integer") type = "number";
|
|
55
57
|
else if (schema.type === "boolean") type = "boolean";
|
|
56
58
|
else if (schema.type === "null") type = "null";
|
|
57
|
-
else if (schema.type === "array" || schema.items) type = `Array<${schemaType(schema.items ?? {}, `${at}/items`, direction)}>`;
|
|
59
|
+
else if (schema.type === "array" || schema.items) type = `Array<${schemaType(schema.items ?? {}, `${at}/items`, direction, references)}>`;
|
|
58
60
|
else if (schema.type === "object" || schema.properties || schema.additionalProperties !== void 0) {
|
|
59
61
|
const required = new Set(schema.required ?? []);
|
|
60
|
-
const properties = Object.entries(schema.properties ?? {}).filter(([, value]) => !(direction === "request" ? value.readOnly : value.writeOnly)).map(([key, value]) => ` ${JSON.stringify(key)}${required.has(key) ? "" : "?"}: ${schemaType(value, `${at}/properties/${key}`, direction)};`);
|
|
61
|
-
if (schema.additionalProperties && typeof schema.additionalProperties === "object") properties.push(` [key: string]: ${schemaType(schema.additionalProperties, `${at}/additionalProperties`, direction)};`);
|
|
62
|
+
const properties = Object.entries(schema.properties ?? {}).filter(([, value]) => !(direction === "request" ? value.readOnly : value.writeOnly)).map(([key, value]) => ` ${JSON.stringify(key)}${required.has(key) ? "" : "?"}: ${schemaType(value, `${at}/properties/${key}`, direction, references)};`);
|
|
63
|
+
if (schema.additionalProperties && typeof schema.additionalProperties === "object") properties.push(` [key: string]: ${schemaType(schema.additionalProperties, `${at}/additionalProperties`, direction, references)};`);
|
|
62
64
|
type = `{\n${properties.join("\n")}\n}`;
|
|
63
65
|
} else if (!schema.type) fail("Unsupported schema without a type", at);
|
|
64
66
|
else fail(`Unsupported schema type ${schema.type}`, at);
|
|
@@ -67,7 +69,7 @@ function schemaType(schema, at, direction = "response") {
|
|
|
67
69
|
function responseSchema(operation, at, success) {
|
|
68
70
|
return Object.entries(operation.responses ?? {}).filter(([status]) => status === "default" ? !success : success === /^2\d\d$/.test(status)).sort(([a], [b]) => a.localeCompare(b, void 0, { numeric: true }));
|
|
69
71
|
}
|
|
70
|
-
function pickContent(content, at, direction) {
|
|
72
|
+
function pickContent(content, at, direction, references) {
|
|
71
73
|
const entries = Object.entries(content ?? {}).sort(([a], [b]) => a.localeCompare(b));
|
|
72
74
|
if (!entries.length) return {
|
|
73
75
|
type: "undefined",
|
|
@@ -76,7 +78,7 @@ function pickContent(content, at, direction) {
|
|
|
76
78
|
};
|
|
77
79
|
const types = entries.map(([mediaType, value]) => ({
|
|
78
80
|
mediaType,
|
|
79
|
-
type: schemaType(value.schema ?? {}, `${at}/content/${mediaType.replace(/~/g, "~0").replace(/\//g, "~1")}/schema`, direction)
|
|
81
|
+
type: schemaType(value.schema ?? {}, `${at}/content/${mediaType.replace(/~/g, "~0").replace(/\//g, "~1")}/schema`, direction, references)
|
|
80
82
|
}));
|
|
81
83
|
const union = [...new Set(types.map((v) => v.type))].join(" | ");
|
|
82
84
|
return {
|
|
@@ -229,6 +231,13 @@ function generateFiles(document) {
|
|
|
229
231
|
names.set(safe, name);
|
|
230
232
|
}
|
|
231
233
|
const schemaLines = [...names].map(([safe, original]) => `export type ${safe} = ${schemaType(schemas[original], pointer("components", "schemas", original))};`);
|
|
234
|
+
const operationSchemaReferences = /* @__PURE__ */ new Set();
|
|
235
|
+
const apiSchemaReferences = /* @__PURE__ */ new Set();
|
|
236
|
+
const apiOperationReferences = /* @__PURE__ */ new Set();
|
|
237
|
+
const contentSchemaReferences = { add(name) {
|
|
238
|
+
operationSchemaReferences.add(name);
|
|
239
|
+
apiSchemaReferences.add(name);
|
|
240
|
+
} };
|
|
232
241
|
const operationTypes = [];
|
|
233
242
|
const descriptors = [];
|
|
234
243
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -271,7 +280,8 @@ function generateFiles(document) {
|
|
|
271
280
|
const list = byLocation[location].sort((a, b) => a.name.localeCompare(b.name));
|
|
272
281
|
if (!list.length) return void 0;
|
|
273
282
|
const name = `${prefix}${pascal(location)}`;
|
|
274
|
-
|
|
283
|
+
apiOperationReferences.add(name);
|
|
284
|
+
operationTypes.push(`export type ${name} = {\n${list.map((p) => ` ${JSON.stringify(p.name)}${p.required || location === "path" ? "" : "?"}: ${schemaType(p.schema ?? {}, `${at}/parameters`, "request", operationSchemaReferences)};`).join("\n")}\n};`);
|
|
275
285
|
const defaultStyle = location === "query" ? "form" : "simple";
|
|
276
286
|
return {
|
|
277
287
|
name,
|
|
@@ -283,7 +293,7 @@ function generateFiles(document) {
|
|
|
283
293
|
};
|
|
284
294
|
};
|
|
285
295
|
const params = renderParameters("path"), query = renderParameters("query"), headers = renderParameters("header");
|
|
286
|
-
const body = operation.requestBody ? pickContent(operation.requestBody.content, `${at}/requestBody`, "request") : void 0;
|
|
296
|
+
const body = operation.requestBody ? pickContent(operation.requestBody.content, `${at}/requestBody`, "request", contentSchemaReferences) : void 0;
|
|
287
297
|
if (body) operationTypes.push(`export type ${prefix}Body = ${body.type};`);
|
|
288
298
|
const successes = responseSchema(operation, at, true);
|
|
289
299
|
if (!successes.length) fail("At least one 2xx response is required", `${at}/responses`, id);
|
|
@@ -295,7 +305,7 @@ function generateFiles(document) {
|
|
|
295
305
|
if (body) chain.push(`body(${body.codec})`);
|
|
296
306
|
for (const [status, response] of successes) {
|
|
297
307
|
if (response.links) fail("Response links are unsupported", `${at}/responses/${status}/links`, id);
|
|
298
|
-
const value = pickContent(response.content, `${at}/responses/${status}`, "response");
|
|
308
|
+
const value = pickContent(response.content, `${at}/responses/${status}`, "response", contentSchemaReferences);
|
|
299
309
|
const typeName = `${prefix}Response${status}`;
|
|
300
310
|
operationTypes.push(`export type ${typeName} = ${value.type};`);
|
|
301
311
|
chain.push(`returns(${status === "200" ? "" : `${status}, `}${value.codec})`);
|
|
@@ -303,7 +313,7 @@ function generateFiles(document) {
|
|
|
303
313
|
if (errors.length) {
|
|
304
314
|
const rendered = errors.map(([status, response]) => {
|
|
305
315
|
if (response.links) fail("Response links are unsupported", `${at}/responses/${status}/links`, id);
|
|
306
|
-
const value = pickContent(response.content, `${at}/responses/${status}`, "response");
|
|
316
|
+
const value = pickContent(response.content, `${at}/responses/${status}`, "response", contentSchemaReferences);
|
|
307
317
|
const typeName = `${prefix}Error${pascal(status)}`;
|
|
308
318
|
operationTypes.push(`export type ${typeName} = ${value.type};`);
|
|
309
319
|
return `${JSON.stringify(status)}: ${value.codec}`;
|
|
@@ -316,12 +326,12 @@ function generateFiles(document) {
|
|
|
316
326
|
const imports = [...new Set(descriptors.join(" ").match(/\b(get|post|put|patch|del|head|options|json|text|urlEncoded|multipart|arrayBuffer|empty|content)\b/g) ?? [])].sort();
|
|
317
327
|
const files = {
|
|
318
328
|
"schemas.ts": `${schemaLines.join("\n\n")}\n`,
|
|
319
|
-
"operations.ts": `${
|
|
329
|
+
"operations.ts": `${operationSchemaReferences.size ? `import type { ${[...operationSchemaReferences].sort().join(", ")} } from "./schemas";\n\n` : ""}${operationTypes.join("\n\n")}\n`,
|
|
320
330
|
"api.ts": `import { ${[
|
|
321
331
|
"defineApi",
|
|
322
332
|
"createClient",
|
|
323
333
|
...imports
|
|
324
|
-
].join(", ")} } from "@askrjs/fetch";\nimport type { ClientOptions } from "@askrjs/fetch";\
|
|
334
|
+
].join(", ")} } from "@askrjs/fetch";\nimport type { ClientOptions } from "@askrjs/fetch";\n${apiSchemaReferences.size ? `import type { ${[...apiSchemaReferences].sort().join(", ")} } from "./schemas";\n` : ""}${apiOperationReferences.size ? `import type { ${[...apiOperationReferences].sort().join(", ")} } from "./operations";\n` : ""}\nexport const api = defineApi({\n${descriptors.join("\n")}\n}, ${JSON.stringify({
|
|
325
335
|
servers: (document.servers ?? []).map((s) => s.url),
|
|
326
336
|
securitySchemes: document.components?.securitySchemes ?? {}
|
|
327
337
|
}, null, 2)});\n\nexport const createApiClient = (options?: ClientOptions) => createClient(api, options);\n`,
|