@zerolith/client 0.1.0-alpha.5 → 0.1.0-alpha.6

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 CHANGED
@@ -7,8 +7,11 @@
7
7
  import { PROBLEM_MEDIA_TYPE, isProblem, resource } from "zerolith";
8
8
  /** Thrown for a response outside 2xx, carrying the problem the server sent or one built from the status line. */
9
9
  export class ProblemError extends Error {
10
+ /** The problem the server sent, with `status` filled from the response when the body omits it; a body that is not a JSON problem yields `status` and a `title` from the status line. */
10
11
  problem;
12
+ /** The response outside 2xx, for its status and headers; its body has already been read when the content type is JSON. */
11
13
  response;
14
+ /** Wraps `problem` and the `response` it came from; the message is the problem's `title`, or `HTTP <status>` when it has none. */
12
15
  constructor(problem, response) {
13
16
  super(problem.title ?? `HTTP ${response.status}`);
14
17
  this.name = "ProblemError";
@@ -37,7 +40,11 @@ function buildRequest(route, input, options, client) {
37
40
  if (value === undefined) {
38
41
  throw new TypeError(`zerolith/client: ${route.method.toUpperCase()} ${route.path} needs the path parameter "${name}"`);
39
42
  }
40
- return encodeURIComponent(String(value));
43
+ const segment = encodeURIComponent(String(value));
44
+ if (segment === "." || segment === "..") {
45
+ throw new TypeError(`zerolith/client: ${route.method.toUpperCase()} ${route.path} got "${segment}" for the path parameter "${name}", which would move the request to another path`);
46
+ }
47
+ return segment;
41
48
  });
42
49
  const base = (client.baseUrl ?? "").replace(/\/$/, "");
43
50
  const headers = new Headers(typeof client.headers === "function" ? client.headers() : client.headers);
@@ -52,6 +59,9 @@ function buildRequest(route, input, options, client) {
52
59
  if (input.body !== undefined) {
53
60
  if (isRawBody(input.body)) {
54
61
  init.body = input.body;
62
+ if (input.body instanceof ReadableStream) {
63
+ init.duplex = "half";
64
+ }
55
65
  }
56
66
  else {
57
67
  init.body = JSON.stringify(input.body);
@@ -62,16 +72,40 @@ function buildRequest(route, input, options, client) {
62
72
  }
63
73
  return new Request(`${base}${path}${searchOf(input.query ?? {})}`, init);
64
74
  }
75
+ /** A successful response's body: JSON parsed, text for `text/*` or no type, a `Blob` for any other type, such as an image, and `undefined` when it is empty. */
65
76
  async function readBody(response) {
66
77
  if (response.status === 204 || response.status === 205) {
67
78
  return undefined;
68
79
  }
69
- const type = response.headers.get("content-type") ?? "";
80
+ const type = (response.headers.get("content-type") ?? "").toLowerCase();
70
81
  if (type.includes("json")) {
71
82
  return response.json();
72
83
  }
73
- const text = await response.text();
74
- return text === "" ? undefined : text;
84
+ if (type === "" || type.startsWith("text/")) {
85
+ const text = await response.text();
86
+ return text === "" ? undefined : text;
87
+ }
88
+ return response.blob();
89
+ }
90
+ /** Calls `run` with a signal that aborts when either `own` or `caller` does, and stops listening to the caller once the call settles. */
91
+ async function withSignals(own, caller, run) {
92
+ if (caller === undefined) {
93
+ return run(own);
94
+ }
95
+ const combined = new AbortController();
96
+ const abort = () => combined.abort();
97
+ if (own.aborted || caller.aborted) {
98
+ combined.abort();
99
+ }
100
+ own.addEventListener("abort", abort);
101
+ caller.addEventListener("abort", abort);
102
+ try {
103
+ return await run(combined.signal);
104
+ }
105
+ finally {
106
+ own.removeEventListener("abort", abort);
107
+ caller.removeEventListener("abort", abort);
108
+ }
75
109
  }
76
110
  async function settle(response) {
77
111
  if (response.ok) {
@@ -110,7 +144,7 @@ export function createClient(routes, options = {}) {
110
144
  query(name, input, callOptions = {}) {
111
145
  return resource({
112
146
  params: input,
113
- loader: ({ params, abort }) => call(name, params, { ...callOptions, signal: abort })
147
+ loader: ({ params, abort }) => withSignals(abort, callOptions.signal, (signal) => call(name, params, { ...callOptions, signal }))
114
148
  });
115
149
  },
116
150
  request(name, ...args) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/port.d.ts CHANGED
@@ -7,31 +7,47 @@
7
7
  import type { Transport } from "./index.js";
8
8
  /** The port shapes supported: a DOM `MessagePort`, Node's, or Electron's `MessagePortMain`. */
9
9
  export interface PortLike {
10
+ /** Sends a message to the other end; `portTransport` posts each `PortRequest` through it and `servePort` each `PortResponse`. */
10
11
  postMessage(message: unknown): void;
12
+ /** Starts delivering messages; called, when present, right after the listener is attached, since a DOM `MessagePort` and Electron's `MessagePortMain` queue messages until it runs. */
11
13
  start?(): void;
14
+ /** Receives messages the `EventTarget` way, as a DOM `MessagePort` does; used in preference to `on` when both exist. */
12
15
  addEventListener?(type: "message", listener: (event: {
13
16
  data: unknown;
14
17
  }) => void): void;
18
+ /** Receives messages the `EventEmitter` way, as Electron's `MessagePortMain` does; used when `addEventListener` is absent, and a port with neither throws a `TypeError`. */
15
19
  on?(type: "message", listener: (event: {
16
20
  data: unknown;
17
21
  }) => void): void;
18
22
  }
19
23
  /** What crosses the port for a request. */
20
24
  export interface PortRequest {
25
+ /** Tags the message as a request, so `servePort` ignores other traffic on the port. */
21
26
  kind: "zerolith:request";
27
+ /** Pairs the request with its response; each `portTransport` numbers its requests from 1. */
22
28
  id: number;
29
+ /** The request's absolute URL, from which `servePort` builds the `Request` its handler receives. */
23
30
  url: string;
31
+ /** The request method as `Request.method` reports it, such as `POST`. */
24
32
  method: string;
33
+ /** The request headers as `[name, value]` pairs with lowercase names, as iterating a `Headers` yields them. */
25
34
  headers: [string, string][];
35
+ /** The body as UTF-8 text, or `null` for a `GET` or `HEAD` request or an empty body. */
26
36
  body: string | null;
27
37
  }
28
38
  /** What crosses the port for a response. */
29
39
  export interface PortResponse {
40
+ /** Tags the message as a response, so `portTransport` ignores other traffic on the port. */
30
41
  kind: "zerolith:response";
42
+ /** The `id` of the request this answers; an answer with no pending request, such as one whose call was aborted, is dropped. */
31
43
  id: number;
44
+ /** The status code of the handler's response, or 500 when the handler threw. */
32
45
  status: number;
46
+ /** The status text of the handler's response, empty when none was set. */
33
47
  statusText: string;
48
+ /** The response headers as `[name, value]` pairs with lowercase names, as iterating a `Headers` yields them. */
34
49
  headers: [string, string][];
50
+ /** The body as UTF-8 text, or `null` when it is empty, as for a 204. */
35
51
  body: string | null;
36
52
  }
37
53
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"port.d.ts","sourceRoot":"","sources":["../src/port.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,+FAA+F;AAC/F,MAAM,WAAW,QAAQ;IAErB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IACpC,KAAK,CAAC,IAAI,IAAI,CAAC;IACf,gBAAgB,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,GAAG,IAAI,CAAC;IACvF,EAAE,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,GAAG,IAAI,CAAC;CAC5E;AAED,2CAA2C;AAC3C,MAAM,WAAW,WAAW;IAExB,IAAI,EAAE,kBAAkB,CAAC;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;IAC5B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,4CAA4C;AAC5C,MAAM,WAAW,YAAY;IAEzB,IAAI,EAAE,mBAAmB,CAAC;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;IAC5B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAmBD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,QAAQ,GAAG,SAAS,CA0CvD;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,GAAG,IAAI,CA4B3G"}
1
+ {"version":3,"file":"port.d.ts","sourceRoot":"","sources":["../src/port.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,+FAA+F;AAC/F,MAAM,WAAW,QAAQ;IAErB,iIAAiI;IACjI,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IACpC,uLAAuL;IACvL,KAAK,CAAC,IAAI,IAAI,CAAC;IACf,wHAAwH;IACxH,gBAAgB,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,GAAG,IAAI,CAAC;IACvF,4KAA4K;IAC5K,EAAE,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,GAAG,IAAI,CAAC;CAC5E;AAED,2CAA2C;AAC3C,MAAM,WAAW,WAAW;IAExB,uFAAuF;IACvF,IAAI,EAAE,kBAAkB,CAAC;IACzB,6FAA6F;IAC7F,EAAE,EAAE,MAAM,CAAC;IACX,oGAAoG;IACpG,GAAG,EAAE,MAAM,CAAC;IACZ,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAC;IACf,+GAA+G;IAC/G,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;IAC5B,wFAAwF;IACxF,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,4CAA4C;AAC5C,MAAM,WAAW,YAAY;IAEzB,4FAA4F;IAC5F,IAAI,EAAE,mBAAmB,CAAC;IAC1B,+HAA+H;IAC/H,EAAE,EAAE,MAAM,CAAC;IACX,gFAAgF;IAChF,MAAM,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,UAAU,EAAE,MAAM,CAAC;IACnB,gHAAgH;IAChH,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;IAC5B,wEAAwE;IACxE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAmBD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,QAAQ,GAAG,SAAS,CA0CvD;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,GAAG,IAAI,CAmC3G"}
package/dist/port.js CHANGED
@@ -68,8 +68,13 @@ export function servePort(port, handler) {
68
68
  response = await handler(new Request(message.url ?? "", { method: message.method ?? "GET", headers: message.headers ?? [], body: message.body ?? null }));
69
69
  }
70
70
  catch (error) {
71
- const title = error instanceof Error ? error.message : String(error);
72
- response = new Response(JSON.stringify({ title, status: 500 }), { status: 500, headers: { "content-type": "application/problem+json" } });
71
+ if (typeof reportError === "function") {
72
+ reportError(error);
73
+ }
74
+ else {
75
+ console.error("zerolith/client: a servePort handler threw", error);
76
+ }
77
+ response = new Response(JSON.stringify({ title: "The handler failed", status: 500 }), { status: 500, headers: { "content-type": "application/problem+json" } });
73
78
  }
74
79
  const text = await response.text();
75
80
  const reply = { kind: "zerolith:response", id, status: response.status, statusText: response.statusText, headers: [...response.headers], body: text === "" ? null : text };
package/dist/port.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"port.js","sourceRoot":"","sources":["../src/port.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"port.js","sourceRoot":"","sources":["../src/port.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zerolith/client",
3
- "version": "0.1.0-alpha.5",
3
+ "version": "0.1.0-alpha.6",
4
4
  "description": "A typed HTTP client for zerolith over a neutral contract: generate the route table and types from an OpenAPI document, call operations with checked inputs, and read failures as RFC 9457 problems",
5
5
  "license": "Apache-2.0",
6
6
  "author": "molexxxx",
@@ -52,9 +52,9 @@
52
52
  "build": "tsc -p tsconfig.build.json"
53
53
  },
54
54
  "peerDependencies": {
55
- "zerolith": "^0.1.0-alpha.5"
55
+ "zerolith": "^0.1.0-alpha.6"
56
56
  },
57
57
  "devDependencies": {
58
- "zerolith": "^0.1.0-alpha.5"
58
+ "zerolith": "^0.1.0-alpha.6"
59
59
  }
60
60
  }
package/src/cli.ts CHANGED
@@ -66,15 +66,23 @@ export async function main(argv: readonly string[]): Promise<number>
66
66
  process.stderr.write(USAGE);
67
67
  return 2;
68
68
  }
69
- const document = JSON.parse(await readFile(parsed.input, "utf8")) as OpenApiDocument;
70
- const code = generateClient(document, parsed.name === undefined ? {} : { apiName: parsed.name });
71
- if (parsed.out === undefined)
69
+ try
72
70
  {
73
- process.stdout.write(code);
71
+ const document = JSON.parse(await readFile(parsed.input, "utf8")) as OpenApiDocument;
72
+ const code = generateClient(document, parsed.name === undefined ? {} : { apiName: parsed.name });
73
+ if (parsed.out === undefined)
74
+ {
75
+ process.stdout.write(code);
76
+ }
77
+ else
78
+ {
79
+ await writeFile(parsed.out, code);
80
+ }
74
81
  }
75
- else
82
+ catch (error)
76
83
  {
77
- await writeFile(parsed.out, code);
84
+ process.stderr.write(`zerolith-client: ${error instanceof Error ? error.message.replace(/^zerolith\/client: /, "") : String(error)}\n`);
85
+ return 1;
78
86
  }
79
87
  return 0;
80
88
  }
package/src/generate.ts CHANGED
@@ -8,95 +8,150 @@
8
8
  /** A JSON Schema object as OpenAPI 3.1 and later carry it (draft 2020-12 plus the OAS vocabulary). */
9
9
  export interface SchemaObject
10
10
  {
11
+ /** A reference to `#/components/schemas/<name>`, rendered as that component's generated type, intersected with the keywords beside it that shape a type, such as `properties`, as OpenAPI 3.1 allows; any other target fails generation. */
11
12
  $ref?: string;
13
+ /** The JSON type, or since OpenAPI 3.1 an array of types rendered as a union; `integer` renders as `number`, and when it is absent the object or array keywords imply one, else the type is `unknown`. */
12
14
  type?: string | string[];
15
+ /** The OpenAPI 3.0 flag that also allows `null`; when `true`, `null` joins the type. */
13
16
  nullable?: boolean;
17
+ /** The allowed values, rendered as a union of their literal types; it takes precedence over `type`, and intersects with a composition keyword or `$ref` beside it. */
14
18
  enum?: unknown[];
19
+ /** The single allowed value (OpenAPI 3.1 and later), rendered as its literal type; it takes precedence over `enum` and `type`, and intersects with a composition keyword or `$ref` beside it. */
15
20
  const?: unknown;
21
+ /** The object's properties, each generated as a member that is optional unless `required` lists it, with the property's `description` as its doc comment. */
16
22
  properties?: Record<string, SchemaObject>;
23
+ /** The names in `properties` that must be present; every other property is generated as optional. */
17
24
  required?: string[];
25
+ /** The schema of keys beyond `properties`, rendered as a `Record<string, ...>` joined to them with `&`; `true` allows `unknown` values, and `false` makes an object without properties `Record<string, never>`. */
18
26
  additionalProperties?: boolean | SchemaObject;
27
+ /** The schema of each array element, rendered as `T[]`, or `unknown[]` without it; beside `prefixItems` it types the elements after the tuple. */
19
28
  items?: SchemaObject;
29
+ /** The schemas of the leading array elements by position (OpenAPI 3.1 and later), rendered as a tuple; without `items` the tuple has no rest element. */
20
30
  prefixItems?: SchemaObject[];
31
+ /** Alternatives of which exactly one matches, rendered as a union; it takes precedence over `anyOf` and `allOf`, and the keywords beside it that shape a type, such as `properties`, intersect with it. */
21
32
  oneOf?: SchemaObject[];
33
+ /** Alternatives of which at least one matches, rendered as a union unless `oneOf` is present; the keywords beside it that shape a type, such as `properties`, intersect with it. */
22
34
  anyOf?: SchemaObject[];
35
+ /** Schemas that all match, rendered as an intersection unless `oneOf` or `anyOf` is present; the keywords beside it that shape a type, such as `properties`, join the intersection. */
23
36
  allOf?: SchemaObject[];
37
+ /** Becomes the doc comment on a component schema's generated type, ahead of `title`, and on an object property's member. */
24
38
  description?: string;
39
+ /** The doc comment on a component schema's generated type when `description` is absent; the type's name comes from the component's key, not from `title`. */
25
40
  title?: string;
26
41
  }
27
42
 
28
43
  /** A Parameter Object or a reference to one. */
29
44
  export interface ParameterObject
30
45
  {
46
+ /** A reference to `#/components/parameters/<name>`, which replaces this object; any other target, or a missing one, fails generation. */
31
47
  $ref?: string;
48
+ /** The member's key in the generated `params` or `query`, matching the `{name}` placeholder for a path parameter; a parameter without `name` or `in` fails generation. */
32
49
  name?: string;
50
+ /** Where the parameter travels: `path` fills `params`, `query` adds a member to `query`, `querystring` (OpenAPI 3.2) lets `query` take any key, and `header` and `cookie` are left out. */
33
51
  in?: "query" | "querystring" | "header" | "path" | "cookie";
52
+ /** Makes a query parameter's member, and the whole `query` input, required; path parameters are generated as required either way, since OpenAPI demands `true` for them. */
34
53
  required?: boolean;
54
+ /** The doc comment on the parameter's generated member. */
35
55
  description?: string;
56
+ /** The schema of the value, rendered as the member's type; without it or `content`, the type is `unknown`. */
36
57
  schema?: SchemaObject;
58
+ /** The single media type entry OpenAPI allows in place of `schema`; when `schema` is absent, the entry's `schema` gives the member's type. */
37
59
  content?: Record<string, MediaTypeObject>;
38
60
  }
39
61
 
40
62
  /** A Media Type Object. */
41
63
  export interface MediaTypeObject
42
64
  {
65
+ /** The content's schema, read for a JSON media type in a body and for any media type in a parameter's `content`; when absent, the type is `unknown`. */
43
66
  schema?: SchemaObject;
44
67
  }
45
68
 
46
69
  /** A Request Body Object or a reference to one. */
47
70
  export interface RequestBodyObject
48
71
  {
72
+ /** A reference to `#/components/requestBodies/<name>`, which replaces this object; any other target, or a missing one, fails generation. */
49
73
  $ref?: string;
74
+ /** A brief description of the body; the generator does not carry it into the output. */
50
75
  description?: string;
76
+ /** Makes the generated `body` input required; OpenAPI's default is `false`, which generates it as optional. */
51
77
  required?: boolean;
78
+ /** The body by media type, where a JSON type takes precedence and gives its schema's type; otherwise `multipart/form-data` gives `FormData`, `application/x-www-form-urlencoded` gives `URLSearchParams`, `text/*` gives `string`, and any other type gives `BodyInit`. */
52
79
  content?: Record<string, MediaTypeObject>;
53
80
  }
54
81
 
55
82
  /** A Response Object or a reference to one. */
56
83
  export interface ResponseObject
57
84
  {
85
+ /** A reference to `#/components/responses/<name>`, which replaces this object; any other target, or a missing one, fails generation. */
58
86
  $ref?: string;
87
+ /** A description of the response; the generator does not carry it into the output. */
59
88
  description?: string;
89
+ /** The response body by media type: a JSON type, when present, gives its schema's type, `text/*` gives `string`, any other type gives `Blob`, as the client reads it, and no content gives `undefined`. */
60
90
  content?: Record<string, MediaTypeObject>;
61
91
  }
62
92
 
63
93
  /** An Operation Object. */
64
94
  export interface OperationObject
65
95
  {
96
+ /** Names the operation's entry in `Api` and `routes`, in camelCase; without it the name comes from the method and path, such as `putNotesById` for `PUT /notes/{id}`, and two entries with one name fail generation. */
66
97
  operationId?: string;
98
+ /** The first paragraph of the doc comment on the operation's entry in `Api`. */
67
99
  summary?: string;
100
+ /** Follows `summary` in the doc comment on the operation's entry in `Api`. */
68
101
  description?: string;
102
+ /** When `true`, the doc comment on the operation's entry ends with `@deprecated`. */
69
103
  deprecated?: boolean;
104
+ /** The operation's parameters, merged with the path item's; one with the same `name` and `in` as a path item parameter replaces it. */
70
105
  parameters?: ParameterObject[];
106
+ /** The request body, generated as the entry's `body` input; without it the entry has no `body`. */
71
107
  requestBody?: RequestBodyObject;
108
+ /** The responses by status code; `output` is the union of every 2xx response, including `2XX`, or of `default` when there is none, and `unknown` without either. */
72
109
  responses?: Record<string, ResponseObject>;
73
110
  }
74
111
 
75
112
  /** A Path Item Object. */
76
113
  export interface PathItemObject
77
114
  {
115
+ /** A reference to `#/components/pathItems/<name>`, which replaces this item; any other target, or a missing one, fails generation. */
78
116
  $ref?: string;
117
+ /** A summary meant for every operation on the path; the generator does not carry it into the output. */
79
118
  summary?: string;
119
+ /** A description meant for every operation on the path; the generator does not carry it into the output. */
80
120
  description?: string;
121
+ /** Parameters shared by every operation on the path; an operation's own parameter with the same `name` and `in` replaces one. */
81
122
  parameters?: ParameterObject[];
123
+ /** The `GET` operation on this path. */
82
124
  get?: OperationObject;
125
+ /** The `PUT` operation on this path. */
83
126
  put?: OperationObject;
127
+ /** The `POST` operation on this path. */
84
128
  post?: OperationObject;
129
+ /** The `DELETE` operation on this path. */
85
130
  delete?: OperationObject;
131
+ /** The `OPTIONS` operation on this path. */
86
132
  options?: OperationObject;
133
+ /** The `HEAD` operation on this path. */
87
134
  head?: OperationObject;
135
+ /** The `PATCH` operation on this path. */
88
136
  patch?: OperationObject;
137
+ /** The `TRACE` operation on this path. */
89
138
  trace?: OperationObject;
139
+ /** The `QUERY` operation on this path (OpenAPI 3.2): a safe, idempotent request that carries a body. */
90
140
  query?: OperationObject;
141
+ /** Operations for other HTTP methods (OpenAPI 3.2), keyed by method name such as `COPY`; each key is lowercased, and one that is not a valid method token fails generation. */
91
142
  additionalOperations?: Record<string, OperationObject>;
92
143
  }
93
144
 
94
145
  /** The parts of an OpenAPI document the generator reads. */
95
146
  export interface OpenApiDocument
96
147
  {
148
+ /** The OpenAPI version, such as `3.1.0`; a value that does not start with `3.` fails generation. */
97
149
  openapi: string;
150
+ /** The API's title and version, which the generated file's header comment names as its source. */
98
151
  info?: { title?: string; version?: string };
152
+ /** Path templates mapped to their items; every operation under them becomes an entry of `Api` and `routes`, sorted by path. */
99
153
  paths?: Record<string, PathItemObject>;
154
+ /** Reusable objects: each entry of `schemas` becomes an exported type named after its key, and `$ref` values resolve into the other sections. */
100
155
  components?: {
101
156
  schemas?: Record<string, SchemaObject>;
102
157
  parameters?: Record<string, ParameterObject>;
@@ -115,7 +170,11 @@ export interface GenerateOptions
115
170
  apiName?: string;
116
171
  }
117
172
 
118
- const METHODS = ["get", "put", "post", "delete", "options", "head", "patch", "trace", "query"] as const;
173
+ /** The methods a Path Item names that the client can send; `trace` is left out, since `fetch` refuses TRACE. */
174
+ const METHODS = ["get", "put", "post", "delete", "options", "head", "patch", "query"] as const;
175
+
176
+ /** Methods `fetch` refuses to send, left out of `additionalOperations` too. */
177
+ const FORBIDDEN_METHODS: ReadonlySet<string> = new Set(["connect", "trace", "track"]);
119
178
  const IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
120
179
 
121
180
  interface Context
@@ -202,6 +261,9 @@ function deref<T extends { $ref?: string }>(context: Context, value: T, section:
202
261
  return current;
203
262
  }
204
263
 
264
+ /** The type name each schema of the document being generated is written under, by schema name, which a `$ref` to the schema uses; `generateClient` sets it before it writes a type. */
265
+ let schemaTypes: ReadonlyMap<string, string> = new Map();
266
+
205
267
  function schemaRefName(ref: string): string
206
268
  {
207
269
  const prefix = "#/components/schemas/";
@@ -209,7 +271,8 @@ function schemaRefName(ref: string): string
209
271
  {
210
272
  fail(`only local schema references are supported, found ${ref}`);
211
273
  }
212
- return typeName(decodeURIComponent(ref.slice(prefix.length)));
274
+ const name = decodeURIComponent(ref.slice(prefix.length));
275
+ return schemaTypes.get(name) ?? typeName(name);
213
276
  }
214
277
 
215
278
  function literal(value: unknown): string
@@ -218,12 +281,24 @@ function literal(value: unknown): string
218
281
  }
219
282
 
220
283
  /** Renders a schema as a TypeScript type. `indent` is the current indentation for nested object types. */
284
+ /** The keywords that combine other schemas; the type they give is intersected with the schema's own keywords. */
285
+ const COMPOSITION_KEYWORDS: ReadonlySet<string> = new Set(["$ref", "allOf", "oneOf", "anyOf"]);
286
+
287
+ /** The keywords that shape a type on their own, beside a composition keyword. */
288
+ const OWN_KEYWORDS: ReadonlySet<string> = new Set(["const", "enum", "type", "properties", "additionalProperties", "items", "prefixItems"]);
289
+
221
290
  function typeOf(schema: SchemaObject | undefined, indent: string): string
222
291
  {
223
292
  if (schema === undefined)
224
293
  {
225
294
  return "unknown";
226
295
  }
296
+ const keywords = Object.keys(schema);
297
+ if (keywords.some((keyword) => COMPOSITION_KEYWORDS.has(keyword)) && keywords.some((keyword) => OWN_KEYWORDS.has(keyword)))
298
+ {
299
+ const part = (keep: (keyword: string) => boolean): SchemaObject => Object.fromEntries(Object.entries(schema).filter(([keyword]) => keep(keyword))) as SchemaObject;
300
+ return `${wrap(typeOf(part((keyword) => !OWN_KEYWORDS.has(keyword)), indent))} & ${wrap(typeOf(part((keyword) => !COMPOSITION_KEYWORDS.has(keyword)), indent))}`;
301
+ }
227
302
  if (schema.$ref !== undefined)
228
303
  {
229
304
  return schemaRefName(schema.$ref);
@@ -472,7 +547,7 @@ function outputType(context: Context, operation: OperationObject): string
472
547
  types.add("undefined");
473
548
  break;
474
549
  default:
475
- types.add("unknown");
550
+ types.add("Blob");
476
551
  }
477
552
  }
478
553
  return [...types].join(" | ");
@@ -481,7 +556,7 @@ function outputType(context: Context, operation: OperationObject): string
481
556
  function entryOf(context: Context, path: string, item: PathItemObject, method: string, operation: OperationObject): Entry
482
557
  {
483
558
  const { params, query, queryRequired } = parameterGroups(context, item, operation);
484
- const lines = [` method: "${method}";`, ` path: ${JSON.stringify(path)};`];
559
+ const lines = [` method: ${JSON.stringify(method)};`, ` path: ${JSON.stringify(path)};`];
485
560
  if (params.length > 0)
486
561
  {
487
562
  lines.push(` params: {\n${params.join("\n")}\n };`);
@@ -500,6 +575,59 @@ function entryOf(context: Context, path: string, item: PathItemObject, method: s
500
575
  return { name: operationName(operation, method, path), method, path, lines, doc: doc === "" ? undefined : doc };
501
576
  }
502
577
 
578
+ /** The type names a generated module uses besides its schemas', with what each is, so a schema that would take one gets a numbered name instead. */
579
+ function reservedTypeNames(apiName: string): Map<string, string>
580
+ {
581
+ return new Map([
582
+ ["Record", "TypeScript's Record type"],
583
+ ["Blob", "the global Blob type"],
584
+ ["FormData", "the global FormData type"],
585
+ ["URLSearchParams", "the global URLSearchParams type"],
586
+ ["BodyInit", "the global BodyInit type"],
587
+ ["RouteTable", "the RouteTable type the module imports"],
588
+ [apiName, "the interface of the operations, which apiName renames"]
589
+ ]);
590
+ }
591
+
592
+ /**
593
+ * The type name every schema is written under, by schema name, with the
594
+ * reason for a name that is not the schema's own. A schema keeps its own
595
+ * name, as `note-item` gives `NoteItem`, unless the module uses that name
596
+ * itself or a schema earlier in sorted order takes it; then it gets the
597
+ * first free name with a number added, as `Record2`.
598
+ */
599
+ function schemaTypeNames(names: readonly string[], apiName: string): Map<string, { type: string; reason?: string }>
600
+ {
601
+ const holders = reservedTypeNames(apiName);
602
+ const assigned = new Map<string, { type: string; reason?: string }>();
603
+ for (const name of names)
604
+ {
605
+ const type = typeName(name);
606
+ if (!holders.has(type))
607
+ {
608
+ holders.set(type, `the type of the schema ${JSON.stringify(name)}`);
609
+ assigned.set(name, { type });
610
+ }
611
+ }
612
+ for (const name of names)
613
+ {
614
+ if (assigned.has(name))
615
+ {
616
+ continue;
617
+ }
618
+ const base = typeName(name);
619
+ let suffix = 2;
620
+ while (holders.has(`${base}${suffix}`))
621
+ {
622
+ suffix++;
623
+ }
624
+ const type = `${base}${suffix}`;
625
+ assigned.set(name, { type, reason: `named ${type} because ${base} is already ${holders.get(base) ?? "taken"}` });
626
+ holders.set(type, `the type of the schema ${JSON.stringify(name)}`);
627
+ }
628
+ return assigned;
629
+ }
630
+
503
631
  /**
504
632
  * Generates the contract module for an OpenAPI 3.x document given as a
505
633
  * parsed object (JSON; convert YAML first). Schemas under
@@ -507,7 +635,12 @@ function entryOf(context: Context, path: string, item: PathItemObject, method: s
507
635
  * entry of the `Api` interface named by its `operationId` (or by its
508
636
  * method and path), and `routes` carries the method and path of each.
509
637
  * Header and cookie parameters, security, and non-local references are
510
- * left out; the client sends headers through its options.
638
+ * left out; the client sends headers through its options. A schema whose
639
+ * type name the module already uses, such as `Record`, `Blob`, or the
640
+ * `Api` interface, or that another schema takes first, as `note-item` and
641
+ * `note_item` both give `NoteItem`, is written under the first free name
642
+ * with a number added, as `Record2`, with a comment that says why, and
643
+ * every `$ref` to it uses that name.
511
644
  */
512
645
  export function generateClient(document: OpenApiDocument, options: GenerateOptions = {}): string
513
646
  {
@@ -524,9 +657,14 @@ export function generateClient(document: OpenApiDocument, options: GenerateOptio
524
657
  out.push("");
525
658
 
526
659
  const schemas = Object.entries(document.components?.schemas ?? {}).toSorted(([a], [b]) => a.localeCompare(b));
660
+ const types = schemaTypeNames(schemas.map(([name]) => name), context.apiName);
661
+ schemaTypes = new Map(Array.from(types, ([name, { type }]) => [name, type]));
527
662
  for (const [name, schema] of schemas)
528
663
  {
529
- out.push(`${comment(schema.description ?? schema.title, "")}export type ${typeName(name)} = ${typeOf(schema, "")};`);
664
+ const { type, reason } = types.get(name) ?? { type: typeName(name) };
665
+ const note = reason === undefined ? undefined : `The schema ${JSON.stringify(name)}, ${reason}.`;
666
+ const text = [schema.description ?? schema.title, note].filter((part) => part !== undefined && part.trim() !== "").join("\n\n");
667
+ out.push(`${comment(text, "")}export type ${type} = ${typeOf(schema, "")};`);
530
668
  out.push("");
531
669
  }
532
670
 
@@ -546,6 +684,14 @@ export function generateClient(document: OpenApiDocument, options: GenerateOptio
546
684
  }
547
685
  for (const [method, operation] of Object.entries(item.additionalOperations ?? {}))
548
686
  {
687
+ if (!/^[a-z0-9!#$%&'*+.^_`|~-]+$/i.test(method))
688
+ {
689
+ fail(`${JSON.stringify(path)} has the additional operation ${JSON.stringify(method)}, which is not an HTTP method name`);
690
+ }
691
+ if (FORBIDDEN_METHODS.has(method.toLowerCase()))
692
+ {
693
+ continue;
694
+ }
549
695
  operations.push([method.toLowerCase(), operation]);
550
696
  }
551
697
  for (const [method, operation] of operations)
@@ -565,7 +711,7 @@ export function generateClient(document: OpenApiDocument, options: GenerateOptio
565
711
  out.push("}");
566
712
  out.push("");
567
713
  out.push("export const routes = {");
568
- out.push(entries.map((entry) => ` ${key(entry.name)}: { method: "${entry.method}", path: ${JSON.stringify(entry.path)} }`).join(",\n"));
714
+ out.push(entries.map((entry) => ` ${key(entry.name)}: { method: ${JSON.stringify(entry.method)}, path: ${JSON.stringify(entry.path)} }`).join(",\n"));
569
715
  out.push(`} as const satisfies RouteTable<${context.apiName}>;`);
570
716
  out.push("");
571
717
  return out.join("\n");