@substrat-run/control-plane-client 0.1.0 → 0.1.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.
@@ -0,0 +1,222 @@
1
+ import { problemDetail } from '@substrat-run/contracts';
2
+ import { DEV_ACTOR_HEADER, SERVICE_TOKEN_HEADER } from './headers.js';
3
+ /** A non-2xx (or unreachable) control-plane response. `status` is 0 on a transport error. */
4
+ export class ControlPlaneError extends Error {
5
+ status;
6
+ probe;
7
+ constructor(status, message,
8
+ /**
9
+ * The provider's own answer, when the plane refused a connect because the credential
10
+ * was rejected upstream (#605, 422). Carried so a console can show WHY — "Scrive:
11
+ * No valid access credentials were provided" — instead of a generic save failure.
12
+ */
13
+ probe, detail = {}) {
14
+ super(message, 'cause' in detail ? { cause: detail.cause } : undefined);
15
+ this.status = status;
16
+ this.probe = probe;
17
+ this.name = 'ControlPlaneError';
18
+ this.body = detail.body;
19
+ this.statusText = detail.statusText;
20
+ this.headers = detail.headers;
21
+ this.url = detail.url;
22
+ this.malformed = detail.malformed === true;
23
+ }
24
+ /** The refusal's raw body text; `undefined` for an error the transport did not read off a response. */
25
+ body;
26
+ statusText;
27
+ headers;
28
+ url;
29
+ /** True for a 2xx answer that was not the JSON the route promises. */
30
+ malformed;
31
+ }
32
+ /**
33
+ * Whether a failed call proves it changed nothing (#2010): the plane or the deployment behind it
34
+ * refused it (a 4xx), or that deployment does not have the route (501). Any other failure — a
35
+ * 502 that lost the deployment's answer, another 5xx, a malformed 2xx, a transport error
36
+ * (status 0), or no status at all — may follow a write that landed, so what it did is unknown
37
+ * and the caller reads the state before retrying. Takes any error carrying a numeric `status`,
38
+ * so an app's own error class for the same responses reads the same way.
39
+ */
40
+ export function provesNothingChanged(error) {
41
+ const status = error?.status;
42
+ return typeof status === 'number' && ((status >= 400 && status < 500) || status === 501);
43
+ }
44
+ /**
45
+ * The caller built a request the client refuses to send — today, one that would carry two
46
+ * credentials. A mistake in how the client was used, raised BEFORE anything leaves, so it is
47
+ * not a `ControlPlaneError` (no plane answered, none was found unreachable) and not a
48
+ * `TypeError` either: `fetch` itself throws `TypeError` for a network failure, and a caller
49
+ * that retries on transport errors must be able to tell the two apart by class.
50
+ */
51
+ export class ControlPlaneUsageError extends Error {
52
+ constructor(message) {
53
+ super(message);
54
+ this.name = 'ControlPlaneUsageError';
55
+ }
56
+ }
57
+ export class ControlPlaneTransport {
58
+ baseUrl;
59
+ actor;
60
+ serviceToken;
61
+ fetchImpl;
62
+ credentials;
63
+ extraHeaders;
64
+ contentType;
65
+ onResponse;
66
+ constructor(options) {
67
+ this.baseUrl = options.baseUrl.replace(/\/$/, '');
68
+ this.actor = options.actor;
69
+ this.serviceToken = options.serviceToken;
70
+ this.credentials = options.credentials;
71
+ this.extraHeaders = options.headers;
72
+ this.contentType = options.contentType === undefined ? 'application/json' : options.contentType;
73
+ this.onResponse = options.onResponse;
74
+ // Resolved at CALL time, and called as a function on the global: workerd throws
75
+ // "Illegal invocation" if `fetch` is called with a `this` other than the global scope
76
+ // (which `this.fetchImpl(...)` would otherwise set to this instance), and reading
77
+ // `globalThis.fetch` per call — rather than binding it once here — lets a caller swap it
78
+ // afterwards. An injected fetch is used as-is.
79
+ this.fetchImpl = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
80
+ }
81
+ /**
82
+ * One request on the wire, exactly as `send` makes it, and nothing more: the base URL, the
83
+ * credential and the headers are applied, and the `Response` comes back whatever its status.
84
+ * A `fetch` rejection propagates unchanged. For a caller that owns its own reading of
85
+ * the answer — a retry loop, a multipart upload — and wants the transport's addressing and
86
+ * credentials without its error policy.
87
+ */
88
+ async request(path, init) {
89
+ const res = await this.fetchImpl(`${this.baseUrl}${path}`, {
90
+ ...init,
91
+ ...(this.credentials ? { credentials: this.credentials } : {}),
92
+ headers: this.headersFor(init?.headers),
93
+ });
94
+ this.onResponse?.({ status: res.status, headers: res.headers });
95
+ return res;
96
+ }
97
+ /**
98
+ * The headers one request goes out with, merged through the `Headers` API so that names
99
+ * compare case-insensitively and every `HeadersInit` shape — a record, a tuple array or a
100
+ * `Headers` instance — is read the same way. Spreading the call's headers as if they were a
101
+ * record dropped a `Headers` instance's entries altogether, and let `Content-Type` and
102
+ * `content-type` ride side by side. Lowest first: the `headers` option, the transport's own
103
+ * credential, the content type, then the call's own.
104
+ */
105
+ headersFor(call) {
106
+ const merged = new Headers(this.extraHeaders);
107
+ // One credential per request (#980): a service token identifies the caller as
108
+ // the plane's service actor and is checked BEFORE the dev-actor stub, so the
109
+ // actor header would be ignored there — and a dev-only header has no business
110
+ // leaving a production caller at all. Without a token, the actor header IS the
111
+ // (local, UNSAFE) credential.
112
+ //
113
+ // That holds against the `headers` option too: a client that has a credential of its own
114
+ // drops BOTH credential headers from the option before applying it, so a stale entry for
115
+ // the OTHER header cannot ride beside the one the client chose. (Setting only the chosen
116
+ // one would leave, say, an `x-platform-actor` from the map next to the token.) A client
117
+ // with none — the CLI, which hands over a resolved map — passes the map through as it is.
118
+ if (this.serviceToken || this.actor !== null) {
119
+ merged.delete(SERVICE_TOKEN_HEADER);
120
+ merged.delete(DEV_ACTOR_HEADER);
121
+ }
122
+ if (this.serviceToken)
123
+ merged.set(SERVICE_TOKEN_HEADER, this.serviceToken);
124
+ else if (this.actor !== null)
125
+ merged.set(DEV_ACTOR_HEADER, this.actor);
126
+ if (this.contentType !== null)
127
+ merged.set('content-type', this.contentType);
128
+ // A call that names a credential of its own REPLACES the client's: the other credential
129
+ // header goes with it, so the request still carries exactly one. (Merging the call's header
130
+ // over the client's would send both whenever they differ in which header they use.)
131
+ const callHeaders = new Headers(call);
132
+ if (callHeaders.has(SERVICE_TOKEN_HEADER) || callHeaders.has(DEV_ACTOR_HEADER)) {
133
+ merged.delete(SERVICE_TOKEN_HEADER);
134
+ merged.delete(DEV_ACTOR_HEADER);
135
+ }
136
+ callHeaders.forEach((value, name) => merged.set(name, value));
137
+ // Whatever way it was assembled, two credentials never leave together: a call that names
138
+ // both, or an option map that carries both to a client with none of its own, is refused
139
+ // here, before anything is sent, rather than left for the plane to pick between.
140
+ if (merged.has(SERVICE_TOKEN_HEADER) && merged.has(DEV_ACTOR_HEADER)) {
141
+ throw new ControlPlaneUsageError(`a request names two credentials (${SERVICE_TOKEN_HEADER} and ${DEV_ACTOR_HEADER}) — send exactly one`);
142
+ }
143
+ return Object.fromEntries(merged.entries());
144
+ }
145
+ /** The full URL a path is requested at — for a message that names it. */
146
+ urlFor(path) {
147
+ return `${this.baseUrl}${path}`;
148
+ }
149
+ /**
150
+ * One request, and the one place a failure is read: a transport error or a non-2xx answer
151
+ * throws `ControlPlaneError`, so what comes back is always a successful `Response`. A 404
152
+ * is handed back as-is when the caller allows it.
153
+ */
154
+ async send(path, init, allow404 = false) {
155
+ let res;
156
+ try {
157
+ res = await this.request(path, init);
158
+ }
159
+ catch (e) {
160
+ // A refused request is the caller's mistake, not an unreachable plane: it never left.
161
+ if (e instanceof ControlPlaneUsageError)
162
+ throw e;
163
+ // A transport failure (control plane down) must fail closed, not silently
164
+ // pass — a vertical that cannot reach the authority does not get to run.
165
+ throw new ControlPlaneError(0, `control plane unreachable: ${e.message}`, undefined, {
166
+ cause: e,
167
+ url: this.urlFor(path),
168
+ });
169
+ }
170
+ if (res.status === 404 && allow404)
171
+ return res;
172
+ if (!res.ok) {
173
+ // `problemDetail` is the one reading of a failed body (#971): the RFC 9457
174
+ // `detail` first, the deprecated `error` duplicate second, both against the
175
+ // published schema — so this works either way, and keeps working the day the
176
+ // duplicate is deleted. The status line is the fallback only when the body
177
+ // said nothing readable.
178
+ const text = await res.text().catch(() => undefined);
179
+ let body = null;
180
+ try {
181
+ body = JSON.parse(text ?? '');
182
+ }
183
+ catch {
184
+ // Not JSON, or unreadable: `problemDetail(null)` is undefined and the status line speaks.
185
+ }
186
+ // A connect the provider refused carries the provider's own answer beside the
187
+ // sentence (#605) — read off the same parse, so no caller re-reads the body for it.
188
+ const probe = body !== null && typeof body === 'object' ? body.probe : undefined;
189
+ throw new ControlPlaneError(res.status, problemDetail(body) ?? `${res.status} ${res.statusText}`, probe, { body: text, statusText: res.statusText, headers: res.headers, url: this.urlFor(path) });
190
+ }
191
+ return res;
192
+ }
193
+ async call(path, init, allow404 = false) {
194
+ const res = await this.send(path, init, allow404);
195
+ if (res.status === 404 && allow404)
196
+ return undefined;
197
+ return res.status === 204 ? undefined : (await res.json());
198
+ }
199
+ /**
200
+ * A JSON answer, read from its text so a 2xx that is not JSON says what it was: a
201
+ * `ControlPlaneError` flagged `malformed`, carrying the `body` and `url` — the common
202
+ * misconfiguration being a base URL that points at a web page rather than the API. Unlike
203
+ * `call`, no status is special-cased: a 204 is an empty body, which is not JSON.
204
+ */
205
+ async read(path, init) {
206
+ const res = await this.send(path, init);
207
+ const text = await res.text();
208
+ try {
209
+ return JSON.parse(text);
210
+ }
211
+ catch {
212
+ const url = this.urlFor(path);
213
+ throw new ControlPlaneError(res.status, `got a non-JSON response from ${url}`, undefined, {
214
+ body: text,
215
+ headers: res.headers,
216
+ url,
217
+ malformed: true,
218
+ });
219
+ }
220
+ }
221
+ }
222
+ //# sourceMappingURL=transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@substrat-run/control-plane-client",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "The typed HTTP client for the Substrat control-plane API — one transport (credentials, problem documents, ControlPlaneError) for the CLI, the console and a vertical's connect seam",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -22,23 +22,23 @@
22
22
  "dist",
23
23
  "LICENSE"
24
24
  ],
25
- "scripts": {
26
- "build": "tsc -p tsconfig.json",
27
- "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json --noEmit",
28
- "test": "vitest run"
29
- },
30
25
  "dependencies": {
31
- "@substrat-run/contracts": "workspace:^"
26
+ "@substrat-run/contracts": "^0.137.0"
32
27
  },
33
28
  "devDependencies": {
34
- "@types/node": "catalog:",
35
- "typescript": "catalog:",
36
- "vitest": "catalog:"
29
+ "@types/node": "^22.20.4",
30
+ "typescript": "^7.0.0",
31
+ "vitest": "^3.2.7"
37
32
  },
38
33
  "publishConfig": {
39
34
  "access": "public"
40
35
  },
41
36
  "engines": {
42
37
  "node": ">=20"
38
+ },
39
+ "scripts": {
40
+ "build": "tsc -p tsconfig.json",
41
+ "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json --noEmit",
42
+ "test": "vitest run"
43
43
  }
44
- }
44
+ }