ankka-console 0.10.0 → 0.12.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.
Files changed (75) hide show
  1. package/dist/client/control-plane.d.ts +87 -1
  2. package/dist/client/control-plane.js +201 -1
  3. package/dist/client/schemas.d.ts +1835 -0
  4. package/dist/client/schemas.js +595 -0
  5. package/dist/context.d.ts +98 -0
  6. package/dist/context.js +73 -0
  7. package/dist/extensions/render.d.ts +2 -0
  8. package/dist/extensions/render.js +5 -0
  9. package/dist/extensions/types.d.ts +1 -1
  10. package/dist/extensions/types.js +29 -0
  11. package/dist/fonts/inter-latin-ext-wght-normal.woff2 +0 -0
  12. package/dist/fonts/inter-latin-wght-normal.woff2 +0 -0
  13. package/dist/index.d.ts +9 -2
  14. package/dist/index.js +7 -1
  15. package/dist/routes/auth.sign-out.js +8 -3
  16. package/dist/routes/front.d.ts +62 -2
  17. package/dist/routes/front.js +45 -10
  18. package/dist/routes/logs.d.ts +92 -0
  19. package/dist/routes/logs.js +15 -14
  20. package/dist/routes/machines.d.ts +78 -0
  21. package/dist/routes/machines.js +78 -0
  22. package/dist/routes/members.js +11 -8
  23. package/dist/routes/organization-new.js +7 -4
  24. package/dist/routes/organization.d.ts +29 -1
  25. package/dist/routes/organization.js +38 -9
  26. package/dist/routes/project-new.js +8 -4
  27. package/dist/routes/project.d.ts +347 -2
  28. package/dist/routes/project.js +182 -9
  29. package/dist/routes/service-apply.d.ts +5 -2
  30. package/dist/routes/service-apply.js +17 -14
  31. package/dist/routes/service-history.d.ts +32 -0
  32. package/dist/routes/service-history.js +82 -0
  33. package/dist/routes/service-topology.d.ts +210 -0
  34. package/dist/routes/service-topology.js +54 -0
  35. package/dist/routes/service.d.ts +91 -10
  36. package/dist/routes/service.js +87 -23
  37. package/dist/routes/stream.service.js +18 -1
  38. package/dist/routes/tokens.js +8 -6
  39. package/dist/routes.js +3 -0
  40. package/dist/runtime.d.ts +3 -1
  41. package/dist/runtime.js +1 -0
  42. package/dist/styles.css +2 -481
  43. package/dist/testing/fake-control-plane.d.ts +334 -1
  44. package/dist/testing/fake-control-plane.js +1194 -15
  45. package/dist/testing/index.d.ts +1 -1
  46. package/dist/testing/scenarios.js +6 -0
  47. package/dist/theme.css +66 -0
  48. package/dist/ui/console.d.ts +19 -6
  49. package/dist/ui/console.js +9 -2
  50. package/dist/ui/errors.js +7 -5
  51. package/dist/ui/grants.d.ts +3 -0
  52. package/dist/ui/grants.js +13 -0
  53. package/dist/ui/hosting.d.ts +11 -0
  54. package/dist/ui/hosting.js +20 -0
  55. package/dist/ui/primitives/button.d.ts +11 -0
  56. package/dist/ui/primitives/button.js +21 -0
  57. package/dist/ui/primitives/select.d.ts +10 -0
  58. package/dist/ui/primitives/select.js +4 -0
  59. package/dist/ui/primitives/switch.d.ts +9 -0
  60. package/dist/ui/primitives/switch.js +4 -0
  61. package/dist/ui/shape.d.ts +4 -0
  62. package/dist/ui/shape.js +36 -0
  63. package/dist/ui/shell.d.ts +76 -0
  64. package/dist/ui/shell.js +114 -0
  65. package/dist/ui/tokens.d.ts +82 -0
  66. package/dist/ui/tokens.js +172 -0
  67. package/dist/ui/topology/Graph.d.ts +13 -0
  68. package/dist/ui/topology/Graph.js +56 -0
  69. package/dist/ui/topology/Table.d.ts +10 -0
  70. package/dist/ui/topology/Table.js +14 -0
  71. package/dist/ui/topology/layout.d.ts +113 -0
  72. package/dist/ui/topology/layout.js +177 -0
  73. package/dist/ui/use-stream.d.ts +251 -1
  74. package/dist/ui/use-stream.js +11 -1
  75. package/package.json +11 -3
@@ -1,4 +1,4 @@
1
- import { type AuthDiscovery, type CreateDeployToken, type CreateOrganization, type DeployTokenCreated, type DeployTokenSummary, type HistoryEntry, type LogsResponse, type MembersResponse, type OrganizationSummary, type ProjectDetail, type ProjectSummary, type Quota, type Role, type ServiceStatus, type SetRegistry, type Whoami } from "./schemas.ts";
1
+ import { type AuthDiscovery, type BrokerDeclarationRequest, type ByteRatesRequest, type CreateDeployToken, type CreateOrganization, type DeployTokenCreated, type DeployTokenSummary, type GrantDetail, type GrantRequest, type HistoryEntry, type LogsResponse, type MachineRegistered, type MachineSummary, type MembersResponse, type OrganizationSummary, type ProjectBroker, type ProjectStatus, type CredentialReissued, type DatabaseSetting, type InstallationStatus, type ProjectHistoryView, type RestoreHoldStatus, type RehearsalView, type RestoreView, type ProjectDetail, type ProjectSecretSummary, type ProjectSummary, type ProjectTopic, type Quota, type ReceivedGrantDetail, type Role, type RolledBack, type ServiceStatus, type ServiceTopology, type SetRegistry, type TopicDeclarationRequest, type Whoami, type Installation } from "./schemas.ts";
2
2
  /** `fetch`'s shape; the server supplies one that presents the console's certificate. */
3
3
  export type Transport = (url: string, init: RequestInit) => Promise<Response>;
4
4
  /**
@@ -8,9 +8,13 @@ export type Transport = (url: string, init: RequestInit) => Promise<Response>;
8
8
  export type BearerSource = (options: {
9
9
  refresh: boolean;
10
10
  }) => Promise<string | null>;
11
+ /** What a grantee's organization may do to a grant offered to it, as the route's last segment says it. */
12
+ export type GrantAnswer = "accept" | "decline" | "relinquish";
11
13
  export interface LogsQuery {
12
14
  instance?: string;
13
15
  previous?: boolean;
16
+ /** The platform's container (the sidecar, or a web-hosted service's proxy) instead of the developer's. */
17
+ platform?: boolean;
14
18
  tail?: number;
15
19
  since?: number;
16
20
  }
@@ -31,6 +35,8 @@ export declare class ControlPlaneClient {
31
35
  constructor(options: ControlPlaneClientOptions);
32
36
  authDiscovery(): Promise<AuthDiscovery>;
33
37
  whoami(): Promise<Whoami>;
38
+ /** Its version, and its cloud when it names a provider; the key's name for an owner alone (feature 044). */
39
+ installation(): Promise<Installation>;
34
40
  listOrganizations(): Promise<OrganizationSummary[]>;
35
41
  getOrganization(id: string): Promise<OrganizationSummary>;
36
42
  createOrganization(id: string, body: CreateOrganization): Promise<void>;
@@ -49,6 +55,19 @@ export declare class ControlPlaneClient {
49
55
  tokens(id: string): Promise<DeployTokenSummary[]>;
50
56
  createToken(id: string, body: CreateDeployToken): Promise<DeployTokenCreated>;
51
57
  revokeToken(id: string, tokenId: string): Promise<void>;
58
+ /** The organization's registered machines; never a secret. */
59
+ machines(id: string): Promise<MachineSummary[]>;
60
+ /** Registers a machine. The reply carries its client secret, the only time it is ever shown. */
61
+ registerMachine(id: string, name: string): Promise<MachineRegistered>;
62
+ deleteMachine(id: string, name: string): Promise<void>;
63
+ setMachineByteRates(id: string, name: string, rates: ByteRatesRequest): Promise<MachineSummary>;
64
+ /** What the organization's machines and its projects' services hold, or are offered, from other projects. */
65
+ organizationGrants(id: string): Promise<ReceivedGrantDetail[]>;
66
+ /**
67
+ * Answers a grant from the grantee's side, as an owner of its organization: accepts or declines a
68
+ * pending one, or relinquishes an accepted one.
69
+ */
70
+ answerGrant(id: string, grantId: string, verb: GrantAnswer): Promise<void>;
52
71
  listProjects(): Promise<ProjectSummary[]>;
53
72
  getProject(id: string): Promise<ProjectDetail>;
54
73
  createProject(id: string, name: string, organizationId: string): Promise<void>;
@@ -56,12 +75,79 @@ export declare class ControlPlaneClient {
56
75
  deleteProject(id: string): Promise<void>;
57
76
  setRegistry(id: string, body: SetRegistry): Promise<void>;
58
77
  clearRegistry(id: string): Promise<void>;
78
+ /** Where the project's new buckets in Google Cloud Storage are made; moves no bucket. */
79
+ setProjectLocation(id: string, location: string): Promise<void>;
80
+ /** The installation's default location for the project's new buckets again. */
81
+ clearProjectLocation(id: string): Promise<void>;
82
+ /** Entries of a project secret, merged into what it holds. The values are never read back. */
83
+ setProjectSecret(id: string, name: string, entries: Record<string, string>): Promise<void>;
84
+ unsetProjectSecretEntry(id: string, name: string, entry: string): Promise<void>;
85
+ listProjectSecrets(id: string): Promise<ProjectSecretSummary[]>;
86
+ /** Declares a topic on the project, or raises its partitions; its compaction and contract with it. */
87
+ declareTopic(id: string, name: string, request: TopicDeclarationRequest): Promise<void>;
88
+ /** The schema document a topic's contract was declared with. */
89
+ topicSchema(id: string, name: string): Promise<unknown>;
90
+ /** Stops declaring a topic; it stays on the broker. */
91
+ removeTopic(id: string, name: string): Promise<void>;
92
+ listTopics(id: string): Promise<ProjectTopic[]>;
93
+ /** Declares a broker beside the installation's, or changes where it is; the project secret holds its credential. */
94
+ declareBroker(id: string, name: string, request: BrokerDeclarationRequest): Promise<void>;
95
+ /** Stops declaring a broker; a service naming it is refused at its next start. */
96
+ removeBroker(id: string, name: string): Promise<void>;
97
+ listBrokers(id: string): Promise<ProjectBroker[]>;
98
+ /** Whether the project is backed up, how far back it can be restored, its database and its restores. */
99
+ projectStatus(id: string): Promise<ProjectStatus>;
100
+ /** Restores the project's database to a moment, into a new cluster beside the current one. Owners only. */
101
+ restoreProject(id: string, moment: string, line?: string): Promise<RestoreView>;
102
+ /** Issues the project's backup credential again; the old key stops working. Owners only. */
103
+ reissueBackupCredential(id: string): Promise<CredentialReissued>;
104
+ listRestores(id: string): Promise<RestoreView[]>;
105
+ /** Who did what to the project's database, newest first. */
106
+ projectHistory(id: string): Promise<ProjectHistoryView[]>;
107
+ projectDatabase(id: string): Promise<DatabaseSetting>;
108
+ /** Sets what the project asks of its database, whole: replicas, synchronous writes, retention, rehearsals. */
109
+ setProjectDatabase(id: string, setting: DatabaseSetting): Promise<DatabaseSetting>;
110
+ /** Where the installation's backups go, for how long, and how safely: `backups` on `GET /installation`. */
111
+ backups(): Promise<InstallationStatus>;
112
+ /** Whether the control plane is held after its own database was restored, and what differs. */
113
+ restoreHold(): Promise<RestoreHoldStatus>;
114
+ /** Releases a held control plane; a platform administrator's alone. */
115
+ releaseRestoreHold(): Promise<RestoreHoldStatus>;
116
+ getRestore(id: string, name: string): Promise<RestoreView>;
117
+ /** Rehearses a restore in a namespace of its own; it changes nothing of the project. */
118
+ rehearseProject(id: string, moment?: string): Promise<RehearsalView>;
119
+ listRehearsals(id: string): Promise<RehearsalView[]>;
120
+ /** Moves one service to another of its project's database clusters: a restore, or back. */
121
+ switchService(projectId: string, name: string, cluster: string): Promise<ServiceStatus>;
122
+ /** Grants a service of another project, or a machine, one route, method, topic or the erasure. */
123
+ makeGrant(id: string, request: GrantRequest): Promise<GrantDetail>;
124
+ /** The project's grants, live and ended, each with whether it is in effect and why not. */
125
+ grants(id: string): Promise<GrantDetail[]>;
126
+ /** What other projects granted this project's services, and every change to each. */
127
+ receivedGrants(id: string): Promise<ReceivedGrantDetail[]>;
128
+ /** Ends a grant from the grantor's side: a pending one is withdrawn, an accepted one revoked. */
129
+ endGrant(id: string, grantId: string): Promise<void>;
59
130
  listServices(projectId: string): Promise<ServiceStatus[]>;
60
131
  getService(projectId: string, name: string): Promise<ServiceStatus>;
61
132
  /** The descriptor is sent as the text the person gave; the control plane is the one that validates it. */
62
133
  applyService(projectId: string, name: string, descriptor: string): Promise<ServiceStatus>;
63
134
  serviceOperation(projectId: string, name: string, operation: "pause" | "resume" | "restart" | "expose" | "unexpose"): Promise<void>;
135
+ /** Add a custom hostname; a refusal is the control plane's, shown as it says it. */
136
+ addHostname(projectId: string, name: string, hostname: string): Promise<ServiceStatus>;
137
+ removeHostname(projectId: string, name: string, hostname: string): Promise<ServiceStatus>;
138
+ /** Issues the service's storage credential again; the old one works for the rotation grace. */
139
+ reissueStorageCredential(projectId: string, name: string): Promise<ServiceStatus>;
140
+ /** Applies the installation's current bucket settings to the service's bucket in Google Cloud Storage. */
141
+ reapplyStorageSettings(projectId: string, name: string): Promise<ServiceStatus>;
142
+ /** Moves the service's bucket from Garage to Google Cloud Storage; an omitted bound is the control plane's default. */
143
+ moveStorage(projectId: string, name: string, writePauseBound?: string): Promise<ServiceStatus>;
64
144
  deleteService(projectId: string, name: string): Promise<void>;
145
+ /** Roll a service back to a generation: its descriptor applied again, as a new generation. */
146
+ rollback(projectId: string, name: string, generation: number): Promise<RolledBack>;
147
+ /** The descriptor applied at a generation, as the control plane accepts one. */
148
+ descriptor(projectId: string, name: string, generation: number): Promise<unknown>;
65
149
  history(projectId: string, name: string): Promise<HistoryEntry[]>;
66
150
  logs(projectId: string, name: string, query?: LogsQuery): Promise<LogsResponse>;
151
+ /** Every instance's topology, merged by the control plane; 404 when no instance runs. */
152
+ topology(projectId: string, name: string): Promise<ServiceTopology>;
67
153
  }
@@ -1,5 +1,7 @@
1
1
  import { ControlPlaneError, ControlPlaneUnreachable, SignInRequired } from "./errors.js";
2
- import { authDiscoverySchema, deployTokenCreatedSchema, deployTokenSummarySchema, historyEntrySchema, logsResponseSchema, membersResponseSchema, organizationSummarySchema, projectDetailSchema, projectSummarySchema, serviceStatusSchema, whoamiSchema, } from "./schemas.js";
2
+ import { authDiscoverySchema, deployTokenCreatedSchema, deployTokenSummarySchema, grantDetailSchema, historyEntrySchema, machineRegisteredSchema, machineSummarySchema, logsResponseSchema, membersResponseSchema, organizationSummarySchema, projectBrokerSchema, projectStatusSchema, credentialReissuedSchema, databaseSettingSchema, projectHistoryViewSchema, restoreHoldStatusSchema, rehearsalViewSchema, restoreViewSchema, projectDetailSchema, projectSecretSummarySchema, projectSummarySchema, projectTopicSchema, receivedGrantDetailSchema, rolledBackSchema, serviceStatusSchema, serviceTopologySchema, whoamiSchema, installationSchema, } from "./schemas.js";
3
+ /** A descriptor is passed through as the control plane wrote it, as `applyService` takes one. */
4
+ const anyJson = { parse: (value) => value };
3
5
  const arrayOf = (schema) => ({
4
6
  parse: (value) => {
5
7
  if (!Array.isArray(value))
@@ -31,6 +33,11 @@ export class ControlPlaneClient {
31
33
  whoami() {
32
34
  return this.#call("GET", "/auth/whoami", { schema: whoamiSchema });
33
35
  }
36
+ // ── The installation ──────────────────────────────────────────────────────
37
+ /** Its version, and its cloud when it names a provider; the key's name for an owner alone (feature 044). */
38
+ installation() {
39
+ return this.#call("GET", "/installation", { schema: installationSchema });
40
+ }
34
41
  // ── Organizations ─────────────────────────────────────────────────────────
35
42
  listOrganizations() {
36
43
  return this.#call("GET", "/organizations", { schema: arrayOf(organizationSummarySchema) });
@@ -97,6 +104,35 @@ export class ControlPlaneClient {
97
104
  revokeToken(id, tokenId) {
98
105
  return this.#call("DELETE", `/organizations/${segment(id)}/tokens/${segment(tokenId)}`);
99
106
  }
107
+ // ── Machines and the grants an organization holds (feature 040) ─────────
108
+ /** The organization's registered machines; never a secret. */
109
+ machines(id) {
110
+ return this.#call("GET", `/organizations/${segment(id)}/machines`, { schema: arrayOf(machineSummarySchema) });
111
+ }
112
+ /** Registers a machine. The reply carries its client secret, the only time it is ever shown. */
113
+ registerMachine(id, name) {
114
+ return this.#call("POST", `/organizations/${segment(id)}/machines`, { body: { name }, schema: machineRegisteredSchema });
115
+ }
116
+ deleteMachine(id, name) {
117
+ return this.#call("DELETE", `/organizations/${segment(id)}/machines/${segment(name)}`);
118
+ }
119
+ setMachineByteRates(id, name, rates) {
120
+ return this.#call("PUT", `/organizations/${segment(id)}/machines/${segment(name)}/byte-rates`, {
121
+ body: rates,
122
+ schema: machineSummarySchema,
123
+ });
124
+ }
125
+ /** What the organization's machines and its projects' services hold, or are offered, from other projects. */
126
+ organizationGrants(id) {
127
+ return this.#call("GET", `/organizations/${segment(id)}/grants`, { schema: arrayOf(receivedGrantDetailSchema) });
128
+ }
129
+ /**
130
+ * Answers a grant from the grantee's side, as an owner of its organization: accepts or declines a
131
+ * pending one, or relinquishes an accepted one.
132
+ */
133
+ answerGrant(id, grantId, verb) {
134
+ return this.#call("POST", `/organizations/${segment(id)}/grants/${segment(grantId)}/${verb}`);
135
+ }
100
136
  // ── Projects ──────────────────────────────────────────────────────────────
101
137
  listProjects() {
102
138
  return this.#call("GET", "/projects", { schema: arrayOf(projectSummarySchema) });
@@ -119,6 +155,123 @@ export class ControlPlaneClient {
119
155
  clearRegistry(id) {
120
156
  return this.#call("DELETE", `/projects/${segment(id)}/registry`);
121
157
  }
158
+ /** Where the project's new buckets in Google Cloud Storage are made; moves no bucket. */
159
+ setProjectLocation(id, location) {
160
+ return this.#call("PUT", `/projects/${segment(id)}/location`, { body: { location } });
161
+ }
162
+ /** The installation's default location for the project's new buckets again. */
163
+ clearProjectLocation(id) {
164
+ return this.#call("DELETE", `/projects/${segment(id)}/location`);
165
+ }
166
+ /** Entries of a project secret, merged into what it holds. The values are never read back. */
167
+ setProjectSecret(id, name, entries) {
168
+ return this.#call("PUT", `/projects/${segment(id)}/secrets/${segment(name)}`, { body: { entries } });
169
+ }
170
+ unsetProjectSecretEntry(id, name, entry) {
171
+ return this.#call("DELETE", `/projects/${segment(id)}/secrets/${segment(name)}?entry=${segment(entry)}`);
172
+ }
173
+ listProjectSecrets(id) {
174
+ return this.#call("GET", `/projects/${segment(id)}/secrets`, { schema: arrayOf(projectSecretSummarySchema) });
175
+ }
176
+ /** Declares a topic on the project, or raises its partitions; its compaction and contract with it. */
177
+ declareTopic(id, name, request) {
178
+ return this.#call("PUT", `/projects/${segment(id)}/topics/${segment(name)}`, { body: request });
179
+ }
180
+ /** The schema document a topic's contract was declared with. */
181
+ topicSchema(id, name) {
182
+ return this.#call("GET", `/projects/${segment(id)}/topics/${segment(name)}/schema`, { schema: { parse: (v) => v } });
183
+ }
184
+ /** Stops declaring a topic; it stays on the broker. */
185
+ removeTopic(id, name) {
186
+ return this.#call("DELETE", `/projects/${segment(id)}/topics/${segment(name)}`);
187
+ }
188
+ listTopics(id) {
189
+ return this.#call("GET", `/projects/${segment(id)}/topics`, { schema: arrayOf(projectTopicSchema) });
190
+ }
191
+ /** Declares a broker beside the installation's, or changes where it is; the project secret holds its credential. */
192
+ declareBroker(id, name, request) {
193
+ return this.#call("PUT", `/projects/${segment(id)}/brokers/${segment(name)}`, { body: request });
194
+ }
195
+ /** Stops declaring a broker; a service naming it is refused at its next start. */
196
+ removeBroker(id, name) {
197
+ return this.#call("DELETE", `/projects/${segment(id)}/brokers/${segment(name)}`);
198
+ }
199
+ listBrokers(id) {
200
+ return this.#call("GET", `/projects/${segment(id)}/brokers`, { schema: arrayOf(projectBrokerSchema) });
201
+ }
202
+ // ── Backups and recovery ──────────────────────────────────────────────────
203
+ /** Whether the project is backed up, how far back it can be restored, its database and its restores. */
204
+ projectStatus(id) {
205
+ return this.#call("GET", `/projects/${segment(id)}/status`, { schema: projectStatusSchema });
206
+ }
207
+ /** Restores the project's database to a moment, into a new cluster beside the current one. Owners only. */
208
+ restoreProject(id, moment, line) {
209
+ return this.#call("POST", `/projects/${segment(id)}/restores`, { body: { moment, line }, schema: restoreViewSchema });
210
+ }
211
+ /** Issues the project's backup credential again; the old key stops working. Owners only. */
212
+ reissueBackupCredential(id) {
213
+ return this.#call("POST", `/projects/${segment(id)}/backups/credential`, { schema: credentialReissuedSchema });
214
+ }
215
+ listRestores(id) {
216
+ return this.#call("GET", `/projects/${segment(id)}/restores`, { schema: arrayOf(restoreViewSchema) });
217
+ }
218
+ /** Who did what to the project's database, newest first. */
219
+ projectHistory(id) {
220
+ return this.#call("GET", `/projects/${segment(id)}/history`, { schema: arrayOf(projectHistoryViewSchema) });
221
+ }
222
+ projectDatabase(id) {
223
+ return this.#call("GET", `/projects/${segment(id)}/database`, { schema: databaseSettingSchema });
224
+ }
225
+ /** Sets what the project asks of its database, whole: replicas, synchronous writes, retention, rehearsals. */
226
+ setProjectDatabase(id, setting) {
227
+ return this.#call("PUT", `/projects/${segment(id)}/database`, { body: setting, schema: databaseSettingSchema });
228
+ }
229
+ /** Where the installation's backups go, for how long, and how safely: `backups` on `GET /installation`. */
230
+ async backups() {
231
+ const installation = await this.installation();
232
+ if (!installation.backups)
233
+ throw new Error("the control plane reported nothing of its backups");
234
+ return installation.backups;
235
+ }
236
+ /** Whether the control plane is held after its own database was restored, and what differs. */
237
+ restoreHold() {
238
+ return this.#call("GET", "/installation/restore", { schema: restoreHoldStatusSchema });
239
+ }
240
+ /** Releases a held control plane; a platform administrator's alone. */
241
+ releaseRestoreHold() {
242
+ return this.#call("POST", "/installation/restore/release", { schema: restoreHoldStatusSchema });
243
+ }
244
+ getRestore(id, name) {
245
+ return this.#call("GET", `/projects/${segment(id)}/restores/${segment(name)}`, { schema: restoreViewSchema });
246
+ }
247
+ /** Rehearses a restore in a namespace of its own; it changes nothing of the project. */
248
+ rehearseProject(id, moment) {
249
+ return this.#call("POST", `/projects/${segment(id)}/rehearsals`, { body: moment ? { moment } : {}, schema: rehearsalViewSchema });
250
+ }
251
+ listRehearsals(id) {
252
+ return this.#call("GET", `/projects/${segment(id)}/rehearsals`, { schema: arrayOf(rehearsalViewSchema) });
253
+ }
254
+ /** Moves one service to another of its project's database clusters: a restore, or back. */
255
+ switchService(projectId, name, cluster) {
256
+ return this.#call("POST", `/services/${segment(projectId)}/${segment(name)}/switch`, { body: { cluster }, schema: serviceStatusSchema });
257
+ }
258
+ // ── Grants (feature 040) ──────────────────────────────────────────────────
259
+ /** Grants a service of another project, or a machine, one route, method, topic or the erasure. */
260
+ makeGrant(id, request) {
261
+ return this.#call("POST", `/projects/${segment(id)}/grants`, { body: request, schema: grantDetailSchema });
262
+ }
263
+ /** The project's grants, live and ended, each with whether it is in effect and why not. */
264
+ grants(id) {
265
+ return this.#call("GET", `/projects/${segment(id)}/grants`, { schema: arrayOf(grantDetailSchema) });
266
+ }
267
+ /** What other projects granted this project's services, and every change to each. */
268
+ receivedGrants(id) {
269
+ return this.#call("GET", `/projects/${segment(id)}/grants/received`, { schema: arrayOf(receivedGrantDetailSchema) });
270
+ }
271
+ /** Ends a grant from the grantor's side: a pending one is withdrawn, an accepted one revoked. */
272
+ endGrant(id, grantId) {
273
+ return this.#call("DELETE", `/projects/${segment(id)}/grants/${segment(grantId)}`);
274
+ }
122
275
  // ── Services ──────────────────────────────────────────────────────────────
123
276
  listServices(projectId) {
124
277
  return this.#call("GET", `/services/${segment(projectId)}`, { schema: arrayOf(serviceStatusSchema) });
@@ -136,9 +289,48 @@ export class ControlPlaneClient {
136
289
  serviceOperation(projectId, name, operation) {
137
290
  return this.#call("POST", `/services/${segment(projectId)}/${segment(name)}/${operation}`);
138
291
  }
292
+ /** Add a custom hostname; a refusal is the control plane's, shown as it says it. */
293
+ addHostname(projectId, name, hostname) {
294
+ return this.#call("PUT", `/services/${segment(projectId)}/${segment(name)}/hostnames/${segment(hostname)}`, {
295
+ schema: serviceStatusSchema,
296
+ });
297
+ }
298
+ removeHostname(projectId, name, hostname) {
299
+ return this.#call("DELETE", `/services/${segment(projectId)}/${segment(name)}/hostnames/${segment(hostname)}`, {
300
+ schema: serviceStatusSchema,
301
+ });
302
+ }
303
+ /** Issues the service's storage credential again; the old one works for the rotation grace. */
304
+ reissueStorageCredential(projectId, name) {
305
+ return this.#call("POST", `/services/${segment(projectId)}/${segment(name)}/storage-credential`, { schema: serviceStatusSchema });
306
+ }
307
+ /** Applies the installation's current bucket settings to the service's bucket in Google Cloud Storage. */
308
+ reapplyStorageSettings(projectId, name) {
309
+ return this.#call("POST", `/services/${segment(projectId)}/${segment(name)}/storage/settings`, { schema: serviceStatusSchema });
310
+ }
311
+ /** Moves the service's bucket from Garage to Google Cloud Storage; an omitted bound is the control plane's default. */
312
+ moveStorage(projectId, name, writePauseBound) {
313
+ return this.#call("POST", `/services/${segment(projectId)}/${segment(name)}/storage/move`, {
314
+ body: writePauseBound ? { writePauseBound } : {},
315
+ schema: serviceStatusSchema,
316
+ });
317
+ }
139
318
  deleteService(projectId, name) {
140
319
  return this.#call("DELETE", `/services/${segment(projectId)}/${segment(name)}`);
141
320
  }
321
+ /** Roll a service back to a generation: its descriptor applied again, as a new generation. */
322
+ rollback(projectId, name, generation) {
323
+ return this.#call("POST", `/services/${segment(projectId)}/${segment(name)}/rollback`, {
324
+ body: { generation },
325
+ schema: rolledBackSchema,
326
+ });
327
+ }
328
+ /** The descriptor applied at a generation, as the control plane accepts one. */
329
+ descriptor(projectId, name, generation) {
330
+ return this.#call("GET", `/services/${segment(projectId)}/${segment(name)}/descriptor?generation=${generation}`, {
331
+ schema: anyJson,
332
+ });
333
+ }
142
334
  history(projectId, name) {
143
335
  return this.#call("GET", `/services/${segment(projectId)}/${segment(name)}/history`, {
144
336
  schema: arrayOf(historyEntrySchema),
@@ -150,6 +342,8 @@ export class ControlPlaneClient {
150
342
  params.set("instance", query.instance);
151
343
  if (query.previous)
152
344
  params.set("previous", "true");
345
+ if (query.platform)
346
+ params.set("platform", "true");
153
347
  if (query.tail !== undefined)
154
348
  params.set("tail", String(query.tail));
155
349
  if (query.since !== undefined)
@@ -159,6 +353,12 @@ export class ControlPlaneClient {
159
353
  schema: logsResponseSchema,
160
354
  });
161
355
  }
356
+ /** Every instance's topology, merged by the control plane; 404 when no instance runs. */
357
+ topology(projectId, name) {
358
+ return this.#call("GET", `/services/${segment(projectId)}/${segment(name)}/topology`, {
359
+ schema: serviceTopologySchema,
360
+ });
361
+ }
162
362
  // ── Transport ─────────────────────────────────────────────────────────────
163
363
  async #call(method, path, options = {}) {
164
364
  const url = this.#baseUrl + path;