@runlot/api 0.1.0-rc.73 → 0.1.0-rc.75
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/index.d.ts +215 -179
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +232 -194
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const API_VERSION = "v1";
|
|
2
|
-
/**
|
|
2
|
+
/** The shared query for browsing. Empty values are not carried — the server reads "empty means default". */
|
|
3
3
|
function gitQuery(ref, path) {
|
|
4
4
|
const q = new URLSearchParams();
|
|
5
5
|
if (ref !== "")
|
|
@@ -8,7 +8,7 @@ function gitQuery(ref, path) {
|
|
|
8
8
|
q.set("path", path);
|
|
9
9
|
return q.size > 0 ? `?${q.toString()}` : "";
|
|
10
10
|
}
|
|
11
|
-
/**
|
|
11
|
+
/** The exception that carries the server's error envelope unchanged. */
|
|
12
12
|
export class ApiError extends Error {
|
|
13
13
|
status;
|
|
14
14
|
code;
|
|
@@ -23,10 +23,11 @@ export class ApiError extends Error {
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
|
-
* cp-public
|
|
26
|
+
* The cp-public client.
|
|
27
27
|
*
|
|
28
|
-
* CLI
|
|
29
|
-
*
|
|
28
|
+
* The CLI and the dashboard use the same one. If each called fetch itself, their
|
|
29
|
+
* error handling would drift a little, and that difference shows up during an
|
|
30
|
+
* incident.
|
|
30
31
|
*/
|
|
31
32
|
function dbPath(org, name) {
|
|
32
33
|
return `/v1/orgs/${encodeURIComponent(org)}/projects/${encodeURIComponent(name)}/database`;
|
|
@@ -54,13 +55,15 @@ export class Client {
|
|
|
54
55
|
body = (await res.json());
|
|
55
56
|
}
|
|
56
57
|
catch {
|
|
57
|
-
//
|
|
58
|
-
//
|
|
58
|
+
// An error response that is not JSON (a 502 made by a proxy, for instance)
|
|
59
|
+
// is raised as the same exception — so the caller does not have to handle
|
|
60
|
+
// two shapes of failure.
|
|
59
61
|
}
|
|
60
62
|
if (body === undefined && res.status >= 502 && res.status <= 504) {
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
//
|
|
63
|
+
// The edge replaced the 5xx body with its own HTML. The code and reason
|
|
64
|
+
// the server wrote are not here — so say that, rather than "unknown". On
|
|
65
|
+
// 2026-09-10 a registrar rejection came out this way as
|
|
66
|
+
// "unknown: POST … → 502".
|
|
64
67
|
body = {
|
|
65
68
|
error: {
|
|
66
69
|
code: "edge_" + res.status,
|
|
@@ -74,7 +77,7 @@ export class Client {
|
|
|
74
77
|
return undefined;
|
|
75
78
|
return (await res.json());
|
|
76
79
|
}
|
|
77
|
-
/**
|
|
80
|
+
/** A body that is not JSON (the raw mail). Error handling is the same as request. */
|
|
78
81
|
async text(method, path) {
|
|
79
82
|
const res = await fetch(new URL(path, this.opts.baseUrl), { method, headers: this.headers() });
|
|
80
83
|
if (!res.ok) {
|
|
@@ -83,7 +86,7 @@ export class Client {
|
|
|
83
86
|
body = (await res.json());
|
|
84
87
|
}
|
|
85
88
|
catch {
|
|
86
|
-
//
|
|
89
|
+
// The same reason as in request
|
|
87
90
|
}
|
|
88
91
|
throw new ApiError(res.status, body, `${method} ${path} → ${res.status}`);
|
|
89
92
|
}
|
|
@@ -101,8 +104,9 @@ export class Client {
|
|
|
101
104
|
return this.json("POST", "/v1/auth/dev/login", { subject });
|
|
102
105
|
}
|
|
103
106
|
/**
|
|
104
|
-
*
|
|
105
|
-
*
|
|
107
|
+
* Takes the sign-up and sends the confirmation mail. Neither a session nor a
|
|
108
|
+
* password is created here — the password is chosen in `verifyEmail` by
|
|
109
|
+
* whoever opens the mailbox.
|
|
106
110
|
*/
|
|
107
111
|
passwordSignUp(email, name) {
|
|
108
112
|
return this.json("POST", "/v1/auth/password/sign-up", {
|
|
@@ -113,38 +117,40 @@ export class Client {
|
|
|
113
117
|
passwordSignIn(email, password) {
|
|
114
118
|
return this.json("POST", "/v1/auth/password/sign-in", { email, password });
|
|
115
119
|
}
|
|
116
|
-
/**
|
|
120
|
+
/** The same accepted response comes back whether or not the email exists. */
|
|
117
121
|
passwordForgot(email) {
|
|
118
122
|
return this.json("POST", "/v1/auth/password/forgot", { email });
|
|
119
123
|
}
|
|
120
124
|
passwordReset(token, password) {
|
|
121
125
|
return this.json("POST", "/v1/auth/password/reset", { token, password });
|
|
122
126
|
}
|
|
123
|
-
/**
|
|
127
|
+
/** Uses the confirmation link and sets the account's first password. */
|
|
124
128
|
verifyEmail(token, password) {
|
|
125
129
|
return this.json("POST", "/v1/auth/email/verify", { token, password });
|
|
126
130
|
}
|
|
127
131
|
/**
|
|
128
|
-
* GitHub OAuth
|
|
129
|
-
* `window.location`
|
|
132
|
+
* The GitHub OAuth start URL. It is a 302 the browser follows, so it goes into
|
|
133
|
+
* `window.location` rather than being called with fetch. `redirect` has to be
|
|
134
|
+
* on the server's allow-list.
|
|
130
135
|
*/
|
|
131
136
|
githubStartUrl(redirect) {
|
|
132
137
|
const u = new URL("/v1/auth/github/start", this.opts.baseUrl);
|
|
133
138
|
u.searchParams.set("redirect", redirect);
|
|
134
139
|
return u.toString();
|
|
135
140
|
}
|
|
136
|
-
/** Google OAuth
|
|
141
|
+
/** The Google OAuth start URL. Uses the same dashboard callback shape as GitHub. */
|
|
137
142
|
googleStartUrl(redirect) {
|
|
138
143
|
const u = new URL("/v1/auth/google/start", this.opts.baseUrl);
|
|
139
144
|
u.searchParams.set("redirect", redirect);
|
|
140
145
|
return u.toString();
|
|
141
146
|
}
|
|
142
147
|
/**
|
|
143
|
-
*
|
|
148
|
+
* The names of this deployment (app domain, docs address). Callable without auth.
|
|
144
149
|
*
|
|
145
|
-
*
|
|
146
|
-
* §3.2) —
|
|
147
|
-
*
|
|
150
|
+
* This exists so the dashboard does not hard-code a domain into the screen
|
|
151
|
+
* (docs/environments.md §3.2) — hard-coded, the dev screen tells people a
|
|
152
|
+
* production address, and because that address opens, nobody ever says it is
|
|
153
|
+
* wrong.
|
|
148
154
|
*/
|
|
149
155
|
siteConfig() {
|
|
150
156
|
return this.json("GET", "/v1/config");
|
|
@@ -155,21 +161,22 @@ export class Client {
|
|
|
155
161
|
listOrgs() {
|
|
156
162
|
return this.json("GET", "/v1/orgs");
|
|
157
163
|
}
|
|
158
|
-
/**
|
|
164
|
+
/** Changes the display name only (admin). The slug is the name that becomes an address, so it is not on this surface. */
|
|
159
165
|
updateOrg(org, name) {
|
|
160
166
|
return this.json("PATCH", `/v1/orgs/${encodeURIComponent(org)}`, { name });
|
|
161
167
|
}
|
|
162
168
|
/**
|
|
163
|
-
*
|
|
169
|
+
* Deletes the organization (admin). **This cannot be undone.**
|
|
164
170
|
*
|
|
165
|
-
* `confirm`
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
* `details.holdings`
|
|
169
|
-
*
|
|
171
|
+
* `confirm` has to be the organization slug exactly — the confirmation lives on
|
|
172
|
+
* the server because three places call this surface (the screen, the CLI, and
|
|
173
|
+
* scripts). If a live project, domain, repository, or DNS zone is left, it is a
|
|
174
|
+
* 409 `org_not_empty` and `details.holdings` says what is left — **projects
|
|
175
|
+
* already deleted are not counted.**
|
|
170
176
|
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
177
|
+
* The `purgeAfter` in the answer is when the rows actually disappear. By the
|
|
178
|
+
* moment it returns the organization is already gone from every surface; what
|
|
179
|
+
* remains is retention until that time.
|
|
173
180
|
*/
|
|
174
181
|
deleteOrg(org, confirm) {
|
|
175
182
|
return this.json("DELETE", `/v1/orgs/${encodeURIComponent(org)}`, { confirm });
|
|
@@ -178,38 +185,40 @@ export class Client {
|
|
|
178
185
|
return this.json("POST", "/v1/orgs", { slug, name });
|
|
179
186
|
}
|
|
180
187
|
/**
|
|
181
|
-
*
|
|
188
|
+
* The organization's recent notifications (docs/limits.md §3.4).
|
|
182
189
|
*
|
|
183
|
-
* `sentAt`
|
|
184
|
-
*
|
|
190
|
+
* A row with no `sentAt` is either still queued or has no email address to
|
|
191
|
+
* reach — and for the second, this list is the only channel, so the screen
|
|
192
|
+
* draws both states the same way.
|
|
185
193
|
*/
|
|
186
194
|
listNotifications(org, limit) {
|
|
187
195
|
const q = limit === undefined ? "" : `?limit=${limit}`;
|
|
188
196
|
return this.json("GET", `/v1/orgs/${encodeURIComponent(org)}/notifications${q}`);
|
|
189
197
|
}
|
|
190
198
|
/**
|
|
191
|
-
*
|
|
192
|
-
*
|
|
199
|
+
* The plan and billing state (docs/billing.md). If `enabled` is false, this
|
|
200
|
+
* deployment has no billing — the screen shows only a contact address.
|
|
193
201
|
*/
|
|
194
202
|
getBilling(org) {
|
|
195
203
|
return this.json("GET", `/v1/orgs/${encodeURIComponent(org)}/billing`);
|
|
196
204
|
}
|
|
197
205
|
/**
|
|
198
|
-
*
|
|
199
|
-
*
|
|
206
|
+
* A Pro checkout session. Send the browser to the `url` that comes back. Even
|
|
207
|
+
* once payment finishes, the thing that changes the plan is the webhook, so
|
|
208
|
+
* read `getBilling` again after the browser returns.
|
|
200
209
|
*/
|
|
201
210
|
createBillingCheckout(org, returnTo) {
|
|
202
211
|
return this.json("POST", `/v1/orgs/${encodeURIComponent(org)}/billing/checkout`, { returnTo });
|
|
203
212
|
}
|
|
204
|
-
/**
|
|
213
|
+
/** A one-time `url` for the customer portal (payment method, receipts, cancellation). */
|
|
205
214
|
createBillingPortal(org, returnTo) {
|
|
206
215
|
return this.json("POST", `/v1/orgs/${encodeURIComponent(org)}/billing/portal`, { returnTo });
|
|
207
216
|
}
|
|
208
|
-
/**
|
|
217
|
+
/** The customer session token for the payment-method embed (1 hour). An org with no payment history gets a 404. */
|
|
209
218
|
createBillingSession(org) {
|
|
210
219
|
return this.json("POST", `/v1/orgs/${encodeURIComponent(org)}/billing/session`, {});
|
|
211
220
|
}
|
|
212
|
-
/**
|
|
221
|
+
/** Schedules cancellation at the end of the period (`cancel: true`), or undoes it. There is no immediate revocation. */
|
|
213
222
|
cancelBilling(org, body) {
|
|
214
223
|
return this.json("POST", `/v1/orgs/${encodeURIComponent(org)}/billing/cancel`, body);
|
|
215
224
|
}
|
|
@@ -223,35 +232,37 @@ export class Client {
|
|
|
223
232
|
return this.json("GET", `/v1/orgs/${encodeURIComponent(org)}/projects/${encodeURIComponent(name)}`);
|
|
224
233
|
}
|
|
225
234
|
/**
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
235
|
+
* Renames the project (admin). **The default address moves with it and there is
|
|
236
|
+
* no alias** — which is why the response carries the new address. Unlike the
|
|
237
|
+
* org slug, this name can change because it is the inner label of the org
|
|
238
|
+
* wildcard certificate.
|
|
229
239
|
*/
|
|
230
240
|
updateProject(org, name, next) {
|
|
231
241
|
return this.json("PATCH", this.projectPath(org, name), { name: next });
|
|
232
242
|
}
|
|
233
243
|
/**
|
|
234
|
-
*
|
|
235
|
-
*
|
|
244
|
+
* Starts tearing the project down (admin). **This cannot be undone and leaves
|
|
245
|
+
* no recovery generation.** `confirm` has to be the project name exactly.
|
|
236
246
|
*/
|
|
237
247
|
deleteProject(org, name, confirm) {
|
|
238
248
|
return this.json("DELETE", this.projectPath(org, name), { confirm });
|
|
239
249
|
}
|
|
240
|
-
/**
|
|
250
|
+
/** Watches the teardown operation to its end even after the project is gone. */
|
|
241
251
|
getOrgOperation(org, opId) {
|
|
242
252
|
return this.json("GET", `/v1/orgs/${encodeURIComponent(org)}/operations/${encodeURIComponent(opId)}`);
|
|
243
253
|
}
|
|
244
|
-
/**
|
|
254
|
+
/** Everything the project's first screen takes in one call. Usage calls `getUsage` alongside it. */
|
|
245
255
|
getProjectOverview(org, name) {
|
|
246
256
|
return this.json("GET", `${this.projectPath(org, name)}/overview`);
|
|
247
257
|
}
|
|
248
258
|
listDeployments(org, name) {
|
|
249
259
|
return this.json("GET", `/v1/orgs/${encodeURIComponent(org)}/projects/${encodeURIComponent(name)}/deployments`);
|
|
250
260
|
}
|
|
251
|
-
/**
|
|
261
|
+
/** The body is the bundle. One digest covers all of the code and the assets. */
|
|
252
262
|
/**
|
|
253
|
-
*
|
|
254
|
-
* createDeployment) —
|
|
263
|
+
* Uploads the bundle. `headers` carries the provenance headers
|
|
264
|
+
* (`X-Runlot-Git-*`, openapi createDeployment) — the body is the bundle
|
|
265
|
+
* itself, so the metadata can only travel in headers.
|
|
255
266
|
*/
|
|
256
267
|
deploy(org, name, bundle, headers = {}) {
|
|
257
268
|
return this.request("POST", `/v1/orgs/${encodeURIComponent(org)}/projects/${encodeURIComponent(name)}/deployments`, {
|
|
@@ -259,7 +270,7 @@ export class Client {
|
|
|
259
270
|
headers: { ...headers, "Content-Type": "application/gzip" },
|
|
260
271
|
});
|
|
261
272
|
}
|
|
262
|
-
/**
|
|
273
|
+
/** Idempotent. Called twice, the password is not rolled and the same connection details come back. */
|
|
263
274
|
createDatabase(org, name) {
|
|
264
275
|
return this.json("POST", `/v1/orgs/${encodeURIComponent(org)}/projects/${encodeURIComponent(name)}/database`);
|
|
265
276
|
}
|
|
@@ -267,17 +278,18 @@ export class Client {
|
|
|
267
278
|
return this.json("GET", `/v1/orgs/${encodeURIComponent(org)}/projects/${encodeURIComponent(name)}/database`);
|
|
268
279
|
}
|
|
269
280
|
/**
|
|
270
|
-
*
|
|
271
|
-
* viewer
|
|
281
|
+
* The connection credentials as well. The route is split from `getDatabase`
|
|
282
|
+
* because of roles — a viewer may know whether there is a database, but not
|
|
283
|
+
* the password (proto/openapi.yaml).
|
|
272
284
|
*/
|
|
273
285
|
getDatabaseConnect(org, name) {
|
|
274
286
|
return this.json("GET", `/v1/orgs/${encodeURIComponent(org)}/projects/${encodeURIComponent(name)}/database/connect`);
|
|
275
287
|
}
|
|
276
|
-
/**
|
|
288
|
+
/** The endpoint only — no credentials. A viewer gets it too. Used by `runlot port-forward`. */
|
|
277
289
|
getDatabaseEndpoint(org, name) {
|
|
278
290
|
return this.json("GET", `${dbPath(org, name)}/endpoint`);
|
|
279
291
|
}
|
|
280
|
-
/**
|
|
292
|
+
/** Issues a short-lived machine credential. The password is carried in this response and nowhere else. */
|
|
281
293
|
createDatabaseToken(org, name, ttlSeconds) {
|
|
282
294
|
return this.json("POST", `${dbPath(org, name)}/tokens`, ttlSeconds === undefined ? {} : { ttlSeconds });
|
|
283
295
|
}
|
|
@@ -288,61 +300,65 @@ export class Client {
|
|
|
288
300
|
return this.json("DELETE", `${dbPath(org, name)}/tokens/${encodeURIComponent(user)}`);
|
|
289
301
|
}
|
|
290
302
|
/**
|
|
291
|
-
*
|
|
303
|
+
* Makes one diagnostic image.
|
|
292
304
|
*
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
* (proto/openapi.yaml).
|
|
305
|
+
* **Not idempotent** — every call makes another image. And it is slow: the
|
|
306
|
+
* response comes after the dump finishes, and that project's actor is held
|
|
307
|
+
* for the whole of it (proto/openapi.yaml).
|
|
296
308
|
*/
|
|
297
309
|
exportDatabase(org, name) {
|
|
298
310
|
return this.json("POST", `/v1/orgs/${encodeURIComponent(org)}/projects/${encodeURIComponent(name)}/database/export`);
|
|
299
311
|
}
|
|
300
312
|
/**
|
|
301
|
-
*
|
|
302
|
-
*
|
|
313
|
+
* Every generation a restore can choose, newest first. `lastCheckedAt` is the
|
|
314
|
+
* heartbeat of the no-change skip — it is what tells "no new generations"
|
|
315
|
+
* apart from "the backup is dead".
|
|
303
316
|
*/
|
|
304
317
|
listGenerations(org, name) {
|
|
305
318
|
return this.json("GET", `${this.dbPath(org, name)}/generations`);
|
|
306
319
|
}
|
|
307
320
|
/**
|
|
308
|
-
*
|
|
321
|
+
* Rolls back to a generation. **Writes made since then are lost.**
|
|
309
322
|
*
|
|
310
|
-
*
|
|
311
|
-
* `getDatabaseOperation
|
|
312
|
-
*
|
|
323
|
+
* It ends at 202 — what comes back is an operation id, and progress is read
|
|
324
|
+
* through `getDatabaseOperation`. The server does not resolve `latest`: the
|
|
325
|
+
* `(epoch, seq)` chosen from the list is carried as-is (proto/openapi.yaml).
|
|
313
326
|
*/
|
|
314
327
|
restoreDatabase(org, name, epoch, seq) {
|
|
315
328
|
return this.json("POST", `${this.dbPath(org, name)}/restore`, { epoch, seq });
|
|
316
329
|
}
|
|
317
330
|
/**
|
|
318
|
-
*
|
|
319
|
-
* `
|
|
320
|
-
*
|
|
321
|
-
*
|
|
331
|
+
* Rolls back to a restore point **inside** a backup (pg-feather docs/37):
|
|
332
|
+
* `txnId` is that commit, `at` is the last commit at or before that time. One
|
|
333
|
+
* backup carries the commits in between in order, so the place you can roll
|
|
334
|
+
* back to is not tied to a backup boundary. Everything else is as in
|
|
335
|
+
* restoreDatabase — 202, an operation id, and one safety backup before the
|
|
336
|
+
* rollback.
|
|
322
337
|
*/
|
|
323
338
|
restoreDatabaseTo(org, name, target) {
|
|
324
339
|
return this.json("POST", `${this.dbPath(org, name)}/restore`, target);
|
|
325
340
|
}
|
|
326
341
|
/**
|
|
327
|
-
*
|
|
328
|
-
*
|
|
342
|
+
* Removes the database. `confirm` has to match the project name **exactly**;
|
|
343
|
+
* if it does not, the server refuses with 400 `confirm_mismatch`.
|
|
329
344
|
*/
|
|
330
345
|
deleteDatabase(org, name, confirm) {
|
|
331
346
|
return this.json("POST", `${this.dbPath(org, name)}/delete`, { confirm });
|
|
332
347
|
}
|
|
333
|
-
/**
|
|
348
|
+
/** The progress of one operation. The operation id is not a credential; it lives under the project path. */
|
|
334
349
|
getDatabaseOperation(org, name, opId) {
|
|
335
350
|
return this.json("GET", `${this.dbPath(org, name)}/operations/${encodeURIComponent(opId)}`);
|
|
336
351
|
}
|
|
337
|
-
/**
|
|
352
|
+
/** Aborts an operation that has not yet reached a phase it cannot come back from. */
|
|
338
353
|
abortDatabaseOperation(org, name, opId) {
|
|
339
354
|
return this.json("POST", `${this.dbPath(org, name)}/operations/${encodeURIComponent(opId)}/abort`);
|
|
340
355
|
}
|
|
341
356
|
// ---- Phase 6 (docs/phase6.md) ----
|
|
342
357
|
/**
|
|
343
|
-
*
|
|
344
|
-
*
|
|
345
|
-
*
|
|
358
|
+
* A live tail of the logs. It is **a stream, not JSON**, so it does not go
|
|
359
|
+
* through `request` — what comes back is an async iterable of line-by-line
|
|
360
|
+
* `LogEvent`, and aborting `signal` closes the connection. An error response
|
|
361
|
+
* (4xx/5xx) is the same `ApiError` as in every other method.
|
|
346
362
|
*/
|
|
347
363
|
async *tailLogs(org, name, opts = {}) {
|
|
348
364
|
const q = new URLSearchParams();
|
|
@@ -362,7 +378,7 @@ export class Client {
|
|
|
362
378
|
body = (await res.json());
|
|
363
379
|
}
|
|
364
380
|
catch {
|
|
365
|
-
//
|
|
381
|
+
// The same reason as in request above.
|
|
366
382
|
}
|
|
367
383
|
throw new ApiError(res.status, body, `GET ${path} → ${res.status}`);
|
|
368
384
|
}
|
|
@@ -390,14 +406,14 @@ export class Client {
|
|
|
390
406
|
yield JSON.parse(rest);
|
|
391
407
|
}
|
|
392
408
|
finally {
|
|
393
|
-
//
|
|
409
|
+
// If the consumer ends early (break), the server's follow has to end too.
|
|
394
410
|
await reader.cancel().catch(() => { });
|
|
395
411
|
}
|
|
396
412
|
}
|
|
397
413
|
listSecrets(org, name) {
|
|
398
414
|
return this.json("GET", `${this.projectPath(org, name)}/secrets`);
|
|
399
415
|
}
|
|
400
|
-
/**
|
|
416
|
+
/** The value travels in this call only. It is in no response at all. */
|
|
401
417
|
setSecret(org, name, secret, value) {
|
|
402
418
|
return this.json("PUT", `${this.projectPath(org, name)}/secrets/${encodeURIComponent(secret)}`, { value });
|
|
403
419
|
}
|
|
@@ -408,64 +424,66 @@ export class Client {
|
|
|
408
424
|
const q = hours === undefined ? "" : `?hours=${hours}`;
|
|
409
425
|
return this.json("GET", `${this.projectPath(org, name)}/usage${q}`);
|
|
410
426
|
}
|
|
411
|
-
// ----
|
|
412
|
-
/**
|
|
427
|
+
// ---- Object storage (docs/storage.md §8) ----
|
|
428
|
+
/** Grant, prefix, quota, usage. `usedBytes` lags by the node's reporting interval. */
|
|
413
429
|
getStorage(org, name) {
|
|
414
430
|
return this.json("GET", `${this.projectPath(org, name)}/storage`);
|
|
415
431
|
}
|
|
416
432
|
/**
|
|
417
|
-
*
|
|
418
|
-
*
|
|
419
|
-
*
|
|
433
|
+
* Grants storage. It is idempotent, so pressing twice leaves the prefix as it
|
|
434
|
+
* was. Taking neither the prefix nor the quota as an argument is the point —
|
|
435
|
+
* the prefix is the isolation boundary and the quota is set by the plan
|
|
436
|
+
* (proto/openapi.yaml).
|
|
420
437
|
*/
|
|
421
438
|
grantStorage(org, name) {
|
|
422
439
|
return this.json("POST", `${this.projectPath(org, name)}/storage`);
|
|
423
440
|
}
|
|
424
|
-
/**
|
|
441
|
+
/** Removes the grant. **The objects are not deleted** (docs/storage.md §7). */
|
|
425
442
|
revokeStorage(org, name) {
|
|
426
443
|
return this.json("DELETE", `${this.projectPath(org, name)}/storage`);
|
|
427
444
|
}
|
|
428
|
-
// ----
|
|
429
|
-
/**
|
|
445
|
+
// ---- Email (docs/email.md §5) ----
|
|
446
|
+
/** Grant, address, sender, limits, recent counts. The address comes back as a preview even without a grant. */
|
|
430
447
|
getEmail(org, name) {
|
|
431
448
|
return this.json("GET", `${this.projectPath(org, name)}/email`);
|
|
432
449
|
}
|
|
433
|
-
/**
|
|
450
|
+
/** Turns it on. **Idempotent** — we choose the address. The deploy reads `"email": true` and calls this. */
|
|
434
451
|
grantEmail(org, name) {
|
|
435
452
|
return this.json("POST", `${this.projectPath(org, name)}/email`);
|
|
436
453
|
}
|
|
437
|
-
/**
|
|
454
|
+
/** Turns it off. Mail arriving at that address now gets a 550. */
|
|
438
455
|
revokeEmail(org, name) {
|
|
439
456
|
return this.json("DELETE", `${this.projectPath(org, name)}/email`);
|
|
440
457
|
}
|
|
441
458
|
// ---- AI (docs/ai.md §6) ----
|
|
442
459
|
/**
|
|
443
|
-
*
|
|
444
|
-
*
|
|
460
|
+
* The model catalogue. **Being logged in is enough to see it** — what you can
|
|
461
|
+
* buy is not an organization's secret, and this list is the price table.
|
|
445
462
|
*
|
|
446
|
-
*
|
|
447
|
-
*
|
|
463
|
+
* Prices are integer **micro-cents** per million tokens (1 µ¢ = 1e-8 USD).
|
|
464
|
+
* Not letting the server pick the decimal place is the point — a small call
|
|
465
|
+
* rounded to cents becomes 0.
|
|
448
466
|
*/
|
|
449
467
|
listAIModels() {
|
|
450
468
|
return this.json("GET", "/v1/ai/models");
|
|
451
469
|
}
|
|
452
|
-
/**
|
|
470
|
+
/** Grant, limits, the org balance, 24 h usage. The limits come back as a preview even without a grant. */
|
|
453
471
|
getAI(org, name) {
|
|
454
472
|
return this.json("GET", `${this.projectPath(org, name)}/ai`);
|
|
455
473
|
}
|
|
456
|
-
/**
|
|
474
|
+
/** Turns it on. **Idempotent.** The deploy reads `"ai": true` and calls this. */
|
|
457
475
|
grantAI(org, name) {
|
|
458
476
|
return this.json("POST", `${this.projectPath(org, name)}/ai`);
|
|
459
477
|
}
|
|
460
|
-
/**
|
|
478
|
+
/** Turns it off. **The ledger and the balance are untouched** — the balance belongs to the org and the grant belongs to the project. */
|
|
461
479
|
revokeAI(org, name) {
|
|
462
480
|
return this.json("DELETE", `${this.projectPath(org, name)}/ai`);
|
|
463
481
|
}
|
|
464
|
-
/** org
|
|
482
|
+
/** The org's credit balance and its automatic top-up settings. The balance is in micro-cents and can be negative. */
|
|
465
483
|
getAICredit(org) {
|
|
466
484
|
return this.json("GET", `/v1/orgs/${encodeURIComponent(org)}/ai/credit`);
|
|
467
485
|
}
|
|
468
|
-
/** org
|
|
486
|
+
/** One page of the org ledger. The cursor is `ledger_id` — cutting on time splits the same millisecond. */
|
|
469
487
|
listAILedger(org, opts = {}) {
|
|
470
488
|
const q = new URLSearchParams();
|
|
471
489
|
if (opts.limit)
|
|
@@ -476,20 +494,22 @@ export class Client {
|
|
|
476
494
|
return this.json("GET", `/v1/orgs/${encodeURIComponent(org)}/ai/ledger${qs ? "?" + qs : ""}`);
|
|
477
495
|
}
|
|
478
496
|
/**
|
|
479
|
-
*
|
|
480
|
-
*
|
|
497
|
+
* The credit top-up checkout. **A browser coming back is not evidence** — the
|
|
498
|
+
* one thing that raises the balance is the `order.paid` webhook, and the
|
|
499
|
+
* screen polls the balance after the success event.
|
|
481
500
|
*/
|
|
482
501
|
createAICreditCheckout(org, microCents, returnTo) {
|
|
483
502
|
return this.json("POST", `/v1/orgs/${encodeURIComponent(org)}/ai/credit/checkout`, { microCents, returnTo });
|
|
484
503
|
}
|
|
485
504
|
/**
|
|
486
|
-
*
|
|
487
|
-
*
|
|
505
|
+
* Turns automatic top-up on or off. **Turning it on is explicit consent**
|
|
506
|
+
* (docs/ai.md §3.5) — the screen shows the charging terms verbatim and calls
|
|
507
|
+
* this only after the box is checked.
|
|
488
508
|
*/
|
|
489
509
|
setAIAutoTopup(org, body) {
|
|
490
510
|
return this.json("PUT", `/v1/orgs/${encodeURIComponent(org)}/ai/autotopup`, body);
|
|
491
511
|
}
|
|
492
|
-
/**
|
|
512
|
+
/** One page of the mail log (docs/email.md §6.1). Newest first, cursor-paged. */
|
|
493
513
|
listMailLog(org, name, opts = {}) {
|
|
494
514
|
const q = new URLSearchParams();
|
|
495
515
|
if (opts.direction)
|
|
@@ -501,106 +521,118 @@ export class Client {
|
|
|
501
521
|
const qs = q.toString();
|
|
502
522
|
return this.json("GET", `${this.projectPath(org, name)}/email/log${qs ? "?" + qs : ""}`);
|
|
503
523
|
}
|
|
504
|
-
/**
|
|
524
|
+
/** The metadata of one message. */
|
|
505
525
|
getMailLog(org, name, mailId) {
|
|
506
526
|
return this.json("GET", `${this.projectPath(org, name)}/email/log/${encodeURIComponent(mailId)}`);
|
|
507
527
|
}
|
|
508
|
-
/**
|
|
528
|
+
/** The URL of the raw message — the screen uses it as a download link (.eml). */
|
|
509
529
|
mailLogRawURL(org, name, mailId) {
|
|
510
530
|
return new URL(`${this.projectPath(org, name)}/email/log/${encodeURIComponent(mailId)}/raw`, this.opts.baseUrl).toString();
|
|
511
531
|
}
|
|
512
|
-
/**
|
|
532
|
+
/** The raw text. What the detail view draws the headers and body from. */
|
|
513
533
|
getMailLogRaw(org, name, mailId) {
|
|
514
534
|
return this.text("GET", `${this.projectPath(org, name)}/email/log/${encodeURIComponent(mailId)}/raw`);
|
|
515
535
|
}
|
|
516
|
-
// ----
|
|
536
|
+
// ---- End-user authentication (docs/auth.md §9) ----
|
|
517
537
|
/**
|
|
518
|
-
*
|
|
519
|
-
* DB
|
|
538
|
+
* Grant, settings, providers, hosts. **The signed-up users do not come** —
|
|
539
|
+
* that table is inside the project DB (docs/auth.md §6) and only the worker's
|
|
540
|
+
* `env.auth` sees it.
|
|
520
541
|
*
|
|
521
|
-
* `providers`
|
|
522
|
-
*
|
|
542
|
+
* `providers` carries the client id only. The client secret is sealed and
|
|
543
|
+
* appears in no response.
|
|
523
544
|
*/
|
|
524
545
|
getAuth(org, name) {
|
|
525
546
|
return this.json("GET", `${this.projectPath(org, name)}/auth`);
|
|
526
547
|
}
|
|
527
548
|
/**
|
|
528
|
-
*
|
|
549
|
+
* Turns auth on (member). Idempotent.
|
|
529
550
|
*
|
|
530
|
-
*
|
|
531
|
-
*
|
|
532
|
-
*
|
|
551
|
+
* **With no database it is 409 `database_required`** — the user table lives
|
|
552
|
+
* inside the project DB, so standing up the grant alone leaves no table the
|
|
553
|
+
* moment the worker starts. The caller catches that code and points at the
|
|
554
|
+
* `"database": true` declaration — the deploy never sees this code, because
|
|
555
|
+
* its ordering rules it out.
|
|
533
556
|
*/
|
|
534
557
|
grantAuth(org, name) {
|
|
535
558
|
return this.json("POST", `${this.projectPath(org, name)}/auth`);
|
|
536
559
|
}
|
|
537
560
|
/**
|
|
538
|
-
*
|
|
539
|
-
*
|
|
540
|
-
*
|
|
561
|
+
* Changes the settings (member). **A partial update** — only the fields given
|
|
562
|
+
* change. Sending the whole thing on one switch means that when two people
|
|
563
|
+
* touch the same card, the later save overwrites the earlier one. With no
|
|
564
|
+
* grant it is 404 `not_granted`.
|
|
541
565
|
*/
|
|
542
566
|
updateAuthSettings(org, name, settings) {
|
|
543
567
|
return this.json("PATCH", `${this.projectPath(org, name)}/auth`, { settings });
|
|
544
568
|
}
|
|
545
569
|
/**
|
|
546
|
-
*
|
|
547
|
-
*
|
|
570
|
+
* Turns auth off (admin). **The signed-up users are not deleted** — the
|
|
571
|
+
* `runlot_auth` tables stay in the project DB, and turning it back on lets the
|
|
572
|
+
* same people log in unchanged.
|
|
548
573
|
*/
|
|
549
574
|
revokeAuth(org, name) {
|
|
550
575
|
return this.json("DELETE", `${this.projectPath(org, name)}/auth`);
|
|
551
576
|
}
|
|
552
577
|
/**
|
|
553
|
-
*
|
|
578
|
+
* Registers or replaces a social provider (admin). It is the same PUT, so it
|
|
579
|
+
* is idempotent.
|
|
554
580
|
*
|
|
555
|
-
* **`clientSecret`
|
|
556
|
-
* `https://<host>/__runlot/auth/callback/<provider>`
|
|
557
|
-
*
|
|
581
|
+
* **`clientSecret` cannot be read back.** The callback URL is
|
|
582
|
+
* `https://<host>/__runlot/auth/callback/<provider>` for each host, and the
|
|
583
|
+
* list of hosts is in the response's `hosts`.
|
|
558
584
|
*/
|
|
559
585
|
setAuthProvider(org, name, provider, clientId, clientSecret) {
|
|
560
586
|
return this.json("PUT", `${this.projectPath(org, name)}/auth/providers/${encodeURIComponent(provider)}`, { clientId, clientSecret });
|
|
561
587
|
}
|
|
562
588
|
/**
|
|
563
|
-
*
|
|
564
|
-
*
|
|
589
|
+
* Removes a provider (admin). Someone who signed up only through that provider
|
|
590
|
+
* loses their way in, but the user row stays — register it again and they come
|
|
591
|
+
* back to the same account.
|
|
565
592
|
*/
|
|
566
593
|
deleteAuthProvider(org, name, provider) {
|
|
567
594
|
return this.json("DELETE", `${this.projectPath(org, name)}/auth/providers/${encodeURIComponent(provider)}`);
|
|
568
595
|
}
|
|
569
|
-
// ----
|
|
596
|
+
// ---- Deployment access control (docs/access.md §7) ----
|
|
570
597
|
/**
|
|
571
|
-
*
|
|
572
|
-
*
|
|
598
|
+
* Who can reach this deployment. **No values** — both the password and the
|
|
599
|
+
* bypass secret live only as hashes, and what comes back here is the fact that
|
|
600
|
+
* there is one: `hasPassword` and `hasBypass`.
|
|
573
601
|
*
|
|
574
|
-
*
|
|
575
|
-
*
|
|
602
|
+
* A project that has never set a policy is `mode: "public"` with no
|
|
603
|
+
* `updatedAt`. The screen tells those two apart for the reason in §7 — a
|
|
604
|
+
* sentence about the state instead of an empty state.
|
|
576
605
|
*/
|
|
577
606
|
getAccess(org, name) {
|
|
578
607
|
return this.json("GET", `${this.projectPath(org, name)}/access`);
|
|
579
608
|
}
|
|
580
609
|
/**
|
|
581
|
-
*
|
|
582
|
-
*
|
|
610
|
+
* Changes the policy (admin). Without a `password`, **the existing password
|
|
611
|
+
* stays** — if a save that only changes the mode wiped the password, coming
|
|
612
|
+
* back to that mode would mean setting it again.
|
|
583
613
|
*/
|
|
584
614
|
setAccess(org, name, mode, password) {
|
|
585
615
|
return this.json("PUT", `${this.projectPath(org, name)}/access`, { mode, password });
|
|
586
616
|
}
|
|
587
617
|
/**
|
|
588
|
-
*
|
|
589
|
-
*
|
|
618
|
+
* Makes a bypass secret for automation (admin). **The value is in this answer
|
|
619
|
+
* and nowhere else** — the screen shows it once on arrival and can never fetch
|
|
620
|
+
* it again. Calling again kills the old value.
|
|
590
621
|
*/
|
|
591
622
|
newAccessBypass(org, name) {
|
|
592
623
|
return this.json("POST", `${this.projectPath(org, name)}/access/bypass`);
|
|
593
624
|
}
|
|
594
|
-
/**
|
|
625
|
+
/** Removes the bypass (admin). Removing one that does not exist also succeeds. */
|
|
595
626
|
revokeAccessBypass(org, name) {
|
|
596
627
|
return this.json("DELETE", `${this.projectPath(org, name)}/access/bypass`);
|
|
597
628
|
}
|
|
598
629
|
/**
|
|
599
|
-
*
|
|
630
|
+
* Gets a one-time address back into a protected deployment (docs/access.md §3.4-3).
|
|
600
631
|
*
|
|
601
|
-
*
|
|
602
|
-
* (
|
|
603
|
-
*
|
|
632
|
+
* It is **not** under the project path: all the calling screen knows is the
|
|
633
|
+
* hostname (the original request went there), and the server derives the org
|
|
634
|
+
* and project names from it. Doing `location.replace` with the `redirect` that
|
|
635
|
+
* comes back is the caller's job.
|
|
604
636
|
*/
|
|
605
637
|
authorizeAccess(to, next) {
|
|
606
638
|
return this.json("POST", "/v1/access/authorize", { to, next });
|
|
@@ -608,7 +640,7 @@ export class Client {
|
|
|
608
640
|
listMembers(org) {
|
|
609
641
|
return this.json("GET", `/v1/orgs/${encodeURIComponent(org)}/members`);
|
|
610
642
|
}
|
|
611
|
-
/**
|
|
643
|
+
/** This is not an invite — the user must already have logged in once (404 `user_not_found`). */
|
|
612
644
|
addMember(org, provider, subject, role) {
|
|
613
645
|
return this.json("POST", `/v1/orgs/${encodeURIComponent(org)}/members`, { provider, subject, role });
|
|
614
646
|
}
|
|
@@ -616,67 +648,71 @@ export class Client {
|
|
|
616
648
|
return this.json("PUT", `/v1/orgs/${encodeURIComponent(org)}/members/${encodeURIComponent(userId)}`, { role });
|
|
617
649
|
}
|
|
618
650
|
/**
|
|
619
|
-
*
|
|
620
|
-
* (docs/members.md).
|
|
651
|
+
* Removing someone else is admin; **removing yourself is leaving**, which
|
|
652
|
+
* anyone can do (docs/members.md). The last admin can do neither
|
|
653
|
+
* (409 `last_admin`).
|
|
621
654
|
*/
|
|
622
655
|
removeMember(org, userId) {
|
|
623
656
|
return this.json("DELETE", `/v1/orgs/${encodeURIComponent(org)}/members/${encodeURIComponent(userId)}`);
|
|
624
657
|
}
|
|
625
|
-
// ---
|
|
626
|
-
/**
|
|
658
|
+
// --- Invites (docs/members.md) ---
|
|
659
|
+
/** Pending and expired invites (admin). Accepted and revoked ones are history, so they do not come. */
|
|
627
660
|
listInvites(org) {
|
|
628
661
|
return this.json("GET", `/v1/orgs/${encodeURIComponent(org)}/invites`);
|
|
629
662
|
}
|
|
630
663
|
/**
|
|
631
|
-
*
|
|
632
|
-
*
|
|
664
|
+
* Invites by email (admin). **Calling again with the same address is a
|
|
665
|
+
* resend** — the same row is updated rather than a new one created, and the
|
|
666
|
+
* server gives 200 instead of 201.
|
|
633
667
|
*/
|
|
634
668
|
createInvite(org, email, role) {
|
|
635
669
|
return this.json("POST", `/v1/orgs/${encodeURIComponent(org)}/invites`, { email, role });
|
|
636
670
|
}
|
|
637
|
-
/**
|
|
671
|
+
/** Revokes an invite (admin). Mail that has not gone out yet does not go out. */
|
|
638
672
|
revokeInvite(org, inviteId) {
|
|
639
673
|
return this.json("DELETE", `/v1/orgs/${encodeURIComponent(org)}/invites/${encodeURIComponent(inviteId)}`);
|
|
640
674
|
}
|
|
641
675
|
/**
|
|
642
|
-
*
|
|
643
|
-
*
|
|
676
|
+
* A preview of the invite. **No session needed** — whoever got the link has
|
|
677
|
+
* not logged in yet, and they need to know what they are accepting before
|
|
678
|
+
* they do.
|
|
644
679
|
*/
|
|
645
680
|
getInvite(token) {
|
|
646
681
|
return this.json("GET", `/v1/invites/${encodeURIComponent(token)}`);
|
|
647
682
|
}
|
|
648
|
-
/**
|
|
683
|
+
/** Accepts (login required). If they are already a member the role does not change. */
|
|
649
684
|
acceptInvite(token) {
|
|
650
685
|
return this.json("POST", `/v1/invites/${encodeURIComponent(token)}/accept`);
|
|
651
686
|
}
|
|
652
687
|
// --- domains: D1 hostnames methods ---
|
|
653
|
-
/**
|
|
688
|
+
/** The default hostname and the custom domains come together (viewer). */
|
|
654
689
|
listHostnames(org, name) {
|
|
655
690
|
return this.json("GET", `${this.projectPath(org, name)}/hostnames`);
|
|
656
691
|
}
|
|
657
692
|
/**
|
|
658
|
-
*
|
|
693
|
+
* Attaches a custom domain (member).
|
|
659
694
|
*
|
|
660
|
-
*
|
|
661
|
-
*
|
|
695
|
+
* Idempotent — sending the same name again returns the existing row
|
|
696
|
+
* unchanged. The response's `verify` is the CNAME and TXT the customer has to
|
|
697
|
+
* add.
|
|
662
698
|
*/
|
|
663
699
|
addHostname(org, name, hostname) {
|
|
664
700
|
return this.json("POST", `${this.projectPath(org, name)}/hostnames`, { hostname });
|
|
665
701
|
}
|
|
666
|
-
/**
|
|
702
|
+
/** Runs the same check the background verifier makes, right now (member). */
|
|
667
703
|
verifyHostname(org, name, host) {
|
|
668
704
|
return this.json("POST", `${this.projectPath(org, name)}/hostnames/${encodeURIComponent(host)}/verify`);
|
|
669
705
|
}
|
|
670
|
-
/**
|
|
706
|
+
/** Detaches a custom domain (member). The default hostname gives a 400. */
|
|
671
707
|
deleteHostname(org, name, host) {
|
|
672
708
|
return this.json("DELETE", `${this.projectPath(org, name)}/hostnames/${encodeURIComponent(host)}`);
|
|
673
709
|
}
|
|
674
710
|
// --- domains: D2 dns methods ---
|
|
675
|
-
/**
|
|
711
|
+
/** The organization's zones. No `rrsets` — those ride only on a single-zone lookup. */
|
|
676
712
|
listZones(org) {
|
|
677
713
|
return this.json("GET", this.dnsPath(org));
|
|
678
714
|
}
|
|
679
|
-
/**
|
|
715
|
+
/** Idempotent. A zone that already exists comes back unchanged. */
|
|
680
716
|
createZone(org, name) {
|
|
681
717
|
return this.json("POST", this.dnsPath(org), { name });
|
|
682
718
|
}
|
|
@@ -687,8 +723,9 @@ export class Client {
|
|
|
687
723
|
return this.json("DELETE", `${this.dnsPath(org)}/${encodeURIComponent(zone)}`);
|
|
688
724
|
}
|
|
689
725
|
/**
|
|
690
|
-
*
|
|
691
|
-
*
|
|
726
|
+
* Replaces one rrset **whole**. `records` is the complete new set for that
|
|
727
|
+
* (name, type), so adding a single value means sending the current ones with
|
|
728
|
+
* it.
|
|
692
729
|
*/
|
|
693
730
|
putRRset(org, zone, name, type, ttl, records) {
|
|
694
731
|
return this.json("PUT", this.rrsetPath(org, zone, name, type), { ttl, records });
|
|
@@ -704,39 +741,40 @@ export class Client {
|
|
|
704
741
|
setZoneDS(org, zone, ds) {
|
|
705
742
|
return this.json("PUT", `${this.dnsPath(org)}/${encodeURIComponent(zone)}/dnssec/ds`, { ds });
|
|
706
743
|
}
|
|
707
|
-
/**
|
|
744
|
+
/** The shared prefix of the DNS surface. */
|
|
708
745
|
dnsPath(org) {
|
|
709
746
|
return `/v1/orgs/${encodeURIComponent(org)}/dns/zones`;
|
|
710
747
|
}
|
|
711
|
-
/** `@`
|
|
748
|
+
/** Both `@` and `*` arrive as a name. Keeps the same string out of four places. */
|
|
712
749
|
rrsetPath(org, zone, name, type) {
|
|
713
750
|
return (`${this.dnsPath(org)}/${encodeURIComponent(zone)}` +
|
|
714
751
|
`/rrsets/${encodeURIComponent(name)}/${encodeURIComponent(type)}`);
|
|
715
752
|
}
|
|
716
753
|
// --- domains: D3 domains methods ---
|
|
717
754
|
/**
|
|
718
|
-
*
|
|
755
|
+
* Looks a name up (member or above).
|
|
719
756
|
*
|
|
720
|
-
* `priceCents`
|
|
721
|
-
*
|
|
722
|
-
*
|
|
757
|
+
* `priceCents` is the first year of registration; `renewalCents` is one year
|
|
758
|
+
* of renewal. They are separate because a first-year discount is common —
|
|
759
|
+
* folded into one, the screen cannot say "buy it for $1, then $15 next year".
|
|
723
760
|
*/
|
|
724
761
|
checkDomains(org, names) {
|
|
725
762
|
return this.json("POST", `${this.domainsPath(org)}/check`, { names });
|
|
726
763
|
}
|
|
727
764
|
/**
|
|
728
|
-
*
|
|
765
|
+
* Buys a domain (admin — this spends money).
|
|
729
766
|
*
|
|
730
|
-
* **ICANN
|
|
731
|
-
*
|
|
767
|
+
* **The ICANN verification mail goes to `contact.email`.** The screen has to
|
|
768
|
+
* say so before the purchase (docs/domains.md §5.5).
|
|
732
769
|
*
|
|
733
|
-
*
|
|
734
|
-
*
|
|
770
|
+
* Sending the same request again is safe — a finished domain comes back
|
|
771
|
+
* unchanged, and a purchase that stopped part way resumes from where it
|
|
772
|
+
* stopped.
|
|
735
773
|
*/
|
|
736
774
|
purchaseDomain(org, body) {
|
|
737
775
|
return this.json("POST", this.domainsPath(org), body);
|
|
738
776
|
}
|
|
739
|
-
/**
|
|
777
|
+
/** Imports a domain you already own (admin). Creates the zone only; the registrar is not called. */
|
|
740
778
|
importDomain(org, name) {
|
|
741
779
|
return this.json("POST", `${this.domainsPath(org)}/import`, { name });
|
|
742
780
|
}
|
|
@@ -750,11 +788,11 @@ export class Client {
|
|
|
750
788
|
return this.json("POST", `${this.domainPath(org, name)}/renew`, { years });
|
|
751
789
|
}
|
|
752
790
|
/**
|
|
753
|
-
*
|
|
791
|
+
* Changes only the **intent** to auto-renew.
|
|
754
792
|
*
|
|
755
|
-
*
|
|
756
|
-
*
|
|
757
|
-
*
|
|
793
|
+
* Auto-renew at the registrar stays on — a domain is an asset you lose if you
|
|
794
|
+
* are a day late, so we renew first and bill afterwards. The screen has to say
|
|
795
|
+
* this alongside.
|
|
758
796
|
*/
|
|
759
797
|
setDomainAutoRenew(org, name, enabled) {
|
|
760
798
|
return this.json("POST", `${this.domainPath(org, name)}/auto-renew`, { enabled });
|
|
@@ -762,37 +800,37 @@ export class Client {
|
|
|
762
800
|
setDomainLock(org, name, locked) {
|
|
763
801
|
return this.json("POST", `${this.domainPath(org, name)}/lock`, { locked });
|
|
764
802
|
}
|
|
765
|
-
/**
|
|
803
|
+
/** The transfer auth code. The audit records that one was issued and **not the code itself**. */
|
|
766
804
|
domainAuthCode(org, name) {
|
|
767
805
|
return this.json("POST", `${this.domainPath(org, name)}/auth-code`);
|
|
768
806
|
}
|
|
769
|
-
/**
|
|
807
|
+
/** Re-reads from the registrar right now (member). */
|
|
770
808
|
syncDomain(org, name) {
|
|
771
809
|
return this.json("POST", `${this.domainPath(org, name)}/sync`);
|
|
772
810
|
}
|
|
773
|
-
/**
|
|
811
|
+
/** The shared prefix of the domain surface. Keeps the same string out of ten places. */
|
|
774
812
|
domainsPath(org) {
|
|
775
813
|
return `/v1/orgs/${encodeURIComponent(org)}/domains`;
|
|
776
814
|
}
|
|
777
815
|
domainPath(org, name) {
|
|
778
816
|
return `${this.domainsPath(org)}/${encodeURIComponent(name)}`;
|
|
779
817
|
}
|
|
780
|
-
/**
|
|
818
|
+
/** The shared prefix of the project surface. */
|
|
781
819
|
projectPath(org, name) {
|
|
782
820
|
return `/v1/orgs/${encodeURIComponent(org)}/projects/${encodeURIComponent(name)}`;
|
|
783
821
|
}
|
|
784
|
-
/**
|
|
822
|
+
/** The shared prefix of the database surface. Keeps the same string out of five places. */
|
|
785
823
|
dbPath(org, name) {
|
|
786
824
|
return `/v1/orgs/${encodeURIComponent(org)}/projects/${encodeURIComponent(name)}/database`;
|
|
787
825
|
}
|
|
788
|
-
// ----
|
|
826
|
+
// ---- Self-hosted Git (docs/git-hosting.md §12.1, §19) ----
|
|
789
827
|
//
|
|
790
|
-
//
|
|
791
|
-
//
|
|
792
|
-
//
|
|
828
|
+
// **git does the repository work and we handle only the metadata.** That is
|
|
829
|
+
// why there is nothing like `pushRepo` here — an API that wraps git either
|
|
830
|
+
// reimplements every one of git's flags or boxes the user in.
|
|
793
831
|
//
|
|
794
|
-
//
|
|
795
|
-
//
|
|
832
|
+
// A repository **belongs to a project**. That is why every method starts with
|
|
833
|
+
// (org, project) — the same shape as the database and the secrets.
|
|
796
834
|
listRepos(org, project) {
|
|
797
835
|
return this.json("GET", this.repoPath(org, project));
|
|
798
836
|
}
|
|
@@ -808,7 +846,7 @@ export class Client {
|
|
|
808
846
|
deleteRepo(org, project, name) {
|
|
809
847
|
return this.json("DELETE", `${this.repoPath(org, project)}/${encodeURIComponent(name)}`);
|
|
810
848
|
}
|
|
811
|
-
/** ref
|
|
849
|
+
/** The ref history. The lead for recovering a commit lost to a force-push is here. */
|
|
812
850
|
listRefUpdates(org, project, name) {
|
|
813
851
|
return this.json("GET", `${this.repoPath(org, project)}/${encodeURIComponent(name)}/ref-updates`);
|
|
814
852
|
}
|
|
@@ -824,14 +862,14 @@ export class Client {
|
|
|
824
862
|
listAccessTokens() {
|
|
825
863
|
return this.json("GET", "/v1/me/tokens");
|
|
826
864
|
}
|
|
827
|
-
/**
|
|
865
|
+
/** The plaintext is carried in this response **once** and never again. */
|
|
828
866
|
createAccessToken(body) {
|
|
829
867
|
return this.json("POST", "/v1/me/tokens", body);
|
|
830
868
|
}
|
|
831
869
|
revokeAccessToken(tokenId) {
|
|
832
870
|
return this.json("DELETE", `/v1/me/tokens/${encodeURIComponent(tokenId)}`);
|
|
833
871
|
}
|
|
834
|
-
// ----
|
|
872
|
+
// ---- Browsing (docs/git-hosting.md §12.2) ----
|
|
835
873
|
getRepoRefs(org, project, name) {
|
|
836
874
|
return this.json("GET", `${this.repoPath(org, project)}/${encodeURIComponent(name)}/refs`);
|
|
837
875
|
}
|
|
@@ -850,7 +888,7 @@ export class Client {
|
|
|
850
888
|
const qs = q.size > 0 ? `?${q.toString()}` : "";
|
|
851
889
|
return this.json("GET", `${this.repoPath(org, project)}/${encodeURIComponent(name)}/commits${qs}`);
|
|
852
890
|
}
|
|
853
|
-
/**
|
|
891
|
+
/** The shared prefix of the repository surface. It sits under the project (§19). */
|
|
854
892
|
repoPath(org, project) {
|
|
855
893
|
return `${this.projectPath(org, project)}/repos`;
|
|
856
894
|
}
|