@printobserver/sdk 0.2.0

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.
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The client every generated method is a method of.
3
+ *
4
+ * It carries three things and no more: where the supervisor is, what
5
+ * authenticates to it, and who this client acts as. The third is why no
6
+ * generated method takes an actor: a call cannot act as somebody the client is
7
+ * not.
8
+ *
9
+ * The one request this makes goes through the platform's own `fetch` rather
10
+ * than a dependency. This client speaks to a supervisor over a loopback or a
11
+ * local network — the supervisor is the thing beside the printer — and a
12
+ * package a supervision tool takes as a dependency is better without a
13
+ * transport stack of its own.
14
+ */
15
+ import type { Actor } from "./contract.ts";
16
+ import { GeneratedClient } from "./contract.ts";
17
+ /** How this client is pointed at one supervisor. */
18
+ export interface ClientOptions {
19
+ /**
20
+ * Where the supervisor answers, as its own configuration writes it —
21
+ * `http://127.0.0.1:8420` — or as a bare `host:port`.
22
+ */
23
+ server: string;
24
+ /** Who this client acts as. */
25
+ actor: Actor;
26
+ /**
27
+ * What authenticates to it. This server requires none of its API callers; a
28
+ * credential is for a deployment that has put something in front of it that
29
+ * does.
30
+ */
31
+ credential?: string;
32
+ }
33
+ /**
34
+ * A typed client of one printobserver supervisor.
35
+ *
36
+ * One method per public operation that supervisor serves, each generated from
37
+ * the same checked-in description the server's own routes are folded out of.
38
+ */
39
+ export declare class Client extends GeneratedClient {
40
+ /** Where the supervisor answers, as an origin this client fetches from. */
41
+ readonly origin: string;
42
+ /** What authenticates to it, where anything does. */
43
+ readonly credential: string | undefined;
44
+ constructor(options: ClientOptions);
45
+ protected call<T>(method: string, target: string, asked: Array<[string, string]>, sending: Record<string, unknown> | undefined): Promise<T>;
46
+ }
package/dist/client.js ADDED
@@ -0,0 +1,135 @@
1
+ /**
2
+ * The client every generated method is a method of.
3
+ *
4
+ * It carries three things and no more: where the supervisor is, what
5
+ * authenticates to it, and who this client acts as. The third is why no
6
+ * generated method takes an actor: a call cannot act as somebody the client is
7
+ * not.
8
+ *
9
+ * The one request this makes goes through the platform's own `fetch` rather
10
+ * than a dependency. This client speaks to a supervisor over a loopback or a
11
+ * local network — the supervisor is the thing beside the printer — and a
12
+ * package a supervision tool takes as a dependency is better without a
13
+ * transport stack of its own.
14
+ */
15
+ import { GeneratedClient } from "./contract.js";
16
+ import { PrinterRefused, Refused, Rejected, Unreachable, Unreadable } from "./surface.js";
17
+ /** The status a rejected action is answered under. */
18
+ const REJECTED_STATUS = 409;
19
+ /** The status an action the machine itself refused is answered under. */
20
+ const MACHINE_REFUSED_STATUS = 502;
21
+ /** The status a read or a write that was carried out is answered under. */
22
+ const SUCCESS_STATUS = 200;
23
+ /** The media type every operation takes a body in and answers in. */
24
+ const MEDIA_TYPE = "application/json";
25
+ /**
26
+ * A typed client of one printobserver supervisor.
27
+ *
28
+ * One method per public operation that supervisor serves, each generated from
29
+ * the same checked-in description the server's own routes are folded out of.
30
+ */
31
+ export class Client extends GeneratedClient {
32
+ /** Where the supervisor answers, as an origin this client fetches from. */
33
+ origin;
34
+ /** What authenticates to it, where anything does. */
35
+ credential;
36
+ constructor(options) {
37
+ super(options.actor);
38
+ this.origin = origin(options.server);
39
+ this.credential = options.credential;
40
+ }
41
+ async call(method, target, asked, sending) {
42
+ const headers = { Accept: MEDIA_TYPE };
43
+ if (sending !== undefined) {
44
+ headers["Content-Type"] = MEDIA_TYPE;
45
+ }
46
+ if (this.credential !== undefined) {
47
+ headers.Authorization = `Bearer ${this.credential}`;
48
+ }
49
+ let answered;
50
+ try {
51
+ answered = await fetch(`${this.origin}${written(target, asked)}`, {
52
+ method,
53
+ headers,
54
+ ...(sending === undefined ? {} : { body: JSON.stringify(sending) }),
55
+ });
56
+ }
57
+ catch (unreachable) {
58
+ throw new Unreachable(this.origin, String(unreachable));
59
+ }
60
+ const said = await answered.text();
61
+ if (answered.status === SUCCESS_STATUS) {
62
+ return document(answered.status, said);
63
+ }
64
+ if (answered.status === REJECTED_STATUS) {
65
+ throw rejection(answered.status, said);
66
+ }
67
+ if (answered.status === MACHINE_REFUSED_STATUS) {
68
+ throw machineRefusal(answered.status, said);
69
+ }
70
+ throw new Refused(answered.status, refusalText(said));
71
+ }
72
+ }
73
+ /** One supervisor's address, as an origin `fetch` takes. */
74
+ function origin(server) {
75
+ const trimmed = server.trim().replace(/\/+$/, "");
76
+ return trimmed.startsWith("http://") || trimmed.startsWith("https://")
77
+ ? trimmed
78
+ : `http://${trimmed}`;
79
+ }
80
+ /** The request target one call is made to, with what it asks for. */
81
+ function written(path, asked) {
82
+ if (asked.length === 0) {
83
+ return path;
84
+ }
85
+ const query = asked
86
+ .map(([name, value]) => `${encodeURIComponent(name)}=${encodeURIComponent(value)}`)
87
+ .join("&");
88
+ return `${path}?${query}`;
89
+ }
90
+ /** One answer, parsed. */
91
+ function document(status, said) {
92
+ try {
93
+ return JSON.parse(said);
94
+ }
95
+ catch (unreadable) {
96
+ throw new Unreadable(status, String(unreadable));
97
+ }
98
+ }
99
+ /** What the supervisor said about a request it will not act on. */
100
+ function refusalText(said) {
101
+ try {
102
+ const answer = JSON.parse(said);
103
+ if (typeof answer === "object" && answer !== null && "error" in answer) {
104
+ const stated = answer.error;
105
+ if (typeof stated === "string") {
106
+ return stated;
107
+ }
108
+ }
109
+ }
110
+ catch {
111
+ return "it said nothing this client can read";
112
+ }
113
+ return "it said nothing this client can read";
114
+ }
115
+ /** The policy's own refusal, as the answer to it carries it. */
116
+ function rejection(status, said) {
117
+ const answer = document(status, said);
118
+ const decision = answer.record.decision;
119
+ if (typeof decision !== "object" || !("rejected" in decision)) {
120
+ return new Unreadable(status, "the supervisor refused this action and answered a record whose decision is not a refusal");
121
+ }
122
+ const reason = decision.rejected;
123
+ let requested;
124
+ let allowed;
125
+ if (typeof reason === "object" && "out_of_bounds" in reason) {
126
+ requested = reason.out_of_bounds.requested;
127
+ allowed = reason.out_of_bounds.allowed;
128
+ }
129
+ return new Rejected(reason, requested, allowed, answer);
130
+ }
131
+ /** The machine's own refusal of an action the policy accepted. */
132
+ function machineRefusal(status, said) {
133
+ const answer = document(status, said);
134
+ return new PrinterRefused(answer.printer_refusal ?? "it said nothing this client can read", answer);
135
+ }