primitive-admin 1.2.0-alpha.0 → 1.2.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/README.md +6 -4
  2. package/dist/bin/primitive.js +22 -24
  3. package/dist/bin/primitive.js.map +1 -1
  4. package/dist/src/commands/agent-sessions.d.ts +20 -0
  5. package/dist/src/commands/agent-sessions.js +275 -0
  6. package/dist/src/commands/agent-sessions.js.map +1 -0
  7. package/dist/src/commands/apps-children.d.ts +54 -0
  8. package/dist/src/commands/apps-children.js +578 -0
  9. package/dist/src/commands/apps-children.js.map +1 -0
  10. package/dist/src/commands/apps.js +88 -9
  11. package/dist/src/commands/apps.js.map +1 -1
  12. package/dist/src/commands/auth-sessions.d.ts +3 -3
  13. package/dist/src/commands/auth-sessions.js +117 -23
  14. package/dist/src/commands/auth-sessions.js.map +1 -1
  15. package/dist/src/commands/auth.js +248 -15
  16. package/dist/src/commands/auth.js.map +1 -1
  17. package/dist/src/commands/connections.js +1 -5
  18. package/dist/src/commands/connections.js.map +1 -1
  19. package/dist/src/commands/databases.js +7 -109
  20. package/dist/src/commands/databases.js.map +1 -1
  21. package/dist/src/commands/documents.d.ts +29 -0
  22. package/dist/src/commands/documents.js +676 -191
  23. package/dist/src/commands/documents.js.map +1 -1
  24. package/dist/src/commands/env.d.ts +9 -1
  25. package/dist/src/commands/env.js +158 -45
  26. package/dist/src/commands/env.js.map +1 -1
  27. package/dist/src/commands/functions.js +7 -1
  28. package/dist/src/commands/functions.js.map +1 -1
  29. package/dist/src/commands/init.js +2 -1
  30. package/dist/src/commands/init.js.map +1 -1
  31. package/dist/src/commands/integrations.js +14 -2
  32. package/dist/src/commands/integrations.js.map +1 -1
  33. package/dist/src/commands/prompts.js +4 -0
  34. package/dist/src/commands/prompts.js.map +1 -1
  35. package/dist/src/commands/scripts.js +1 -1
  36. package/dist/src/commands/scripts.js.map +1 -1
  37. package/dist/src/commands/sessions.js +3 -8
  38. package/dist/src/commands/sessions.js.map +1 -1
  39. package/dist/src/commands/sync-app-settings.d.ts +25 -7
  40. package/dist/src/commands/sync-app-settings.js +81 -20
  41. package/dist/src/commands/sync-app-settings.js.map +1 -1
  42. package/dist/src/commands/sync.d.ts +183 -12
  43. package/dist/src/commands/sync.js +974 -452
  44. package/dist/src/commands/sync.js.map +1 -1
  45. package/dist/src/commands/tokens.js +1 -6
  46. package/dist/src/commands/tokens.js.map +1 -1
  47. package/dist/src/commands/users.js +77 -2
  48. package/dist/src/commands/users.js.map +1 -1
  49. package/dist/src/lib/api-client.d.ts +161 -35
  50. package/dist/src/lib/api-client.js +210 -86
  51. package/dist/src/lib/api-client.js.map +1 -1
  52. package/dist/src/lib/app-settings-descriptor.d.ts +9 -1
  53. package/dist/src/lib/app-settings-descriptor.js +22 -10
  54. package/dist/src/lib/app-settings-descriptor.js.map +1 -1
  55. package/dist/src/lib/auth-flow.d.ts +41 -1
  56. package/dist/src/lib/auth-flow.js +45 -16
  57. package/dist/src/lib/auth-flow.js.map +1 -1
  58. package/dist/src/lib/child-apps-local.d.ts +184 -0
  59. package/dist/src/lib/child-apps-local.js +282 -0
  60. package/dist/src/lib/child-apps-local.js.map +1 -0
  61. package/dist/src/lib/ci-session-request.d.ts +22 -0
  62. package/dist/src/lib/ci-session-request.js +51 -0
  63. package/dist/src/lib/ci-session-request.js.map +1 -0
  64. package/dist/src/lib/codegen-shared/generatedFiles.d.ts +4 -1
  65. package/dist/src/lib/codegen-shared/generatedFiles.js +19 -13
  66. package/dist/src/lib/codegen-shared/generatedFiles.js.map +1 -1
  67. package/dist/src/lib/config-object-descriptor.js +5 -5
  68. package/dist/src/lib/config-object-descriptor.js.map +1 -1
  69. package/dist/src/lib/config-payload.js +2 -3
  70. package/dist/src/lib/config-payload.js.map +1 -1
  71. package/dist/src/lib/config-surface.d.ts +2 -1
  72. package/dist/src/lib/config-surface.js +7 -28
  73. package/dist/src/lib/config-surface.js.map +1 -1
  74. package/dist/src/lib/credentials-store.d.ts +17 -2
  75. package/dist/src/lib/credentials-store.js +72 -6
  76. package/dist/src/lib/credentials-store.js.map +1 -1
  77. package/dist/src/lib/document-export-lookups.d.ts +31 -0
  78. package/dist/src/lib/document-export-lookups.js +53 -0
  79. package/dist/src/lib/document-export-lookups.js.map +1 -0
  80. package/dist/src/lib/document-export-permissions.d.ts +7 -5
  81. package/dist/src/lib/document-export-permissions.js +7 -5
  82. package/dist/src/lib/document-export-permissions.js.map +1 -1
  83. package/dist/src/lib/env-resolver-core.d.ts +69 -1
  84. package/dist/src/lib/env-resolver-core.js +137 -20
  85. package/dist/src/lib/env-resolver-core.js.map +1 -1
  86. package/dist/src/lib/env-resolver.d.ts +14 -2
  87. package/dist/src/lib/env-resolver.js +32 -10
  88. package/dist/src/lib/env-resolver.js.map +1 -1
  89. package/dist/src/lib/function-run.d.ts +4 -1
  90. package/dist/src/lib/function-run.js +24 -4
  91. package/dist/src/lib/function-run.js.map +1 -1
  92. package/dist/src/lib/function-sync.d.ts +5 -2
  93. package/dist/src/lib/function-sync.js +7 -4
  94. package/dist/src/lib/function-sync.js.map +1 -1
  95. package/dist/src/lib/generated-config-surfaces.d.ts +49 -50
  96. package/dist/src/lib/generated-config-surfaces.js +283 -166
  97. package/dist/src/lib/generated-config-surfaces.js.map +1 -1
  98. package/dist/src/lib/generated-sdk-types.d.ts +1 -1
  99. package/dist/src/lib/generated-sdk-types.js +1 -1
  100. package/dist/src/lib/generated-sdk-types.js.map +1 -1
  101. package/dist/src/lib/init-adopt.js +12 -3
  102. package/dist/src/lib/init-adopt.js.map +1 -1
  103. package/dist/src/lib/local-state.d.ts +17 -1
  104. package/dist/src/lib/local-state.js +94 -15
  105. package/dist/src/lib/local-state.js.map +1 -1
  106. package/dist/src/lib/local-test-cases.d.ts +5 -2
  107. package/dist/src/lib/local-test-cases.js +7 -4
  108. package/dist/src/lib/local-test-cases.js.map +1 -1
  109. package/dist/src/lib/log-inspection.d.ts +25 -0
  110. package/dist/src/lib/log-inspection.js +24 -0
  111. package/dist/src/lib/log-inspection.js.map +1 -1
  112. package/dist/src/lib/logout-admin-session.d.ts +2 -1
  113. package/dist/src/lib/logout-admin-session.js +7 -1
  114. package/dist/src/lib/logout-admin-session.js.map +1 -1
  115. package/dist/src/lib/pull-write.d.ts +31 -0
  116. package/dist/src/lib/pull-write.js +49 -0
  117. package/dist/src/lib/pull-write.js.map +1 -0
  118. package/dist/src/lib/root-import-decision.d.ts +59 -0
  119. package/dist/src/lib/root-import-decision.js +95 -0
  120. package/dist/src/lib/root-import-decision.js.map +1 -0
  121. package/dist/src/lib/scope-request.d.ts +69 -0
  122. package/dist/src/lib/scope-request.js +188 -0
  123. package/dist/src/lib/scope-request.js.map +1 -0
  124. package/dist/src/lib/session-id.d.ts +9 -0
  125. package/dist/src/lib/session-id.js +28 -0
  126. package/dist/src/lib/session-id.js.map +1 -0
  127. package/dist/src/lib/snapshots.d.ts +17 -1
  128. package/dist/src/lib/snapshots.js +50 -2
  129. package/dist/src/lib/snapshots.js.map +1 -1
  130. package/dist/src/lib/sync-paths.d.ts +35 -0
  131. package/dist/src/lib/sync-paths.js +57 -5
  132. package/dist/src/lib/sync-paths.js.map +1 -1
  133. package/dist/src/lib/transient-retry.d.ts +34 -0
  134. package/dist/src/lib/transient-retry.js +57 -0
  135. package/dist/src/lib/transient-retry.js.map +1 -0
  136. package/dist/src/types/index.d.ts +21 -4
  137. package/package.json +4 -3
@@ -85,6 +85,15 @@ export function parseErrorResponse(response, text, path) {
85
85
  // Non-JSON body. Surface the existing `<!DOCTYPE` special-case (an HTML
86
86
  // 404 page from hitting the wrong path) so we don't regress the helpful
87
87
  // "API endpoint not found" message at api-client.ts:343.
88
+ //
89
+ // An HTML page with any other status is not a wrong path: it keeps its
90
+ // status, so a gateway's 502 is retried and a 403 is not read as absence
91
+ // (#4148).
92
+ if (text.includes("<!DOCTYPE") && response.status !== 404) {
93
+ return {
94
+ message: `HTTP ${response.status}: the server answered with an HTML error page`,
95
+ };
96
+ }
88
97
  if (text.includes("<!DOCTYPE")) {
89
98
  const where = path ? `: ${path}` : "";
90
99
  return {
@@ -295,60 +304,113 @@ export class ApiClient {
295
304
  async request(path, options = {}) {
296
305
  const credentials = await this.ensureAuthenticated();
297
306
  const url = `${credentials.serverUrl}${path}`;
298
- const headers = {
299
- Authorization: `Bearer ${credentials.accessToken}`,
300
- "Content-Type": "application/json",
301
- ...(options.headers || {}),
302
- };
303
- // Include globalAdminAppId if available (required for admin API calls)
304
- if (credentials.globalAdminAppId) {
305
- headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
306
- }
307
- const response = await fetchWithTLS(url, {
308
- ...options,
309
- headers,
310
- });
311
- const text = await response.text();
312
- if (!response.ok) {
313
- const parsed = parseErrorResponse(response, text, path);
314
- // Preserve the `<!DOCTYPE` → 404 ApiError shape (status forced to 404).
315
- if (parsed.htmlNotFound) {
316
- throw new ApiError(parsed.message, 404);
317
- }
318
- // Narrow details to the record shape for the issue #666 typed-exception
319
- // dispatch below. Conflict metadata may live under `details.*` (canonical
320
- // location per A1) or on the top-level envelope (legacy).
321
- const detailsRecord = parsed.details && !Array.isArray(parsed.details)
322
- ? parsed.details
323
- : undefined;
324
- const serverModifiedAt = detailsRecord?.serverModifiedAt ?? parsed.raw?.serverModifiedAt;
325
- const expectedModifiedAt = detailsRecord?.expectedModifiedAt ?? parsed.raw?.expectedModifiedAt;
326
- // Typed exceptions for the schema-feature (issue #666).
327
- if (response.status === 409 && parsed.code === "CONFLICT") {
328
- throw new ConflictError(parsed.message, serverModifiedAt, expectedModifiedAt, detailsRecord);
329
- }
330
- if (response.status === 409 && parsed.code === "OPS_EXIST") {
331
- throw new OpsExistError(parsed.message, detailsRecord);
332
- }
333
- if (response.status === 400 && parsed.code === "TOML_PARSE_ERROR") {
334
- throw new TomlParseError(parsed.message, detailsRecord);
335
- }
336
- if (response.status === 422 && parsed.code === "SCHEMA_REQUIRED") {
337
- throw new SchemaRequiredError(parsed.message, detailsRecord);
307
+ const send = async (accessToken) => {
308
+ const headers = {
309
+ Authorization: `Bearer ${accessToken}`,
310
+ "Content-Type": "application/json",
311
+ ...(options.headers || {}),
312
+ };
313
+ // Include globalAdminAppId if available (required for admin API calls)
314
+ if (credentials.globalAdminAppId) {
315
+ headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
338
316
  }
339
- if (response.status === 422 && parsed.code === "OPERATION_REFERENCES_UNDEFINED") {
340
- throw new OperationRefError(parsed.message, detailsRecord);
317
+ const response = await fetchWithTLS(url, {
318
+ ...options,
319
+ headers,
320
+ });
321
+ return { response, text: await response.text() };
322
+ };
323
+ let { response, text } = await send(credentials.accessToken);
324
+ // #3889 — a 403 naming `admin` is retried once with a stored, unexpired
325
+ // step-up. A retry refused 401 means the step-up is gone (revoked or
326
+ // expired early): drop it and show the original refusal. The retry's own
327
+ // refusal (say, an unpinned step-up on an app route) is shown as it is.
328
+ if (response.status === 403 && this.stepUpApplies(response, text, path)) {
329
+ const retried = await send(credentials.stepUp.accessToken);
330
+ if (retried.response.ok) {
331
+ return retried.text ? JSON.parse(retried.text) : null;
341
332
  }
342
- if (response.status === 422 && parsed.code === "SCHEMA_BREAKS_OPERATIONS") {
343
- throw new SchemaBreaksOpsError(parsed.message, detailsRecord);
333
+ if (retried.response.status === 401) {
334
+ this.dropStepUp();
344
335
  }
345
- if (response.status === 422 && parsed.code === "SCHEMA_HAS_UNCHECKABLE_OPS") {
346
- throw new SchemaHasUncheckableOpsError(parsed.message, detailsRecord);
336
+ else {
337
+ ({ response, text } = retried);
347
338
  }
348
- throw new ApiError(parsed.message, response.status, parsed.code, parsed.details);
339
+ }
340
+ if (!response.ok) {
341
+ throw this.responseError(response, text, path);
349
342
  }
350
343
  return text ? JSON.parse(text) : null;
351
344
  }
345
+ /**
346
+ * Whether a 403 should be retried with the stored step-up: it names
347
+ * `admin` among the missing scopes, and an unexpired step-up is stored.
348
+ */
349
+ stepUpApplies(response, text, path) {
350
+ const stepUp = this.credentials?.stepUp;
351
+ if (!stepUp?.accessToken)
352
+ return false;
353
+ if (new Date(stepUp.expiresAt).getTime() <= Date.now())
354
+ return false;
355
+ const details = parseErrorResponse(response, text, path).details;
356
+ const missing = details && !Array.isArray(details) ? details.missing : undefined;
357
+ return Array.isArray(missing) && missing.includes("admin");
358
+ }
359
+ /** Forget a step-up the server no longer accepts. */
360
+ dropStepUp() {
361
+ if (!this.credentials?.stepUp)
362
+ return;
363
+ const { stepUp: _dropped, ...rest } = this.credentials;
364
+ this.credentials = rest;
365
+ saveCredentials(rest);
366
+ }
367
+ /** The typed error a failed response throws. */
368
+ responseError(response, text, path) {
369
+ const parsed = parseErrorResponse(response, text, path);
370
+ // Preserve the `<!DOCTYPE` → 404 ApiError shape.
371
+ if (parsed.htmlNotFound) {
372
+ return new ApiError(parsed.message, 404);
373
+ }
374
+ // Narrow details to the record shape for the issue #666 typed-exception
375
+ // dispatch below. Conflict metadata may live under `details.*` (canonical
376
+ // location per A1) or on the top-level envelope (legacy).
377
+ const detailsRecord = parsed.details && !Array.isArray(parsed.details)
378
+ ? parsed.details
379
+ : undefined;
380
+ const serverModifiedAt = detailsRecord?.serverModifiedAt ?? parsed.raw?.serverModifiedAt;
381
+ const expectedModifiedAt = detailsRecord?.expectedModifiedAt ?? parsed.raw?.expectedModifiedAt;
382
+ // Typed exceptions for the schema-feature (issue #666).
383
+ if (response.status === 409 && parsed.code === "CONFLICT") {
384
+ return new ConflictError(parsed.message, serverModifiedAt, expectedModifiedAt, detailsRecord);
385
+ }
386
+ if (response.status === 409 && parsed.code === "OPS_EXIST") {
387
+ return new OpsExistError(parsed.message, detailsRecord);
388
+ }
389
+ if (response.status === 400 && parsed.code === "TOML_PARSE_ERROR") {
390
+ return new TomlParseError(parsed.message, detailsRecord);
391
+ }
392
+ if (response.status === 422 && parsed.code === "SCHEMA_REQUIRED") {
393
+ return new SchemaRequiredError(parsed.message, detailsRecord);
394
+ }
395
+ if (response.status === 422 && parsed.code === "OPERATION_REFERENCES_UNDEFINED") {
396
+ return new OperationRefError(parsed.message, detailsRecord);
397
+ }
398
+ if (response.status === 422 && parsed.code === "SCHEMA_BREAKS_OPERATIONS") {
399
+ return new SchemaBreaksOpsError(parsed.message, detailsRecord);
400
+ }
401
+ if (response.status === 422 && parsed.code === "SCHEMA_HAS_UNCHECKABLE_OPS") {
402
+ return new SchemaHasUncheckableOpsError(parsed.message, detailsRecord);
403
+ }
404
+ return new ApiError(parsed.message, response.status, parsed.code, parsed.details);
405
+ }
406
+ /**
407
+ * `POST /admin/api/auth/token` — derive a token no wider than the stored
408
+ * one (`primitive token --scope`, #3889). A wider request is refused with
409
+ * `TOKEN_WIDER_THAN_PRESENTED`.
410
+ */
411
+ async exchangeToken(body) {
412
+ return this.post("/admin/api/auth/token", body);
413
+ }
352
414
  // Helper for GET requests with query params
353
415
  async get(path, params) {
354
416
  let fullPath = path;
@@ -410,6 +472,10 @@ export class ApiClient {
410
472
  qs.set("limit", String(params.limit));
411
473
  if (params?.cursor)
412
474
  qs.set("cursor", params.cursor);
475
+ // `kind` filters by lineage: `parent` is apps with no parent, `child`
476
+ // child apps. Omitted lists every app the caller is assigned to.
477
+ if (params?.kind)
478
+ qs.set("kind", params.kind);
413
479
  const q = qs.toString();
414
480
  const resp = await this.get(`/admin/api/admins/me/apps${q ? `?${q}` : ""}`);
415
481
  return {
@@ -463,9 +529,54 @@ export class ApiClient {
463
529
  });
464
530
  return drainAppsUntilConverged(drainOnce);
465
531
  }
532
+ /**
533
+ * Create an app. An ordinary app takes `name` (and, for a super-admin,
534
+ * `initialAdminEmail`). A child app takes `parentAppId` and `childSlug`
535
+ * instead, plus the optional `branch`, `idleDays` (7-30, default 30) and
536
+ * `copySecrets` (default true); the server names it `<parent name> /
537
+ * <slug>` and copies the parent's settings.
538
+ */
466
539
  async createApp(data) {
467
540
  return this.post("/admin/api/apps", data);
468
541
  }
542
+ /**
543
+ * One page of an app's child apps, in creation order
544
+ * (`GET /admin/api/apps/{appId}/children`).
545
+ */
546
+ async listAppChildren(parentAppId, params) {
547
+ const qs = new URLSearchParams();
548
+ if (params?.limit !== undefined)
549
+ qs.set("limit", String(params.limit));
550
+ if (params?.cursor)
551
+ qs.set("cursor", params.cursor);
552
+ const q = qs.toString();
553
+ const resp = await this.get(`/admin/api/apps/${encodeURIComponent(parentAppId)}/children${q ? `?${q}` : ""}`);
554
+ return {
555
+ items: resp?.items ?? [],
556
+ hasMore: resp?.hasMore ?? false,
557
+ nextCursor: resp?.nextCursor ?? null,
558
+ };
559
+ }
560
+ /**
561
+ * Add one origin to a child's preview origins
562
+ * (`POST /admin/api/apps/{parentAppId}/children/{slug}/preview-origins`).
563
+ */
564
+ async addChildPreviewOrigin(parentAppId, slug, origin) {
565
+ return this.post(this.childPreviewOriginsPath(parentAppId, slug), { origin });
566
+ }
567
+ /**
568
+ * Remove one origin from a child's preview origins
569
+ * (`DELETE /admin/api/apps/{parentAppId}/children/{slug}/preview-origins`).
570
+ */
571
+ async removeChildPreviewOrigin(parentAppId, slug, origin) {
572
+ return this.request(this.childPreviewOriginsPath(parentAppId, slug), {
573
+ method: "DELETE",
574
+ body: JSON.stringify({ origin }),
575
+ });
576
+ }
577
+ childPreviewOriginsPath(parentAppId, slug) {
578
+ return `/admin/api/apps/${encodeURIComponent(parentAppId)}/children/${encodeURIComponent(slug)}/preview-origins`;
579
+ }
469
580
  async getApp(appId) {
470
581
  return this.get(`/admin/api/apps/${appId}`);
471
582
  }
@@ -487,6 +598,14 @@ export class ApiClient {
487
598
  async addUserByEmail(appId, data) {
488
599
  return this.post(`/admin/api/apps/${appId}/users/add-by-email`, data);
489
600
  }
601
+ /**
602
+ * Set a member's display name and/or avatar URL
603
+ * (`PATCH /admin/api/apps/:appId/users/:userId/profile`). An omitted field
604
+ * is left alone; `avatarUrl: null` or `""` clears the avatar.
605
+ */
606
+ async setUserProfile(appId, userId, data) {
607
+ return this.patch(`/admin/api/apps/${appId}/users/${userId}/profile`, data);
608
+ }
490
609
  async mintTestJwt(appId, userId, role) {
491
610
  return this.post(`/admin/api/apps/${appId}/users/${userId}/mint-test-jwt`, role ? { role } : {});
492
611
  }
@@ -1037,6 +1156,29 @@ export class ApiClient {
1037
1156
  nextCursor: result?.nextCursor ?? null,
1038
1157
  };
1039
1158
  }
1159
+ /**
1160
+ * One page of one user's agent sessions (#3809): the app API's sessions
1161
+ * list, which an app owner or admin may read for any user.
1162
+ */
1163
+ async listAgentSessions(appId, params) {
1164
+ const result = await this.get(`/app/${appId}/api/agent-sessions`, params);
1165
+ return {
1166
+ items: result?.items ?? [],
1167
+ hasMore: result?.hasMore,
1168
+ nextCursor: result?.nextCursor ?? null,
1169
+ };
1170
+ }
1171
+ /**
1172
+ * One agent session (#3809): its binding, status, members and active turn,
1173
+ * and for an app owner or admin the session object's state. Never its rows.
1174
+ */
1175
+ async getAgentSession(appId, sessionId) {
1176
+ return this.get(`/app/${appId}/api/agent-sessions/${encodeURIComponent(sessionId)}`);
1177
+ }
1178
+ /** Delete an agent session and its document (#3809); a repeat succeeds again. */
1179
+ async deleteAgentSession(appId, sessionId) {
1180
+ return this.delete(`/app/${appId}/api/agent-sessions/${encodeURIComponent(sessionId)}`);
1181
+ }
1040
1182
  async listSessions(appId, userId, params) {
1041
1183
  const result = await this.get(`/admin/api/apps/${appId}/sessions`, {
1042
1184
  userId,
@@ -1890,43 +2032,6 @@ export class ApiClient {
1890
2032
  async deleteDatabase(appId, databaseId) {
1891
2033
  return this.delete(`/app/${appId}/api/databases/${databaseId}`);
1892
2034
  }
1893
- /**
1894
- * Read a database's CEL context dict.
1895
- *
1896
- * The HTTP path stays `/metadata` because the wire field name is still
1897
- * `metadata`; only the client/CLI-facing helper names were reframed.
1898
- *
1899
- * @deprecated Use resource metadata categories instead (`primitive metadata
1900
- * get database <id> <category>`, #1420, #1815).
1901
- */
1902
- async getDatabaseCelContext(appId, databaseId) {
1903
- return this.get(`/app/${appId}/api/databases/${databaseId}/metadata`);
1904
- }
1905
- /**
1906
- * Update a database's CEL context dict (merge with existing).
1907
- *
1908
- * @deprecated Use resource metadata categories instead (`primitive metadata
1909
- * set database <id> <category>`, #1420, #1815).
1910
- */
1911
- async updateDatabaseCelContext(appId, databaseId, celContext) {
1912
- return this.patch(`/app/${appId}/api/databases/${databaseId}/metadata`, celContext);
1913
- }
1914
- /**
1915
- * @deprecated Database CEL context is deprecated; use resource metadata
1916
- * categories instead (`primitive metadata get database <id> <category>`,
1917
- * #1420, #1815).
1918
- */
1919
- async getDatabaseMetadata(appId, databaseId) {
1920
- return this.getDatabaseCelContext(appId, databaseId);
1921
- }
1922
- /**
1923
- * @deprecated Database CEL context is deprecated; use resource metadata
1924
- * categories instead (`primitive metadata set database <id> <category>`,
1925
- * #1420, #1815).
1926
- */
1927
- async updateDatabaseMetadata(appId, databaseId, metadata) {
1928
- return this.updateDatabaseCelContext(appId, databaseId, metadata);
1929
- }
1930
2035
  // ============================================
1931
2036
  // DATABASE PERMISSIONS
1932
2037
  // ============================================
@@ -2890,8 +2995,13 @@ export class ApiClient {
2890
2995
  async listDocumentPendingInvitations(appId, documentId) {
2891
2996
  return this.get(`/app/${appId}/api/documents/${documentId}/pending-invitations`);
2892
2997
  }
2893
- async listDocumentBlobs(appId, documentId) {
2894
- return this.get(`/app/${appId}/api/documents/${documentId}/blobs`);
2998
+ /**
2999
+ * One page of a document's uploaded blobs (the `{ items, hasMore,
3000
+ * nextCursor? }` envelope); pass the previous page's `nextCursor` for the
3001
+ * next.
3002
+ */
3003
+ async listDocumentBlobs(appId, documentId, params) {
3004
+ return this.get(`/app/${appId}/api/documents/${documentId}/blobs`, params);
2895
3005
  }
2896
3006
  async downloadBlob(appId, documentId, blobId) {
2897
3007
  const credentials = await this.ensureAuthenticated();
@@ -2935,6 +3045,20 @@ export class ApiClient {
2935
3045
  async listDocumentAliases(appId, documentId) {
2936
3046
  return this.get(`/app/${appId}/api/documents/${documentId}/aliases`);
2937
3047
  }
3048
+ /**
3049
+ * The document a user's alias points at, or null when the user holds no
3050
+ * alias under that key (#4148).
3051
+ */
3052
+ async getDocumentAlias(appId, aliasScope, aliasKey, userId) {
3053
+ try {
3054
+ return await this.get(`/app/${appId}/api/document-aliases/${aliasScope}/${encodeURIComponent(aliasKey)}`, { userId });
3055
+ }
3056
+ catch (error) {
3057
+ if (error instanceof ApiError && error.statusCode === 404)
3058
+ return null;
3059
+ throw error;
3060
+ }
3061
+ }
2938
3062
  async setDocumentAlias(appId, aliasScope, aliasKey, documentId, ownerUserId, mustNotExist) {
2939
3063
  return this.put(`/app/${appId}/api/document-aliases/${aliasScope}/${encodeURIComponent(aliasKey)}`, {
2940
3064
  documentId,