@zerolith/client 0.0.0-stage → 0.1.0-alpha.2

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/port.js ADDED
@@ -0,0 +1,81 @@
1
+ function listen(port, handler) {
2
+ if (typeof port.addEventListener === "function") {
3
+ port.addEventListener("message", (event) => handler(event.data));
4
+ }
5
+ else if (typeof port.on === "function") {
6
+ port.on("message", (event) => handler(event.data));
7
+ }
8
+ else {
9
+ throw new TypeError("zerolith/client: the port has neither addEventListener nor on");
10
+ }
11
+ port.start?.();
12
+ }
13
+ /**
14
+ * A `Transport` that sends each request over the port as a message and
15
+ * resolves with the response that comes back under the same id. Aborting
16
+ * the request's signal rejects the call; the message already sent is
17
+ * answered and ignored.
18
+ */
19
+ export function portTransport(port) {
20
+ const pending = new Map();
21
+ let next = 1;
22
+ listen(port, (data) => {
23
+ const message = data;
24
+ if (message === null || typeof message !== "object" || message.kind !== "zerolith:response" || typeof message.id !== "number") {
25
+ return;
26
+ }
27
+ const waiting = pending.get(message.id);
28
+ if (waiting === undefined) {
29
+ return;
30
+ }
31
+ pending.delete(message.id);
32
+ waiting.resolve(new Response(message.body ?? null, { status: message.status ?? 200, statusText: message.statusText ?? "", headers: message.headers ?? [] }));
33
+ });
34
+ return async (request) => {
35
+ const id = next++;
36
+ const body = request.method === "GET" || request.method === "HEAD" ? null : await request.text();
37
+ const message = { kind: "zerolith:request", id, url: request.url, method: request.method, headers: [...request.headers], body: body === "" ? null : body };
38
+ return new Promise((resolve, reject) => {
39
+ if (request.signal.aborted) {
40
+ reject(request.signal.reason ?? new DOMException("The request was aborted", "AbortError"));
41
+ return;
42
+ }
43
+ pending.set(id, { resolve, reject });
44
+ request.signal.addEventListener("abort", () => {
45
+ if (pending.delete(id)) {
46
+ reject(request.signal.reason ?? new DOMException("The request was aborted", "AbortError"));
47
+ }
48
+ });
49
+ port.postMessage(message);
50
+ });
51
+ };
52
+ }
53
+ /**
54
+ * Answers the requests `portTransport` sends by building a `Request`,
55
+ * calling `handler`, and posting the response back. A handler that throws
56
+ * answers with a 500 problem so the client sees a `ProblemError`.
57
+ */
58
+ export function servePort(port, handler) {
59
+ listen(port, (data) => {
60
+ const message = data;
61
+ if (message === null || typeof message !== "object" || message.kind !== "zerolith:request" || typeof message.id !== "number" || typeof message.url !== "string") {
62
+ return;
63
+ }
64
+ const id = message.id;
65
+ const answer = async () => {
66
+ let response;
67
+ try {
68
+ response = await handler(new Request(message.url ?? "", { method: message.method ?? "GET", headers: message.headers ?? [], body: message.body ?? null }));
69
+ }
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" } });
73
+ }
74
+ const text = await response.text();
75
+ const reply = { kind: "zerolith:response", id, status: response.status, statusText: response.statusText, headers: [...response.headers], body: text === "" ? null : text };
76
+ port.postMessage(reply);
77
+ };
78
+ void answer();
79
+ });
80
+ }
81
+ //# sourceMappingURL=port.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"port.js","sourceRoot":"","sources":["../src/port.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,60 @@
1
1
  {
2
- "name": "@zerolith/client",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
2
+ "name": "@zerolith/client",
3
+ "version": "0.1.0-alpha.2",
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
+ "license": "Apache-2.0",
6
+ "author": "molexxxx",
7
+ "homepage": "https://github.com/molexxxx/zerolith#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/molexxxx/zerolith.git",
11
+ "directory": "packages/client"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/molexxxx/zerolith/issues"
15
+ },
16
+ "keywords": [
17
+ "zerolith",
18
+ "openapi",
19
+ "typed-client",
20
+ "problem-details"
21
+ ],
22
+ "type": "module",
23
+ "sideEffects": false,
24
+ "engines": {
25
+ "node": ">=24.0.0"
26
+ },
27
+ "bin": {
28
+ "zerolith-client": "dist/cli.js"
29
+ },
30
+ "exports": {
31
+ ".": {
32
+ "types": "./dist/index.d.ts",
33
+ "default": "./dist/index.js"
34
+ },
35
+ "./generate": {
36
+ "types": "./dist/generate.d.ts",
37
+ "default": "./dist/generate.js"
38
+ },
39
+ "./port": {
40
+ "types": "./dist/port.d.ts",
41
+ "default": "./dist/port.js"
42
+ },
43
+ "./package.json": "./package.json"
44
+ },
45
+ "files": [
46
+ "dist",
47
+ "src",
48
+ "LICENSE",
49
+ "README.md"
50
+ ],
51
+ "scripts": {
52
+ "build": "tsc -p tsconfig.build.json"
53
+ },
54
+ "peerDependencies": {
55
+ "zerolith": "^0.1.0-alpha.2"
56
+ },
57
+ "devDependencies": {
58
+ "zerolith": "^0.1.0-alpha.2"
59
+ }
60
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `zerolith-client <openapi.json> [--out <file.ts>]`: writes the contract
4
+ * module for an OpenAPI document to a file, or to standard output.
5
+ */
6
+ import { readFile, writeFile } from "node:fs/promises";
7
+ import process from "node:process";
8
+ import { type OpenApiDocument, generateClient } from "./generate.js";
9
+
10
+ const USAGE = "usage: zerolith-client <openapi.json> [--out <file.ts>] [--name <Api>]\n";
11
+
12
+ interface Arguments
13
+ {
14
+ input: string | undefined;
15
+ out: string | undefined;
16
+ name: string | undefined;
17
+ }
18
+
19
+ function parse(argv: readonly string[]): Arguments
20
+ {
21
+ const parsed: Arguments = { input: undefined, out: undefined, name: undefined };
22
+ for (let index = 0; index < argv.length; index++)
23
+ {
24
+ const argument = argv[index] ?? "";
25
+ if (argument === "--out" || argument === "--name")
26
+ {
27
+ const value = argv[index + 1];
28
+ if (value === undefined)
29
+ {
30
+ throw new Error(`${argument} needs a value`);
31
+ }
32
+ parsed[argument === "--out" ? "out" : "name"] = value;
33
+ index++;
34
+ }
35
+ else if (argument.startsWith("--"))
36
+ {
37
+ throw new Error(`unknown option ${argument}`);
38
+ }
39
+ else if (parsed.input === undefined)
40
+ {
41
+ parsed.input = argument;
42
+ }
43
+ else
44
+ {
45
+ throw new Error(`unexpected argument ${argument}`);
46
+ }
47
+ }
48
+ return parsed;
49
+ }
50
+
51
+ /** Runs the command with the given arguments and returns the exit code. */
52
+ export async function main(argv: readonly string[]): Promise<number>
53
+ {
54
+ let parsed: Arguments;
55
+ try
56
+ {
57
+ parsed = parse(argv);
58
+ }
59
+ catch (error)
60
+ {
61
+ process.stderr.write(`zerolith-client: ${error instanceof Error ? error.message : String(error)}\n${USAGE}`);
62
+ return 2;
63
+ }
64
+ if (parsed.input === undefined)
65
+ {
66
+ process.stderr.write(USAGE);
67
+ return 2;
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)
72
+ {
73
+ process.stdout.write(code);
74
+ }
75
+ else
76
+ {
77
+ await writeFile(parsed.out, code);
78
+ }
79
+ return 0;
80
+ }
81
+
82
+ if (process.argv[1] !== undefined && /zerolith-client|[\\/]cli\.js$/.test(process.argv[1]))
83
+ {
84
+ process.exitCode = await main(process.argv.slice(2));
85
+ }