@substrat-run/control-plane-client 0.1.0 → 0.1.1
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/README.md +1 -1
- package/dist/builder-client.d.ts +76 -0
- package/dist/builder-client.d.ts.map +1 -0
- package/dist/builder-client.js +150 -0
- package/dist/builder-client.js.map +1 -0
- package/dist/builder-types.d.ts +191 -0
- package/dist/builder-types.d.ts.map +1 -0
- package/dist/builder-types.js +2 -0
- package/dist/builder-types.js.map +1 -0
- package/dist/client.d.ts +111 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +122 -0
- package/dist/client.js.map +1 -0
- package/dist/headers.d.ts +20 -0
- package/dist/headers.d.ts.map +1 -0
- package/dist/headers.js +20 -0
- package/dist/headers.js.map +1 -0
- package/dist/identity-tenants.d.ts +30 -0
- package/dist/identity-tenants.d.ts.map +1 -0
- package/dist/identity-tenants.js +23 -0
- package/dist/identity-tenants.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/transport.d.ts +162 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +222 -0
- package/dist/transport.js.map +1 -0
- package/package.json +11 -11
package/dist/client.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { denialQuery } from '@substrat-run/contracts';
|
|
2
|
+
import { ControlPlaneError, ControlPlaneTransport } from './transport.js';
|
|
3
|
+
import { identityTenantsResponse } from './identity-tenants.js';
|
|
4
|
+
// The error is raised by the transport; this is the path callers (and `errors.ts`)
|
|
5
|
+
// have always imported it from.
|
|
6
|
+
export { ControlPlaneError };
|
|
7
|
+
export class ControlPlaneClient extends ControlPlaneTransport {
|
|
8
|
+
constructor(options) {
|
|
9
|
+
super(options);
|
|
10
|
+
}
|
|
11
|
+
// -- registration (idempotent, mirrors HostAdmin) --------------------------
|
|
12
|
+
createTenant(input) {
|
|
13
|
+
return this.call('/tenants', { method: 'POST', body: JSON.stringify(input) });
|
|
14
|
+
}
|
|
15
|
+
grantEntitlement(tenantId, key, plan) {
|
|
16
|
+
return this.call(`/tenants/${tenantId}/entitlements/${key}`, {
|
|
17
|
+
method: 'PUT',
|
|
18
|
+
body: plan === undefined ? undefined : JSON.stringify(plan),
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
provisionScope(input) {
|
|
22
|
+
return this.call('/scopes', { method: 'POST', body: JSON.stringify(input) });
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Confirm the scope exists here, moving the directory row provisioning → active.
|
|
26
|
+
*
|
|
27
|
+
* In this (push) direction the vertical has already built the scope locally, so
|
|
28
|
+
* registering and confirming are the same moment — but they stay two calls so
|
|
29
|
+
* `provisionScope` means one thing everywhere, and so the directory is never the
|
|
30
|
+
* one deciding a scope is ready (K-31).
|
|
31
|
+
*/
|
|
32
|
+
activateScope(tenantId, scopeId) {
|
|
33
|
+
return this.call(`/tenants/${tenantId}/scopes/${scopeId}/activate`, { method: 'POST' });
|
|
34
|
+
}
|
|
35
|
+
// -- reads used for gating -------------------------------------------------
|
|
36
|
+
getTenant(tenantId) {
|
|
37
|
+
return this.call(`/tenants/${tenantId}`, undefined, true);
|
|
38
|
+
}
|
|
39
|
+
getScopeRecord(tenantId, scopeId) {
|
|
40
|
+
return this.call(`/tenants/${tenantId}/scopes/${scopeId}`, undefined, true);
|
|
41
|
+
}
|
|
42
|
+
listEntitlements(tenantId) {
|
|
43
|
+
return this.call(`/tenants/${tenantId}/entitlements`);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The tenants a login builds for, each flagged with whether it holds the `builder`
|
|
47
|
+
* entitlement — the builder studio's membership read (builder-plane.md §4). `externalId`
|
|
48
|
+
* is the login's OIDC subject. Service-token gated: an unset token is refused by the
|
|
49
|
+
* plane, never bypassed.
|
|
50
|
+
*
|
|
51
|
+
* The answer is PARSED, not asserted: a plane that renamed `entitled`, or answered an
|
|
52
|
+
* `{ error }` body with a 200, would hand a cast `entitled: undefined` — falsy, so the
|
|
53
|
+
* studio would lock the tenant out with the ordinary "not enabled" page and nothing
|
|
54
|
+
* anywhere would say the directory had changed shape. A refused parse throws instead.
|
|
55
|
+
* The message names the shape that was wrong, never the body — the body is directory
|
|
56
|
+
* facts about a person's tenants.
|
|
57
|
+
*/
|
|
58
|
+
async identityTenants(externalId) {
|
|
59
|
+
const res = await this.send('/internal/builder/identity-tenants', {
|
|
60
|
+
method: 'POST',
|
|
61
|
+
body: JSON.stringify({ externalId }),
|
|
62
|
+
});
|
|
63
|
+
const parsed = identityTenantsResponse.safeParse(await res.json().catch(() => null));
|
|
64
|
+
if (!parsed.success) {
|
|
65
|
+
throw new ControlPlaneError(res.status, `identity-tenants returned an unexpected shape: ${parsed.error.message}`);
|
|
66
|
+
}
|
|
67
|
+
return parsed.data.tenants;
|
|
68
|
+
}
|
|
69
|
+
// -- read-only scope-DB introspection (§5.4 admin-query RPC) ----------------
|
|
70
|
+
/** Every table in the scope's own database, with row counts (Data view). */
|
|
71
|
+
listScopeTables(tenantId, scopeId) {
|
|
72
|
+
return this.call(`/tenants/${tenantId}/scopes/${scopeId}/tables`);
|
|
73
|
+
}
|
|
74
|
+
/** A bounded page of one table of the scope's database. */
|
|
75
|
+
readScopeTable(tenantId, scopeId, input) {
|
|
76
|
+
const q = new URLSearchParams({ limit: String(input.limit), offset: String(input.offset) });
|
|
77
|
+
return this.call(`/tenants/${tenantId}/scopes/${scopeId}/tables/${encodeURIComponent(input.table)}?${q}`);
|
|
78
|
+
}
|
|
79
|
+
/** One read-only SQL statement against the scope's database — the console (#219). */
|
|
80
|
+
queryScope(tenantId, scopeId, input) {
|
|
81
|
+
return this.call(`/tenants/${tenantId}/scopes/${scopeId}/query`, {
|
|
82
|
+
method: 'POST',
|
|
83
|
+
body: JSON.stringify(input),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
// -- the denial log (K-35, #867) --------------------------------------------
|
|
87
|
+
/**
|
|
88
|
+
* The scope's recorded permission refusals, bucketed per (actor, permission) with the
|
|
89
|
+
* window's own facts beside them — the view to open first, because the raw log's
|
|
90
|
+
* volume is attacker-influenceable and a prober can flood a newest-first page.
|
|
91
|
+
*/
|
|
92
|
+
summarizeDenials(tenantId, scopeId, filter) {
|
|
93
|
+
return this.call(`/tenants/${tenantId}/scopes/${scopeId}/denials/summary${denialQuery(filter)}`);
|
|
94
|
+
}
|
|
95
|
+
/** The raw rows behind a bucket, newest first. */
|
|
96
|
+
listDenials(tenantId, scopeId, filter) {
|
|
97
|
+
return this.call(`/tenants/${tenantId}/scopes/${scopeId}/denials${denialQuery(filter)}`);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The gate. Throws unless the tenant is active AND the scope exists and is
|
|
101
|
+
* active — the same fail-closed logic the kernel's `validateScopeAccess`
|
|
102
|
+
* applies locally, so a tenant-level cascade suspend bites too, not just a
|
|
103
|
+
* per-scope one. Call it before handing a request to the local scope host.
|
|
104
|
+
*/
|
|
105
|
+
async assertScopeActive(tenantId, scopeId) {
|
|
106
|
+
const [tenant, scope] = await Promise.all([
|
|
107
|
+
this.getTenant(tenantId),
|
|
108
|
+
this.getScopeRecord(tenantId, scopeId),
|
|
109
|
+
]);
|
|
110
|
+
if (!tenant)
|
|
111
|
+
throw new ControlPlaneError(403, `unknown tenant: ${tenantId}`);
|
|
112
|
+
if (tenant.status !== 'active') {
|
|
113
|
+
throw new ControlPlaneError(403, `tenant not active (status: ${tenant.status}): ${tenantId}`);
|
|
114
|
+
}
|
|
115
|
+
if (!scope)
|
|
116
|
+
throw new ControlPlaneError(403, `unknown scope for tenant: (${tenantId}, ${scopeId})`);
|
|
117
|
+
if (scope.status !== 'active') {
|
|
118
|
+
throw new ControlPlaneError(403, `scope not active (status: ${scope.status}): ${scopeId}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The header names a caller and the control plane agree on. They live in the client
|
|
3
|
+
* package because it is the side every caller can import without taking the server with
|
|
4
|
+
* it; `@substrat-run/control-plane-api` re-exports them from its `auth.ts`, so the server
|
|
5
|
+
* and the clients read one spelling (#971).
|
|
6
|
+
*/
|
|
7
|
+
/** Header the dev stub reads. Mirrors the demos' `x-principal` dev affordance. */
|
|
8
|
+
export declare const DEV_ACTOR_HEADER = "x-platform-actor";
|
|
9
|
+
/** Header a SERVICE (a vertical registering itself) presents — not a staff subject. */
|
|
10
|
+
export declare const SERVICE_TOKEN_HEADER = "x-service-token";
|
|
11
|
+
/**
|
|
12
|
+
* Header naming the tenant a caller ACTS FOR. For a builder session it selects the
|
|
13
|
+
* workspace (`--tenant`) the builder reader narrows to. For a staff/service caller it is
|
|
14
|
+
* the slug-resolution pin (#417): vertical routes form `<tenantSlug>/<slug>` from it the
|
|
15
|
+
* way a pinned push does, so `versions <bare> --tenant <t>` over a service token reaches
|
|
16
|
+
* the same registry row a builder session would. It never widens access — staff reach
|
|
17
|
+
* everything already, and for builders the tenant must be one of their memberships.
|
|
18
|
+
*/
|
|
19
|
+
export declare const TENANT_HEADER = "x-substrat-tenant";
|
|
20
|
+
//# sourceMappingURL=headers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"headers.d.ts","sourceRoot":"","sources":["../src/headers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,kFAAkF;AAClF,eAAO,MAAM,gBAAgB,qBAAqB,CAAC;AAEnD,uFAAuF;AACvF,eAAO,MAAM,oBAAoB,oBAAoB,CAAC;AAEtD;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa,sBAAsB,CAAC"}
|
package/dist/headers.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The header names a caller and the control plane agree on. They live in the client
|
|
3
|
+
* package because it is the side every caller can import without taking the server with
|
|
4
|
+
* it; `@substrat-run/control-plane-api` re-exports them from its `auth.ts`, so the server
|
|
5
|
+
* and the clients read one spelling (#971).
|
|
6
|
+
*/
|
|
7
|
+
/** Header the dev stub reads. Mirrors the demos' `x-principal` dev affordance. */
|
|
8
|
+
export const DEV_ACTOR_HEADER = 'x-platform-actor';
|
|
9
|
+
/** Header a SERVICE (a vertical registering itself) presents — not a staff subject. */
|
|
10
|
+
export const SERVICE_TOKEN_HEADER = 'x-service-token';
|
|
11
|
+
/**
|
|
12
|
+
* Header naming the tenant a caller ACTS FOR. For a builder session it selects the
|
|
13
|
+
* workspace (`--tenant`) the builder reader narrows to. For a staff/service caller it is
|
|
14
|
+
* the slug-resolution pin (#417): vertical routes form `<tenantSlug>/<slug>` from it the
|
|
15
|
+
* way a pinned push does, so `versions <bare> --tenant <t>` over a service token reaches
|
|
16
|
+
* the same registry row a builder session would. It never widens access — staff reach
|
|
17
|
+
* everything already, and for builders the tenant must be one of their memberships.
|
|
18
|
+
*/
|
|
19
|
+
export const TENANT_HEADER = 'x-substrat-tenant';
|
|
20
|
+
//# sourceMappingURL=headers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"headers.js","sourceRoot":"","sources":["../src/headers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,kFAAkF;AAClF,MAAM,CAAC,MAAM,gBAAgB,GAAG,kBAAkB,CAAC;AAEnD,uFAAuF;AACvF,MAAM,CAAC,MAAM,oBAAoB,GAAG,iBAAiB,CAAC;AAEtD;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { z } from '@substrat-run/contracts';
|
|
2
|
+
/**
|
|
3
|
+
* A tenant a login builds for, as the control plane's studio membership read states it
|
|
4
|
+
* (`POST /internal/builder/identity-tenants`, builder-plane.md §4).
|
|
5
|
+
*
|
|
6
|
+
* The three directory fields are parsed with the SAME schemas the `tenant` record
|
|
7
|
+
* publishes (`contracts/src/tenancy.ts`), not with generic strings: the id is a ULID, the
|
|
8
|
+
* slug is the constrained slug, the name is non-empty. That costs nothing in lockout risk
|
|
9
|
+
* — `createTenantInput` picks those same three, so a tenant that exists satisfied them at
|
|
10
|
+
* creation — and it buys the half a loose parse would miss. A non-empty but malformed id
|
|
11
|
+
* passes `z.string()` and then becomes a Durable Object key and a team route, which is the
|
|
12
|
+
* downstream use this parse exists to protect.
|
|
13
|
+
*/
|
|
14
|
+
export declare const identityTenant: z.ZodObject<{
|
|
15
|
+
id: z.core.$ZodBranded<z.ZodString, "TenantId", "out">;
|
|
16
|
+
slug: z.ZodString;
|
|
17
|
+
name: z.ZodString;
|
|
18
|
+
entitled: z.ZodBoolean;
|
|
19
|
+
}, z.core.$strip>;
|
|
20
|
+
/** The `/internal/builder/identity-tenants` response body. */
|
|
21
|
+
export declare const identityTenantsResponse: z.ZodObject<{
|
|
22
|
+
tenants: z.ZodArray<z.ZodObject<{
|
|
23
|
+
id: z.core.$ZodBranded<z.ZodString, "TenantId", "out">;
|
|
24
|
+
slug: z.ZodString;
|
|
25
|
+
name: z.ZodString;
|
|
26
|
+
entitled: z.ZodBoolean;
|
|
27
|
+
}, z.core.$strip>>;
|
|
28
|
+
}, z.core.$strip>;
|
|
29
|
+
export type IdentityTenant = z.infer<typeof identityTenant>;
|
|
30
|
+
//# sourceMappingURL=identity-tenants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity-tenants.d.ts","sourceRoot":"","sources":["../src/identity-tenants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,CAAC,EAAE,MAAM,yBAAyB,CAAC;AAE5D;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,cAAc;;;;;iBAMzB,CAAC;AAEH,8DAA8D;AAC9D,eAAO,MAAM,uBAAuB;;;;;;;iBAAiD,CAAC;AAEtF,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { slug, tenantId, z } from '@substrat-run/contracts';
|
|
2
|
+
/**
|
|
3
|
+
* A tenant a login builds for, as the control plane's studio membership read states it
|
|
4
|
+
* (`POST /internal/builder/identity-tenants`, builder-plane.md §4).
|
|
5
|
+
*
|
|
6
|
+
* The three directory fields are parsed with the SAME schemas the `tenant` record
|
|
7
|
+
* publishes (`contracts/src/tenancy.ts`), not with generic strings: the id is a ULID, the
|
|
8
|
+
* slug is the constrained slug, the name is non-empty. That costs nothing in lockout risk
|
|
9
|
+
* — `createTenantInput` picks those same three, so a tenant that exists satisfied them at
|
|
10
|
+
* creation — and it buys the half a loose parse would miss. A non-empty but malformed id
|
|
11
|
+
* passes `z.string()` and then becomes a Durable Object key and a team route, which is the
|
|
12
|
+
* downstream use this parse exists to protect.
|
|
13
|
+
*/
|
|
14
|
+
export const identityTenant = z.object({
|
|
15
|
+
id: tenantId,
|
|
16
|
+
slug,
|
|
17
|
+
name: z.string().min(1),
|
|
18
|
+
/** Whether the tenant holds the `builder` entitlement (the control plane applies expiry at read). */
|
|
19
|
+
entitled: z.boolean(),
|
|
20
|
+
});
|
|
21
|
+
/** The `/internal/builder/identity-tenants` response body. */
|
|
22
|
+
export const identityTenantsResponse = z.object({ tenants: z.array(identityTenant) });
|
|
23
|
+
//# sourceMappingURL=identity-tenants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity-tenants.js","sourceRoot":"","sources":["../src/identity-tenants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,yBAAyB,CAAC;AAE5D;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC;IACrC,EAAE,EAAE,QAAQ;IACZ,IAAI;IACJ,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,qGAAqG;IACrG,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE;CACtB,CAAC,CAAC;AAEH,8DAA8D;AAC9D,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@substrat-run/control-plane-client` — the typed HTTP client for the control-plane API.
|
|
3
|
+
*
|
|
4
|
+
* Apache-2.0 on purpose (LICENSING.md): the CLI a builder runs against their own code, and
|
|
5
|
+
* the console and the connect seam, all import it, and a client library that
|
|
6
|
+
* copyleft-captured its callers would capture every vertical built on Substrat. The AGPL
|
|
7
|
+
* server it talks to is `@substrat-run/control-plane-api`, which re-exports this surface.
|
|
8
|
+
*
|
|
9
|
+
* Everything reachable from here is `fetch` + `@substrat-run/contracts` — never `hono`,
|
|
10
|
+
* never a Node builtin — so a bundler can follow it into a page. `test/entry.test.ts`
|
|
11
|
+
* holds that line.
|
|
12
|
+
*/
|
|
13
|
+
export { ControlPlaneTransport, ControlPlaneError, ControlPlaneUsageError, provesNothingChanged } from './transport.js';
|
|
14
|
+
export type { ControlPlaneTransportOptions, ControlPlaneErrorDetail } from './transport.js';
|
|
15
|
+
export { ControlPlaneClient } from './client.js';
|
|
16
|
+
export type { ControlPlaneClientOptions, ClientProvisionScopeInput } from './client.js';
|
|
17
|
+
export { identityTenant, identityTenantsResponse } from './identity-tenants.js';
|
|
18
|
+
export type { IdentityTenant } from './identity-tenants.js';
|
|
19
|
+
export { DEV_ACTOR_HEADER, SERVICE_TOKEN_HEADER, TENANT_HEADER } from './headers.js';
|
|
20
|
+
export { ControlPlaneBuilderClient, WALK_PAGE_LIMIT, walkPages } from './builder-client.js';
|
|
21
|
+
export type { PageRequest } from './builder-client.js';
|
|
22
|
+
export type * from './builder-types.js';
|
|
23
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACxH,YAAY,EAAE,4BAA4B,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAC5F,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,YAAY,EAAE,yBAAyB,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AACxF,OAAO,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChF,YAAY,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACrF,OAAO,EAAE,yBAAyB,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAC5F,YAAY,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,mBAAmB,oBAAoB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@substrat-run/control-plane-client` — the typed HTTP client for the control-plane API.
|
|
3
|
+
*
|
|
4
|
+
* Apache-2.0 on purpose (LICENSING.md): the CLI a builder runs against their own code, and
|
|
5
|
+
* the console and the connect seam, all import it, and a client library that
|
|
6
|
+
* copyleft-captured its callers would capture every vertical built on Substrat. The AGPL
|
|
7
|
+
* server it talks to is `@substrat-run/control-plane-api`, which re-exports this surface.
|
|
8
|
+
*
|
|
9
|
+
* Everything reachable from here is `fetch` + `@substrat-run/contracts` — never `hono`,
|
|
10
|
+
* never a Node builtin — so a bundler can follow it into a page. `test/entry.test.ts`
|
|
11
|
+
* holds that line.
|
|
12
|
+
*/
|
|
13
|
+
export { ControlPlaneTransport, ControlPlaneError, ControlPlaneUsageError, provesNothingChanged } from './transport.js';
|
|
14
|
+
export { ControlPlaneClient } from './client.js';
|
|
15
|
+
export { identityTenant, identityTenantsResponse } from './identity-tenants.js';
|
|
16
|
+
export { DEV_ACTOR_HEADER, SERVICE_TOKEN_HEADER, TENANT_HEADER } from './headers.js';
|
|
17
|
+
export { ControlPlaneBuilderClient, WALK_PAGE_LIMIT, walkPages } from './builder-client.js';
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAExH,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEjD,OAAO,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAEhF,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACrF,OAAO,EAAE,yBAAyB,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import type { ConnectionProbe } from '@substrat-run/contracts';
|
|
2
|
+
/**
|
|
3
|
+
* The one HTTP seam every control-plane caller shares (#971): how a request is
|
|
4
|
+
* authenticated, how a failure is read, and what an empty answer is. The callers
|
|
5
|
+
* differ in what they ask — `ControlPlaneClient` is the vertical's connect seam,
|
|
6
|
+
* `ControlPlaneStaffClient` the console's staff surface — not in how they ask it.
|
|
7
|
+
*
|
|
8
|
+
* Browser-safe: nothing here reaches past `fetch` and `@substrat-run/contracts`, which is
|
|
9
|
+
* why the console can import it (`@substrat-run/control-plane-client`).
|
|
10
|
+
*/
|
|
11
|
+
export interface ControlPlaneTransportOptions {
|
|
12
|
+
/** Base URL of the control-plane API; a path such as `/api` is fine in a browser. */
|
|
13
|
+
baseUrl: string;
|
|
14
|
+
/**
|
|
15
|
+
* The dev-only `x-platform-actor` header, sent only when no `serviceToken` is set. `null`
|
|
16
|
+
* sends none: the console's session mode, where the staff cookie authenticates instead.
|
|
17
|
+
*/
|
|
18
|
+
actor: string | null;
|
|
19
|
+
serviceToken?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Defaults to the global `fetch`, looked up at CALL time rather than captured when the
|
|
22
|
+
* client is built — so a caller that swaps `globalThis.fetch` afterwards (a test, an
|
|
23
|
+
* instrumented runtime) is honoured, and workerd still sees the global as the receiver.
|
|
24
|
+
*/
|
|
25
|
+
fetch?: typeof globalThis.fetch;
|
|
26
|
+
/** Passed to `fetch` only when set — `'include'` carries the console's staff session cookie. */
|
|
27
|
+
credentials?: RequestCredentials;
|
|
28
|
+
/**
|
|
29
|
+
* Extra headers sent with every request — for a caller that already holds a resolved
|
|
30
|
+
* credential header map (the CLI's `Authorization: Bearer …` / `x-service-token`, plus the
|
|
31
|
+
* tenant it acts for). PRECEDENCE, lowest first: these, then the transport's own
|
|
32
|
+
* credential (`serviceToken` / `actor`), then the content type, then the request's own
|
|
33
|
+
* `init.headers`. A client that has a credential of its own ignores any credential header
|
|
34
|
+
* (`x-service-token`, `x-platform-actor`, any case) in this map, and a call that names a
|
|
35
|
+
* credential replaces the client's, so exactly one credential leaves per request — two are
|
|
36
|
+
* refused with a `ControlPlaneUsageError` before anything is sent; a single call can still override
|
|
37
|
+
* anything else.
|
|
38
|
+
*/
|
|
39
|
+
headers?: Record<string, string>;
|
|
40
|
+
/**
|
|
41
|
+
* The `content-type` sent on every request. Defaults to `'application/json'` (what every
|
|
42
|
+
* existing caller has always sent, bodyless GETs included); `null` sends none, for a caller
|
|
43
|
+
* whose requests set their own per call.
|
|
44
|
+
*/
|
|
45
|
+
contentType?: string | null;
|
|
46
|
+
/**
|
|
47
|
+
* Called with every response that arrives — success or refusal — before it is read: the
|
|
48
|
+
* hook for a header the plane stamps on any answer (the CLI's version advisory). Never
|
|
49
|
+
* called for a transport failure, and a throw from it is the caller's own.
|
|
50
|
+
*/
|
|
51
|
+
onResponse?: (res: {
|
|
52
|
+
status: number;
|
|
53
|
+
headers: Headers;
|
|
54
|
+
}) => void;
|
|
55
|
+
}
|
|
56
|
+
/** What a refusal carried beyond its sentence, for a caller that renders its own. */
|
|
57
|
+
export interface ControlPlaneErrorDetail {
|
|
58
|
+
/**
|
|
59
|
+
* The response body as text, exactly as it arrived. `''` is a body that was empty;
|
|
60
|
+
* `undefined` is one that could not be read (the stream failed), which a caller that
|
|
61
|
+
* prints a status line instead — as the CLI does — tells apart.
|
|
62
|
+
*/
|
|
63
|
+
body?: string;
|
|
64
|
+
/** The status line's reason phrase (`Bad Gateway`). */
|
|
65
|
+
statusText?: string;
|
|
66
|
+
/** The response headers. */
|
|
67
|
+
headers?: Headers;
|
|
68
|
+
/** The full URL requested. */
|
|
69
|
+
url?: string;
|
|
70
|
+
/** Set on a 2xx whose body was not the JSON the route promises — `body` says what it was. */
|
|
71
|
+
malformed?: true;
|
|
72
|
+
/** The underlying failure, for a transport error (`status` 0): the error `fetch` threw. */
|
|
73
|
+
cause?: unknown;
|
|
74
|
+
}
|
|
75
|
+
/** A non-2xx (or unreachable) control-plane response. `status` is 0 on a transport error. */
|
|
76
|
+
export declare class ControlPlaneError extends Error {
|
|
77
|
+
readonly status: number;
|
|
78
|
+
/**
|
|
79
|
+
* The provider's own answer, when the plane refused a connect because the credential
|
|
80
|
+
* was rejected upstream (#605, 422). Carried so a console can show WHY — "Scrive:
|
|
81
|
+
* No valid access credentials were provided" — instead of a generic save failure.
|
|
82
|
+
*/
|
|
83
|
+
readonly probe?: ConnectionProbe | undefined;
|
|
84
|
+
constructor(status: number, message: string,
|
|
85
|
+
/**
|
|
86
|
+
* The provider's own answer, when the plane refused a connect because the credential
|
|
87
|
+
* was rejected upstream (#605, 422). Carried so a console can show WHY — "Scrive:
|
|
88
|
+
* No valid access credentials were provided" — instead of a generic save failure.
|
|
89
|
+
*/
|
|
90
|
+
probe?: ConnectionProbe | undefined, detail?: ControlPlaneErrorDetail);
|
|
91
|
+
/** The refusal's raw body text; `undefined` for an error the transport did not read off a response. */
|
|
92
|
+
readonly body: string | undefined;
|
|
93
|
+
readonly statusText: string | undefined;
|
|
94
|
+
readonly headers: Headers | undefined;
|
|
95
|
+
readonly url: string | undefined;
|
|
96
|
+
/** True for a 2xx answer that was not the JSON the route promises. */
|
|
97
|
+
readonly malformed: boolean;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Whether a failed call proves it changed nothing (#2010): the plane or the deployment behind it
|
|
101
|
+
* refused it (a 4xx), or that deployment does not have the route (501). Any other failure — a
|
|
102
|
+
* 502 that lost the deployment's answer, another 5xx, a malformed 2xx, a transport error
|
|
103
|
+
* (status 0), or no status at all — may follow a write that landed, so what it did is unknown
|
|
104
|
+
* and the caller reads the state before retrying. Takes any error carrying a numeric `status`,
|
|
105
|
+
* so an app's own error class for the same responses reads the same way.
|
|
106
|
+
*/
|
|
107
|
+
export declare function provesNothingChanged(error: unknown): boolean;
|
|
108
|
+
/**
|
|
109
|
+
* The caller built a request the client refuses to send — today, one that would carry two
|
|
110
|
+
* credentials. A mistake in how the client was used, raised BEFORE anything leaves, so it is
|
|
111
|
+
* not a `ControlPlaneError` (no plane answered, none was found unreachable) and not a
|
|
112
|
+
* `TypeError` either: `fetch` itself throws `TypeError` for a network failure, and a caller
|
|
113
|
+
* that retries on transport errors must be able to tell the two apart by class.
|
|
114
|
+
*/
|
|
115
|
+
export declare class ControlPlaneUsageError extends Error {
|
|
116
|
+
constructor(message: string);
|
|
117
|
+
}
|
|
118
|
+
export declare class ControlPlaneTransport {
|
|
119
|
+
private readonly baseUrl;
|
|
120
|
+
private readonly actor;
|
|
121
|
+
private readonly serviceToken?;
|
|
122
|
+
private readonly fetchImpl;
|
|
123
|
+
private readonly credentials?;
|
|
124
|
+
private readonly extraHeaders?;
|
|
125
|
+
private readonly contentType;
|
|
126
|
+
private readonly onResponse?;
|
|
127
|
+
constructor(options: ControlPlaneTransportOptions);
|
|
128
|
+
/**
|
|
129
|
+
* One request on the wire, exactly as `send` makes it, and nothing more: the base URL, the
|
|
130
|
+
* credential and the headers are applied, and the `Response` comes back whatever its status.
|
|
131
|
+
* A `fetch` rejection propagates unchanged. For a caller that owns its own reading of
|
|
132
|
+
* the answer — a retry loop, a multipart upload — and wants the transport's addressing and
|
|
133
|
+
* credentials without its error policy.
|
|
134
|
+
*/
|
|
135
|
+
request(path: string, init?: RequestInit): Promise<Response>;
|
|
136
|
+
/**
|
|
137
|
+
* The headers one request goes out with, merged through the `Headers` API so that names
|
|
138
|
+
* compare case-insensitively and every `HeadersInit` shape — a record, a tuple array or a
|
|
139
|
+
* `Headers` instance — is read the same way. Spreading the call's headers as if they were a
|
|
140
|
+
* record dropped a `Headers` instance's entries altogether, and let `Content-Type` and
|
|
141
|
+
* `content-type` ride side by side. Lowest first: the `headers` option, the transport's own
|
|
142
|
+
* credential, the content type, then the call's own.
|
|
143
|
+
*/
|
|
144
|
+
private headersFor;
|
|
145
|
+
/** The full URL a path is requested at — for a message that names it. */
|
|
146
|
+
urlFor(path: string): string;
|
|
147
|
+
/**
|
|
148
|
+
* One request, and the one place a failure is read: a transport error or a non-2xx answer
|
|
149
|
+
* throws `ControlPlaneError`, so what comes back is always a successful `Response`. A 404
|
|
150
|
+
* is handed back as-is when the caller allows it.
|
|
151
|
+
*/
|
|
152
|
+
protected send(path: string, init?: RequestInit, allow404?: boolean): Promise<Response>;
|
|
153
|
+
protected call<T>(path: string, init?: RequestInit, allow404?: boolean): Promise<T>;
|
|
154
|
+
/**
|
|
155
|
+
* A JSON answer, read from its text so a 2xx that is not JSON says what it was: a
|
|
156
|
+
* `ControlPlaneError` flagged `malformed`, carrying the `body` and `url` — the common
|
|
157
|
+
* misconfiguration being a base URL that points at a web page rather than the API. Unlike
|
|
158
|
+
* `call`, no status is special-cased: a 204 is an empty body, which is not JSON.
|
|
159
|
+
*/
|
|
160
|
+
protected read<T>(path: string, init?: RequestInit): Promise<T>;
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"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"}
|