@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.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와 대시보드가 같은 것을 쓴다. 각자 fetch를 부르면 오류 해석이 조금씩
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
- // 오류 응답이 JSON이 아닌 경우(프록시가 만든 502 등)도 같은 예외로
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
- // 엣지가 5xx 의 본문을 자기 HTML 로 바꿨다. 서버가 적은 code·이유는 여기
62
- // 없다 — "unknown" 대신 그 사실을 말한다. 2026-09-10 에 등록기관 거절이
63
- // 이 모양으로 "unknown: POST … → 502" 가 됐다.
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
- /** JSON 이 아닌 본문 (메일 원문). 오류 처리는 request 와 같다. */
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
- // request 와 같은 이유
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
- * 비밀번호는 메일함을 여는 사람이 `verifyEmail` 에서 고른다.
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
- /** 이메일 존재 여부와 무관하게 같은 accepted 응답을 받는다. */
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 시작 URL. 브라우저가 따라가는 302 라 fetch 로 부르지 않고
129
- * `window.location` 에 넣는다. `redirect` 는 서버 허용 목록에 있어야 한다.
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 시작 URL. GitHub 과 같은 대시보드 콜백 모양을 쓴다. */
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
- * 대시보드가 화면에 도메인을 박지 않기 위한 자리다 (docs/environments.md
146
- * §3.2) — 박아 두면 dev 화면이 프로덕션 주소를 안내하고, 그 안내는 열리기
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
- /** 표시 이름만 바꾼다 (admin). 슬러그는 주소가 되는 이름이라 이 표면에 없다. */
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
- * 조직을 지운다 (admin). **되돌릴 수 없다.**
169
+ * Deletes the organization (admin). **This cannot be undone.**
164
170
  *
165
- * `confirm` 은 조직 슬러그 그대로여야 한다 — 확인이 서버에 있는 이유는
166
- * 이 표면을 부르는 곳이 셋(화면·CLI·스크립트)이기 때문이다. 살아 있는
167
- * 프로젝트·도메인·저장소·DNS 존이 남아 있으면 409 `org_not_empty` 이고
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
- * 답의 `purgeAfter` 는 행이 실제로 사라지는 시각이다. 돌아온 순간 조직은
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
- * org 의 최근 알림 (docs/limits.md §3.4).
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
- * 요금제와 결제 상태 (docs/billing.md). `enabled` 가 거짓이면 이 배치에 결제가
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
- * 프로 체크아웃 세션. 돌아온 `url` 로 브라우저를 보낸다. 결제가 끝나도 요금제를
199
- * 바꾸는 것은 웹훅이라, 돌아온 뒤 `getBilling` 을 다시 읽는다.
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
- /** 고객 포털(결제 수단·영수증·해지)의 일회용 `url`. */
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
- /** 결제 수단 임베드의 고객 세션 토큰 (1 시간). 결제 이력 없는 org 는 404. */
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
- /** 기간 끝 해지 예약(`cancel: true`) 또는 되돌리기. 즉시 회수는 없다. */
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
- * 프로젝트 이름을 바꾼다 (admin). **기본 주소도 함께 움직이고 별칭은 없다** —
227
- * 응답이 새 주소를 싣는 이유다. org 슬러그와 달리 바꿀 수 있는 이유는
228
- * 그 이름이 org 와일드카드 인증서의 안쪽 라벨이기 때문이다.
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
- * 프로젝트 종결을 시작한다 (admin). **되돌릴 수 없고 복구 세대를 남기지
235
- * 않는다.** `confirm` 은 프로젝트 이름 그대로여야 한다.
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
- /** 프로젝트 첫 화면이 한 번에 받는 것. 사용량은 `getUsage` 를 나란히 부른다. */
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
- * 번들을 올린다. `headers` 는 출처 헤더다 (`X-Runlot-Git-*`, openapi
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
- * 접속 자격까지. `getDatabase` 와 라우트가 갈린 것은 역할 때문이다 —
271
- * viewer 는 여부를 알아도 되지만 비밀번호는 아니다 (proto/openapi.yaml).
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
- /** 접속 끝점만 — 자격 없음. viewer 도 받는다. `runlot port-forward` 가 쓴다. */
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
- * 복원이 고를 수 있는 세대 전부. 최신 순이고, `lastCheckedAt` 은 무변경
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
- * 202 로 끝난다 — 돌아오는 것은 연산 id 이고, 진행은
311
- * `getDatabaseOperation` 으로 본다. `latest` 는 서버가 풀지 않는다:
312
- * 목록에서 고른 `(epoch, seq)` 를 그대로 싣는다 (proto/openapi.yaml).
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
- * 백업 **안**의 복원점으로 되돌린다 (pg-feather docs/37): `txnId` 는 그 커밋,
319
- * `at` 은 그 시각 이하의 마지막 커밋. 백업 하나가 그 사이의 커밋을 순서대로
320
- * 담으므로 되돌릴 자리가 백업 경계에 묶이지 않는다. 그 밖은 restoreDatabase 와
321
- * 같다 — 202, 연산 id, 되돌리기 전에 안전 백업 하나.
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
- * 데이터베이스를 없앤다. `confirm` 은 프로젝트 이름과 **정확히** 같아야
328
- * 하고, 다르면 서버가 400 `confirm_mismatch` 로 거절한다.
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
- /** 연산 하나의 진행. 연산 id 는 자격이 아니라 프로젝트 경로 아래에 있다. */
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
- /** 되돌릴 수 없는 phase 앞의 연산을 중단한다. */
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
- * 로그 라이브 테일. **JSON 이 아니라 스트림**이라 `request` 를 안 지난다 —
344
- * 돌려주는 것은 줄 단위 `LogEvent` 의 async iterable 이고, `signal` 을 abort
345
- * 하면 연결이 끊긴다. 오류 응답(4xx/5xx)은 다른 메서드와 같은 `ApiError` 다.
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
- // 위의 request 와 같은 이유.
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
- // 소비자가 일찍 끝내면(break) 서버 쪽 follow 도 끝나야 한다.
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
- // ---- 오브젝트 스토리지 (docs/storage.md §8) ----
412
- /** 부여 여부·접두·쿼터·사용량. `usedBytes` 는 노드 보고 주기만큼 늦다. */
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
- * 정한다 (proto/openapi.yaml).
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
- /** 부여를 뗀다. **객체는 지워지지 않는다** (docs/storage.md §7). */
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
- // ---- 이메일 (docs/email.md §5) ----
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
- /** 켠다. **멱등**이다 — 주소는 우리가 정한다. 배포가 `"email": true` 를 읽고 부른다. */
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
- /** 끈다. 그 주소로 오는 메일은 이제 550 이다. */
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
- * 모델 카탈로그. **로그인만 하면 볼 수 있다** — 무엇을 살 수 있는지는 org 의
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
- * 단가는 100 만 토큰당 **마이크로센트** 정수다 (1 µ¢ = 1e-8 USD). 소수 자리를
447
- * 서버가 정하지 않는 것이 요점이다 — 작은 호출이 센트로 반올림하면 0 이 된다.
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
- /** 부여 여부·상한·org 잔액·24 h 사용량. 부여가 없어도 상한은 안내값으로 온다. */
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
- /** 켠다. **멱등**이다. 배포가 `"ai": true` 를 읽고 부른다. */
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
- /** 끈다. **원장과 잔액은 그대로다** — 잔액은 org 의 것이고 부여는 프로젝트의 것이다. */
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 원장 한 장. 커서는 `ledger_id` — 시각으로 자르면 같은 밀리초가 갈린다. */
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
- * 것은 `order.paid` 웹훅 하나이고, 화면은 성공 사건 뒤에 잔액을 폴링한다.
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
- * 자동 충전을 켜거나 끈다. **켜는 것은 명시 동의다** (docs/ai.md §3.5) — 화면이
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
- /** 메일 로그 한 장 (docs/email.md §6.1). 시각 역순, 커서. */
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
- /** 원문의 URL — 화면이 내려받기 링크로 쓴다 (.eml). */
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
- // ---- 최종 사용자 인증 (docs/auth.md §9) ----
536
+ // ---- End-user authentication (docs/auth.md §9) ----
517
537
  /**
518
- * 부여 여부·설정·프로바이더·호스트. **가입자는 안 온다** — 그 표는 프로젝트
519
- * DB 안이고 (docs/auth.md §6) 워커의 `env.auth` 만 본다.
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` 에는 client id 만 있다. 클라이언트 시크릿은 봉인돼 어느
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
- * 인증을 켠다 (member). 멱등이다.
549
+ * Turns auth on (member). Idempotent.
529
550
  *
530
- * **데이터베이스가 없으면 409 `database_required`** 다 — 가입자 표가
531
- * 프로젝트 DB 안이라 부여만 세우면 워커가 뜨는 순간 표가 없다. 부르는 쪽은
532
- * 그 코드를 잡아 `"database": true` 선언을 안내한다 — 배포는 순서로 이 코드를 안 본다.
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
- * 설정을 바꾼다 (member). **부분 갱신이다** — 준 칸만 바뀐다. 스위치 하나에
539
- * 전체를 실어 보내면 두 사람이 같은 카드를 만질 때 나중 저장이 앞 저장을
540
- * 덮는다. 부여가 없으면 404 `not_granted`.
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
- * 인증을 해제한다 (admin). **가입자는 안 지워진다** — `runlot_auth` 의 표는
547
- * 프로젝트 DB 에 남고, 다시 켜면 같은 사람들이 그대로 로그인한다.
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
- * 소셜 프로바이더를 등록하거나 교체한다 (admin). 같은 PUT 이라 멱등이다.
578
+ * Registers or replaces a social provider (admin). It is the same PUT, so it
579
+ * is idempotent.
554
580
  *
555
- * **`clientSecret` 은 다시 볼 수 없다.** 콜백 URL 은 호스트마다
556
- * `https://<host>/__runlot/auth/callback/<provider>` 이고, 호스트 목록은
557
- * 응답의 `hosts` 에 있다.
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
- * 프로바이더를 뗀다 (admin). 그 프로바이더로만 가입한 사람은 로그인할 길을
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
- // ---- 배포 접근 제어 (docs/access.md §7) ----
596
+ // ---- Deployment access control (docs/access.md §7) ----
570
597
  /**
571
- * 이 배포에 누가 닿을 수 있는가. **값은 없다** — 비밀번호도 우회 시크릿도
572
- * 해시로만 살고, 여기 오는 것은 `hasPassword`·`hasBypass` 라는 사실이다.
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
- * 정책을 한 번도 안 정한 프로젝트는 `mode: "public"` 에 `updatedAt` 이
575
- * 없다. 화면이 그 둘을 가르는 이유는 §7 — 빈 상태 대신 상태 문장이다.
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
- * 정책을 바꾼다 (admin). `password` 를 안 보내면 **있던 비밀번호가 남는다** —
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
- * 자동화 우회 시크릿을 만든다 (admin). **값은 이 답에만 있다** — 화면은
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
- /** 우회를 뗀다 (admin). 없는 우회를 떼는 것도 성공이다. */
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
- * 보호된 배포로 돌아가는 1 회용 주소를 받는다 (docs/access.md §3.4-3).
630
+ * Gets a one-time address back into a protected deployment (docs/access.md §3.4-3).
600
631
  *
601
- * 프로젝트 경로 아래가 **아니다**: 부르는 화면이 아는 것은 호스트네임뿐이고
602
- * (원 요청이 그리로 갔다), org·프로젝트 이름은 서버가 그것에서 유도한다.
603
- * 받은 `redirect` 로 `location.replace` 하는 것이 호출부의 일이다.
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
- /** 초대가 아니다 — 사용자는 이미 한 번 로그인해 있어야 한다 (404 `user_not_found`). */
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
- * 남을 빼는 것은 admin, **자기를 빼는 것은 탈퇴**라 누구나 할 수 있다
620
- * (docs/members.md). 마지막 admin 은 둘 다 못 한다 (409 `last_admin`).
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
- // --- 초대 (docs/members.md) ---
626
- /** 대기·만료된 초대 (admin). 수락·폐기된 것은 이력이라 안 온다. */
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
- * 이메일로 부른다 (admin). **같은 주소로 다시 부르면 재발송**이다 — 새 행이
632
- * 아니라 같은 행이 갱신되고 서버가 201 대신 200 을 준다.
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
- /** 폐기 (admin). 아직 안 나간 메일은 안 나간다. */
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
- /** 기본 호스트네임과 커스텀 도메인이 함께 온다 (viewer). */
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
- * 커스텀 도메인을 건다 (member).
693
+ * Attaches a custom domain (member).
659
694
  *
660
- * 멱등이다 — 같은 이름을 다시 보내면 있던 행이 그대로 온다. 응답의 `verify`
661
- * 가 고객이 넣어야 하는 CNAME·TXT 다.
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
- /** 배경 검증과 같은 판정을 즉시 부른다 (member). */
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
- /** 커스텀 도메인을 뗀다 (member). 기본 호스트네임은 400 이다. */
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
- /** 이 조직의 존 목록. `rrsets` 는 없다 — 단건 조회에만 실려요. */
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
- * rrset 하나를 **통째로** 바꿔요. `records` 가 그 (이름, 타입) 의 새 전부라,
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
- /** DNS 표면의 공통 접두. */
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
- * 이름을 찾아본다 (member 이상).
755
+ * Looks a name up (member or above).
719
756
  *
720
- * `priceCents` 는 첫 등록 1 년, `renewalCents` 는 갱신 1 년이다. 둘이
721
- * 나뉘어 있는 이유는 첫해 할인이 흔해서다 — 하나로 접으면 "1 달러에 사서
722
- * 다음 해 15 달러" 를 화면이 말할 수 없다.
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
- * 도메인을 산다 (admin — 돈을 써요).
765
+ * Buys a domain (admin — this spends money).
729
766
  *
730
- * **ICANN 검증 메일이 `contact.email` 로 가요.** 화면은 사기 전에 그
731
- * 사실을 알려야 해요 (docs/domains.md §5.5).
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
- /** 이미 가진 도메인을 가져와요 (admin). 등록기관을 부르지 않고 존만 만들어요. */
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
- /** 지금 등록기관에서 다시 읽어요 (member). */
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
- // ---- 자체 Git 호스팅 (docs/git-hosting.md §12.1, §19) ----
826
+ // ---- Self-hosted Git (docs/git-hosting.md §12.1, §19) ----
789
827
  //
790
- // **저장소 조작은 git 이 하고 우리는 메타데이터만 다룬다.** `pushRepo` 같은
791
- // 것이 여기 없는 이유다 — git 을 감싸는 API 는 git 의 모든 플래그를 다시
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
- // 저장소는 **프로젝트의 것**이다. 그래서 모든 메서드가 (org, project) 로
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 이력. force-push 로 잃은 커밋을 되찾는 단서가 여기 있다. */
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
- // ---- 브라우즈 (docs/git-hosting.md §12.2) ----
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
- /** 저장소 표면의 공통 접두다. 프로젝트 아래에 있다 (§19). */
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
  }