@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.
Files changed (2) hide show
  1. package/dist/generate.js +26 -16
  2. 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
- return pascal(name);
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
- operationTypes.push(`export type ${name} = {\n${list.map((p) => ` ${JSON.stringify(p.name)}${p.required || location === "path" ? "" : "?"}: ${schemaType(p.schema ?? {}, `${at}/parameters`, "request")};`).join("\n")}\n};`);
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": `${names.size ? `import type { ${[...names.keys()].join(", ")} } from "./schemas";\n\n` : ""}${operationTypes.join("\n\n")}\n`,
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";\nimport type { ${operationTypes.map((line) => line.match(/^export type (\w+)/)?.[1]).filter(Boolean).join(", ")} } from "./operations";\n\nexport const api = defineApi({\n${descriptors.join("\n")}\n}, ${JSON.stringify({
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`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askrjs/cli",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "description": "Unified CLI for the Askr platform",
5
5
  "homepage": "https://github.com/askrjs/askr-cli#readme",
6
6
  "bugs": {