primitive-admin 1.0.63 → 1.0.64
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +132 -42
- package/assets/skill/skills/primitive-platform/SKILL.md +142 -661
- package/dist/bin/primitive.js +121 -41
- package/dist/bin/primitive.js.map +1 -1
- package/dist/src/commands/admins.js +37 -50
- package/dist/src/commands/admins.js.map +1 -1
- package/dist/src/commands/analytics.js +178 -9
- package/dist/src/commands/analytics.js.map +1 -1
- package/dist/src/commands/apps.js +12 -5
- package/dist/src/commands/apps.js.map +1 -1
- package/dist/src/commands/auth-sessions.d.ts +7 -0
- package/dist/src/commands/auth-sessions.js +144 -0
- package/dist/src/commands/auth-sessions.js.map +1 -0
- package/dist/src/commands/auth.js +76 -116
- package/dist/src/commands/auth.js.map +1 -1
- package/dist/src/commands/blob-buckets.js +2 -1
- package/dist/src/commands/blob-buckets.js.map +1 -1
- package/dist/src/commands/catalog.js +26 -10
- package/dist/src/commands/catalog.js.map +1 -1
- package/dist/src/commands/collection-type-configs.js +3 -10
- package/dist/src/commands/collection-type-configs.js.map +1 -1
- package/dist/src/commands/collections.js +736 -19
- package/dist/src/commands/collections.js.map +1 -1
- package/dist/src/commands/config.js +13 -27
- package/dist/src/commands/config.js.map +1 -1
- package/dist/src/commands/connections.js +8 -9
- package/dist/src/commands/connections.js.map +1 -1
- package/dist/src/commands/cron-triggers.js +2 -1
- package/dist/src/commands/cron-triggers.js.map +1 -1
- package/dist/src/commands/database-type-configs.js +5 -12
- package/dist/src/commands/database-type-configs.js.map +1 -1
- package/dist/src/commands/databases.js +59 -72
- package/dist/src/commands/databases.js.map +1 -1
- package/dist/src/commands/documents.d.ts +58 -0
- package/dist/src/commands/documents.js +1623 -95
- package/dist/src/commands/documents.js.map +1 -1
- package/dist/src/commands/email-templates.js +2 -1
- package/dist/src/commands/email-templates.js.map +1 -1
- package/dist/src/commands/env.d.ts +1 -1
- package/dist/src/commands/env.js +89 -43
- package/dist/src/commands/env.js.map +1 -1
- package/dist/src/commands/feature-flags.js +2 -1
- package/dist/src/commands/feature-flags.js.map +1 -1
- package/dist/src/commands/functions.d.ts +20 -0
- package/dist/src/commands/functions.js +1630 -0
- package/dist/src/commands/functions.js.map +1 -0
- package/dist/src/commands/group-type-configs.js +2 -1
- package/dist/src/commands/group-type-configs.js.map +1 -1
- package/dist/src/commands/groups.js +30 -17
- package/dist/src/commands/groups.js.map +1 -1
- package/dist/src/commands/guides.js +17 -11
- package/dist/src/commands/guides.js.map +1 -1
- package/dist/src/commands/init.d.ts +12 -15
- package/dist/src/commands/init.js +44 -92
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/integrations.js +35 -12
- package/dist/src/commands/integrations.js.map +1 -1
- package/dist/src/commands/locks.js +17 -2
- package/dist/src/commands/locks.js.map +1 -1
- package/dist/src/commands/metadata-category-configs.js +2 -1
- package/dist/src/commands/metadata-category-configs.js.map +1 -1
- package/dist/src/commands/metadata.js +8 -1
- package/dist/src/commands/metadata.js.map +1 -1
- package/dist/src/commands/prompts.js +106 -59
- package/dist/src/commands/prompts.js.map +1 -1
- package/dist/src/commands/rule-sets.js +3 -10
- package/dist/src/commands/rule-sets.js.map +1 -1
- package/dist/src/commands/scripts.js +33 -28
- package/dist/src/commands/scripts.js.map +1 -1
- package/dist/src/commands/secrets.js +2 -1
- package/dist/src/commands/secrets.js.map +1 -1
- package/dist/src/commands/sessions.js +10 -9
- package/dist/src/commands/sessions.js.map +1 -1
- package/dist/src/commands/sync-app-settings.js +3 -5
- package/dist/src/commands/sync-app-settings.js.map +1 -1
- package/dist/src/commands/sync.d.ts +390 -13
- package/dist/src/commands/sync.js +2613 -451
- package/dist/src/commands/sync.js.map +1 -1
- package/dist/src/commands/tokens.js +2 -1
- package/dist/src/commands/tokens.js.map +1 -1
- package/dist/src/commands/users.js +19 -10
- package/dist/src/commands/users.js.map +1 -1
- package/dist/src/commands/vars.js +2 -1
- package/dist/src/commands/vars.js.map +1 -1
- package/dist/src/commands/waitlist.js +2 -1
- package/dist/src/commands/waitlist.js.map +1 -1
- package/dist/src/commands/webhooks.d.ts +29 -0
- package/dist/src/commands/webhooks.js +135 -64
- package/dist/src/commands/webhooks.js.map +1 -1
- package/dist/src/commands/workflows.d.ts +1 -29
- package/dist/src/commands/workflows.js +113 -179
- package/dist/src/commands/workflows.js.map +1 -1
- package/dist/src/lib/api-client.d.ts +628 -14
- package/dist/src/lib/api-client.js +776 -70
- package/dist/src/lib/api-client.js.map +1 -1
- package/dist/src/lib/app-settings-descriptor.d.ts +1 -1
- package/dist/src/lib/app-settings-descriptor.js +9 -1
- package/dist/src/lib/app-settings-descriptor.js.map +1 -1
- package/dist/src/lib/block-selector.d.ts +58 -0
- package/dist/src/lib/block-selector.js +92 -0
- package/dist/src/lib/block-selector.js.map +1 -0
- package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.d.ts +24 -54
- package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js +31 -153
- package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js.map +1 -1
- package/dist/src/lib/collection-export.d.ts +184 -0
- package/dist/src/lib/collection-export.js +252 -0
- package/dist/src/lib/collection-export.js.map +1 -0
- package/dist/src/lib/config-json-field.d.ts +28 -0
- package/dist/src/lib/config-json-field.js +56 -0
- package/dist/src/lib/config-json-field.js.map +1 -0
- package/dist/src/lib/config-object-descriptor.js +88 -6
- package/dist/src/lib/config-object-descriptor.js.map +1 -1
- package/dist/src/lib/config-payload.d.ts +8 -1
- package/dist/src/lib/config-payload.js +49 -4
- package/dist/src/lib/config-payload.js.map +1 -1
- package/dist/src/lib/config-surface.d.ts +12 -1
- package/dist/src/lib/config-surface.js +71 -3
- package/dist/src/lib/config-surface.js.map +1 -1
- package/dist/src/lib/config.d.ts +31 -13
- package/dist/src/lib/config.js +34 -70
- package/dist/src/lib/config.js.map +1 -1
- package/dist/src/lib/credentials-store.d.ts +37 -22
- package/dist/src/lib/credentials-store.js +103 -97
- package/dist/src/lib/credentials-store.js.map +1 -1
- package/dist/src/lib/db-codegen/dbTypeIR.js +8 -0
- package/dist/src/lib/db-codegen/dbTypeIR.js.map +1 -1
- package/dist/src/lib/deprecation.d.ts +22 -0
- package/dist/src/lib/deprecation.js +43 -0
- package/dist/src/lib/deprecation.js.map +1 -0
- package/dist/src/lib/document-export-permissions.d.ts +30 -0
- package/dist/src/lib/document-export-permissions.js +54 -0
- package/dist/src/lib/document-export-permissions.js.map +1 -0
- package/dist/src/lib/document-ingest-artifact.d.ts +120 -0
- package/dist/src/lib/document-ingest-artifact.js +505 -0
- package/dist/src/lib/document-ingest-artifact.js.map +1 -0
- package/dist/src/lib/document-ingest-input.d.ts +51 -0
- package/dist/src/lib/document-ingest-input.js +132 -0
- package/dist/src/lib/document-ingest-input.js.map +1 -0
- package/dist/src/lib/document-ingest-rows.d.ts +137 -0
- package/dist/src/lib/document-ingest-rows.js +181 -0
- package/dist/src/lib/document-ingest-rows.js.map +1 -0
- package/dist/src/lib/document-ingest.d.ts +101 -0
- package/dist/src/lib/document-ingest.js +384 -0
- package/dist/src/lib/document-ingest.js.map +1 -0
- package/dist/src/lib/env-resolver-core.d.ts +89 -18
- package/dist/src/lib/env-resolver-core.js +145 -23
- package/dist/src/lib/env-resolver-core.js.map +1 -1
- package/dist/src/lib/env-resolver.d.ts +18 -3
- package/dist/src/lib/env-resolver.js +23 -3
- package/dist/src/lib/env-resolver.js.map +1 -1
- package/dist/src/lib/function-bundle.d.ts +147 -0
- package/dist/src/lib/function-bundle.js +341 -0
- package/dist/src/lib/function-bundle.js.map +1 -0
- package/dist/src/lib/function-collect.d.ts +123 -0
- package/dist/src/lib/function-collect.js +610 -0
- package/dist/src/lib/function-collect.js.map +1 -0
- package/dist/src/lib/function-db-types.d.ts +202 -0
- package/dist/src/lib/function-db-types.js +870 -0
- package/dist/src/lib/function-db-types.js.map +1 -0
- package/dist/src/lib/function-document-types.d.ts +144 -0
- package/dist/src/lib/function-document-types.js +370 -0
- package/dist/src/lib/function-document-types.js.map +1 -0
- package/dist/src/lib/function-grants-preflight.d.ts +64 -0
- package/dist/src/lib/function-grants-preflight.js +105 -0
- package/dist/src/lib/function-grants-preflight.js.map +1 -0
- package/dist/src/lib/function-log-lines.d.ts +76 -0
- package/dist/src/lib/function-log-lines.js +160 -0
- package/dist/src/lib/function-log-lines.js.map +1 -0
- package/dist/src/lib/function-log-row.d.ts +29 -0
- package/dist/src/lib/function-log-row.js +73 -0
- package/dist/src/lib/function-log-row.js.map +1 -0
- package/dist/src/lib/function-log-tail.d.ts +132 -0
- package/dist/src/lib/function-log-tail.js +262 -0
- package/dist/src/lib/function-log-tail.js.map +1 -0
- package/dist/src/lib/function-run.d.ts +289 -0
- package/dist/src/lib/function-run.js +389 -0
- package/dist/src/lib/function-run.js.map +1 -0
- package/dist/src/lib/function-schema-codegen.d.ts +143 -0
- package/dist/src/lib/function-schema-codegen.js +420 -0
- package/dist/src/lib/function-schema-codegen.js.map +1 -0
- package/dist/src/lib/function-sync.d.ts +459 -0
- package/dist/src/lib/function-sync.js +1258 -0
- package/dist/src/lib/function-sync.js.map +1 -0
- package/dist/src/lib/function-trigger-listing.d.ts +27 -0
- package/dist/src/lib/function-trigger-listing.js +70 -0
- package/dist/src/lib/function-trigger-listing.js.map +1 -0
- package/dist/src/lib/function-typecheck.d.ts +86 -0
- package/dist/src/lib/function-typecheck.js +370 -0
- package/dist/src/lib/function-typecheck.js.map +1 -0
- package/dist/src/lib/function-versions.d.ts +122 -0
- package/dist/src/lib/function-versions.js +182 -0
- package/dist/src/lib/function-versions.js.map +1 -0
- package/dist/src/lib/generated-allowlist.js +4 -0
- package/dist/src/lib/generated-allowlist.js.map +1 -1
- package/dist/src/lib/generated-config-surfaces.d.ts +2332 -78
- package/dist/src/lib/generated-config-surfaces.js +6979 -468
- package/dist/src/lib/generated-config-surfaces.js.map +1 -1
- package/dist/src/lib/generated-sdk-types.d.ts +12 -0
- package/dist/src/lib/generated-sdk-types.js +13 -0
- package/dist/src/lib/generated-sdk-types.js.map +1 -0
- package/dist/src/lib/init-adopt.js +2 -2
- package/dist/src/lib/init-adopt.js.map +1 -1
- package/dist/src/lib/init-assets.js +3 -3
- package/dist/src/lib/init-config.d.ts +1 -1
- package/dist/src/lib/init-ios-links.d.ts +6 -5
- package/dist/src/lib/init-ios-links.js +9 -8
- package/dist/src/lib/init-ios-links.js.map +1 -1
- package/dist/src/lib/init-plan.d.ts +1 -1
- package/dist/src/lib/init-plan.js +1 -1
- package/dist/src/lib/init-xcode.d.ts +6 -5
- package/dist/src/lib/init-xcode.js +34 -10
- package/dist/src/lib/init-xcode.js.map +1 -1
- package/dist/src/lib/ios-app-id.d.ts +34 -0
- package/dist/src/lib/ios-app-id.js +69 -0
- package/dist/src/lib/ios-app-id.js.map +1 -0
- package/dist/src/lib/list-options.d.ts +68 -0
- package/dist/src/lib/list-options.js +89 -0
- package/dist/src/lib/list-options.js.map +1 -0
- package/dist/src/lib/local-state.d.ts +1 -1
- package/dist/src/lib/local-state.js +1 -1
- package/dist/src/lib/local-test-cases.d.ts +1 -1
- package/dist/src/lib/local-test-cases.js +2 -1
- package/dist/src/lib/local-test-cases.js.map +1 -1
- package/dist/src/lib/log-inspection.d.ts +152 -5
- package/dist/src/lib/log-inspection.js +179 -2
- package/dist/src/lib/log-inspection.js.map +1 -1
- package/dist/src/lib/logout-admin-session.d.ts +33 -0
- package/dist/src/lib/logout-admin-session.js +70 -0
- package/dist/src/lib/logout-admin-session.js.map +1 -0
- package/dist/src/lib/output.d.ts +15 -0
- package/dist/src/lib/output.js +28 -0
- package/dist/src/lib/output.js.map +1 -1
- package/dist/src/lib/paginate.d.ts +15 -0
- package/dist/src/lib/paginate.js +17 -0
- package/dist/src/lib/paginate.js.map +1 -1
- package/dist/src/lib/project-config.d.ts +38 -21
- package/dist/src/lib/project-config.js +57 -47
- package/dist/src/lib/project-config.js.map +1 -1
- package/dist/src/lib/prompt-cost-format.d.ts +11 -0
- package/dist/src/lib/prompt-cost-format.js +41 -0
- package/dist/src/lib/prompt-cost-format.js.map +1 -0
- package/dist/src/lib/prompt-schema-codegen.d.ts +147 -0
- package/dist/src/lib/prompt-schema-codegen.js +462 -0
- package/dist/src/lib/prompt-schema-codegen.js.map +1 -0
- package/dist/src/lib/refresh-admin-credentials.d.ts +9 -1
- package/dist/src/lib/refresh-admin-credentials.js +22 -2
- package/dist/src/lib/refresh-admin-credentials.js.map +1 -1
- package/dist/src/lib/resolve-owner.d.ts +19 -0
- package/dist/src/lib/resolve-owner.js +20 -0
- package/dist/src/lib/resolve-owner.js.map +1 -0
- package/dist/src/lib/skill-installer.d.ts +49 -3
- package/dist/src/lib/skill-installer.js +275 -100
- package/dist/src/lib/skill-installer.js.map +1 -1
- package/dist/src/lib/snapshot-audit-source.d.ts +45 -0
- package/dist/src/lib/snapshot-audit-source.js +58 -0
- package/dist/src/lib/snapshot-audit-source.js.map +1 -0
- package/dist/src/lib/snapshot-audit-store.d.ts +52 -0
- package/dist/src/lib/snapshot-audit-store.js +196 -0
- package/dist/src/lib/snapshot-audit-store.js.map +1 -0
- package/dist/src/lib/snapshot-audit.d.ts +207 -0
- package/dist/src/lib/snapshot-audit.js +431 -0
- package/dist/src/lib/snapshot-audit.js.map +1 -0
- package/dist/src/lib/snapshot-build-rows.d.ts +60 -0
- package/dist/src/lib/snapshot-build-rows.js +87 -0
- package/dist/src/lib/snapshot-build-rows.js.map +1 -0
- package/dist/src/lib/snapshot-build.d.ts +50 -0
- package/dist/src/lib/snapshot-build.js +111 -0
- package/dist/src/lib/snapshot-build.js.map +1 -0
- package/dist/src/lib/snapshot-manifest-layout.d.ts +61 -0
- package/dist/src/lib/snapshot-manifest-layout.js +70 -0
- package/dist/src/lib/snapshot-manifest-layout.js.map +1 -0
- package/dist/src/lib/snapshots.d.ts +8 -9
- package/dist/src/lib/snapshots.js +20 -83
- package/dist/src/lib/snapshots.js.map +1 -1
- package/dist/src/lib/step-run-table.d.ts +43 -0
- package/dist/src/lib/step-run-table.js +129 -0
- package/dist/src/lib/step-run-table.js.map +1 -0
- package/dist/src/lib/storage-pending-retry.d.ts +26 -0
- package/dist/src/lib/storage-pending-retry.js +42 -0
- package/dist/src/lib/storage-pending-retry.js.map +1 -0
- package/dist/src/lib/swift-codegen/agentGenerator.d.ts +42 -0
- package/dist/src/lib/swift-codegen/agentGenerator.js +118 -0
- package/dist/src/lib/swift-codegen/agentGenerator.js.map +1 -0
- package/dist/src/lib/swift-codegen/banners.d.ts +24 -0
- package/dist/src/lib/swift-codegen/banners.js +25 -0
- package/dist/src/lib/swift-codegen/banners.js.map +1 -0
- package/dist/src/lib/swift-codegen/dbGenerator.js +19 -7
- package/dist/src/lib/swift-codegen/dbGenerator.js.map +1 -1
- package/dist/src/lib/swift-codegen/functionGenerator.d.ts +139 -0
- package/dist/src/lib/swift-codegen/functionGenerator.js +462 -0
- package/dist/src/lib/swift-codegen/functionGenerator.js.map +1 -0
- package/dist/src/lib/swift-codegen/generator.d.ts +7 -1
- package/dist/src/lib/swift-codegen/generator.js +28 -11
- package/dist/src/lib/swift-codegen/generator.js.map +1 -1
- package/dist/src/lib/swift-codegen/schemaToSwift.d.ts +15 -0
- package/dist/src/lib/swift-codegen/schemaToSwift.js +21 -4
- package/dist/src/lib/swift-codegen/schemaToSwift.js.map +1 -1
- package/dist/src/lib/sync-paths.d.ts +72 -55
- package/dist/src/lib/sync-paths.js +97 -100
- package/dist/src/lib/sync-paths.js.map +1 -1
- package/dist/src/lib/sync-resource-types.d.ts +23 -4
- package/dist/src/lib/sync-resource-types.js +106 -8
- package/dist/src/lib/sync-resource-types.js.map +1 -1
- package/dist/src/lib/sync-selectors.d.ts +45 -2
- package/dist/src/lib/sync-selectors.js +66 -5
- package/dist/src/lib/sync-selectors.js.map +1 -1
- package/dist/src/lib/template.d.ts +2 -2
- package/dist/src/lib/template.js +2 -2
- package/dist/src/lib/test-case-keys.d.ts +1 -1
- package/dist/src/lib/test-case-keys.js +1 -1
- package/dist/src/lib/test-case-variables.d.ts +14 -0
- package/dist/src/lib/test-case-variables.js +42 -0
- package/dist/src/lib/test-case-variables.js.map +1 -1
- package/dist/src/lib/toml-database-config.js +19 -2
- package/dist/src/lib/toml-database-config.js.map +1 -1
- package/dist/src/lib/webhook-deliver.d.ts +209 -0
- package/dist/src/lib/webhook-deliver.js +519 -0
- package/dist/src/lib/webhook-deliver.js.map +1 -0
- package/dist/src/lib/workflow-codegen/generator.d.ts +1 -1
- package/dist/src/lib/workflow-codegen/generator.js +1 -1
- package/dist/src/lib/workflow-toml-validator.d.ts +17 -8
- package/dist/src/lib/workflow-toml-validator.js +25 -12
- package/dist/src/lib/workflow-toml-validator.js.map +1 -1
- package/dist/src/lib/workflow-usage.d.ts +196 -0
- package/dist/src/lib/workflow-usage.js +310 -0
- package/dist/src/lib/workflow-usage.js.map +1 -0
- package/dist/src/types/index.d.ts +12 -2
- package/package.json +5 -4
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import { loadCredentials, saveCredentials, isTokenExpiringSoon, } from "./config.js";
|
|
2
|
+
import { getCurrentEnvNameSafe } from "./env-resolver.js";
|
|
2
3
|
import { fetchWithTLS } from "./fetch.js";
|
|
3
4
|
import { paginateAll, normalizeCliListEnvelope } from "./paginate.js";
|
|
4
5
|
import { RefreshError, refreshAdminCredentials, } from "./refresh-admin-credentials.js";
|
|
6
|
+
import { drainAppsUntilConverged, } from "./workflow-usage.js";
|
|
7
|
+
/**
|
|
8
|
+
* Page size the admin per-user inventories are asked for (#3399).
|
|
9
|
+
*
|
|
10
|
+
* `MAX_LIST_LIMIT` on the server, so a heavy user's inventory is walked in the
|
|
11
|
+
* fewest round trips the contract allows; a larger value is clamped there.
|
|
12
|
+
*/
|
|
13
|
+
const ADMIN_INVENTORY_PAGE_SIZE = 100;
|
|
5
14
|
export class ApiError extends Error {
|
|
6
15
|
statusCode;
|
|
7
16
|
code;
|
|
@@ -40,6 +49,24 @@ export class ConflictError extends ApiError {
|
|
|
40
49
|
this.name = "ConflictError";
|
|
41
50
|
}
|
|
42
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* The `ApiError` for a non-OK response read by a raw `fetch` call (#3403).
|
|
54
|
+
*
|
|
55
|
+
* The blob methods and the two document-artifact transfers build their own
|
|
56
|
+
* `fetch` call, so they never reached `parseErrorResponse` and never carried a
|
|
57
|
+
* `code` — a download refusal surfaced as `response.statusText` and an upload
|
|
58
|
+
* refusal as the raw JSON body shown to the operator as the message. This
|
|
59
|
+
* routes them through the same parser every other call site uses while keeping
|
|
60
|
+
* their human prefixes.
|
|
61
|
+
*
|
|
62
|
+
* Named for the blob paths it was written for; every raw-`fetch` path in this
|
|
63
|
+
* file answers through it now.
|
|
64
|
+
*/
|
|
65
|
+
export async function blobFailure(response, prefix) {
|
|
66
|
+
const text = await response.text().catch(() => "");
|
|
67
|
+
const parsed = parseErrorResponse(response, text);
|
|
68
|
+
return new ApiError(`${prefix}${parsed.message}`, response.status, parsed.code, parsed.details);
|
|
69
|
+
}
|
|
43
70
|
/**
|
|
44
71
|
* Extract a human-readable error message + structured fields from a non-OK
|
|
45
72
|
* HTTP response body. Single source of truth used by every error-handler call
|
|
@@ -202,16 +229,38 @@ export function normalizeDatabaseOperationResult(raw) {
|
|
|
202
229
|
return envelope;
|
|
203
230
|
}
|
|
204
231
|
export class ApiClient {
|
|
232
|
+
/**
|
|
233
|
+
* Loaded on first use (`ensureAuthenticated`), never in a constructor.
|
|
234
|
+
*
|
|
235
|
+
* There used to be an `apiClient` singleton constructed at MODULE scope, so
|
|
236
|
+
* a constructor body ran while `bin/primitive.ts` was still being imported —
|
|
237
|
+
* before its first `try`, and before any command had said whether it needed
|
|
238
|
+
* the project at all. `loadCredentials()` resolves the environment, so on a
|
|
239
|
+
* project still holding the pre-#3153 layout that read threw the stale-layout
|
|
240
|
+
* refusal out of module evaluation, where not even the `uncaughtException`
|
|
241
|
+
* handler can reach it: the user got Node's raw stack trace, and `guides
|
|
242
|
+
* get` — which reads nothing from the project — died with it (#3273). The
|
|
243
|
+
* singleton is gone (#3154, it had no callers) and the read stays lazy,
|
|
244
|
+
* which is what keeps that shape from coming back.
|
|
245
|
+
*
|
|
246
|
+
* Nothing is lost by waiting, and one thing is gained: a `login` earlier in
|
|
247
|
+
* the same process is visible to a later request.
|
|
248
|
+
*/
|
|
205
249
|
credentials = null;
|
|
206
|
-
constructor() {
|
|
207
|
-
this.credentials = loadCredentials();
|
|
208
|
-
}
|
|
209
250
|
async ensureAuthenticated() {
|
|
210
251
|
if (!this.credentials) {
|
|
211
252
|
this.credentials = loadCredentials();
|
|
212
253
|
}
|
|
213
254
|
if (!this.credentials) {
|
|
214
|
-
|
|
255
|
+
// Name the environment when there is one (#1293). Before #3152 this case
|
|
256
|
+
// surfaced from `resolveAppId`, because an unauthenticated environment
|
|
257
|
+
// resolved no app at all; now the environment always names its app, so
|
|
258
|
+
// "not logged in" is only ever an auth failure, and it is reported here,
|
|
259
|
+
// once, for every command.
|
|
260
|
+
const envName = getCurrentEnvNameSafe();
|
|
261
|
+
throw new ApiError(envName
|
|
262
|
+
? `Not logged in to environment "${envName}". Run 'primitive -e ${envName} login'.`
|
|
263
|
+
: "Not logged in. Run 'primitive login' first.", 401);
|
|
215
264
|
}
|
|
216
265
|
// Check if token needs refresh
|
|
217
266
|
if (isTokenExpiringSoon(this.credentials)) {
|
|
@@ -235,7 +284,10 @@ export class ApiClient {
|
|
|
235
284
|
// error or a server-side rejection. Callers that need a finer
|
|
236
285
|
// distinction (e.g. `primitive token`) consume RefreshError directly
|
|
237
286
|
// from refresh-admin-credentials.ts instead of going through here.
|
|
238
|
-
|
|
287
|
+
// A revoked session (#3885) says so.
|
|
288
|
+
throw new ApiError(err.code === "SESSION_REVOKED"
|
|
289
|
+
? err.message
|
|
290
|
+
: "Token refresh failed. Please login again.", 401, err.code);
|
|
239
291
|
}
|
|
240
292
|
throw err;
|
|
241
293
|
}
|
|
@@ -342,26 +394,79 @@ export class ApiClient {
|
|
|
342
394
|
// ============================================
|
|
343
395
|
// APPS
|
|
344
396
|
// ============================================
|
|
397
|
+
/**
|
|
398
|
+
* One page of the apps the caller can reach.
|
|
399
|
+
*
|
|
400
|
+
* The route paginates cursor-style (default pageSize=25, cap=100). This
|
|
401
|
+
* method used to walk the whole chain so `primitive apps list` could not
|
|
402
|
+
* truncate at 25 (#436); since #3646 the page boundary belongs to the
|
|
403
|
+
* caller instead — `apps list` declares `--limit`/`--cursor` and prints the
|
|
404
|
+
* `hasMore`/`nextCursor` the server reported, and the aggregate readers that
|
|
405
|
+
* really do mean "every app" call `listAllApps()`.
|
|
406
|
+
*/
|
|
407
|
+
async listAppsPage(params) {
|
|
408
|
+
const qs = new URLSearchParams();
|
|
409
|
+
if (params?.limit !== undefined)
|
|
410
|
+
qs.set("limit", String(params.limit));
|
|
411
|
+
if (params?.cursor)
|
|
412
|
+
qs.set("cursor", params.cursor);
|
|
413
|
+
const q = qs.toString();
|
|
414
|
+
const resp = await this.get(`/admin/api/admins/me/apps${q ? `?${q}` : ""}`);
|
|
415
|
+
// The server now emits `items` with `apps` as a deprecation-window alias
|
|
416
|
+
// (#1316). Read `items` first so listings keep working once the alias is
|
|
417
|
+
// removed; fall back to the legacy `apps` key meanwhile.
|
|
418
|
+
return {
|
|
419
|
+
items: resp?.items ?? (Array.isArray(resp?.apps) ? resp.apps : []),
|
|
420
|
+
hasMore: resp?.hasMore,
|
|
421
|
+
nextCursor: resp?.nextCursor ?? resp?.cursor ?? null,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Every app the caller can reach, cursor chain walked (#436).
|
|
426
|
+
*
|
|
427
|
+
* For readers that genuinely need the whole set — an app picker, a sweep —
|
|
428
|
+
* rather than the one page `apps list` prints.
|
|
429
|
+
*/
|
|
345
430
|
async listApps() {
|
|
346
|
-
// The server paginates cursor-style (default pageSize=25, cap=100). Historically
|
|
347
|
-
// this method issued a single un-paginated GET and silently truncated to the
|
|
348
|
-
// first page, which broke `primitive use <app-id>` and `primitive apps list`
|
|
349
|
-
// for admins with more than 25 apps (see #436). Iterate every page.
|
|
350
431
|
const apps = await paginateAll(async (cursor) => {
|
|
432
|
+
const page = await this.listAppsPage({ limit: 100, cursor });
|
|
433
|
+
return { items: page.items, nextCursor: page.nextCursor };
|
|
434
|
+
});
|
|
435
|
+
return { apps };
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Every app on the server, not just the caller's own.
|
|
439
|
+
*
|
|
440
|
+
* Deliberately separate from `listApps()`, whose contract is "apps assigned
|
|
441
|
+
* to me" (`GET /admin/api/admins/me/apps`, backed by `admin.queryApps()`) —
|
|
442
|
+
* the contract every other command depends on. `analytics workflow-usage
|
|
443
|
+
* --all-apps` has to reach apps nobody is assigned to, which is the global
|
|
444
|
+
* route `GET /admin/api/apps` (`App.iterateAll()`), and that route
|
|
445
|
+
* identifies an app as `appId` rather than `id` (D3130-001).
|
|
446
|
+
*
|
|
447
|
+
* One drain of that route is lossy — its cursor re-scans `App.iterateAll()`
|
|
448
|
+
* from the start of each page, so a list that changes under it both repeats
|
|
449
|
+
* and drops rows — so the walk is the union of repeated drains
|
|
450
|
+
* (`drainAppsUntilConverged`, which carries the measurement).
|
|
451
|
+
*/
|
|
452
|
+
async listAllAppsGlobal() {
|
|
453
|
+
const drainOnce = () => paginateAll(async (cursor) => {
|
|
351
454
|
const qs = new URLSearchParams({ limit: "100" });
|
|
352
455
|
if (cursor)
|
|
353
456
|
qs.set("cursor", cursor);
|
|
354
|
-
const resp = await this.get(`/admin/api/
|
|
355
|
-
//
|
|
356
|
-
//
|
|
357
|
-
|
|
457
|
+
const resp = await this.get(`/admin/api/apps?${qs.toString()}`);
|
|
458
|
+
// `items` first, with the `apps` alias (#1316) as the fallback,
|
|
459
|
+
// exactly as `listApps()` reads its own envelope.
|
|
460
|
+
const rows = resp.items ?? (Array.isArray(resp.apps) ? resp.apps : []);
|
|
358
461
|
return {
|
|
359
|
-
items:
|
|
360
|
-
(
|
|
462
|
+
items: rows.map((row) => ({
|
|
463
|
+
appId: String(row.appId ?? row.id ?? ""),
|
|
464
|
+
name: String(row.name ?? ""),
|
|
465
|
+
})),
|
|
361
466
|
nextCursor: resp.nextCursor ?? resp.cursor,
|
|
362
467
|
};
|
|
363
468
|
});
|
|
364
|
-
return
|
|
469
|
+
return drainAppsUntilConverged(drainOnce);
|
|
365
470
|
}
|
|
366
471
|
async createApp(data) {
|
|
367
472
|
return this.post("/admin/api/apps", data);
|
|
@@ -411,9 +516,16 @@ export class ApiClient {
|
|
|
411
516
|
const result = await this.get(path);
|
|
412
517
|
return {
|
|
413
518
|
items: result?.items ?? (Array.isArray(result) ? result : []),
|
|
519
|
+
// `hasMore` travels with the page so the caller prints the server's own
|
|
520
|
+
// page boundary rather than inferring one from the cursor (#3646).
|
|
521
|
+
hasMore: result?.hasMore,
|
|
414
522
|
nextCursor: result?.nextCursor ?? null,
|
|
415
523
|
};
|
|
416
524
|
}
|
|
525
|
+
/**
|
|
526
|
+
* @deprecated Use {@link setUserAvailability} with `"disable"`
|
|
527
|
+
* (`PUT /admin/api/apps/:appId/users/:userId/disable`) instead (#518).
|
|
528
|
+
*/
|
|
417
529
|
async removeUser(appId, userId) {
|
|
418
530
|
return this.delete(`/app/${appId}/api/users/${userId}`);
|
|
419
531
|
}
|
|
@@ -521,8 +633,12 @@ export class ApiClient {
|
|
|
521
633
|
// ============================================
|
|
522
634
|
async listDeferredGrants(appId, params) {
|
|
523
635
|
const result = await this.get(`/app/${appId}/api/deferred-grants`, params);
|
|
636
|
+
// `items` is the key every list envelope is built from (#3646, #1982);
|
|
637
|
+
// `grants` is the deprecated legacy name, read only as a fallback.
|
|
638
|
+
const items = result?.items ?? result?.grants ?? [];
|
|
524
639
|
return {
|
|
525
|
-
|
|
640
|
+
items,
|
|
641
|
+
grants: items,
|
|
526
642
|
nextCursor: result?.nextCursor ?? null,
|
|
527
643
|
hasMore: result?.hasMore,
|
|
528
644
|
};
|
|
@@ -537,6 +653,7 @@ export class ApiClient {
|
|
|
537
653
|
const result = await this.get(`/admin/api/apps/${appId}/integrations`, params);
|
|
538
654
|
return {
|
|
539
655
|
items: result?.items ?? [],
|
|
656
|
+
hasMore: result?.hasMore,
|
|
540
657
|
nextCursor: result?.nextCursor ?? null,
|
|
541
658
|
};
|
|
542
659
|
}
|
|
@@ -647,6 +764,7 @@ export class ApiClient {
|
|
|
647
764
|
const result = await this.get(`/admin/api/apps/${appId}/webhooks`, params);
|
|
648
765
|
return {
|
|
649
766
|
items: result?.items ?? [],
|
|
767
|
+
hasMore: result?.hasMore,
|
|
650
768
|
nextCursor: result?.nextCursor ?? null,
|
|
651
769
|
};
|
|
652
770
|
}
|
|
@@ -678,6 +796,217 @@ export class ApiClient {
|
|
|
678
796
|
async setWebhookAvailability(appId, webhookId, action) {
|
|
679
797
|
return this.post(`/admin/api/apps/${appId}/webhooks/${webhookId}/${action}`, {});
|
|
680
798
|
}
|
|
799
|
+
// ── Server functions (#3179) ─────────────────────────────────────────
|
|
800
|
+
//
|
|
801
|
+
// The `functions` family mirrors workflows and scripts: a header with the
|
|
802
|
+
// gate and the entry, and append-only immutable config versions carrying the
|
|
803
|
+
// authored TOML bytes, the sources and the built bundle. The listing drains
|
|
804
|
+
// server-side, so it has no cursor.
|
|
805
|
+
async listFunctions(appId) {
|
|
806
|
+
const result = await this.get(`/admin/api/apps/${appId}/functions`);
|
|
807
|
+
return { items: result?.items ?? [] };
|
|
808
|
+
}
|
|
809
|
+
async getFunction(appId, functionId) {
|
|
810
|
+
return this.get(`/admin/api/apps/${appId}/functions/${functionId}`);
|
|
811
|
+
}
|
|
812
|
+
async createFunction(appId, payload) {
|
|
813
|
+
return this.post(`/admin/api/apps/${appId}/functions`, payload);
|
|
814
|
+
}
|
|
815
|
+
async updateFunction(appId, functionId, payload) {
|
|
816
|
+
return this.patch(`/admin/api/apps/${appId}/functions/${functionId}`, payload);
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* Archive a function, or destroy it with `hard`. Archiving KEEPS the key
|
|
820
|
+
* reserved in the app's single key namespace; the hard delete is what
|
|
821
|
+
* releases it, destroys every config version and removes the R2 objects.
|
|
822
|
+
*/
|
|
823
|
+
async deleteFunction(appId, functionId, options) {
|
|
824
|
+
const query = options?.hard ? "?hard=true" : "";
|
|
825
|
+
return this.delete(`/admin/api/apps/${appId}/functions/${functionId}${query}`);
|
|
826
|
+
}
|
|
827
|
+
/** #2803 — the operational verb pair for a server function. */
|
|
828
|
+
async setFunctionAvailability(appId, functionId, action) {
|
|
829
|
+
return this.post(`/admin/api/apps/${appId}/functions/${functionId}/${action}`, {});
|
|
830
|
+
}
|
|
831
|
+
/** Every config version of a function, oldest first. */
|
|
832
|
+
/**
|
|
833
|
+
* A function's runs, newest first (#3181).
|
|
834
|
+
*
|
|
835
|
+
* A function run has no `workflowId`, so no workflow runs listing can see
|
|
836
|
+
* it — this is the only surface that lists one. Cursor-paginated like the
|
|
837
|
+
* other admin listings.
|
|
838
|
+
*/
|
|
839
|
+
async listFunctionRuns(appId, functionId, params) {
|
|
840
|
+
const result = await this.get(`/admin/api/apps/${appId}/functions/${functionId}/runs`, params);
|
|
841
|
+
return { items: result?.items ?? [], nextCursor: result?.nextCursor ?? null };
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* A function run's step trace — #3348.
|
|
845
|
+
*
|
|
846
|
+
* The function-scoped twin of `getWorkflowStepRuns`, and it answers with the
|
|
847
|
+
* same rows in the same shape: `workflows/:idOrKey/runs/...` resolves its
|
|
848
|
+
* first argument to a workflow DEFINITION, which a function id and a function
|
|
849
|
+
* key alike miss, so a durable run's steps needed a route that addresses the
|
|
850
|
+
* run by the function that owns it.
|
|
851
|
+
*/
|
|
852
|
+
async getFunctionStepRuns(appId, functionId, runId) {
|
|
853
|
+
const result = await this.get(`/admin/api/apps/${appId}/functions/${functionId}/runs/${runId}/steps`);
|
|
854
|
+
return { items: result?.items ?? [] };
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* End a function run that will not settle — #3348.
|
|
858
|
+
*
|
|
859
|
+
* `terminated: false` means the run had already settled and the route
|
|
860
|
+
* reported its outcome instead of overwriting it.
|
|
861
|
+
*/
|
|
862
|
+
async terminateFunctionRun(appId, functionId, runId) {
|
|
863
|
+
return this.post(`/admin/api/apps/${appId}/functions/${functionId}/runs/${runId}/terminate`, {});
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* A function's invocation records, newest first — #3287.
|
|
867
|
+
*
|
|
868
|
+
* The ADMIN route, beside the runs listing above and for the same reason:
|
|
869
|
+
* the CLI acts as an operator, not as a member of the app. The app-API twin
|
|
870
|
+
* (`functions.logs`) is what a FUNCTION reads through the SDK profile.
|
|
871
|
+
*/
|
|
872
|
+
async listFunctionLogs(appId, functionId, params) {
|
|
873
|
+
const result = await this.get(`/admin/api/apps/${appId}/functions/${functionId}/logs`, params);
|
|
874
|
+
return { items: result?.items ?? [], nextCursor: result?.nextCursor ?? null };
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* One invocation record by its id — #3448, behind `functions logs
|
|
878
|
+
* --invocation <id>`.
|
|
879
|
+
*
|
|
880
|
+
* The id the invoke envelope answered with. A record that never existed,
|
|
881
|
+
* one of ANOTHER function, and one the seven-day TTL has expired all answer
|
|
882
|
+
* the same 404, which is what the listing's own filter promises.
|
|
883
|
+
*/
|
|
884
|
+
async getFunctionLog(appId, functionId, invocationId) {
|
|
885
|
+
return this.get(`/admin/api/apps/${appId}/functions/${functionId}/logs/${invocationId}`);
|
|
886
|
+
}
|
|
887
|
+
/**
|
|
888
|
+
* One function run, RECONCILED against the engine — #3448, what
|
|
889
|
+
* `functions runs wait` polls.
|
|
890
|
+
*
|
|
891
|
+
* NOT the runs listing: that reports the STORED status, and a durable run's
|
|
892
|
+
* row stays `running` until something asks the engine, so a wait built on it
|
|
893
|
+
* would never settle.
|
|
894
|
+
*/
|
|
895
|
+
async getFunctionRun(appId, functionId, runId) {
|
|
896
|
+
return this.get(`/admin/api/apps/${appId}/functions/${functionId}/runs/${runId}`);
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* The operator's own APP user — #3448 D3448-009.
|
|
900
|
+
*
|
|
901
|
+
* `getMe()` reads the ADMIN profile (`/admin/api/me`); this is the
|
|
902
|
+
* app-scoped route, which answers the app user `requireAppPermission`
|
|
903
|
+
* provisions for an admin and the `appRole` it gave them. That is the
|
|
904
|
+
* identity a `functions invoke` runs under by default, and printing it is
|
|
905
|
+
* how an operator knows their call bypassed the function's `access` gate.
|
|
906
|
+
*/
|
|
907
|
+
async getAppProfile(appId) {
|
|
908
|
+
return this.get(`/app/${appId}/api/me`);
|
|
909
|
+
}
|
|
910
|
+
/**
|
|
911
|
+
* Invoke a function and hand back the ANSWER, whatever its HTTP status —
|
|
912
|
+
* #3448 D3448-010.
|
|
913
|
+
*
|
|
914
|
+
* `request` throws an `ApiError` on any non-2xx, which is right for every
|
|
915
|
+
* other verb and wrong for this one: a 429 carries a `Retry-After` an
|
|
916
|
+
* operator needs to see, and a 409 mode mismatch carries the version's real
|
|
917
|
+
* mode. Rendering those from an exception means re-deriving what the server
|
|
918
|
+
* already said. So this returns the envelope with its status and its retry
|
|
919
|
+
* hint, and throws only when the request did not complete at all.
|
|
920
|
+
*
|
|
921
|
+
* `bearer` is the `--user` path's minted token; absent, the CLI's own
|
|
922
|
+
* credentials. `mode` is ALWAYS sent (D3448-012): it is what makes the
|
|
923
|
+
* server, not the CLI, the authority on a wrong verb.
|
|
924
|
+
*/
|
|
925
|
+
async invokeFunction(appId, functionKey, body, options) {
|
|
926
|
+
const credentials = await this.ensureAuthenticated();
|
|
927
|
+
const base = `/app/${appId}/api/functions/${encodeURIComponent(functionKey)}`;
|
|
928
|
+
const path = options?.asSystem
|
|
929
|
+
? `${base}/invoke-as-system`
|
|
930
|
+
: options?.runtime === "task"
|
|
931
|
+
? `${base}/start`
|
|
932
|
+
: base;
|
|
933
|
+
const headers = {
|
|
934
|
+
Authorization: `Bearer ${options?.bearer ?? credentials.accessToken}`,
|
|
935
|
+
"Content-Type": "application/json",
|
|
936
|
+
};
|
|
937
|
+
// The CLI's own credentials reach an app-scoped route through the
|
|
938
|
+
// global-admin header; a minted user token does not need it and must not
|
|
939
|
+
// carry it.
|
|
940
|
+
if (!options?.bearer && credentials.globalAdminAppId) {
|
|
941
|
+
headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
|
|
942
|
+
}
|
|
943
|
+
const response = await fetchWithTLS(`${credentials.serverUrl}${path}`, {
|
|
944
|
+
method: "POST",
|
|
945
|
+
headers,
|
|
946
|
+
body: JSON.stringify(body),
|
|
947
|
+
});
|
|
948
|
+
const text = await response.text();
|
|
949
|
+
let parsed = null;
|
|
950
|
+
try {
|
|
951
|
+
parsed = text ? JSON.parse(text) : null;
|
|
952
|
+
}
|
|
953
|
+
catch {
|
|
954
|
+
// A body that is not JSON is still an answer; keep it readable.
|
|
955
|
+
parsed = { error: text };
|
|
956
|
+
}
|
|
957
|
+
const retryAfter = response.headers.get("retry-after");
|
|
958
|
+
const retryAfterSeconds = retryAfter && Number.isFinite(Number(retryAfter)) ? Number(retryAfter) : null;
|
|
959
|
+
return {
|
|
960
|
+
httpStatus: response.status,
|
|
961
|
+
retryAfterSeconds: retryAfterSeconds ??
|
|
962
|
+
(typeof parsed?.retryAfter === "number" ? parsed.retryAfter : null),
|
|
963
|
+
body: parsed,
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* A function's versions, newest first (#3462).
|
|
968
|
+
*
|
|
969
|
+
* Paging is opt-in on the server: with no `limit` and no `cursor` every
|
|
970
|
+
* version comes back, which is what this method did before paging existed and
|
|
971
|
+
* what an already-installed CLI still asks for. A caller that passes either
|
|
972
|
+
* gets the shared `{ items, hasMore, nextCursor }` page.
|
|
973
|
+
*/
|
|
974
|
+
async listFunctionConfigs(appId, functionId, options = {}) {
|
|
975
|
+
const query = new URLSearchParams();
|
|
976
|
+
if (options.limit !== undefined)
|
|
977
|
+
query.set("limit", String(options.limit));
|
|
978
|
+
if (options.cursor)
|
|
979
|
+
query.set("cursor", options.cursor);
|
|
980
|
+
const suffix = query.toString() ? `?${query.toString()}` : "";
|
|
981
|
+
const result = await this.get(`/admin/api/apps/${appId}/functions/${functionId}/configs${suffix}`);
|
|
982
|
+
return {
|
|
983
|
+
items: result?.items ?? [],
|
|
984
|
+
hasMore: result?.hasMore === true,
|
|
985
|
+
nextCursor: result?.nextCursor ?? null,
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* Point the function at one of its OWN existing versions (#3462). Creates no
|
|
990
|
+
* version; the activated version's capabilities, limits and triggers apply
|
|
991
|
+
* from the next call.
|
|
992
|
+
*/
|
|
993
|
+
async activateFunctionConfig(appId, functionId, configId) {
|
|
994
|
+
return this.post(`/admin/api/apps/${appId}/functions/${functionId}/configs/${configId}/activate`, {});
|
|
995
|
+
}
|
|
996
|
+
async getFunctionConfig(appId, functionId, configId) {
|
|
997
|
+
return this.get(`/admin/api/apps/${appId}/functions/${functionId}/configs/${configId}`);
|
|
998
|
+
}
|
|
999
|
+
/**
|
|
1000
|
+
* The stored envelope: the authored TOML bytes, every source file and the
|
|
1001
|
+
* built bundle, all base64. This is what `config pull` writes back verbatim.
|
|
1002
|
+
*/
|
|
1003
|
+
async getFunctionConfigEnvelope(appId, functionId, configId) {
|
|
1004
|
+
return this.get(`/admin/api/apps/${appId}/functions/${functionId}/configs/${configId}/envelope`);
|
|
1005
|
+
}
|
|
1006
|
+
/** Push a config version. The server verifies `contentHash` itself. */
|
|
1007
|
+
async pushFunctionConfig(appId, functionId, payload) {
|
|
1008
|
+
return this.post(`/admin/api/apps/${appId}/functions/${functionId}/configs`, payload);
|
|
1009
|
+
}
|
|
681
1010
|
/** #2803 — the operational verb pair for a prompt (admin API). */
|
|
682
1011
|
async setPromptAvailability(appId, promptId, action) {
|
|
683
1012
|
return this.post(`/admin/api/apps/${appId}/prompts/${promptId}/${action}`, {});
|
|
@@ -717,6 +1046,7 @@ export class ApiClient {
|
|
|
717
1046
|
});
|
|
718
1047
|
return {
|
|
719
1048
|
items: result?.items ?? [],
|
|
1049
|
+
hasMore: result?.hasMore,
|
|
720
1050
|
nextCursor: result?.nextCursor ?? null,
|
|
721
1051
|
};
|
|
722
1052
|
}
|
|
@@ -727,9 +1057,26 @@ export class ApiClient {
|
|
|
727
1057
|
});
|
|
728
1058
|
return {
|
|
729
1059
|
items: result?.items ?? [],
|
|
1060
|
+
hasMore: result?.hasMore,
|
|
1061
|
+
nextCursor: result?.nextCursor ?? null,
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* One page of admin sessions (#3885): the caller's own, or — for a
|
|
1066
|
+
* super-admin — the admin `adminId` names.
|
|
1067
|
+
*/
|
|
1068
|
+
async listAdminSessions(params) {
|
|
1069
|
+
const result = await this.get("/admin/api/auth/sessions", params);
|
|
1070
|
+
return {
|
|
1071
|
+
items: result?.items ?? [],
|
|
1072
|
+
hasMore: result?.hasMore,
|
|
730
1073
|
nextCursor: result?.nextCursor ?? null,
|
|
731
1074
|
};
|
|
732
1075
|
}
|
|
1076
|
+
/** Revoke an admin session (idempotent). */
|
|
1077
|
+
async revokeAdminSession(sessionId) {
|
|
1078
|
+
return this.delete(`/admin/api/auth/sessions/${encodeURIComponent(sessionId)}`);
|
|
1079
|
+
}
|
|
733
1080
|
async testWebhook(appId, webhookId, payload) {
|
|
734
1081
|
return this.post(`/admin/api/apps/${appId}/webhooks/${webhookId}/test`, payload || {});
|
|
735
1082
|
}
|
|
@@ -754,8 +1101,16 @@ export class ApiClient {
|
|
|
754
1101
|
async getLockStatus(appId, key) {
|
|
755
1102
|
return this.post(`/app/${appId}/api/locks/status`, { key });
|
|
756
1103
|
}
|
|
757
|
-
|
|
758
|
-
|
|
1104
|
+
/**
|
|
1105
|
+
* #3562 — `owner` is sent only when one was given, so a call that names none
|
|
1106
|
+
* composes byte for byte the body it always did.
|
|
1107
|
+
*/
|
|
1108
|
+
async acquireLock(appId, key, ttlMs, owner) {
|
|
1109
|
+
return this.post(`/app/${appId}/api/locks/acquire`, {
|
|
1110
|
+
key,
|
|
1111
|
+
ttlMs,
|
|
1112
|
+
...(owner !== undefined ? { owner } : {}),
|
|
1113
|
+
});
|
|
759
1114
|
}
|
|
760
1115
|
async releaseLock(appId, key, handleId) {
|
|
761
1116
|
return this.post(`/app/${appId}/api/locks/release`, {
|
|
@@ -862,6 +1217,7 @@ export class ApiClient {
|
|
|
862
1217
|
const result = await this.get(`/admin/api/apps/${appId}/prompts`, params);
|
|
863
1218
|
return {
|
|
864
1219
|
items: result?.items ?? [],
|
|
1220
|
+
hasMore: result?.hasMore,
|
|
865
1221
|
nextCursor: result?.nextCursor ?? null,
|
|
866
1222
|
};
|
|
867
1223
|
}
|
|
@@ -1009,6 +1365,7 @@ export class ApiClient {
|
|
|
1009
1365
|
const result = await this.get(`/admin/api/apps/${appId}/blocks/${blockType}/${blockId}/test-cases${suffix}`);
|
|
1010
1366
|
return {
|
|
1011
1367
|
items: result?.items ?? result ?? [],
|
|
1368
|
+
hasMore: result?.hasMore,
|
|
1012
1369
|
nextCursor: result?.nextCursor ?? undefined,
|
|
1013
1370
|
};
|
|
1014
1371
|
}
|
|
@@ -1160,12 +1517,23 @@ export class ApiClient {
|
|
|
1160
1517
|
const result = await this.get(`/admin/api/apps/${appId}/workflows`, params);
|
|
1161
1518
|
return {
|
|
1162
1519
|
items: result?.items ?? [],
|
|
1520
|
+
hasMore: result?.hasMore,
|
|
1163
1521
|
nextCursor: result?.nextCursor ?? null,
|
|
1164
1522
|
};
|
|
1165
1523
|
}
|
|
1166
1524
|
async getWorkflow(appId, workflowId) {
|
|
1167
1525
|
return this.get(`/admin/api/apps/${appId}/workflows/${workflowId}`);
|
|
1168
1526
|
}
|
|
1527
|
+
/**
|
|
1528
|
+
* One app's workflow step usage report (#3132).
|
|
1529
|
+
*
|
|
1530
|
+
* `windowDays` is omitted from the query when the caller did not ask for a
|
|
1531
|
+
* window, so the server applies its own default rather than the CLI
|
|
1532
|
+
* restating it on the wire.
|
|
1533
|
+
*/
|
|
1534
|
+
async getWorkflowUsage(appId, params) {
|
|
1535
|
+
return this.get(`/admin/api/apps/${appId}/workflows/usage`, params);
|
|
1536
|
+
}
|
|
1169
1537
|
async createWorkflow(appId, payload) {
|
|
1170
1538
|
return this.post(`/admin/api/apps/${appId}/workflows`, payload);
|
|
1171
1539
|
}
|
|
@@ -1327,12 +1695,27 @@ export class ApiClient {
|
|
|
1327
1695
|
// ============================================
|
|
1328
1696
|
// SUPER ADMIN: ADMINS
|
|
1329
1697
|
// ============================================
|
|
1698
|
+
/**
|
|
1699
|
+
* The signed-in admin's own profile, `role` included.
|
|
1700
|
+
*
|
|
1701
|
+
* The role is read from the server rather than from the cached credentials
|
|
1702
|
+
* file: a role changed after login would otherwise let a stale local record
|
|
1703
|
+
* decide an authorization question.
|
|
1704
|
+
*/
|
|
1705
|
+
async getMe() {
|
|
1706
|
+
return this.get("/admin/api/me");
|
|
1707
|
+
}
|
|
1330
1708
|
async listAllAdmins(params) {
|
|
1331
1709
|
const result = await this.get("/admin/api/admins", params);
|
|
1332
1710
|
// #1316: prefer `nextCursor`; fall back to the deprecated `cursor` alias.
|
|
1333
1711
|
const nextCursor = result?.nextCursor ?? result?.cursor ?? null;
|
|
1712
|
+
const items = result?.items ?? result?.admins ?? [];
|
|
1334
1713
|
return {
|
|
1335
|
-
|
|
1714
|
+
// `items` is the key every list envelope is built from (#3646, #1982);
|
|
1715
|
+
// `admins` is the deprecated legacy name, read only as a fallback.
|
|
1716
|
+
items,
|
|
1717
|
+
admins: items,
|
|
1718
|
+
hasMore: result?.hasMore,
|
|
1336
1719
|
nextCursor,
|
|
1337
1720
|
cursor: nextCursor,
|
|
1338
1721
|
};
|
|
@@ -1367,6 +1750,7 @@ export class ApiClient {
|
|
|
1367
1750
|
const result = await this.get("/admin/api/catalog/prompts", params);
|
|
1368
1751
|
return {
|
|
1369
1752
|
items: result?.items ?? [],
|
|
1753
|
+
hasMore: result?.hasMore,
|
|
1370
1754
|
nextCursor: result?.nextCursor ?? null,
|
|
1371
1755
|
};
|
|
1372
1756
|
}
|
|
@@ -1386,6 +1770,7 @@ export class ApiClient {
|
|
|
1386
1770
|
const result = await this.get("/admin/api/catalog/integrations", params);
|
|
1387
1771
|
return {
|
|
1388
1772
|
items: result?.items ?? [],
|
|
1773
|
+
hasMore: result?.hasMore,
|
|
1389
1774
|
nextCursor: result?.nextCursor ?? null,
|
|
1390
1775
|
};
|
|
1391
1776
|
}
|
|
@@ -1477,30 +1862,42 @@ export class ApiClient {
|
|
|
1477
1862
|
* `options.owner` narrows the result to the databases that user created; the
|
|
1478
1863
|
* server resolves it through the `databasesByCreator` index (#1965).
|
|
1479
1864
|
*/
|
|
1865
|
+
/**
|
|
1866
|
+
* One page of an app's databases (#3646).
|
|
1867
|
+
*
|
|
1868
|
+
* `databases list` used to walk the chain here. The route pages, so the page
|
|
1869
|
+
* boundary belongs to the caller: the verb declares `--limit`/`--cursor` and
|
|
1870
|
+
* prints what came back.
|
|
1871
|
+
*/
|
|
1480
1872
|
async listDatabases(appId, options) {
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
}
|
|
1873
|
+
const params = new URLSearchParams();
|
|
1874
|
+
if (options?.owner)
|
|
1875
|
+
params.set("owner", options.owner);
|
|
1876
|
+
if (options?.limit !== undefined)
|
|
1877
|
+
params.set("limit", String(options.limit));
|
|
1878
|
+
if (options?.cursor)
|
|
1879
|
+
params.set("cursor", options.cursor);
|
|
1880
|
+
const qs = params.toString() ? `?${params.toString()}` : "";
|
|
1881
|
+
const resp = await this.get(`/app/${appId}/api/databases${qs}`);
|
|
1882
|
+
// Tolerant read: a server older than #1958 answers with a bare array.
|
|
1883
|
+
// Without this a published CLI pointed at a not-yet-upgraded server would
|
|
1884
|
+
// print nothing at all. Such a server also predates the `owner` filter
|
|
1885
|
+
// (#1965) and ignores the unknown query parameter, so apply the filter
|
|
1886
|
+
// here — the legacy rows carry `createdBy`, so this reproduces the
|
|
1887
|
+
// server's own post-join filter (#2245). It has no pages either.
|
|
1888
|
+
if (Array.isArray(resp)) {
|
|
1889
|
+
return {
|
|
1890
|
+
items: options?.owner
|
|
1891
|
+
? resp.filter((db) => db?.createdBy === options.owner)
|
|
1892
|
+
: resp,
|
|
1893
|
+
hasMore: false,
|
|
1894
|
+
};
|
|
1895
|
+
}
|
|
1896
|
+
return {
|
|
1897
|
+
items: resp?.items ?? [],
|
|
1898
|
+
hasMore: resp?.hasMore,
|
|
1899
|
+
nextCursor: resp?.nextCursor ?? null,
|
|
1900
|
+
};
|
|
1504
1901
|
}
|
|
1505
1902
|
async createDatabase(appId, data) {
|
|
1506
1903
|
return this.post(`/app/${appId}/api/databases`, data);
|
|
@@ -1519,19 +1916,35 @@ export class ApiClient {
|
|
|
1519
1916
|
*
|
|
1520
1917
|
* The HTTP path stays `/metadata` because the wire field name is still
|
|
1521
1918
|
* `metadata`; only the client/CLI-facing helper names were reframed.
|
|
1919
|
+
*
|
|
1920
|
+
* @deprecated Use resource metadata categories instead (`primitive metadata
|
|
1921
|
+
* get database <id> <category>`, #1420, #1815).
|
|
1522
1922
|
*/
|
|
1523
1923
|
async getDatabaseCelContext(appId, databaseId) {
|
|
1524
1924
|
return this.get(`/app/${appId}/api/databases/${databaseId}/metadata`);
|
|
1525
1925
|
}
|
|
1526
|
-
/**
|
|
1926
|
+
/**
|
|
1927
|
+
* Update a database's CEL context dict (merge with existing).
|
|
1928
|
+
*
|
|
1929
|
+
* @deprecated Use resource metadata categories instead (`primitive metadata
|
|
1930
|
+
* set database <id> <category>`, #1420, #1815).
|
|
1931
|
+
*/
|
|
1527
1932
|
async updateDatabaseCelContext(appId, databaseId, celContext) {
|
|
1528
1933
|
return this.patch(`/app/${appId}/api/databases/${databaseId}/metadata`, celContext);
|
|
1529
1934
|
}
|
|
1530
|
-
/**
|
|
1935
|
+
/**
|
|
1936
|
+
* @deprecated Database CEL context is deprecated; use resource metadata
|
|
1937
|
+
* categories instead (`primitive metadata get database <id> <category>`,
|
|
1938
|
+
* #1420, #1815).
|
|
1939
|
+
*/
|
|
1531
1940
|
async getDatabaseMetadata(appId, databaseId) {
|
|
1532
1941
|
return this.getDatabaseCelContext(appId, databaseId);
|
|
1533
1942
|
}
|
|
1534
|
-
/**
|
|
1943
|
+
/**
|
|
1944
|
+
* @deprecated Database CEL context is deprecated; use resource metadata
|
|
1945
|
+
* categories instead (`primitive metadata set database <id> <category>`,
|
|
1946
|
+
* #1420, #1815).
|
|
1947
|
+
*/
|
|
1535
1948
|
async updateDatabaseMetadata(appId, databaseId, metadata) {
|
|
1536
1949
|
return this.updateDatabaseCelContext(appId, databaseId, metadata);
|
|
1537
1950
|
}
|
|
@@ -1884,9 +2297,20 @@ export class ApiClient {
|
|
|
1884
2297
|
// ============================================
|
|
1885
2298
|
// GROUP MEMBERS
|
|
1886
2299
|
// ============================================
|
|
2300
|
+
/**
|
|
2301
|
+
* One page of a group's members.
|
|
2302
|
+
*
|
|
2303
|
+
* The route paginates cursor-style (no default limit, cap 200) and reports
|
|
2304
|
+
* the boundary, so `groups members list` carries `--limit`/`--cursor` and
|
|
2305
|
+
* forwards them (#3646) rather than printing a first page that claims to be
|
|
2306
|
+
* the whole membership.
|
|
2307
|
+
*/
|
|
1887
2308
|
async listGroupMembers(appId, groupType, groupId, options) {
|
|
1888
|
-
const
|
|
1889
|
-
|
|
2309
|
+
const result = await this.get(`/app/${appId}/api/groups/${groupType}/${groupId}/members`, {
|
|
2310
|
+
...(options?.include ? { include: options.include } : {}),
|
|
2311
|
+
...(options?.limit !== undefined ? { limit: options.limit } : {}),
|
|
2312
|
+
...(options?.cursor ? { cursor: options.cursor } : {}),
|
|
2313
|
+
});
|
|
1890
2314
|
// #1316: prefer `nextCursor`; fall back to the deprecated `cursor` alias
|
|
1891
2315
|
// so the CLI keeps paginating against pre-#1316 servers.
|
|
1892
2316
|
const nextCursor = result?.nextCursor ?? result?.cursor;
|
|
@@ -1940,6 +2364,7 @@ export class ApiClient {
|
|
|
1940
2364
|
const result = await this.get(`/app/${appId}/api/collections${q ? `?${q}` : ""}`);
|
|
1941
2365
|
return {
|
|
1942
2366
|
items: result?.items ?? [],
|
|
2367
|
+
hasMore: result?.hasMore,
|
|
1943
2368
|
nextCursor: result?.nextCursor ?? result?.cursor ?? null,
|
|
1944
2369
|
};
|
|
1945
2370
|
}
|
|
@@ -1953,6 +2378,7 @@ export class ApiClient {
|
|
|
1953
2378
|
const result = await this.get(`/app/${appId}/api/admin/collections${q ? `?${q}` : ""}`);
|
|
1954
2379
|
return {
|
|
1955
2380
|
items: result?.items ?? [],
|
|
2381
|
+
hasMore: result?.hasMore,
|
|
1956
2382
|
nextCursor: result?.nextCursor ?? result?.cursor ?? null,
|
|
1957
2383
|
};
|
|
1958
2384
|
}
|
|
@@ -1981,6 +2407,7 @@ export class ApiClient {
|
|
|
1981
2407
|
const result = await this.get(`/app/${appId}/api/collections/${collectionId}/documents${q ? `?${q}` : ""}`);
|
|
1982
2408
|
return {
|
|
1983
2409
|
items: result?.items ?? [],
|
|
2410
|
+
hasMore: result?.hasMore,
|
|
1984
2411
|
nextCursor: result?.nextCursor ?? result?.cursor ?? null,
|
|
1985
2412
|
};
|
|
1986
2413
|
}
|
|
@@ -2133,9 +2560,82 @@ export class ApiClient {
|
|
|
2133
2560
|
async exportDocumentState(appId, documentId) {
|
|
2134
2561
|
return this.get(`/app/${appId}/api/documents/${documentId}/export/state`);
|
|
2135
2562
|
}
|
|
2563
|
+
/**
|
|
2564
|
+
* The export chain of a large document (#2816, behavior 20): the latest
|
|
2565
|
+
* completed snapshot, the sealed overlays after its epoch, and the open
|
|
2566
|
+
* epoch's overlay inline. Every other artifact arrives as an expiring
|
|
2567
|
+
* signature, read through {@link downloadDocumentArtifact}.
|
|
2568
|
+
*/
|
|
2569
|
+
async exportDocumentChain(appId, documentId) {
|
|
2570
|
+
return this.get(`/app/${appId}/api/documents/${documentId}/export/chain`);
|
|
2571
|
+
}
|
|
2572
|
+
/**
|
|
2573
|
+
* Read one granted artifact of a large document.
|
|
2574
|
+
*
|
|
2575
|
+
* The path comes from the chain and is origin-relative — the room is a
|
|
2576
|
+
* Durable Object and has no reliable idea which host the caller reached it
|
|
2577
|
+
* through — and the signature in it is the whole authorization, so no key is
|
|
2578
|
+
* ever composed here.
|
|
2579
|
+
*/
|
|
2580
|
+
async downloadDocumentArtifact(_appId, grantedPath) {
|
|
2581
|
+
const credentials = await this.ensureAuthenticated();
|
|
2582
|
+
const url = `${credentials.serverUrl}${grantedPath}`;
|
|
2583
|
+
const response = await fetchWithTLS(url);
|
|
2584
|
+
if (!response.ok) {
|
|
2585
|
+
// #3403 — through the shared parser: the refusal this reads is the app
|
|
2586
|
+
// API's coded envelope, and `statusText` carried none of it.
|
|
2587
|
+
throw await blobFailure(response, "Failed to download document artifact: ");
|
|
2588
|
+
}
|
|
2589
|
+
return Buffer.from(await response.arrayBuffer());
|
|
2590
|
+
}
|
|
2136
2591
|
async importDocumentState(appId, documentId, stateBase64) {
|
|
2137
2592
|
return this.post(`/app/${appId}/api/documents/${documentId}/import/state`, { state: stateBase64 });
|
|
2138
2593
|
}
|
|
2594
|
+
/**
|
|
2595
|
+
* Upload one artifact of a large document's import chain (#2816,
|
|
2596
|
+
* behavior 20).
|
|
2597
|
+
*
|
|
2598
|
+
* The bytes travel raw and unmodified: the manifest's digest is over the
|
|
2599
|
+
* STORED bytes, so re-encoding a chunk here would fail its own verification
|
|
2600
|
+
* on install. The query says which artifact it is — the server derives the
|
|
2601
|
+
* key from the document it belongs to, so nothing here composes one.
|
|
2602
|
+
*/
|
|
2603
|
+
async uploadDocumentChainArtifact(appId, documentId, query, body) {
|
|
2604
|
+
const credentials = await this.ensureAuthenticated();
|
|
2605
|
+
const search = new URLSearchParams();
|
|
2606
|
+
for (const [key, value] of Object.entries(query)) {
|
|
2607
|
+
search.set(key, String(value));
|
|
2608
|
+
}
|
|
2609
|
+
const url = `${credentials.serverUrl}/app/${appId}/api/documents/${documentId}` +
|
|
2610
|
+
`/import/chain/artifact?${search.toString()}`;
|
|
2611
|
+
const headers = {
|
|
2612
|
+
Authorization: `Bearer ${credentials.accessToken}`,
|
|
2613
|
+
"Content-Type": "application/octet-stream",
|
|
2614
|
+
};
|
|
2615
|
+
if (credentials.globalAdminAppId) {
|
|
2616
|
+
headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
|
|
2617
|
+
}
|
|
2618
|
+
const response = await fetchWithTLS(url, {
|
|
2619
|
+
method: "POST",
|
|
2620
|
+
headers,
|
|
2621
|
+
body: body,
|
|
2622
|
+
});
|
|
2623
|
+
if (!response.ok) {
|
|
2624
|
+
// #3403 — through the shared parser: reading `error` alone dropped the
|
|
2625
|
+
// envelope's `code` and `details`, so the refusal reached the operator
|
|
2626
|
+
// (and `primitive documents import`) with nothing to branch on.
|
|
2627
|
+
throw await blobFailure(response, "Failed to upload import artifact: ");
|
|
2628
|
+
}
|
|
2629
|
+
return await response.json().catch(() => ({}));
|
|
2630
|
+
}
|
|
2631
|
+
/** Start installing an uploaded chain (#2816, behavior 20). */
|
|
2632
|
+
async installDocumentChain(appId, documentId, plan) {
|
|
2633
|
+
return this.post(`/app/${appId}/api/documents/${documentId}/import/chain`, plan);
|
|
2634
|
+
}
|
|
2635
|
+
/** How far a document's chain install has got (#2816, behavior 20). */
|
|
2636
|
+
async getDocumentChainInstall(appId, documentId) {
|
|
2637
|
+
return this.get(`/app/${appId}/api/documents/${documentId}/import/chain`);
|
|
2638
|
+
}
|
|
2139
2639
|
async getDocument(appId, documentId) {
|
|
2140
2640
|
return this.get(`/app/${appId}/api/documents/${documentId}`);
|
|
2141
2641
|
}
|
|
@@ -2157,6 +2657,18 @@ export class ApiClient {
|
|
|
2157
2657
|
async getDocumentSchema(appId, documentId) {
|
|
2158
2658
|
return this.get(`/app/${appId}/api/documents/${documentId}/schema`);
|
|
2159
2659
|
}
|
|
2660
|
+
/**
|
|
2661
|
+
* The `documentFormat` a records call states, as a query string to APPEND to
|
|
2662
|
+
* a path (#3764).
|
|
2663
|
+
*
|
|
2664
|
+
* `post`, `patch` and `delete` take no params object — only `get` does — so
|
|
2665
|
+
* for every verb but the reads the parameter rides the path. Empty when the
|
|
2666
|
+
* caller states nothing, so an unparameterised call's URL is byte-identical
|
|
2667
|
+
* to what it was.
|
|
2668
|
+
*/
|
|
2669
|
+
documentFormatQuery(documentFormat) {
|
|
2670
|
+
return documentFormat === undefined ? "" : `?documentFormat=${documentFormat}`;
|
|
2671
|
+
}
|
|
2160
2672
|
/**
|
|
2161
2673
|
* Query records in a document model (issue #1964 Phase 3). Reuses the
|
|
2162
2674
|
* server-side `GET documents/:id/records/:model` endpoint, which runs
|
|
@@ -2171,6 +2683,9 @@ export class ApiClient {
|
|
|
2171
2683
|
const query = {};
|
|
2172
2684
|
if (queryOptions?.filter)
|
|
2173
2685
|
query.filter = JSON.stringify(queryOptions.filter);
|
|
2686
|
+
if (queryOptions?.documentFormat !== undefined) {
|
|
2687
|
+
query.documentFormat = queryOptions.documentFormat;
|
|
2688
|
+
}
|
|
2174
2689
|
// Forward `limit` whenever the caller supplied one (including an invalid
|
|
2175
2690
|
// 0/negative) so the server validates and returns a clean 400 rather than
|
|
2176
2691
|
// silently applying the default.
|
|
@@ -2188,6 +2703,9 @@ export class ApiClient {
|
|
|
2188
2703
|
const query = {};
|
|
2189
2704
|
if (queryOptions?.filter)
|
|
2190
2705
|
query.filter = JSON.stringify(queryOptions.filter);
|
|
2706
|
+
if (queryOptions?.documentFormat !== undefined) {
|
|
2707
|
+
query.documentFormat = queryOptions.documentFormat;
|
|
2708
|
+
}
|
|
2191
2709
|
return this.get(`/app/${appId}/api/documents/${documentId}/records/${encodeURIComponent(modelName)}/count`, query);
|
|
2192
2710
|
}
|
|
2193
2711
|
/**
|
|
@@ -2200,8 +2718,9 @@ export class ApiClient {
|
|
|
2200
2718
|
* `{ field, contains }` membership grouping are database-only, which is why
|
|
2201
2719
|
* this signature is narrower than the databases one.
|
|
2202
2720
|
*/
|
|
2203
|
-
async aggregateDocumentRecords(appId, documentId, modelName, options) {
|
|
2204
|
-
return this.post(`/app/${appId}/api/documents/${documentId}/records/${encodeURIComponent(modelName)}/aggregate
|
|
2721
|
+
async aggregateDocumentRecords(appId, documentId, modelName, options, extra) {
|
|
2722
|
+
return this.post(`/app/${appId}/api/documents/${documentId}/records/${encodeURIComponent(modelName)}/aggregate` +
|
|
2723
|
+
this.documentFormatQuery(extra?.documentFormat), { options });
|
|
2205
2724
|
}
|
|
2206
2725
|
/**
|
|
2207
2726
|
* Fetch platform-vocabulary document statistics (issue #1964 Phase 3):
|
|
@@ -2210,6 +2729,111 @@ export class ApiClient {
|
|
|
2210
2729
|
async getDocumentStats(appId, documentId) {
|
|
2211
2730
|
return this.get(`/app/${appId}/api/documents/${documentId}/stats`);
|
|
2212
2731
|
}
|
|
2732
|
+
/**
|
|
2733
|
+
* A large document's snapshot builds, newest first, one page (#3432).
|
|
2734
|
+
* `GET documents/:id/snapshots` → `{ items, hasMore, nextCursor? }`.
|
|
2735
|
+
*/
|
|
2736
|
+
async listDocumentSnapshots(appId, documentId, options = {}) {
|
|
2737
|
+
const params = new URLSearchParams();
|
|
2738
|
+
if (options.limit !== undefined)
|
|
2739
|
+
params.set("limit", String(options.limit));
|
|
2740
|
+
if (options.cursor)
|
|
2741
|
+
params.set("cursor", options.cursor);
|
|
2742
|
+
const query = params.toString();
|
|
2743
|
+
return this.get(`/app/${appId}/api/documents/${documentId}/snapshots${query ? `?${query}` : ""}`);
|
|
2744
|
+
}
|
|
2745
|
+
/**
|
|
2746
|
+
* A large document's bulk-load sessions, newest first (#3434, criterion 9).
|
|
2747
|
+
*
|
|
2748
|
+
* Read-only, through the document read helper's app-admin arm — the same
|
|
2749
|
+
* door the snapshot readers use. The action verb `documents ingest` is
|
|
2750
|
+
* #3435's; these two are the operator's window onto what a session did.
|
|
2751
|
+
*/
|
|
2752
|
+
async listDocumentIngests(appId, documentId, options = {}) {
|
|
2753
|
+
const params = new URLSearchParams();
|
|
2754
|
+
if (options.limit !== undefined)
|
|
2755
|
+
params.set("limit", String(options.limit));
|
|
2756
|
+
if (options.cursor)
|
|
2757
|
+
params.set("cursor", options.cursor);
|
|
2758
|
+
const query = params.toString();
|
|
2759
|
+
return this.get(`/app/${appId}/api/documents/${documentId}/ingest${query ? `?${query}` : ""}`);
|
|
2760
|
+
}
|
|
2761
|
+
/** One bulk-load session's status (#3434). */
|
|
2762
|
+
async getDocumentIngest(appId, documentId, sessionId) {
|
|
2763
|
+
return this.get(`/app/${appId}/api/documents/${documentId}/ingest/${encodeURIComponent(sessionId)}`);
|
|
2764
|
+
}
|
|
2765
|
+
/** Open a bulk-load session on a large document (#3435, criterion 9). */
|
|
2766
|
+
async createDocumentIngest(appId, documentId) {
|
|
2767
|
+
return this.post(`/app/${appId}/api/documents/${documentId}/ingest`, {});
|
|
2768
|
+
}
|
|
2769
|
+
/**
|
|
2770
|
+
* Upload one chunk of a bulk-load session's artifact (#3435).
|
|
2771
|
+
*
|
|
2772
|
+
* `multipart/form-data`: the eight descriptor fields as text parts and the
|
|
2773
|
+
* gzipped bytes as one file part named `chunk`. No `Content-Type` is set on
|
|
2774
|
+
* the request — fetch supplies the boundary, and a header written by hand
|
|
2775
|
+
* would name one the body does not use.
|
|
2776
|
+
*/
|
|
2777
|
+
async uploadDocumentIngestChunk(appId, documentId, sessionId, chunk) {
|
|
2778
|
+
const credentials = await this.ensureAuthenticated();
|
|
2779
|
+
const form = new FormData();
|
|
2780
|
+
form.set("model", chunk.model);
|
|
2781
|
+
form.set("index", String(chunk.index));
|
|
2782
|
+
form.set("rows", String(chunk.rows));
|
|
2783
|
+
form.set("bytes", String(chunk.bytes));
|
|
2784
|
+
form.set("rawBytes", String(chunk.rawBytes));
|
|
2785
|
+
form.set("sha256", chunk.sha256);
|
|
2786
|
+
form.set("firstId", chunk.firstId);
|
|
2787
|
+
form.set("lastId", chunk.lastId);
|
|
2788
|
+
form.set("chunk", new Blob([chunk.body]), `${chunk.index}.ndjson.gz`);
|
|
2789
|
+
const headers = {
|
|
2790
|
+
Authorization: `Bearer ${credentials.accessToken}`,
|
|
2791
|
+
};
|
|
2792
|
+
if (credentials.globalAdminAppId) {
|
|
2793
|
+
headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
|
|
2794
|
+
}
|
|
2795
|
+
const url = `${credentials.serverUrl}/app/${appId}/api/documents/${documentId}` +
|
|
2796
|
+
`/ingest/${encodeURIComponent(sessionId)}/chunks`;
|
|
2797
|
+
const response = await fetchWithTLS(url, {
|
|
2798
|
+
method: "POST",
|
|
2799
|
+
headers,
|
|
2800
|
+
body: form,
|
|
2801
|
+
});
|
|
2802
|
+
const text = await response.text();
|
|
2803
|
+
let result = {};
|
|
2804
|
+
try {
|
|
2805
|
+
result = text ? JSON.parse(text) : {};
|
|
2806
|
+
}
|
|
2807
|
+
catch {
|
|
2808
|
+
result = { error: text };
|
|
2809
|
+
}
|
|
2810
|
+
if (!response.ok) {
|
|
2811
|
+
throw new ApiError(result?.error || `Failed to upload chunk: ${response.statusText}`, response.status, result?.code, result?.details);
|
|
2812
|
+
}
|
|
2813
|
+
return result;
|
|
2814
|
+
}
|
|
2815
|
+
/** Freeze a session's manifest and hand it to the pipeline (#3435). */
|
|
2816
|
+
async commitDocumentIngest(appId, documentId, sessionId) {
|
|
2817
|
+
return this.post(`/app/${appId}/api/documents/${documentId}/ingest/${encodeURIComponent(sessionId)}/commit`, {});
|
|
2818
|
+
}
|
|
2819
|
+
/** Give a session up; allowed until the swap (#3435). */
|
|
2820
|
+
async abortDocumentIngest(appId, documentId, sessionId) {
|
|
2821
|
+
return this.post(`/app/${appId}/api/documents/${documentId}/ingest/${encodeURIComponent(sessionId)}/abort`, {});
|
|
2822
|
+
}
|
|
2823
|
+
/** One snapshot build with its verification result (#3432). */
|
|
2824
|
+
async getDocumentSnapshot(appId, documentId, buildId) {
|
|
2825
|
+
return this.get(`/app/${appId}/api/documents/${documentId}/snapshots/${encodeURIComponent(buildId)}`);
|
|
2826
|
+
}
|
|
2827
|
+
/**
|
|
2828
|
+
* Snapshot a large document now (#3666): seal the open epoch and start the
|
|
2829
|
+
* base build that seal arms.
|
|
2830
|
+
*
|
|
2831
|
+
* Answers either the epoch that was sealed with the build it armed, or
|
|
2832
|
+
* `{ sealed: false, reason: "empty" }` for an epoch that carried nothing.
|
|
2833
|
+
*/
|
|
2834
|
+
async requestDocumentSnapshot(appId, documentId) {
|
|
2835
|
+
return this.post(`/app/${appId}/api/documents/${documentId}/snapshots`, {});
|
|
2836
|
+
}
|
|
2213
2837
|
/**
|
|
2214
2838
|
* Create or replace a record in a document model (issue #1964 Phase 4).
|
|
2215
2839
|
* `POST documents/:id/records/:model` with `{ id, data, options? }` —
|
|
@@ -2217,24 +2841,27 @@ export class ApiClient {
|
|
|
2217
2841
|
* broadcasts to connected clients). Requires read-write+ on the document
|
|
2218
2842
|
* or the app-admin arm. Returns `{ record }`.
|
|
2219
2843
|
*/
|
|
2220
|
-
async saveDocumentRecord(appId, documentId, modelName, body) {
|
|
2221
|
-
return this.post(`/app/${appId}/api/documents/${documentId}/records/${encodeURIComponent(modelName)}
|
|
2844
|
+
async saveDocumentRecord(appId, documentId, modelName, body, extra) {
|
|
2845
|
+
return this.post(`/app/${appId}/api/documents/${documentId}/records/${encodeURIComponent(modelName)}` +
|
|
2846
|
+
this.documentFormatQuery(extra?.documentFormat), body);
|
|
2222
2847
|
}
|
|
2223
2848
|
/**
|
|
2224
2849
|
* Merge fields into an existing record (issue #1964 Phase 4).
|
|
2225
2850
|
* `PATCH documents/:id/records/:model/:recordId` with `{ data, options? }`.
|
|
2226
2851
|
* Returns `{ record }`.
|
|
2227
2852
|
*/
|
|
2228
|
-
async patchDocumentRecord(appId, documentId, modelName, recordId, body) {
|
|
2229
|
-
return this.patch(`/app/${appId}/api/documents/${documentId}/records/${encodeURIComponent(modelName)}/${encodeURIComponent(recordId)}
|
|
2853
|
+
async patchDocumentRecord(appId, documentId, modelName, recordId, body, extra) {
|
|
2854
|
+
return this.patch(`/app/${appId}/api/documents/${documentId}/records/${encodeURIComponent(modelName)}/${encodeURIComponent(recordId)}` +
|
|
2855
|
+
this.documentFormatQuery(extra?.documentFormat), body);
|
|
2230
2856
|
}
|
|
2231
2857
|
/**
|
|
2232
2858
|
* Delete a record from a document model (issue #1964 Phase 4).
|
|
2233
2859
|
* `DELETE documents/:id/records/:model/:recordId`. A missing record id is
|
|
2234
2860
|
* a silent no-op per the facade contract. Returns `{ deleted: true }`.
|
|
2235
2861
|
*/
|
|
2236
|
-
async deleteDocumentRecord(appId, documentId, modelName, recordId) {
|
|
2237
|
-
return this.delete(`/app/${appId}/api/documents/${documentId}/records/${encodeURIComponent(modelName)}/${encodeURIComponent(recordId)}`
|
|
2862
|
+
async deleteDocumentRecord(appId, documentId, modelName, recordId, extra) {
|
|
2863
|
+
return this.delete(`/app/${appId}/api/documents/${documentId}/records/${encodeURIComponent(modelName)}/${encodeURIComponent(recordId)}` +
|
|
2864
|
+
this.documentFormatQuery(extra?.documentFormat));
|
|
2238
2865
|
}
|
|
2239
2866
|
/**
|
|
2240
2867
|
* Apply an ordered multi-model create/patch/delete blob atomically (issue
|
|
@@ -2242,8 +2869,9 @@ export class ApiClient {
|
|
|
2242
2869
|
* a validation failure commits nothing. Returns the facade's
|
|
2243
2870
|
* `{ applied, added, updated, deleted }`.
|
|
2244
2871
|
*/
|
|
2245
|
-
async bulkDocumentRecords(appId, documentId, operations) {
|
|
2246
|
-
return this.post(`/app/${appId}/api/documents/${documentId}/records/bulk
|
|
2872
|
+
async bulkDocumentRecords(appId, documentId, operations, extra) {
|
|
2873
|
+
return this.post(`/app/${appId}/api/documents/${documentId}/records/bulk` +
|
|
2874
|
+
this.documentFormatQuery(extra?.documentFormat), { operations });
|
|
2247
2875
|
}
|
|
2248
2876
|
async createDocument(appId, data) {
|
|
2249
2877
|
return this.post(`/app/${appId}/api/documents`, data);
|
|
@@ -2272,6 +2900,16 @@ export class ApiClient {
|
|
|
2272
2900
|
}
|
|
2273
2901
|
return this.delete(`/app/${appId}/api/documents/${documentId}/permissions/${target.userId}`);
|
|
2274
2902
|
}
|
|
2903
|
+
/**
|
|
2904
|
+
* Resolve a NAMED user's effective access to a document (#3658).
|
|
2905
|
+
*
|
|
2906
|
+
* The admin token is admitted to name a subject (it can already read every
|
|
2907
|
+
* grant, group permission and membership the answer derives from), which is
|
|
2908
|
+
* what makes this an operator read rather than a per-user one.
|
|
2909
|
+
*/
|
|
2910
|
+
async validateDocumentAccessForUser(appId, documentId, userId) {
|
|
2911
|
+
return this.post(`/app/${appId}/api/documents/${documentId}/validate-access`, { userId });
|
|
2912
|
+
}
|
|
2275
2913
|
async getDocumentLinkAccess(appId, documentId) {
|
|
2276
2914
|
return this.get(`/app/${appId}/api/documents/${documentId}/link-access`);
|
|
2277
2915
|
}
|
|
@@ -2281,8 +2919,14 @@ export class ApiClient {
|
|
|
2281
2919
|
async clearDocumentLinkAccess(appId, documentId) {
|
|
2282
2920
|
return this.delete(`/app/${appId}/api/documents/${documentId}/link-access`);
|
|
2283
2921
|
}
|
|
2284
|
-
|
|
2285
|
-
|
|
2922
|
+
/**
|
|
2923
|
+
* Pending deferred grants for a document (#2951 — replaces the removed
|
|
2924
|
+
* legacy `GET /documents/:id/invitations`). Returns the `{ items }`
|
|
2925
|
+
* envelope; rows are already filtered server-side to unresolved, unexpired
|
|
2926
|
+
* grants whose app invitation is still outstanding.
|
|
2927
|
+
*/
|
|
2928
|
+
async listDocumentPendingInvitations(appId, documentId) {
|
|
2929
|
+
return this.get(`/app/${appId}/api/documents/${documentId}/pending-invitations`);
|
|
2286
2930
|
}
|
|
2287
2931
|
async listDocumentBlobs(appId, documentId) {
|
|
2288
2932
|
return this.get(`/app/${appId}/api/documents/${documentId}/blobs`);
|
|
@@ -2298,7 +2942,7 @@ export class ApiClient {
|
|
|
2298
2942
|
}
|
|
2299
2943
|
const response = await fetchWithTLS(url, { headers });
|
|
2300
2944
|
if (!response.ok) {
|
|
2301
|
-
throw
|
|
2945
|
+
throw await blobFailure(response, "Failed to download blob: ");
|
|
2302
2946
|
}
|
|
2303
2947
|
const arrayBuffer = await response.arrayBuffer();
|
|
2304
2948
|
return Buffer.from(arrayBuffer);
|
|
@@ -2322,8 +2966,7 @@ export class ApiClient {
|
|
|
2322
2966
|
body: data,
|
|
2323
2967
|
});
|
|
2324
2968
|
if (!response.ok) {
|
|
2325
|
-
|
|
2326
|
-
throw new ApiError(`Failed to upload blob: ${text}`, response.status);
|
|
2969
|
+
throw await blobFailure(response, "Failed to upload blob: ");
|
|
2327
2970
|
}
|
|
2328
2971
|
return response.json();
|
|
2329
2972
|
}
|
|
@@ -2348,9 +2991,75 @@ export class ApiClient {
|
|
|
2348
2991
|
async lookupUserByEmail(appId, email) {
|
|
2349
2992
|
return this.get(`/app/${appId}/api/users/lookup`, { email });
|
|
2350
2993
|
}
|
|
2994
|
+
/**
|
|
2995
|
+
* The admin inventory of one user's documents, followed to the end (#3399).
|
|
2996
|
+
*
|
|
2997
|
+
* The endpoint speaks the standard list contract (`{ items, hasMore,
|
|
2998
|
+
* nextCursor }`) and caps a page, so one request reads one page: reading it
|
|
2999
|
+
* would truncate a heavy user's inventory at the page size, which is the
|
|
3000
|
+
* bug this issue is about with a smaller number on it. `documents list
|
|
3001
|
+
* --user-id` and `documents export-all` both read this, and both mean "all
|
|
3002
|
+
* of them". `paginateAll` carries the repeat-cursor and max-page guards, so
|
|
3003
|
+
* a server handing out a non-advancing cursor fails loudly instead of
|
|
3004
|
+
* looping forever.
|
|
3005
|
+
*
|
|
3006
|
+
* Tolerant of a pre-contract server, which answers with a bare
|
|
3007
|
+
* `{ documents }` array and no cursor: that reads as a single page.
|
|
3008
|
+
*/
|
|
2351
3009
|
async listAdminDocuments(appId, userId) {
|
|
2352
|
-
|
|
2353
|
-
|
|
3010
|
+
return paginateAll(async (cursor) => {
|
|
3011
|
+
const page = await this.listAdminDocumentsPage(appId, userId, {
|
|
3012
|
+
limit: ADMIN_INVENTORY_PAGE_SIZE,
|
|
3013
|
+
cursor,
|
|
3014
|
+
});
|
|
3015
|
+
return { items: page.items, nextCursor: page.nextCursor };
|
|
3016
|
+
});
|
|
3017
|
+
}
|
|
3018
|
+
/**
|
|
3019
|
+
* One page of the admin inventory of a user's documents (#3646).
|
|
3020
|
+
*
|
|
3021
|
+
* `documents list --user-id` prints exactly this — the page the caller's
|
|
3022
|
+
* `--limit`/`--cursor` asked for, with the server's own page boundary
|
|
3023
|
+
* beside it. `documents export-all`, which really does mean every document,
|
|
3024
|
+
* keeps the walking `listAdminDocuments()` above.
|
|
3025
|
+
*
|
|
3026
|
+
* Tolerant of a pre-contract server, which answers with a bare
|
|
3027
|
+
* `{ documents }` array and no cursor: that reads as a single page.
|
|
3028
|
+
*/
|
|
3029
|
+
async listAdminDocumentsPage(appId, userId, options = {}) {
|
|
3030
|
+
const params = new URLSearchParams({ userId });
|
|
3031
|
+
if (options.limit !== undefined)
|
|
3032
|
+
params.set("limit", String(options.limit));
|
|
3033
|
+
if (options.cursor)
|
|
3034
|
+
params.set("cursor", options.cursor);
|
|
3035
|
+
const result = await this.get(`/admin/api/apps/${appId}/documents?${params.toString()}`);
|
|
3036
|
+
return {
|
|
3037
|
+
items: result?.items ?? result?.documents ?? [],
|
|
3038
|
+
hasMore: result?.hasMore,
|
|
3039
|
+
// #1316: prefer `nextCursor`; the deprecated `cursor` alias keeps a
|
|
3040
|
+
// shipped CLI paginating against a server that still dual-emits.
|
|
3041
|
+
nextCursor: result?.nextCursor ?? result?.cursor ?? null,
|
|
3042
|
+
};
|
|
3043
|
+
}
|
|
3044
|
+
/**
|
|
3045
|
+
* The document `AppUser.rootDocId` points at, or null when the user has none
|
|
3046
|
+
* yet (#3135). A user who is not a member of the app is a 404, which
|
|
3047
|
+
* `documents import` reads as "this export has no target here" — distinct
|
|
3048
|
+
* from "this user has no root document yet".
|
|
3049
|
+
*/
|
|
3050
|
+
async getUserRootDocument(appId, userId) {
|
|
3051
|
+
return this.get(`/admin/api/apps/${appId}/users/${userId}/root-document`);
|
|
3052
|
+
}
|
|
3053
|
+
/**
|
|
3054
|
+
* Get or create the user's root document (#3135).
|
|
3055
|
+
*
|
|
3056
|
+
* The server runs the same idempotent get-or-create sign-in uses, so this
|
|
3057
|
+
* never mints a second root. `created` is true only when this call's claim
|
|
3058
|
+
* won — an import must not report "created", or merge without `--overwrite`,
|
|
3059
|
+
* for a root a concurrent sign-in minted first.
|
|
3060
|
+
*/
|
|
3061
|
+
async ensureUserRootDocument(appId, userId) {
|
|
3062
|
+
return this.post(`/admin/api/apps/${appId}/users/${userId}/root-document`, {});
|
|
2354
3063
|
}
|
|
2355
3064
|
// ============================================
|
|
2356
3065
|
// DATABASE EXPORT / IMPORT
|
|
@@ -2436,8 +3145,7 @@ export class ApiClient {
|
|
|
2436
3145
|
body: data,
|
|
2437
3146
|
});
|
|
2438
3147
|
if (!response.ok) {
|
|
2439
|
-
|
|
2440
|
-
throw new ApiError(`Failed to upload blob: ${text}`, response.status);
|
|
3148
|
+
throw await blobFailure(response, "Failed to upload blob: ");
|
|
2441
3149
|
}
|
|
2442
3150
|
return response.json();
|
|
2443
3151
|
}
|
|
@@ -2452,7 +3160,7 @@ export class ApiClient {
|
|
|
2452
3160
|
}
|
|
2453
3161
|
const response = await fetchWithTLS(url, { headers });
|
|
2454
3162
|
if (!response.ok) {
|
|
2455
|
-
throw
|
|
3163
|
+
throw await blobFailure(response, "Failed to download blob: ");
|
|
2456
3164
|
}
|
|
2457
3165
|
const arrayBuffer = await response.arrayBuffer();
|
|
2458
3166
|
return Buffer.from(arrayBuffer);
|
|
@@ -2476,6 +3184,4 @@ export class ApiClient {
|
|
|
2476
3184
|
return this.get(`/app/${appId}/api/blob-buckets/${encodeURIComponent(bucketIdOrKey)}/blobs/${blobId}/metadata`);
|
|
2477
3185
|
}
|
|
2478
3186
|
}
|
|
2479
|
-
// Export a singleton instance
|
|
2480
|
-
export const apiClient = new ApiClient();
|
|
2481
3187
|
//# sourceMappingURL=api-client.js.map
|