primitive-admin 1.2.0-alpha.0 → 1.2.0-alpha.10
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 +108 -136
- package/assets/skill/skills/primitive-platform/SKILL.md +37 -4
- package/dist/bin/primitive.js +30 -35
- package/dist/bin/primitive.js.map +1 -1
- package/dist/src/commands/agent-sessions.d.ts +133 -0
- package/dist/src/commands/agent-sessions.js +792 -0
- package/dist/src/commands/agent-sessions.js.map +1 -0
- package/dist/src/commands/analytics.js +0 -148
- package/dist/src/commands/analytics.js.map +1 -1
- package/dist/src/commands/apps-children.d.ts +54 -0
- package/dist/src/commands/apps-children.js +631 -0
- package/dist/src/commands/apps-children.js.map +1 -0
- package/dist/src/commands/apps.d.ts +6 -0
- package/dist/src/commands/apps.js +131 -9
- package/dist/src/commands/apps.js.map +1 -1
- package/dist/src/commands/auth-sessions.d.ts +3 -3
- package/dist/src/commands/auth-sessions.js +117 -23
- package/dist/src/commands/auth-sessions.js.map +1 -1
- package/dist/src/commands/auth.js +460 -36
- package/dist/src/commands/auth.js.map +1 -1
- package/dist/src/commands/collections.js +316 -256
- package/dist/src/commands/collections.js.map +1 -1
- package/dist/src/commands/config.d.ts +1 -1
- package/dist/src/commands/config.js +60 -28
- package/dist/src/commands/config.js.map +1 -1
- package/dist/src/commands/connections.js +12 -17
- package/dist/src/commands/connections.js.map +1 -1
- package/dist/src/commands/database-type-configs.js +2 -76
- package/dist/src/commands/database-type-configs.js.map +1 -1
- package/dist/src/commands/databases.d.ts +9 -0
- package/dist/src/commands/databases.js +228 -566
- package/dist/src/commands/databases.js.map +1 -1
- package/dist/src/commands/db3-types.d.ts +15 -0
- package/dist/src/commands/db3-types.js +157 -0
- package/dist/src/commands/db3-types.js.map +1 -0
- package/dist/src/commands/db3.d.ts +12 -0
- package/dist/src/commands/db3.js +1052 -0
- package/dist/src/commands/db3.js.map +1 -0
- package/dist/src/commands/documents.d.ts +121 -2
- package/dist/src/commands/documents.js +1601 -1251
- package/dist/src/commands/documents.js.map +1 -1
- package/dist/src/commands/env.d.ts +9 -1
- package/dist/src/commands/env.js +164 -49
- package/dist/src/commands/env.js.map +1 -1
- package/dist/src/commands/function-trigger-commands.d.ts +28 -0
- package/dist/src/commands/function-trigger-commands.js +341 -0
- package/dist/src/commands/function-trigger-commands.js.map +1 -0
- package/dist/src/commands/functions.js +297 -24
- package/dist/src/commands/functions.js.map +1 -1
- package/dist/src/commands/groups.js +150 -49
- package/dist/src/commands/groups.js.map +1 -1
- package/dist/src/commands/guides.js +5 -5
- package/dist/src/commands/init.d.ts +7 -0
- package/dist/src/commands/init.js +23 -5
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/integrations.js +14 -8
- package/dist/src/commands/integrations.js.map +1 -1
- package/dist/src/commands/metadata-category-configs.d.ts +2 -2
- package/dist/src/commands/metadata-category-configs.js +2 -2
- package/dist/src/commands/prompts.d.ts +7 -0
- package/dist/src/commands/prompts.js +48 -5
- package/dist/src/commands/prompts.js.map +1 -1
- package/dist/src/commands/scripts.d.ts +9 -17
- package/dist/src/commands/scripts.js +24 -479
- package/dist/src/commands/scripts.js.map +1 -1
- package/dist/src/commands/sessions.js +3 -8
- package/dist/src/commands/sessions.js.map +1 -1
- package/dist/src/commands/sync-app-settings.d.ts +36 -8
- package/dist/src/commands/sync-app-settings.js +109 -23
- package/dist/src/commands/sync-app-settings.js.map +1 -1
- package/dist/src/commands/sync.d.ts +436 -613
- package/dist/src/commands/sync.js +2604 -5977
- package/dist/src/commands/sync.js.map +1 -1
- package/dist/src/commands/tokens.js +4 -9
- package/dist/src/commands/tokens.js.map +1 -1
- package/dist/src/commands/users.js +77 -2
- package/dist/src/commands/users.js.map +1 -1
- package/dist/src/commands/workflows.d.ts +5 -83
- package/dist/src/commands/workflows.js +57 -2282
- package/dist/src/commands/workflows.js.map +1 -1
- package/dist/src/lib/aggregate-output.d.ts +25 -0
- package/dist/src/lib/aggregate-output.js +53 -0
- package/dist/src/lib/aggregate-output.js.map +1 -0
- package/dist/src/lib/api-client.d.ts +681 -581
- package/dist/src/lib/api-client.js +880 -851
- package/dist/src/lib/api-client.js.map +1 -1
- package/dist/src/lib/app-settings-descriptor.d.ts +47 -1
- package/dist/src/lib/app-settings-descriptor.js +114 -10
- package/dist/src/lib/app-settings-descriptor.js.map +1 -1
- package/dist/src/lib/auth-fixtures.d.ts +44 -0
- package/dist/src/lib/auth-fixtures.js +61 -0
- package/dist/src/lib/auth-fixtures.js.map +1 -0
- package/dist/src/lib/auth-flow.d.ts +41 -1
- package/dist/src/lib/auth-flow.js +70 -16
- package/dist/src/lib/auth-flow.js.map +1 -1
- package/dist/src/lib/block-layout.d.ts +12 -23
- package/dist/src/lib/block-layout.js +26 -92
- package/dist/src/lib/block-layout.js.map +1 -1
- package/dist/src/lib/block-selector.d.ts +2 -2
- package/dist/src/lib/block-selector.js +2 -2
- package/dist/src/lib/child-apps-local.d.ts +164 -0
- package/dist/src/lib/child-apps-local.js +258 -0
- package/dist/src/lib/child-apps-local.js.map +1 -0
- package/dist/src/lib/ci-session-request.d.ts +22 -0
- package/dist/src/lib/ci-session-request.js +51 -0
- package/dist/src/lib/ci-session-request.js.map +1 -0
- package/dist/src/lib/codegen-shared/generatedFiles.d.ts +10 -8
- package/dist/src/lib/codegen-shared/generatedFiles.js +21 -15
- package/dist/src/lib/codegen-shared/generatedFiles.js.map +1 -1
- package/dist/src/lib/codegen-shared/prettierStable.d.ts +1 -1
- package/dist/src/lib/codegen-shared/prettierStable.js +1 -1
- package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.d.ts +2 -2
- package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js +2 -2
- package/dist/src/lib/collection-export.d.ts +76 -15
- package/dist/src/lib/collection-export.js +116 -23
- package/dist/src/lib/collection-export.js.map +1 -1
- package/dist/src/lib/collection-principal.d.ts +22 -0
- package/dist/src/lib/collection-principal.js +35 -0
- package/dist/src/lib/collection-principal.js.map +1 -0
- package/dist/src/lib/config-object-descriptor.d.ts +3 -22
- package/dist/src/lib/config-object-descriptor.js +56 -291
- package/dist/src/lib/config-object-descriptor.js.map +1 -1
- package/dist/src/lib/config-payload.js +2 -3
- package/dist/src/lib/config-payload.js.map +1 -1
- package/dist/src/lib/config-surface.d.ts +5 -7
- package/dist/src/lib/config-surface.js +18 -40
- package/dist/src/lib/config-surface.js.map +1 -1
- package/dist/src/lib/connections-list.d.ts +10 -0
- package/dist/src/lib/connections-list.js +18 -0
- package/dist/src/lib/connections-list.js.map +1 -0
- package/dist/src/lib/credentials-home.d.ts +58 -0
- package/dist/src/lib/credentials-home.js +204 -0
- package/dist/src/lib/credentials-home.js.map +1 -0
- package/dist/src/lib/credentials-lock.d.ts +88 -0
- package/dist/src/lib/credentials-lock.js +302 -0
- package/dist/src/lib/credentials-lock.js.map +1 -0
- package/dist/src/lib/credentials-store.d.ts +179 -24
- package/dist/src/lib/credentials-store.js +712 -77
- package/dist/src/lib/credentials-store.js.map +1 -1
- package/dist/src/lib/csv.d.ts +8 -9
- package/dist/src/lib/csv.js +3 -4
- package/dist/src/lib/csv.js.map +1 -1
- package/dist/src/lib/custodian.d.ts +8 -0
- package/dist/src/lib/custodian.js +18 -0
- package/dist/src/lib/custodian.js.map +1 -0
- package/dist/src/lib/db-codegen/dbGenerator.js +5 -16
- package/dist/src/lib/db-codegen/dbGenerator.js.map +1 -1
- package/dist/src/lib/db-codegen/dbNaming.d.ts +5 -13
- package/dist/src/lib/db-codegen/dbNaming.js +5 -17
- package/dist/src/lib/db-codegen/dbNaming.js.map +1 -1
- package/dist/src/lib/db-codegen/dbTemplates.d.ts +7 -56
- package/dist/src/lib/db-codegen/dbTemplates.js +25 -125
- package/dist/src/lib/db-codegen/dbTemplates.js.map +1 -1
- package/dist/src/lib/db-codegen/dbTsTypes.d.ts +2 -2
- package/dist/src/lib/db-codegen/dbTsTypes.js +2 -2
- package/dist/src/lib/db-codegen/dbTypeIR.d.ts +67 -2
- package/dist/src/lib/db-codegen/dbTypeIR.js +13 -2
- package/dist/src/lib/db-codegen/dbTypeIR.js.map +1 -1
- package/dist/src/lib/db3-export.d.ts +231 -0
- package/dist/src/lib/db3-export.js +510 -0
- package/dist/src/lib/db3-export.js.map +1 -0
- package/dist/src/lib/db3-index-output.d.ts +27 -0
- package/dist/src/lib/db3-index-output.js +49 -0
- package/dist/src/lib/db3-index-output.js.map +1 -0
- package/dist/src/lib/document-export-lookups.d.ts +31 -0
- package/dist/src/lib/document-export-lookups.js +53 -0
- package/dist/src/lib/document-export-lookups.js.map +1 -0
- package/dist/src/lib/document-export-permissions.d.ts +7 -5
- package/dist/src/lib/document-export-permissions.js +7 -5
- package/dist/src/lib/document-export-permissions.js.map +1 -1
- package/dist/src/lib/document-export-record-log.d.ts +57 -0
- package/dist/src/lib/document-export-record-log.js +215 -0
- package/dist/src/lib/document-export-record-log.js.map +1 -0
- package/dist/src/lib/document-ingest-artifact.d.ts +26 -2
- package/dist/src/lib/document-ingest-artifact.js +42 -8
- package/dist/src/lib/document-ingest-artifact.js.map +1 -1
- package/dist/src/lib/document-ingest-format2.d.ts +36 -0
- package/dist/src/lib/document-ingest-format2.js +78 -0
- package/dist/src/lib/document-ingest-format2.js.map +1 -0
- package/dist/src/lib/document-ingest-input.d.ts +9 -0
- package/dist/src/lib/document-ingest-input.js +28 -1
- package/dist/src/lib/document-ingest-input.js.map +1 -1
- package/dist/src/lib/document-ingest-rows.d.ts +16 -0
- package/dist/src/lib/document-ingest-rows.js +17 -0
- package/dist/src/lib/document-ingest-rows.js.map +1 -1
- package/dist/src/lib/document-ingest.d.ts +33 -1
- package/dist/src/lib/document-ingest.js +49 -0
- package/dist/src/lib/document-ingest.js.map +1 -1
- package/dist/src/lib/document-schema-rows.d.ts +42 -0
- package/dist/src/lib/document-schema-rows.js +53 -0
- package/dist/src/lib/document-schema-rows.js.map +1 -0
- package/dist/src/lib/env-resolver-core.d.ts +70 -2
- package/dist/src/lib/env-resolver-core.js +137 -20
- package/dist/src/lib/env-resolver-core.js.map +1 -1
- package/dist/src/lib/env-resolver.d.ts +14 -2
- package/dist/src/lib/env-resolver.js +32 -10
- package/dist/src/lib/env-resolver.js.map +1 -1
- package/dist/src/lib/function-authorize-check.d.ts +142 -0
- package/dist/src/lib/function-authorize-check.js +310 -0
- package/dist/src/lib/function-authorize-check.js.map +1 -0
- package/dist/src/lib/function-authorize-view.d.ts +15 -0
- package/dist/src/lib/function-authorize-view.js +50 -0
- package/dist/src/lib/function-authorize-view.js.map +1 -0
- package/dist/src/lib/function-bundle.d.ts +38 -18
- package/dist/src/lib/function-bundle.js +96 -35
- package/dist/src/lib/function-bundle.js.map +1 -1
- package/dist/src/lib/function-collect.d.ts +75 -12
- package/dist/src/lib/function-collect.js +427 -12
- package/dist/src/lib/function-collect.js.map +1 -1
- package/dist/src/lib/function-db-types.d.ts +29 -0
- package/dist/src/lib/function-db-types.js +117 -7
- package/dist/src/lib/function-db-types.js.map +1 -1
- package/dist/src/lib/function-document-types.d.ts +5 -3
- package/dist/src/lib/function-document-types.js +10 -14
- package/dist/src/lib/function-document-types.js.map +1 -1
- package/dist/src/lib/function-lint.d.ts +34 -0
- package/dist/src/lib/function-lint.js +112 -0
- package/dist/src/lib/function-lint.js.map +1 -0
- package/dist/src/lib/function-log-row.js +21 -2
- package/dist/src/lib/function-log-row.js.map +1 -1
- package/dist/src/lib/function-run.d.ts +16 -21
- package/dist/src/lib/function-run.js +37 -30
- package/dist/src/lib/function-run.js.map +1 -1
- package/dist/src/lib/function-schema-codegen.d.ts +1 -1
- package/dist/src/lib/function-schema-codegen.js +5 -5
- package/dist/src/lib/function-schema-codegen.js.map +1 -1
- package/dist/src/lib/function-source-roots.d.ts +67 -0
- package/dist/src/lib/function-source-roots.js +148 -0
- package/dist/src/lib/function-source-roots.js.map +1 -0
- package/dist/src/lib/function-sync.d.ts +108 -22
- package/dist/src/lib/function-sync.js +127 -39
- package/dist/src/lib/function-sync.js.map +1 -1
- package/dist/src/lib/function-trigger-listing.d.ts +11 -6
- package/dist/src/lib/function-trigger-listing.js +32 -11
- package/dist/src/lib/function-trigger-listing.js.map +1 -1
- package/dist/src/lib/function-typecheck.d.ts +37 -2
- package/dist/src/lib/function-typecheck.js +210 -28
- package/dist/src/lib/function-typecheck.js.map +1 -1
- package/dist/src/lib/function-versions.d.ts +2 -0
- package/dist/src/lib/function-versions.js +1 -0
- package/dist/src/lib/function-versions.js.map +1 -1
- package/dist/src/lib/generated-allowlist.js +0 -20
- package/dist/src/lib/generated-allowlist.js.map +1 -1
- package/dist/src/lib/generated-config-surfaces.d.ts +703 -201
- package/dist/src/lib/generated-config-surfaces.js +2335 -2033
- package/dist/src/lib/generated-config-surfaces.js.map +1 -1
- package/dist/src/lib/generated-sdk-types.d.ts +1 -1
- package/dist/src/lib/generated-sdk-types.js +1 -1
- package/dist/src/lib/generated-sdk-types.js.map +1 -1
- package/dist/src/lib/init-adopt.js +12 -3
- package/dist/src/lib/init-adopt.js.map +1 -1
- package/dist/src/lib/init-login-note.d.ts +13 -0
- package/dist/src/lib/init-login-note.js +22 -0
- package/dist/src/lib/init-login-note.js.map +1 -0
- package/dist/src/lib/json-value.d.ts +6 -0
- package/dist/src/lib/json-value.js +13 -0
- package/dist/src/lib/json-value.js.map +1 -0
- package/dist/src/lib/load-routes.d.ts +33 -0
- package/dist/src/lib/load-routes.js +35 -0
- package/dist/src/lib/load-routes.js.map +1 -0
- package/dist/src/lib/local-state.d.ts +38 -7
- package/dist/src/lib/local-state.js +172 -49
- package/dist/src/lib/local-state.js.map +1 -1
- package/dist/src/lib/local-test-cases.d.ts +5 -2
- package/dist/src/lib/local-test-cases.js +7 -4
- package/dist/src/lib/local-test-cases.js.map +1 -1
- package/dist/src/lib/log-inspection.d.ts +42 -4
- package/dist/src/lib/log-inspection.js +37 -5
- package/dist/src/lib/log-inspection.js.map +1 -1
- package/dist/src/lib/login-exchange-check.d.ts +18 -0
- package/dist/src/lib/login-exchange-check.js +27 -0
- package/dist/src/lib/login-exchange-check.js.map +1 -0
- package/dist/src/lib/login-scope-lines.d.ts +25 -0
- package/dist/src/lib/login-scope-lines.js +45 -0
- package/dist/src/lib/login-scope-lines.js.map +1 -0
- package/dist/src/lib/logout-admin-session.d.ts +2 -1
- package/dist/src/lib/logout-admin-session.js +7 -1
- package/dist/src/lib/logout-admin-session.js.map +1 -1
- package/dist/src/lib/object-status-filter.d.ts +9 -1
- package/dist/src/lib/object-status-filter.js +9 -1
- package/dist/src/lib/object-status-filter.js.map +1 -1
- package/dist/src/lib/output.js +2 -0
- package/dist/src/lib/output.js.map +1 -1
- package/dist/src/lib/paginate.d.ts +0 -5
- package/dist/src/lib/paginate.js +0 -5
- package/dist/src/lib/paginate.js.map +1 -1
- package/dist/src/lib/pin-refusal-hint.d.ts +12 -0
- package/dist/src/lib/pin-refusal-hint.js +35 -0
- package/dist/src/lib/pin-refusal-hint.js.map +1 -0
- package/dist/src/lib/primitive-env-plugin.d.ts +99 -0
- package/dist/src/lib/primitive-env-plugin.js +408 -0
- package/dist/src/lib/primitive-env-plugin.js.map +1 -0
- package/dist/src/lib/project-config.d.ts +7 -0
- package/dist/src/lib/project-config.js +22 -1
- package/dist/src/lib/project-config.js.map +1 -1
- package/dist/src/lib/project-login-apps.d.ts +8 -0
- package/dist/src/lib/project-login-apps.js +27 -0
- package/dist/src/lib/project-login-apps.js.map +1 -0
- package/dist/src/lib/prompt-schema-codegen.d.ts +10 -3
- package/dist/src/lib/prompt-schema-codegen.js +52 -11
- package/dist/src/lib/prompt-schema-codegen.js.map +1 -1
- package/dist/src/lib/pull-write.d.ts +31 -0
- package/dist/src/lib/pull-write.js +49 -0
- package/dist/src/lib/pull-write.js.map +1 -0
- package/dist/src/lib/push-failure-report.d.ts +45 -0
- package/dist/src/lib/push-failure-report.js +57 -0
- package/dist/src/lib/push-failure-report.js.map +1 -0
- package/dist/src/lib/records-verbs.d.ts +69 -0
- package/dist/src/lib/records-verbs.js +40 -0
- package/dist/src/lib/records-verbs.js.map +1 -0
- package/dist/src/lib/refresh-admin-credentials.d.ts +7 -0
- package/dist/src/lib/refresh-admin-credentials.js +16 -4
- package/dist/src/lib/refresh-admin-credentials.js.map +1 -1
- package/dist/src/lib/root-import-decision.d.ts +65 -0
- package/dist/src/lib/root-import-decision.js +109 -0
- package/dist/src/lib/root-import-decision.js.map +1 -0
- package/dist/src/lib/run-status.d.ts +1 -1
- package/dist/src/lib/run-status.generated.d.ts +2 -2
- package/dist/src/lib/run-status.generated.js +3 -1
- package/dist/src/lib/run-status.generated.js.map +1 -1
- package/dist/src/lib/run-status.js +1 -1
- package/dist/src/lib/schema-codegen/generated-schema-descriptor.d.ts +129 -0
- package/dist/src/lib/schema-codegen/generated-schema-descriptor.js +269 -0
- package/dist/src/lib/schema-codegen/generated-schema-descriptor.js.map +1 -0
- package/dist/src/lib/schema-codegen/naming.d.ts +35 -0
- package/dist/src/lib/schema-codegen/naming.js +83 -0
- package/dist/src/lib/schema-codegen/naming.js.map +1 -0
- package/dist/src/lib/schema-codegen/schemaToTs.d.ts +82 -0
- package/dist/src/lib/schema-codegen/schemaToTs.js +305 -0
- package/dist/src/lib/schema-codegen/schemaToTs.js.map +1 -0
- package/dist/src/lib/scope-request.d.ts +84 -0
- package/dist/src/lib/scope-request.js +235 -0
- package/dist/src/lib/scope-request.js.map +1 -0
- package/dist/src/lib/session-id.d.ts +9 -0
- package/dist/src/lib/session-id.js +28 -0
- package/dist/src/lib/session-id.js.map +1 -0
- package/dist/src/lib/snapshot-build-rows.d.ts +21 -0
- package/dist/src/lib/snapshot-build-rows.js +22 -0
- package/dist/src/lib/snapshot-build-rows.js.map +1 -1
- package/dist/src/lib/snapshot-build.js +8 -3
- package/dist/src/lib/snapshot-build.js.map +1 -1
- package/dist/src/lib/snapshots.d.ts +17 -1
- package/dist/src/lib/snapshots.js +50 -2
- package/dist/src/lib/snapshots.js.map +1 -1
- package/dist/src/lib/swift-codegen/agentGenerator.d.ts +6 -2
- package/dist/src/lib/swift-codegen/agentGenerator.js +57 -5
- package/dist/src/lib/swift-codegen/agentGenerator.js.map +1 -1
- package/dist/src/lib/swift-codegen/banners.d.ts +34 -7
- package/dist/src/lib/swift-codegen/banners.js +37 -7
- package/dist/src/lib/swift-codegen/banners.js.map +1 -1
- package/dist/src/lib/swift-codegen/dbGenerator.d.ts +4 -3
- package/dist/src/lib/swift-codegen/dbGenerator.js +14 -117
- package/dist/src/lib/swift-codegen/dbGenerator.js.map +1 -1
- package/dist/src/lib/swift-codegen/functionGenerator.d.ts +3 -3
- package/dist/src/lib/swift-codegen/functionGenerator.js +23 -18
- package/dist/src/lib/swift-codegen/functionGenerator.js.map +1 -1
- package/dist/src/lib/swift-codegen/generator.d.ts +5 -5
- package/dist/src/lib/swift-codegen/generator.js +35 -40
- package/dist/src/lib/swift-codegen/generator.js.map +1 -1
- package/dist/src/lib/swift-codegen/schemaToSwift.d.ts +3 -3
- package/dist/src/lib/swift-codegen/schemaToSwift.js +4 -4
- package/dist/src/lib/swift-codegen/schemaToSwift.js.map +1 -1
- package/dist/src/lib/swift-codegen/swiftKeys.d.ts +19 -0
- package/dist/src/lib/swift-codegen/swiftKeys.js +31 -0
- package/dist/src/lib/swift-codegen/swiftKeys.js.map +1 -0
- package/dist/src/lib/sync-paths.d.ts +35 -0
- package/dist/src/lib/sync-paths.js +57 -5
- package/dist/src/lib/sync-paths.js.map +1 -1
- package/dist/src/lib/sync-resource-types.d.ts +26 -26
- package/dist/src/lib/sync-resource-types.js +61 -189
- package/dist/src/lib/sync-resource-types.js.map +1 -1
- package/dist/src/lib/sync-selectors.d.ts +5 -1
- package/dist/src/lib/sync-selectors.js +23 -13
- package/dist/src/lib/sync-selectors.js.map +1 -1
- package/dist/src/lib/test-case-file-names.d.ts +3 -5
- package/dist/src/lib/test-case-file-names.js +3 -5
- package/dist/src/lib/test-case-file-names.js.map +1 -1
- package/dist/src/lib/toml-database-config.d.ts +15 -75
- package/dist/src/lib/toml-database-config.js +17 -306
- package/dist/src/lib/toml-database-config.js.map +1 -1
- package/dist/src/lib/transient-retry.d.ts +43 -0
- package/dist/src/lib/transient-retry.js +94 -0
- package/dist/src/lib/transient-retry.js.map +1 -0
- package/dist/src/lib/vars-override.d.ts +105 -0
- package/dist/src/lib/vars-override.js +143 -0
- package/dist/src/lib/vars-override.js.map +1 -0
- package/dist/src/lib/webhook-capture.d.ts +43 -0
- package/dist/src/lib/webhook-capture.js +91 -0
- package/dist/src/lib/webhook-capture.js.map +1 -0
- package/dist/src/lib/webhook-deliver.d.ts +18 -20
- package/dist/src/lib/webhook-deliver.js +34 -89
- package/dist/src/lib/webhook-deliver.js.map +1 -1
- package/dist/src/lib/webhook-test-run.d.ts +32 -0
- package/dist/src/lib/webhook-test-run.js +108 -0
- package/dist/src/lib/webhook-test-run.js.map +1 -0
- package/dist/src/lib/workflow-codegen/generator.d.ts +7 -7
- package/dist/src/lib/workflow-codegen/generator.js +16 -23
- package/dist/src/lib/workflow-codegen/generator.js.map +1 -1
- package/dist/src/lib/workflow-codegen/invokerIR.d.ts +6 -12
- package/dist/src/lib/workflow-codegen/invokerIR.js +4 -5
- package/dist/src/lib/workflow-codegen/invokerIR.js.map +1 -1
- package/dist/src/lib/workflow-toml-validator.d.ts +2 -0
- package/dist/src/lib/workflow-toml-validator.js +28 -1
- package/dist/src/lib/workflow-toml-validator.js.map +1 -1
- package/dist/src/types/index.d.ts +30 -37
- package/dist/src/validators.d.ts +7 -55
- package/dist/src/validators.js +7 -53
- package/dist/src/validators.js.map +1 -1
- package/dist/src/vite.d.ts +17 -0
- package/dist/src/vite.js +18 -0
- package/dist/src/vite.js.map +1 -0
- package/package.json +20 -12
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { loadCredentials, saveCredentials, isTokenExpiringSoon, } from "./config.js";
|
|
2
|
-
import { getCurrentEnvNameSafe } from "./env-resolver.js";
|
|
2
|
+
import { getCurrentEnvNameSafe, getProjectConfig } from "./env-resolver.js";
|
|
3
|
+
import { pinRefusalHint } from "./pin-refusal-hint.js";
|
|
3
4
|
import { fetchWithTLS } from "./fetch.js";
|
|
4
5
|
import { paginateAll, normalizeCliListEnvelope } from "./paginate.js";
|
|
5
6
|
import { RefreshError, refreshAdminCredentials, } from "./refresh-admin-credentials.js";
|
|
6
|
-
import { drainAppsUntilConverged, } from "./workflow-usage.js";
|
|
7
7
|
/**
|
|
8
8
|
* Page size the admin per-user inventories are asked for (#3399).
|
|
9
9
|
*
|
|
@@ -11,6 +11,15 @@ import { drainAppsUntilConverged, } from "./workflow-usage.js";
|
|
|
11
11
|
* fewest round trips the contract allows; a larger value is clamped there.
|
|
12
12
|
*/
|
|
13
13
|
const ADMIN_INVENTORY_PAGE_SIZE = 100;
|
|
14
|
+
/** The agent block's wire keys, in the surface's order. */
|
|
15
|
+
const AGENT_TEST_CASE_WIRE_KEYS = [
|
|
16
|
+
"agentHistory",
|
|
17
|
+
"agentContext",
|
|
18
|
+
"agentTimezone",
|
|
19
|
+
"agentLocale",
|
|
20
|
+
"agentExpectedToolCall",
|
|
21
|
+
"agentExpectedNoToolCall",
|
|
22
|
+
];
|
|
14
23
|
export class ApiError extends Error {
|
|
15
24
|
statusCode;
|
|
16
25
|
code;
|
|
@@ -39,6 +48,15 @@ export class ApiError extends Error {
|
|
|
39
48
|
}
|
|
40
49
|
}
|
|
41
50
|
}
|
|
51
|
+
/** The project's committed environments, or null outside a readable project. */
|
|
52
|
+
function projectEnvironments() {
|
|
53
|
+
try {
|
|
54
|
+
return getProjectConfig()?.environments ?? null;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
42
60
|
export class ConflictError extends ApiError {
|
|
43
61
|
serverModifiedAt;
|
|
44
62
|
expectedModifiedAt;
|
|
@@ -85,6 +103,15 @@ export function parseErrorResponse(response, text, path) {
|
|
|
85
103
|
// Non-JSON body. Surface the existing `<!DOCTYPE` special-case (an HTML
|
|
86
104
|
// 404 page from hitting the wrong path) so we don't regress the helpful
|
|
87
105
|
// "API endpoint not found" message at api-client.ts:343.
|
|
106
|
+
//
|
|
107
|
+
// An HTML page with any other status is not a wrong path: it keeps its
|
|
108
|
+
// status, so a gateway's 502 is retried and a 403 is not read as absence
|
|
109
|
+
// (#4148).
|
|
110
|
+
if (text.includes("<!DOCTYPE") && response.status !== 404) {
|
|
111
|
+
return {
|
|
112
|
+
message: `HTTP ${response.status}: the server answered with an HTML error page`,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
88
115
|
if (text.includes("<!DOCTYPE")) {
|
|
89
116
|
const where = path ? `: ${path}` : "";
|
|
90
117
|
return {
|
|
@@ -116,55 +143,15 @@ export function parseErrorResponse(response, text, path) {
|
|
|
116
143
|
return { message, code, details, raw: errorData };
|
|
117
144
|
}
|
|
118
145
|
/**
|
|
119
|
-
* Typed exception
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
* continue to work; specialized catch blocks can branch on `instanceof`.
|
|
123
|
-
*
|
|
124
|
-
* Per round-2 addendum A1, `details` is always preserved so callers can
|
|
125
|
-
* extract structured offender lists (refs[], operations[], etc.).
|
|
146
|
+
* Typed exception for the database-schema feature (issue #666): a stored
|
|
147
|
+
* schema that does not parse. `details` carries the line and column.
|
|
148
|
+
* (#3189 — the operation gates' exceptions retired with operations.)
|
|
126
149
|
*/
|
|
127
|
-
export class SchemaRequiredError extends ApiError {
|
|
128
|
-
constructor(message, details) {
|
|
129
|
-
super(message, 422, "SCHEMA_REQUIRED", details);
|
|
130
|
-
this.name = "SchemaRequiredError";
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
150
|
function detailsRecord(details) {
|
|
134
151
|
return details && !Array.isArray(details) && typeof details === "object"
|
|
135
152
|
? details
|
|
136
153
|
: undefined;
|
|
137
154
|
}
|
|
138
|
-
export class OperationRefError extends ApiError {
|
|
139
|
-
constructor(message, details) {
|
|
140
|
-
super(message, 422, "OPERATION_REFERENCES_UNDEFINED", details);
|
|
141
|
-
this.name = "OperationRefError";
|
|
142
|
-
}
|
|
143
|
-
get refs() {
|
|
144
|
-
const d = detailsRecord(this.details);
|
|
145
|
-
return Array.isArray(d?.refs) ? d.refs : [];
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
export class SchemaBreaksOpsError extends ApiError {
|
|
149
|
-
constructor(message, details) {
|
|
150
|
-
super(message, 422, "SCHEMA_BREAKS_OPERATIONS", details);
|
|
151
|
-
this.name = "SchemaBreaksOpsError";
|
|
152
|
-
}
|
|
153
|
-
get operations() {
|
|
154
|
-
const d = detailsRecord(this.details);
|
|
155
|
-
return Array.isArray(d?.operations) ? d.operations : [];
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
export class SchemaHasUncheckableOpsError extends ApiError {
|
|
159
|
-
constructor(message, details) {
|
|
160
|
-
super(message, 422, "SCHEMA_HAS_UNCHECKABLE_OPS", details);
|
|
161
|
-
this.name = "SchemaHasUncheckableOpsError";
|
|
162
|
-
}
|
|
163
|
-
get operations() {
|
|
164
|
-
const d = detailsRecord(this.details);
|
|
165
|
-
return Array.isArray(d?.operations) ? d.operations : [];
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
155
|
export class TomlParseError extends ApiError {
|
|
169
156
|
constructor(message, details) {
|
|
170
157
|
super(message, 400, "TOML_PARSE_ERROR", details);
|
|
@@ -179,55 +166,6 @@ export class TomlParseError extends ApiError {
|
|
|
179
166
|
return typeof d?.column === "number" ? d.column : undefined;
|
|
180
167
|
}
|
|
181
168
|
}
|
|
182
|
-
export class OpsExistError extends ApiError {
|
|
183
|
-
constructor(message, details) {
|
|
184
|
-
super(message, 409, "OPS_EXIST", details);
|
|
185
|
-
this.name = "OpsExistError";
|
|
186
|
-
}
|
|
187
|
-
get opCount() {
|
|
188
|
-
const d = detailsRecord(this.details);
|
|
189
|
-
return typeof d?.opCount === "number" ? d.opCount : 0;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* Normalize a `databases operations execute` result to the CLI's one list
|
|
194
|
-
* envelope — but only when the result is a Durable Object query page
|
|
195
|
-
* (issue #2440).
|
|
196
|
-
*
|
|
197
|
-
* A registered operation is a list only sometimes. Of the shapes the server's
|
|
198
|
-
* operation dispatch can return, exactly two carry a top-level `data` array: a
|
|
199
|
-
* bare `query` and a `pipeline` whose `returnField` names a query step. A
|
|
200
|
-
* `count` (`{ count }`), an `aggregate` (`{ result }`), a mutation
|
|
201
|
-
* (`{ results }`), an `applyToQuery` (`{ matched, affected, failed, … }`) and a
|
|
202
|
-
* `returnField: "all"` pipeline (`{ steps: { … } }`) do not, so blanket
|
|
203
|
-
* normalization would corrupt them. This helper therefore tests the shape
|
|
204
|
-
* first and passes everything else through byte-for-byte — including the
|
|
205
|
-
* nested steps of a `returnField: "all"` pipeline, which are operation-defined
|
|
206
|
-
* objects the CLI must not reach into.
|
|
207
|
-
*
|
|
208
|
-
* Deliberately narrow, not a general-purpose predicate: the contract is
|
|
209
|
-
* specifically "an operations-execute result may be a DO query page".
|
|
210
|
-
* `executeDatabaseOperation()` is the only caller.
|
|
211
|
-
*
|
|
212
|
-
* The conversion itself is delegated to `normalizeCliListEnvelope()` so cursor
|
|
213
|
-
* handling, `prevCursor` dropping and `hasMore` derivation have exactly one
|
|
214
|
-
* implementation. That helper throws on an unrecognized shape — correct when
|
|
215
|
-
* the payload is supposed to be a list, wrong here, where the payload is the
|
|
216
|
-
* caller's own result — which is why the shape test has to run first. `_timing`
|
|
217
|
-
* (`--timing`) is re-attached afterwards, since it rides alongside the envelope
|
|
218
|
-
* rather than inside it.
|
|
219
|
-
*/
|
|
220
|
-
export function normalizeDatabaseOperationResult(raw) {
|
|
221
|
-
if (raw === null || typeof raw !== "object" || Array.isArray(raw))
|
|
222
|
-
return raw;
|
|
223
|
-
const page = raw;
|
|
224
|
-
if (!Array.isArray(page.data))
|
|
225
|
-
return raw;
|
|
226
|
-
const envelope = normalizeCliListEnvelope(page);
|
|
227
|
-
if (page._timing !== undefined)
|
|
228
|
-
envelope._timing = page._timing;
|
|
229
|
-
return envelope;
|
|
230
|
-
}
|
|
231
169
|
export class ApiClient {
|
|
232
170
|
/**
|
|
233
171
|
* Loaded on first use (`ensureAuthenticated`), never in a constructor.
|
|
@@ -284,8 +222,9 @@ export class ApiClient {
|
|
|
284
222
|
// error or a server-side rejection. Callers that need a finer
|
|
285
223
|
// distinction (e.g. `primitive token`) consume RefreshError directly
|
|
286
224
|
// from refresh-admin-credentials.ts instead of going through here.
|
|
287
|
-
// A
|
|
288
|
-
|
|
225
|
+
// A refusal naming the session says why: revoked (#3885), or a login
|
|
226
|
+
// made before scoped sessions (#3891).
|
|
227
|
+
throw new ApiError(err.code === "SESSION_REVOKED" || err.code === "SESSION_UNSCOPED"
|
|
289
228
|
? err.message
|
|
290
229
|
: "Token refresh failed. Please login again.", 401, err.code);
|
|
291
230
|
}
|
|
@@ -295,60 +234,133 @@ export class ApiClient {
|
|
|
295
234
|
async request(path, options = {}) {
|
|
296
235
|
const credentials = await this.ensureAuthenticated();
|
|
297
236
|
const url = `${credentials.serverUrl}${path}`;
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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);
|
|
237
|
+
const send = async (accessToken) => {
|
|
238
|
+
const headers = {
|
|
239
|
+
Authorization: `Bearer ${accessToken}`,
|
|
240
|
+
"Content-Type": "application/json",
|
|
241
|
+
...(options.headers || {}),
|
|
242
|
+
};
|
|
243
|
+
// Include globalAdminAppId if available (required for admin API calls)
|
|
244
|
+
if (credentials.globalAdminAppId) {
|
|
245
|
+
headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
|
|
338
246
|
}
|
|
339
|
-
|
|
340
|
-
|
|
247
|
+
const response = await fetchWithTLS(url, {
|
|
248
|
+
...options,
|
|
249
|
+
headers,
|
|
250
|
+
});
|
|
251
|
+
return { response, text: await response.text() };
|
|
252
|
+
};
|
|
253
|
+
let { response, text } = await send(credentials.accessToken);
|
|
254
|
+
// #3889 — a 403 naming `admin` is retried once with a stored, unexpired
|
|
255
|
+
// step-up, and (#4564) so is a write a protected app refused for want of
|
|
256
|
+
// one. A retry refused 401 means the step-up is gone (revoked or expired
|
|
257
|
+
// early): drop it and show the original refusal. The retry's own refusal
|
|
258
|
+
// (say, a step-up without the scope this command needs) is shown as it is.
|
|
259
|
+
if (response.status === 403 && this.stepUpApplies(response, text, path)) {
|
|
260
|
+
const retried = await send(credentials.stepUp.accessToken);
|
|
261
|
+
if (retried.response.ok) {
|
|
262
|
+
return retried.text ? JSON.parse(retried.text) : null;
|
|
341
263
|
}
|
|
342
|
-
if (response.status ===
|
|
343
|
-
|
|
264
|
+
if (retried.response.status === 401) {
|
|
265
|
+
this.dropStepUp();
|
|
344
266
|
}
|
|
345
|
-
|
|
346
|
-
|
|
267
|
+
else {
|
|
268
|
+
({ response, text } = retried);
|
|
347
269
|
}
|
|
348
|
-
|
|
270
|
+
}
|
|
271
|
+
if (!response.ok) {
|
|
272
|
+
throw this.responseError(response, text, path);
|
|
349
273
|
}
|
|
350
274
|
return text ? JSON.parse(text) : null;
|
|
351
275
|
}
|
|
276
|
+
/**
|
|
277
|
+
* Whether a 403 should be retried with the stored step-up: it names
|
|
278
|
+
* `admin` among the missing scopes, or it is a protected app's refusal of
|
|
279
|
+
* a token that is no step-up, and an unexpired step-up is stored.
|
|
280
|
+
*/
|
|
281
|
+
stepUpApplies(response, text, path) {
|
|
282
|
+
const stepUp = this.credentials?.stepUp;
|
|
283
|
+
if (!stepUp?.accessToken)
|
|
284
|
+
return false;
|
|
285
|
+
if (new Date(stepUp.expiresAt).getTime() <= Date.now())
|
|
286
|
+
return false;
|
|
287
|
+
const parsed = parseErrorResponse(response, text, path);
|
|
288
|
+
if (parsed.code === "PROTECTED_APP_STEP_UP_REQUIRED")
|
|
289
|
+
return true;
|
|
290
|
+
const details = parsed.details;
|
|
291
|
+
const missing = details && !Array.isArray(details) ? details.missing : undefined;
|
|
292
|
+
return Array.isArray(missing) && missing.includes("admin");
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* #4564 — a request outside `request` (an upload, a function invocation)
|
|
296
|
+
* sent with the CLI's own credentials, retried once with the stored step-up
|
|
297
|
+
* exactly as `request` retries. `init` builds the request for a bearer. The
|
|
298
|
+
* answer is the retry's, unless the retry was refused 401: the step-up is
|
|
299
|
+
* dropped and the original refusal comes back, still readable.
|
|
300
|
+
*/
|
|
301
|
+
async fetchWithStepUp(url, path, accessToken, init) {
|
|
302
|
+
const response = await fetchWithTLS(url, init(accessToken));
|
|
303
|
+
if (response.status !== 403)
|
|
304
|
+
return response;
|
|
305
|
+
// Read once to decide; hand the caller a response it can still read.
|
|
306
|
+
const text = await response.text();
|
|
307
|
+
const original = new Response(text, {
|
|
308
|
+
status: response.status,
|
|
309
|
+
statusText: response.statusText,
|
|
310
|
+
headers: response.headers,
|
|
311
|
+
});
|
|
312
|
+
if (!this.stepUpApplies(response, text, path))
|
|
313
|
+
return original;
|
|
314
|
+
const retried = await fetchWithTLS(url, init(this.credentials.stepUp.accessToken));
|
|
315
|
+
if (retried.status === 401) {
|
|
316
|
+
await retried.text().catch(() => "");
|
|
317
|
+
this.dropStepUp();
|
|
318
|
+
return original;
|
|
319
|
+
}
|
|
320
|
+
return retried;
|
|
321
|
+
}
|
|
322
|
+
/** Forget a step-up the server no longer accepts. */
|
|
323
|
+
dropStepUp() {
|
|
324
|
+
if (!this.credentials?.stepUp)
|
|
325
|
+
return;
|
|
326
|
+
const { stepUp: _dropped, ...rest } = this.credentials;
|
|
327
|
+
this.credentials = rest;
|
|
328
|
+
saveCredentials(rest);
|
|
329
|
+
}
|
|
330
|
+
/** The typed error a failed response throws. */
|
|
331
|
+
responseError(response, text, path) {
|
|
332
|
+
const parsed = parseErrorResponse(response, text, path);
|
|
333
|
+
// Preserve the `<!DOCTYPE` → 404 ApiError shape.
|
|
334
|
+
if (parsed.htmlNotFound) {
|
|
335
|
+
return new ApiError(parsed.message, 404);
|
|
336
|
+
}
|
|
337
|
+
// Narrow details to the record shape for the issue #666 typed-exception
|
|
338
|
+
// dispatch below. Conflict metadata may live under `details.*` (canonical
|
|
339
|
+
// location per A1) or on the top-level envelope (legacy).
|
|
340
|
+
const detailsRecord = parsed.details && !Array.isArray(parsed.details)
|
|
341
|
+
? parsed.details
|
|
342
|
+
: undefined;
|
|
343
|
+
const serverModifiedAt = detailsRecord?.serverModifiedAt ?? parsed.raw?.serverModifiedAt;
|
|
344
|
+
const expectedModifiedAt = detailsRecord?.expectedModifiedAt ?? parsed.raw?.expectedModifiedAt;
|
|
345
|
+
// Typed exceptions for the schema-feature (issue #666).
|
|
346
|
+
if (response.status === 409 && parsed.code === "CONFLICT") {
|
|
347
|
+
return new ConflictError(parsed.message, serverModifiedAt, expectedModifiedAt, detailsRecord);
|
|
348
|
+
}
|
|
349
|
+
if (response.status === 400 && parsed.code === "TOML_PARSE_ERROR") {
|
|
350
|
+
return new TomlParseError(parsed.message, detailsRecord);
|
|
351
|
+
}
|
|
352
|
+
// #4512 — a pin refusal on another environment's app names its login.
|
|
353
|
+
const hint = pinRefusalHint(parsed, this.credentials?.serverUrl ?? "", projectEnvironments());
|
|
354
|
+
return new ApiError(hint ? `${parsed.message} ${hint}` : parsed.message, response.status, parsed.code, parsed.details);
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* `POST /admin/api/auth/token` — derive a token no wider than the stored
|
|
358
|
+
* one (`primitive token --scope`, #3889). A wider request is refused with
|
|
359
|
+
* `TOKEN_WIDER_THAN_PRESENTED`.
|
|
360
|
+
*/
|
|
361
|
+
async exchangeToken(body) {
|
|
362
|
+
return this.post("/admin/api/auth/token", body);
|
|
363
|
+
}
|
|
352
364
|
// Helper for GET requests with query params
|
|
353
365
|
async get(path, params) {
|
|
354
366
|
let fullPath = path;
|
|
@@ -410,6 +422,10 @@ export class ApiClient {
|
|
|
410
422
|
qs.set("limit", String(params.limit));
|
|
411
423
|
if (params?.cursor)
|
|
412
424
|
qs.set("cursor", params.cursor);
|
|
425
|
+
// `kind` filters by lineage: `parent` is apps with no parent, `child`
|
|
426
|
+
// child apps. Omitted lists every app the caller is assigned to.
|
|
427
|
+
if (params?.kind)
|
|
428
|
+
qs.set("kind", params.kind);
|
|
413
429
|
const q = qs.toString();
|
|
414
430
|
const resp = await this.get(`/admin/api/admins/me/apps${q ? `?${q}` : ""}`);
|
|
415
431
|
return {
|
|
@@ -432,40 +448,53 @@ export class ApiClient {
|
|
|
432
448
|
return { apps };
|
|
433
449
|
}
|
|
434
450
|
/**
|
|
435
|
-
*
|
|
436
|
-
*
|
|
437
|
-
*
|
|
438
|
-
*
|
|
439
|
-
*
|
|
440
|
-
|
|
441
|
-
* route `GET /admin/api/apps` (`App.iterateAll()`), and that route
|
|
442
|
-
* identifies an app as `appId` rather than `id` (D3130-001).
|
|
443
|
-
*
|
|
444
|
-
* One drain of that route is lossy — its cursor re-scans `App.iterateAll()`
|
|
445
|
-
* from the start of each page, so a list that changes under it both repeats
|
|
446
|
-
* and drops rows — so the walk is the union of repeated drains
|
|
447
|
-
* (`drainAppsUntilConverged`, which carries the measurement).
|
|
448
|
-
*/
|
|
449
|
-
async listAllAppsGlobal() {
|
|
450
|
-
const drainOnce = () => paginateAll(async (cursor) => {
|
|
451
|
-
const qs = new URLSearchParams({ limit: "100" });
|
|
452
|
-
if (cursor)
|
|
453
|
-
qs.set("cursor", cursor);
|
|
454
|
-
const resp = await this.get(`/admin/api/apps?${qs.toString()}`);
|
|
455
|
-
const rows = resp.items ?? [];
|
|
456
|
-
return {
|
|
457
|
-
items: rows.map((row) => ({
|
|
458
|
-
appId: String(row.appId ?? row.id ?? ""),
|
|
459
|
-
name: String(row.name ?? ""),
|
|
460
|
-
})),
|
|
461
|
-
nextCursor: resp.nextCursor,
|
|
462
|
-
};
|
|
463
|
-
});
|
|
464
|
-
return drainAppsUntilConverged(drainOnce);
|
|
465
|
-
}
|
|
451
|
+
* Create an app. An ordinary app takes `name` (and, for a super-admin,
|
|
452
|
+
* `initialAdminEmail`). A child app takes `parentAppId` and `childSlug`
|
|
453
|
+
* instead, plus the optional `branch`, `idleDays` (7-30, default 30) and
|
|
454
|
+
* `copySecrets` (default true); the server names it `<parent name> /
|
|
455
|
+
* <slug>` and copies the parent's settings.
|
|
456
|
+
*/
|
|
466
457
|
async createApp(data) {
|
|
467
458
|
return this.post("/admin/api/apps", data);
|
|
468
459
|
}
|
|
460
|
+
/**
|
|
461
|
+
* One page of an app's child apps, in creation order
|
|
462
|
+
* (`GET /admin/api/apps/{appId}/children`).
|
|
463
|
+
*/
|
|
464
|
+
async listAppChildren(parentAppId, params) {
|
|
465
|
+
const qs = new URLSearchParams();
|
|
466
|
+
if (params?.limit !== undefined)
|
|
467
|
+
qs.set("limit", String(params.limit));
|
|
468
|
+
if (params?.cursor)
|
|
469
|
+
qs.set("cursor", params.cursor);
|
|
470
|
+
const q = qs.toString();
|
|
471
|
+
const resp = await this.get(`/admin/api/apps/${encodeURIComponent(parentAppId)}/children${q ? `?${q}` : ""}`);
|
|
472
|
+
return {
|
|
473
|
+
items: resp?.items ?? [],
|
|
474
|
+
hasMore: resp?.hasMore ?? false,
|
|
475
|
+
nextCursor: resp?.nextCursor ?? null,
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Add one origin to a child's preview origins
|
|
480
|
+
* (`POST /admin/api/apps/{parentAppId}/children/{slug}/preview-origins`).
|
|
481
|
+
*/
|
|
482
|
+
async addChildPreviewOrigin(parentAppId, slug, origin) {
|
|
483
|
+
return this.post(this.childPreviewOriginsPath(parentAppId, slug), { origin });
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Remove one origin from a child's preview origins
|
|
487
|
+
* (`DELETE /admin/api/apps/{parentAppId}/children/{slug}/preview-origins`).
|
|
488
|
+
*/
|
|
489
|
+
async removeChildPreviewOrigin(parentAppId, slug, origin) {
|
|
490
|
+
return this.request(this.childPreviewOriginsPath(parentAppId, slug), {
|
|
491
|
+
method: "DELETE",
|
|
492
|
+
body: JSON.stringify({ origin }),
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
childPreviewOriginsPath(parentAppId, slug) {
|
|
496
|
+
return `/admin/api/apps/${encodeURIComponent(parentAppId)}/children/${encodeURIComponent(slug)}/preview-origins`;
|
|
497
|
+
}
|
|
469
498
|
async getApp(appId) {
|
|
470
499
|
return this.get(`/admin/api/apps/${appId}`);
|
|
471
500
|
}
|
|
@@ -487,6 +516,14 @@ export class ApiClient {
|
|
|
487
516
|
async addUserByEmail(appId, data) {
|
|
488
517
|
return this.post(`/admin/api/apps/${appId}/users/add-by-email`, data);
|
|
489
518
|
}
|
|
519
|
+
/**
|
|
520
|
+
* Set a member's display name and/or avatar URL
|
|
521
|
+
* (`PATCH /admin/api/apps/:appId/users/:userId/profile`). An omitted field
|
|
522
|
+
* is left alone; `avatarUrl: null` or `""` clears the avatar.
|
|
523
|
+
*/
|
|
524
|
+
async setUserProfile(appId, userId, data) {
|
|
525
|
+
return this.patch(`/admin/api/apps/${appId}/users/${userId}/profile`, data);
|
|
526
|
+
}
|
|
490
527
|
async mintTestJwt(appId, userId, role) {
|
|
491
528
|
return this.post(`/admin/api/apps/${appId}/users/${userId}/mint-test-jwt`, role ? { role } : {});
|
|
492
529
|
}
|
|
@@ -646,7 +683,7 @@ export class ApiClient {
|
|
|
646
683
|
nextCursor: result?.nextCursor ?? null,
|
|
647
684
|
};
|
|
648
685
|
}
|
|
649
|
-
/** Integration detail (
|
|
686
|
+
/** Integration detail (`requestConfig` is on the detail, not the list summary). */
|
|
650
687
|
async getIntegration(appId, integrationId) {
|
|
651
688
|
return this.get(`/admin/api/apps/${appId}/integrations/${integrationId}`);
|
|
652
689
|
}
|
|
@@ -686,10 +723,6 @@ export class ApiClient {
|
|
|
686
723
|
const result = await this.get(`/admin/api/apps/${appId}/integrations/${integrationId}/logs`, params);
|
|
687
724
|
return result?.items ?? [];
|
|
688
725
|
}
|
|
689
|
-
async listWorkflowRunIntegrationLogs(appId, runId, params) {
|
|
690
|
-
const result = await this.get(`/admin/api/apps/${appId}/workflows/runs/${runId}/integration-logs`, params);
|
|
691
|
-
return result?.items ?? [];
|
|
692
|
-
}
|
|
693
726
|
// ============================================
|
|
694
727
|
// APP SECRETS
|
|
695
728
|
// ============================================
|
|
@@ -749,42 +782,6 @@ export class ApiClient {
|
|
|
749
782
|
// ============================================
|
|
750
783
|
// WEBHOOKS
|
|
751
784
|
// ============================================
|
|
752
|
-
async listWebhooks(appId, params) {
|
|
753
|
-
const result = await this.get(`/admin/api/apps/${appId}/webhooks`, params);
|
|
754
|
-
return {
|
|
755
|
-
items: result?.items ?? [],
|
|
756
|
-
hasMore: result?.hasMore,
|
|
757
|
-
nextCursor: result?.nextCursor ?? null,
|
|
758
|
-
};
|
|
759
|
-
}
|
|
760
|
-
async getWebhook(appId, webhookId) {
|
|
761
|
-
return this.get(`/admin/api/apps/${appId}/webhooks/${webhookId}`);
|
|
762
|
-
}
|
|
763
|
-
async createWebhook(appId, payload) {
|
|
764
|
-
return this.post(`/admin/api/apps/${appId}/webhooks`, payload);
|
|
765
|
-
}
|
|
766
|
-
async updateWebhook(appId, webhookId, payload, expectedModifiedAt) {
|
|
767
|
-
const body = expectedModifiedAt ? { ...payload, expectedModifiedAt } : payload;
|
|
768
|
-
return this.patch(`/admin/api/apps/${appId}/webhooks/${webhookId}`, body);
|
|
769
|
-
}
|
|
770
|
-
/**
|
|
771
|
-
* Delete a webhook. Archives it by default; `hard` hard-deletes the row
|
|
772
|
-
* instead (#2232), which is what frees a slot under the per-app webhook cap
|
|
773
|
-
* and releases the webhook key for reuse. Same `?hard=true` verb the admin
|
|
774
|
-
* integrations delete uses.
|
|
775
|
-
*/
|
|
776
|
-
async deleteWebhook(appId, webhookId, options) {
|
|
777
|
-
const query = options?.hard ? "?hard=true" : "";
|
|
778
|
-
return this.delete(`/admin/api/apps/${appId}/webhooks/${webhookId}${query}`);
|
|
779
|
-
}
|
|
780
|
-
/**
|
|
781
|
-
* #2803 — the operational verb pair for a webhook. It posts to the
|
|
782
|
-
* enable/disable route rather than PATCHing `status`, which is server-owned
|
|
783
|
-
* and refused on every configuration write path.
|
|
784
|
-
*/
|
|
785
|
-
async setWebhookAvailability(appId, webhookId, action) {
|
|
786
|
-
return this.post(`/admin/api/apps/${appId}/webhooks/${webhookId}/${action}`, {});
|
|
787
|
-
}
|
|
788
785
|
// ── Server functions (#3179) ─────────────────────────────────────────
|
|
789
786
|
//
|
|
790
787
|
// The `functions` family mirrors workflows and scripts: a header with the
|
|
@@ -829,6 +826,38 @@ export class ApiClient {
|
|
|
829
826
|
const result = await this.get(`/admin/api/apps/${appId}/functions/${functionId}/runs`, params);
|
|
830
827
|
return { items: result?.items ?? [], nextCursor: result?.nextCursor ?? null };
|
|
831
828
|
}
|
|
829
|
+
// ── A function's trigger operations (#3190) ──
|
|
830
|
+
// The operational home of a function's webhook and cron triggers, keyed by
|
|
831
|
+
// the function and a cron entry's name rather than a trigger row's id.
|
|
832
|
+
functionTriggersPath(appId, functionId) {
|
|
833
|
+
return `/admin/api/apps/${appId}/functions/${encodeURIComponent(functionId)}/triggers`;
|
|
834
|
+
}
|
|
835
|
+
/** The function's webhook delivery log, newest first. */
|
|
836
|
+
async listFunctionWebhookEvents(appId, functionId, params) {
|
|
837
|
+
const result = await this.get(`${this.functionTriggersPath(appId, functionId)}/webhook/events`, params);
|
|
838
|
+
const nextCursor = result?.nextCursor ?? null;
|
|
839
|
+
return {
|
|
840
|
+
items: result?.items ?? [],
|
|
841
|
+
hasMore: result?.hasMore ?? !!nextCursor,
|
|
842
|
+
nextCursor,
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
/** Sign a preview delivery for the function's webhook trigger. */
|
|
846
|
+
async testFunctionWebhook(appId, functionId, payload) {
|
|
847
|
+
return this.post(`${this.functionTriggersPath(appId, functionId)}/webhook/test`, payload || {});
|
|
848
|
+
}
|
|
849
|
+
/** Check captured headers and a captured body against the trigger's verifier. */
|
|
850
|
+
async verifyFunctionWebhook(appId, functionId, payload) {
|
|
851
|
+
return this.post(`${this.functionTriggersPath(appId, functionId)}/webhook/verify`, payload);
|
|
852
|
+
}
|
|
853
|
+
/** Point the trigger's `signingSecret` at a new `{{secrets.KEY}}` reference. */
|
|
854
|
+
async rotateFunctionWebhookSecret(appId, functionId, payload) {
|
|
855
|
+
return this.post(`${this.functionTriggersPath(appId, functionId)}/webhook/rotate-secret`, payload);
|
|
856
|
+
}
|
|
857
|
+
/** One cron entry verb: `fire`, `fire-scheduled`, `enable` or `disable`. */
|
|
858
|
+
async functionCronEntryAction(appId, functionId, name, action) {
|
|
859
|
+
return this.post(`${this.functionTriggersPath(appId, functionId)}/cron/${encodeURIComponent(name)}/${action}`, {});
|
|
860
|
+
}
|
|
832
861
|
/**
|
|
833
862
|
* A function run's step trace — #3348.
|
|
834
863
|
*
|
|
@@ -842,6 +871,16 @@ export class ApiClient {
|
|
|
842
871
|
const result = await this.get(`/admin/api/apps/${appId}/functions/${functionId}/runs/${runId}/steps`);
|
|
843
872
|
return { items: result?.items ?? [] };
|
|
844
873
|
}
|
|
874
|
+
/**
|
|
875
|
+
* The integration calls one function run made — #3190.
|
|
876
|
+
*
|
|
877
|
+
* The run → integration-logs pivot, addressed by the function that owns the
|
|
878
|
+
* run (it was `workflows/runs/{runId}/integration-logs`).
|
|
879
|
+
*/
|
|
880
|
+
async listFunctionRunIntegrationLogs(appId, functionId, runId, options) {
|
|
881
|
+
const result = await this.get(`/admin/api/apps/${appId}/functions/${functionId}/runs/${runId}/integration-logs`, options?.limit !== undefined ? { limit: options.limit } : undefined);
|
|
882
|
+
return { items: result?.items ?? [] };
|
|
883
|
+
}
|
|
845
884
|
/**
|
|
846
885
|
* End a function run that will not settle — #3348.
|
|
847
886
|
*
|
|
@@ -914,26 +953,31 @@ export class ApiClient {
|
|
|
914
953
|
async invokeFunction(appId, functionKey, body, options) {
|
|
915
954
|
const credentials = await this.ensureAuthenticated();
|
|
916
955
|
const base = `/app/${appId}/api/functions/${encodeURIComponent(functionKey)}`;
|
|
956
|
+
const task = options?.runtime === "task";
|
|
917
957
|
const path = options?.asSystem
|
|
918
|
-
? `${base}
|
|
919
|
-
:
|
|
958
|
+
? `${base}/${task ? "start-as-system" : "invoke-as-system"}`
|
|
959
|
+
: task
|
|
920
960
|
? `${base}/start`
|
|
921
961
|
: base;
|
|
922
|
-
const
|
|
923
|
-
|
|
924
|
-
|
|
962
|
+
const init = (bearer) => {
|
|
963
|
+
const headers = {
|
|
964
|
+
Authorization: `Bearer ${bearer}`,
|
|
965
|
+
"Content-Type": "application/json",
|
|
966
|
+
};
|
|
967
|
+
// The CLI's own credentials reach an app-scoped route through the
|
|
968
|
+
// global-admin header; a minted user token does not need it and must
|
|
969
|
+
// not carry it.
|
|
970
|
+
if (!options?.bearer && credentials.globalAdminAppId) {
|
|
971
|
+
headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
|
|
972
|
+
}
|
|
973
|
+
return { method: "POST", headers, body: JSON.stringify(body) };
|
|
925
974
|
};
|
|
926
|
-
|
|
927
|
-
//
|
|
928
|
-
//
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
const response = await fetchWithTLS(`${credentials.serverUrl}${path}`, {
|
|
933
|
-
method: "POST",
|
|
934
|
-
headers,
|
|
935
|
-
body: JSON.stringify(body),
|
|
936
|
-
});
|
|
975
|
+
const url = `${credentials.serverUrl}${path}`;
|
|
976
|
+
// A minted user token is sent as it is; the CLI's own credentials get the
|
|
977
|
+
// step-up retry a protected app's refusal asks for (#4564).
|
|
978
|
+
const response = options?.bearer
|
|
979
|
+
? await fetchWithTLS(url, init(options.bearer))
|
|
980
|
+
: await this.fetchWithStepUp(url, path, credentials.accessToken, init);
|
|
937
981
|
const text = await response.text();
|
|
938
982
|
let parsed = null;
|
|
939
983
|
try {
|
|
@@ -952,6 +996,38 @@ export class ApiClient {
|
|
|
952
996
|
body: parsed,
|
|
953
997
|
};
|
|
954
998
|
}
|
|
999
|
+
/**
|
|
1000
|
+
* Run only a function's `authorize` and hand back the ANSWER, whatever its
|
|
1001
|
+
* HTTP status — #4732. Like `invokeFunction`, it returns a refusal rather
|
|
1002
|
+
* than throwing it, so `functions authorize` renders the server's own
|
|
1003
|
+
* answer (and a case run records it as a failed row); it throws only when
|
|
1004
|
+
* the request did not complete. The CLI's own credentials, with the
|
|
1005
|
+
* step-up retry a protected app's refusal asks for.
|
|
1006
|
+
*/
|
|
1007
|
+
async authorizeFunction(appId, functionId, body) {
|
|
1008
|
+
const credentials = await this.ensureAuthenticated();
|
|
1009
|
+
const path = `/admin/api/apps/${appId}/functions/${encodeURIComponent(functionId)}/authorize`;
|
|
1010
|
+
const init = (bearer) => {
|
|
1011
|
+
const headers = {
|
|
1012
|
+
Authorization: `Bearer ${bearer}`,
|
|
1013
|
+
"Content-Type": "application/json",
|
|
1014
|
+
};
|
|
1015
|
+
if (credentials.globalAdminAppId) {
|
|
1016
|
+
headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
|
|
1017
|
+
}
|
|
1018
|
+
return { method: "POST", headers, body: JSON.stringify(body) };
|
|
1019
|
+
};
|
|
1020
|
+
const response = await this.fetchWithStepUp(`${credentials.serverUrl}${path}`, path, credentials.accessToken, init);
|
|
1021
|
+
const text = await response.text();
|
|
1022
|
+
let parsed = null;
|
|
1023
|
+
try {
|
|
1024
|
+
parsed = text ? JSON.parse(text) : null;
|
|
1025
|
+
}
|
|
1026
|
+
catch {
|
|
1027
|
+
parsed = { error: text };
|
|
1028
|
+
}
|
|
1029
|
+
return { httpStatus: response.status, body: parsed };
|
|
1030
|
+
}
|
|
955
1031
|
/**
|
|
956
1032
|
* A function's versions, newest first (#3462).
|
|
957
1033
|
*
|
|
@@ -1004,27 +1080,14 @@ export class ApiClient {
|
|
|
1004
1080
|
async setIntegrationAvailability(appId, integrationId, action) {
|
|
1005
1081
|
return this.post(`/admin/api/apps/${appId}/integrations/${integrationId}/${action}`, {});
|
|
1006
1082
|
}
|
|
1007
|
-
async rotateWebhookSecret(appId, webhookId, payload) {
|
|
1008
|
-
return this.post(`/admin/api/apps/${appId}/webhooks/${webhookId}/rotate-secret`, payload);
|
|
1009
|
-
}
|
|
1010
|
-
async listWebhookEvents(appId, webhookId, params) {
|
|
1011
|
-
const result = await this.get(`/admin/api/apps/${appId}/webhooks/${webhookId}/events`, params);
|
|
1012
|
-
const nextCursor = result?.nextCursor ?? null;
|
|
1013
|
-
return {
|
|
1014
|
-
items: result?.items ?? [],
|
|
1015
|
-
hasMore: result?.hasMore ?? !!nextCursor,
|
|
1016
|
-
nextCursor,
|
|
1017
|
-
};
|
|
1018
|
-
}
|
|
1019
1083
|
// ============================================
|
|
1020
1084
|
// CONNECTIONS INSPECTION (#1968)
|
|
1021
1085
|
// ============================================
|
|
1022
1086
|
/**
|
|
1023
1087
|
* List the live connections the server believes exist for exactly one of a
|
|
1024
|
-
* document
|
|
1088
|
+
* document or user. Read-only inspection over admin-api. Rows are
|
|
1025
1089
|
* per-connection-×-subscription bindings, so `connectionId` is not unique
|
|
1026
|
-
* across rows.
|
|
1027
|
-
* bound CEL input — typed `unknown`.
|
|
1090
|
+
* across rows.
|
|
1028
1091
|
*/
|
|
1029
1092
|
async listConnections(appId, selector, params) {
|
|
1030
1093
|
const result = await this.get(`/admin/api/apps/${appId}/connections`, {
|
|
@@ -1037,6 +1100,29 @@ export class ApiClient {
|
|
|
1037
1100
|
nextCursor: result?.nextCursor ?? null,
|
|
1038
1101
|
};
|
|
1039
1102
|
}
|
|
1103
|
+
/**
|
|
1104
|
+
* One page of one user's agent sessions (#3809): the app API's sessions
|
|
1105
|
+
* list, which an app owner or admin may read for any user.
|
|
1106
|
+
*/
|
|
1107
|
+
async listAgentSessions(appId, params) {
|
|
1108
|
+
const result = await this.get(`/app/${appId}/api/agent-sessions`, params);
|
|
1109
|
+
return {
|
|
1110
|
+
items: result?.items ?? [],
|
|
1111
|
+
hasMore: result?.hasMore,
|
|
1112
|
+
nextCursor: result?.nextCursor ?? null,
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
/**
|
|
1116
|
+
* One agent session (#3809): its binding, status, members and active turn,
|
|
1117
|
+
* and for an app owner or admin the session object's state. Never its rows.
|
|
1118
|
+
*/
|
|
1119
|
+
async getAgentSession(appId, sessionId) {
|
|
1120
|
+
return this.get(`/app/${appId}/api/agent-sessions/${encodeURIComponent(sessionId)}`);
|
|
1121
|
+
}
|
|
1122
|
+
/** Delete an agent session and its document (#3809); a repeat succeeds again. */
|
|
1123
|
+
async deleteAgentSession(appId, sessionId) {
|
|
1124
|
+
return this.delete(`/app/${appId}/api/agent-sessions/${encodeURIComponent(sessionId)}`);
|
|
1125
|
+
}
|
|
1040
1126
|
async listSessions(appId, userId, params) {
|
|
1041
1127
|
const result = await this.get(`/admin/api/apps/${appId}/sessions`, {
|
|
1042
1128
|
userId,
|
|
@@ -1064,20 +1150,6 @@ export class ApiClient {
|
|
|
1064
1150
|
async revokeAdminSession(sessionId) {
|
|
1065
1151
|
return this.delete(`/admin/api/auth/sessions/${encodeURIComponent(sessionId)}`);
|
|
1066
1152
|
}
|
|
1067
|
-
async testWebhook(appId, webhookId, payload) {
|
|
1068
|
-
return this.post(`/admin/api/apps/${appId}/webhooks/${webhookId}/test`, payload || {});
|
|
1069
|
-
}
|
|
1070
|
-
/**
|
|
1071
|
-
* Run a webhook's configured verifier over headers and a body captured from
|
|
1072
|
-
* a real delivery, and report whether that signature checks out (#2445).
|
|
1073
|
-
*
|
|
1074
|
-
* `headers` is sent as ordered `[name, value]` pairs so a capture carrying a
|
|
1075
|
-
* field name on more than one line survives the round trip. Nothing is
|
|
1076
|
-
* delivered: no delivery record, no workflow run.
|
|
1077
|
-
*/
|
|
1078
|
-
async verifyWebhook(appId, webhookId, payload) {
|
|
1079
|
-
return this.post(`/admin/api/apps/${appId}/webhooks/${webhookId}/verify`, payload);
|
|
1080
|
-
}
|
|
1081
1153
|
// ============================================
|
|
1082
1154
|
// NAMED LOCKS (#1518)
|
|
1083
1155
|
// ============================================
|
|
@@ -1108,51 +1180,6 @@ export class ApiClient {
|
|
|
1108
1180
|
// ============================================
|
|
1109
1181
|
// CRON TRIGGERS
|
|
1110
1182
|
// ============================================
|
|
1111
|
-
async listCronTriggers(appId) {
|
|
1112
|
-
const result = await this.get(`/app/${appId}/api/cron-triggers`);
|
|
1113
|
-
return { items: result?.items ?? [] };
|
|
1114
|
-
}
|
|
1115
|
-
async getCronTrigger(appId, triggerId) {
|
|
1116
|
-
return this.get(`/app/${appId}/api/cron-triggers/${triggerId}`);
|
|
1117
|
-
}
|
|
1118
|
-
async createCronTrigger(appId, payload) {
|
|
1119
|
-
return this.post(`/app/${appId}/api/cron-triggers`, payload);
|
|
1120
|
-
}
|
|
1121
|
-
async updateCronTrigger(appId, triggerId, payload) {
|
|
1122
|
-
return this.put(`/app/${appId}/api/cron-triggers/${triggerId}`, payload);
|
|
1123
|
-
}
|
|
1124
|
-
/**
|
|
1125
|
-
* Retire a cron trigger. The default soft-deletes (writes the `archived`
|
|
1126
|
-
* tombstone); `{ hard: true }` destroys the row, which is what
|
|
1127
|
-
* `config push --prune` passes (#2803) so a pruned trigger stops consuming
|
|
1128
|
-
* the per-app cap and holding its key.
|
|
1129
|
-
*/
|
|
1130
|
-
async deleteCronTrigger(appId, triggerId, options = {}) {
|
|
1131
|
-
const query = options.hard ? "?hard=true" : "";
|
|
1132
|
-
return this.delete(`/app/${appId}/api/cron-triggers/${triggerId}${query}`);
|
|
1133
|
-
}
|
|
1134
|
-
async disableCronTrigger(appId, triggerId) {
|
|
1135
|
-
return this.post(`/app/${appId}/api/cron-triggers/${triggerId}/disable`, {});
|
|
1136
|
-
}
|
|
1137
|
-
async enableCronTrigger(appId, triggerId) {
|
|
1138
|
-
return this.post(`/app/${appId}/api/cron-triggers/${triggerId}/enable`, {});
|
|
1139
|
-
}
|
|
1140
|
-
async testCronTrigger(appId, triggerId) {
|
|
1141
|
-
return this.post(`/app/${appId}/api/cron-triggers/${triggerId}/test`, {});
|
|
1142
|
-
}
|
|
1143
|
-
// ============================================
|
|
1144
|
-
// ITERATIONS (iterate-users introspection / reset — #1209)
|
|
1145
|
-
// ============================================
|
|
1146
|
-
async listIterations(appId) {
|
|
1147
|
-
const result = await this.get(`/app/${appId}/api/iterations`);
|
|
1148
|
-
return { items: result?.items ?? [] };
|
|
1149
|
-
}
|
|
1150
|
-
async getIteration(appId, iterationName) {
|
|
1151
|
-
return this.get(`/app/${appId}/api/iterations/${encodeURIComponent(iterationName)}`);
|
|
1152
|
-
}
|
|
1153
|
-
async resetIteration(appId, iterationName) {
|
|
1154
|
-
return this.post(`/app/${appId}/api/iterations/${encodeURIComponent(iterationName)}/reset`, {});
|
|
1155
|
-
}
|
|
1156
1183
|
// ============================================
|
|
1157
1184
|
// RESOURCE METADATA (values — issue #1352)
|
|
1158
1185
|
// ============================================
|
|
@@ -1257,16 +1284,12 @@ export class ApiClient {
|
|
|
1257
1284
|
return this.post(`/admin/api/apps/${appId}/prompts/${promptId}/configs/${configId}/duplicate`, payload || {});
|
|
1258
1285
|
}
|
|
1259
1286
|
// ============================================
|
|
1260
|
-
// SCRIPTS
|
|
1287
|
+
// SCRIPTS — retained history (#3190)
|
|
1261
1288
|
// ============================================
|
|
1262
1289
|
//
|
|
1263
|
-
//
|
|
1264
|
-
//
|
|
1265
|
-
//
|
|
1266
|
-
// time. CLI `config push` reads `transforms/*.rhai` files and reconciles
|
|
1267
|
-
// them: a new file → create script (mints a default config); a changed
|
|
1268
|
-
// file → create a new config + activate it (zero fan-out — referencing
|
|
1269
|
-
// workflows pick up the new body on their next run).
|
|
1290
|
+
// Scripts retired with the Rhai runtime. A `Script` HEADER
|
|
1291
|
+
// (name/description/activeConfigId) and its versioned `ScriptConfig` rows
|
|
1292
|
+
// stay readable until their retention ends; nothing writes them.
|
|
1270
1293
|
async listScripts(appId) {
|
|
1271
1294
|
const result = await this.get(`/admin/api/apps/${appId}/scripts`);
|
|
1272
1295
|
return { items: result?.items ?? [] };
|
|
@@ -1279,57 +1302,18 @@ export class ApiClient {
|
|
|
1279
1302
|
* is a distinct block version: `configId` is the stable version selector,
|
|
1280
1303
|
* `contentHash` is its content identity, and `status` reports whether it is
|
|
1281
1304
|
* `active`/`draft`/`archived`. The script header's `activeConfigId` names
|
|
1282
|
-
* the live
|
|
1283
|
-
* commands to run against a specific pinned version.
|
|
1305
|
+
* the version that was live. Used by `scripts configs list`.
|
|
1284
1306
|
*/
|
|
1285
1307
|
async listScriptConfigs(appId, scriptId) {
|
|
1286
1308
|
const result = await this.get(`/admin/api/apps/${appId}/scripts/${scriptId}/configs`);
|
|
1287
1309
|
return { items: result?.items ?? [] };
|
|
1288
1310
|
}
|
|
1289
|
-
async createScript(appId, payload) {
|
|
1290
|
-
// Mints the script header + a default `active` config carrying the body.
|
|
1291
|
-
return this.post(`/admin/api/apps/${appId}/scripts`, payload);
|
|
1292
|
-
}
|
|
1293
|
-
/**
|
|
1294
|
-
* Push a new body for an existing script by creating a new `ScriptConfig`
|
|
1295
|
-
* and activating it (the prompt-model "edit → activate" flow). Referencing
|
|
1296
|
-
* workflows pick up the new body on their next run with no fan-out. The
|
|
1297
|
-
* config name is unique per script, so we mint a timestamped name.
|
|
1298
|
-
*/
|
|
1299
|
-
async pushScriptBody(appId, scriptId, body,
|
|
1300
|
-
/**
|
|
1301
|
-
* The active state the caller believes holds right now (#2731 B10).
|
|
1302
|
-
* Creating an inactive config races nothing, so the guard sits on the
|
|
1303
|
-
* ACTIVATION step: if the active config has moved since the caller read it,
|
|
1304
|
-
* the server rejects with a 409 instead of overwriting a concurrent script
|
|
1305
|
-
* change. It takes BOTH halves of "what is active", because a config's body
|
|
1306
|
-
* can be edited in place without the active id changing — the id alone
|
|
1307
|
-
* would let such an edit through. Omitted (e.g. `--force`) means activate
|
|
1308
|
-
* unconditionally.
|
|
1309
|
-
*/
|
|
1310
|
-
expectedActive) {
|
|
1311
|
-
const configName = `sync-${Date.now()}`;
|
|
1312
|
-
const config = await this.post(`/admin/api/apps/${appId}/scripts/${scriptId}/configs`, { configName, body });
|
|
1313
|
-
const activateBody = {};
|
|
1314
|
-
if (expectedActive?.configId) {
|
|
1315
|
-
activateBody.expectedActiveConfigId = expectedActive.configId;
|
|
1316
|
-
}
|
|
1317
|
-
if (expectedActive?.modifiedAt) {
|
|
1318
|
-
activateBody.expectedActiveModifiedAt = expectedActive.modifiedAt;
|
|
1319
|
-
}
|
|
1320
|
-
await this.post(`/admin/api/apps/${appId}/scripts/${scriptId}/configs/${config.configId}/activate`, activateBody);
|
|
1321
|
-
return config;
|
|
1322
|
-
}
|
|
1323
1311
|
/**
|
|
1324
|
-
*
|
|
1325
|
-
*
|
|
1326
|
-
* references by name — without it the server answers 409.
|
|
1312
|
+
* One version of a script, body included. A body the server cannot load
|
|
1313
|
+
* answers a 404 naming the version rather than an empty body.
|
|
1327
1314
|
*/
|
|
1328
|
-
async
|
|
1329
|
-
|
|
1330
|
-
? `/admin/api/apps/${appId}/scripts/${scriptId}?force=true`
|
|
1331
|
-
: `/admin/api/apps/${appId}/scripts/${scriptId}`;
|
|
1332
|
-
return this.delete(path);
|
|
1315
|
+
async getScriptConfig(appId, scriptId, configId) {
|
|
1316
|
+
return this.get(`/admin/api/apps/${appId}/scripts/${scriptId}/configs/${configId}`);
|
|
1333
1317
|
}
|
|
1334
1318
|
async getPromptSchema(appId, promptId) {
|
|
1335
1319
|
return this.get(`/admin/api/apps/${appId}/prompts/${promptId}/schema`);
|
|
@@ -1387,6 +1371,13 @@ export class ApiClient {
|
|
|
1387
1371
|
if (payload.expectedJsonSubset) {
|
|
1388
1372
|
serverPayload.expectedJsonSubset = payload.expectedJsonSubset;
|
|
1389
1373
|
}
|
|
1374
|
+
// #4262 — an agent case's `[test.agent]` keys, each when the caller set it:
|
|
1375
|
+
// presence, not truthiness, so `agentExpectedNoToolCall: false` travels. A
|
|
1376
|
+
// chat case sets none, and its body is unchanged.
|
|
1377
|
+
for (const key of AGENT_TEST_CASE_WIRE_KEYS) {
|
|
1378
|
+
if (payload[key] !== undefined)
|
|
1379
|
+
serverPayload[key] = payload[key];
|
|
1380
|
+
}
|
|
1390
1381
|
return this.post(`/admin/api/apps/${appId}/blocks/${blockType}/${blockId}/test-cases`, serverPayload);
|
|
1391
1382
|
}
|
|
1392
1383
|
async updateTestCase(appId, blockType, blockId, testCaseId, payload) {
|
|
@@ -1421,6 +1412,11 @@ export class ApiClient {
|
|
|
1421
1412
|
}
|
|
1422
1413
|
if (payload.key !== undefined)
|
|
1423
1414
|
serverPayload.key = payload.key;
|
|
1415
|
+
// #4262 — as on create; an explicit `null` clears the field.
|
|
1416
|
+
for (const key of AGENT_TEST_CASE_WIRE_KEYS) {
|
|
1417
|
+
if (payload[key] !== undefined)
|
|
1418
|
+
serverPayload[key] = payload[key];
|
|
1419
|
+
}
|
|
1424
1420
|
return this.patch(`/admin/api/apps/${appId}/blocks/${blockType}/${blockId}/test-cases/${testCaseId}`, serverPayload);
|
|
1425
1421
|
}
|
|
1426
1422
|
async deleteTestCase(appId, blockType, blockId, testCaseId) {
|
|
@@ -1436,17 +1432,15 @@ export class ApiClient {
|
|
|
1436
1432
|
const credentials = await this.ensureAuthenticated();
|
|
1437
1433
|
const encodedFilename = encodeURIComponent(filename);
|
|
1438
1434
|
const url = `${credentials.serverUrl}/admin/api/apps/${appId}/blocks/${blockType}/${blockId}/test-cases/${testCaseId}/attachments/${encodedFilename}`;
|
|
1439
|
-
const
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
method: "PUT",
|
|
1448
|
-
headers,
|
|
1449
|
-
body: data,
|
|
1435
|
+
const response = await this.fetchWithStepUp(url, url, credentials.accessToken, (bearer) => {
|
|
1436
|
+
const headers = {
|
|
1437
|
+
Authorization: `Bearer ${bearer}`,
|
|
1438
|
+
"Content-Type": contentType,
|
|
1439
|
+
};
|
|
1440
|
+
if (credentials.globalAdminAppId) {
|
|
1441
|
+
headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
|
|
1442
|
+
}
|
|
1443
|
+
return { method: "PUT", headers, body: data };
|
|
1450
1444
|
});
|
|
1451
1445
|
const text = await response.text();
|
|
1452
1446
|
if (!response.ok) {
|
|
@@ -1498,7 +1492,7 @@ export class ApiClient {
|
|
|
1498
1492
|
return { items: result?.items ?? result ?? [] };
|
|
1499
1493
|
}
|
|
1500
1494
|
// ============================================
|
|
1501
|
-
// WORKFLOWS
|
|
1495
|
+
// WORKFLOWS — retained history, read-only (#3190)
|
|
1502
1496
|
// ============================================
|
|
1503
1497
|
async listWorkflows(appId, params) {
|
|
1504
1498
|
const result = await this.get(`/admin/api/apps/${appId}/workflows`, params);
|
|
@@ -1511,140 +1505,39 @@ export class ApiClient {
|
|
|
1511
1505
|
async getWorkflow(appId, workflowId) {
|
|
1512
1506
|
return this.get(`/admin/api/apps/${appId}/workflows/${workflowId}`);
|
|
1513
1507
|
}
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
*/
|
|
1521
|
-
async getWorkflowUsage(appId, params) {
|
|
1522
|
-
return this.get(`/admin/api/apps/${appId}/workflows/usage`, params);
|
|
1508
|
+
// ============================================
|
|
1509
|
+
// WORKFLOW CONFIGURATIONS
|
|
1510
|
+
// ============================================
|
|
1511
|
+
async listWorkflowConfigs(appId, workflowId) {
|
|
1512
|
+
const result = await this.get(`/admin/api/apps/${appId}/workflows/${workflowId}/configs`);
|
|
1513
|
+
return { items: result?.items ?? [] };
|
|
1523
1514
|
}
|
|
1524
|
-
async
|
|
1525
|
-
return this.
|
|
1515
|
+
async getWorkflowConfig(appId, workflowId, configId) {
|
|
1516
|
+
return this.get(`/admin/api/apps/${appId}/workflows/${workflowId}/configs/${configId}`);
|
|
1526
1517
|
}
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1518
|
+
// ============================================
|
|
1519
|
+
// ANALYTICS
|
|
1520
|
+
// ============================================
|
|
1521
|
+
async getAnalyticsTopUsers(appId, params) {
|
|
1522
|
+
return this.get(`/app/${appId}/api/analytics/users/top`, params);
|
|
1530
1523
|
}
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
*
|
|
1534
|
-
* The plain call ARCHIVES: the definition row stays, so its runs, revisions
|
|
1535
|
-
* and test cases keep resolving. `{ hard: true }` performs the full cascade
|
|
1536
|
-
* and is what releases the `workflowKey` — the flag `config push --prune`
|
|
1537
|
-
* sends, for the same reason it sends it for webhooks and integrations.
|
|
1538
|
-
*/
|
|
1539
|
-
async deleteWorkflow(appId, workflowId, options) {
|
|
1540
|
-
const path = `/admin/api/apps/${appId}/workflows/${workflowId}`;
|
|
1541
|
-
return this.delete(options?.hard ? `${path}?hard=true` : path);
|
|
1524
|
+
async getAnalyticsIntegrationMetrics(appId, params) {
|
|
1525
|
+
return this.get(`/app/${appId}/api/analytics/integrations`, params);
|
|
1542
1526
|
}
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
* PATCH: `config push` owns every configuration field, and taking a workflow
|
|
1546
|
-
* out of service must not touch one, or an operator's action would show up as
|
|
1547
|
-
* drift against the repo. Same route shape as `cron-triggers pause|resume`.
|
|
1548
|
-
*/
|
|
1549
|
-
async disableWorkflow(appId, workflowId) {
|
|
1550
|
-
return this.post(`/app/${appId}/api/workflows/${workflowId}/disable`, {});
|
|
1527
|
+
async getAnalyticsOverviewDau(appId) {
|
|
1528
|
+
return this.get(`/app/${appId}/api/analytics/overview/dau`);
|
|
1551
1529
|
}
|
|
1552
|
-
async
|
|
1553
|
-
return this.
|
|
1530
|
+
async getAnalyticsOverviewWau(appId) {
|
|
1531
|
+
return this.get(`/app/${appId}/api/analytics/overview/wau`);
|
|
1554
1532
|
}
|
|
1555
|
-
async
|
|
1556
|
-
return this.
|
|
1533
|
+
async getAnalyticsOverviewMau(appId) {
|
|
1534
|
+
return this.get(`/app/${appId}/api/analytics/overview/mau`);
|
|
1557
1535
|
}
|
|
1558
|
-
async
|
|
1559
|
-
return this.
|
|
1536
|
+
async getAnalyticsOverviewGrowth(appId, params) {
|
|
1537
|
+
return this.get(`/app/${appId}/api/analytics/overview/growth`, params);
|
|
1560
1538
|
}
|
|
1561
|
-
async
|
|
1562
|
-
return this.get(`/
|
|
1563
|
-
}
|
|
1564
|
-
async listWorkflowRuns(appId, workflowId, params) {
|
|
1565
|
-
const result = await this.get(`/admin/api/apps/${appId}/workflows/${workflowId}/runs`, params);
|
|
1566
|
-
const nextCursor = result?.nextCursor ?? null;
|
|
1567
|
-
return {
|
|
1568
|
-
items: result?.items ?? [],
|
|
1569
|
-
hasMore: result?.hasMore,
|
|
1570
|
-
nextCursor,
|
|
1571
|
-
resumeAfter: result?.resumeAfter,
|
|
1572
|
-
scanned: typeof result?.scanned === "number" ? result.scanned : undefined,
|
|
1573
|
-
};
|
|
1574
|
-
}
|
|
1575
|
-
/**
|
|
1576
|
-
* Workflow runs started by one app user, across every workflow (#1967).
|
|
1577
|
-
* Backed by `GET /admin/api/apps/{appId}/users/{userId}/workflow-runs`,
|
|
1578
|
-
* which resolves the app user before querying the `runsByUser` index.
|
|
1579
|
-
*/
|
|
1580
|
-
async listUserWorkflowRuns(appId, userId, params) {
|
|
1581
|
-
const result = await this.get(`/admin/api/apps/${appId}/users/${encodeURIComponent(userId)}/workflow-runs`, params);
|
|
1582
|
-
const nextCursor = result?.nextCursor ?? null;
|
|
1583
|
-
return {
|
|
1584
|
-
items: result?.items ?? [],
|
|
1585
|
-
hasMore: result?.hasMore,
|
|
1586
|
-
nextCursor,
|
|
1587
|
-
scanned: typeof result?.scanned === "number" ? result.scanned : undefined,
|
|
1588
|
-
};
|
|
1589
|
-
}
|
|
1590
|
-
async getWorkflowRunStatus(appId, workflowId, runId) {
|
|
1591
|
-
return this.get(`/admin/api/apps/${appId}/workflows/${workflowId}/runs/${runId}/status`);
|
|
1592
|
-
}
|
|
1593
|
-
async getWorkflowStepRuns(appId, workflowId, runId) {
|
|
1594
|
-
const result = await this.get(`/admin/api/apps/${appId}/workflows/${workflowId}/runs/${runId}/steps`);
|
|
1595
|
-
return { items: result?.items ?? [] };
|
|
1596
|
-
}
|
|
1597
|
-
// Workflow analytics have one client path, `getAnalyticsTopWorkflows`
|
|
1598
|
-
// (issue #2766): `getTopWorkflows` was a second name for the same endpoint,
|
|
1599
|
-
// and `getWorkflowAnalytics` called a route the server never registered.
|
|
1600
|
-
// ============================================
|
|
1601
|
-
// WORKFLOW CONFIGURATIONS
|
|
1602
|
-
// ============================================
|
|
1603
|
-
async listWorkflowConfigs(appId, workflowId) {
|
|
1604
|
-
const result = await this.get(`/admin/api/apps/${appId}/workflows/${workflowId}/configs`);
|
|
1605
|
-
return { items: result?.items ?? [] };
|
|
1606
|
-
}
|
|
1607
|
-
async getWorkflowConfig(appId, workflowId, configId) {
|
|
1608
|
-
return this.get(`/admin/api/apps/${appId}/workflows/${workflowId}/configs/${configId}`);
|
|
1609
|
-
}
|
|
1610
|
-
async createWorkflowConfig(appId, workflowId, payload) {
|
|
1611
|
-
return this.post(`/admin/api/apps/${appId}/workflows/${workflowId}/configs`, payload);
|
|
1612
|
-
}
|
|
1613
|
-
async updateWorkflowConfig(appId, workflowId, configId, payload) {
|
|
1614
|
-
return this.patch(`/admin/api/apps/${appId}/workflows/${workflowId}/configs/${configId}`, payload);
|
|
1615
|
-
}
|
|
1616
|
-
async archiveWorkflowConfig(appId, workflowId, configId) {
|
|
1617
|
-
return this.delete(`/admin/api/apps/${appId}/workflows/${workflowId}/configs/${configId}`);
|
|
1618
|
-
}
|
|
1619
|
-
async activateWorkflowConfig(appId, workflowId, configId) {
|
|
1620
|
-
return this.post(`/admin/api/apps/${appId}/workflows/${workflowId}/configs/${configId}/activate`, {});
|
|
1621
|
-
}
|
|
1622
|
-
async duplicateWorkflowConfig(appId, workflowId, configId, payload) {
|
|
1623
|
-
return this.post(`/admin/api/apps/${appId}/workflows/${workflowId}/configs/${configId}/duplicate`, payload || {});
|
|
1624
|
-
}
|
|
1625
|
-
// ============================================
|
|
1626
|
-
// ANALYTICS
|
|
1627
|
-
// ============================================
|
|
1628
|
-
async getAnalyticsTopUsers(appId, params) {
|
|
1629
|
-
return this.get(`/app/${appId}/api/analytics/users/top`, params);
|
|
1630
|
-
}
|
|
1631
|
-
async getAnalyticsIntegrationMetrics(appId, params) {
|
|
1632
|
-
return this.get(`/app/${appId}/api/analytics/integrations`, params);
|
|
1633
|
-
}
|
|
1634
|
-
async getAnalyticsOverviewDau(appId) {
|
|
1635
|
-
return this.get(`/app/${appId}/api/analytics/overview/dau`);
|
|
1636
|
-
}
|
|
1637
|
-
async getAnalyticsOverviewWau(appId) {
|
|
1638
|
-
return this.get(`/app/${appId}/api/analytics/overview/wau`);
|
|
1639
|
-
}
|
|
1640
|
-
async getAnalyticsOverviewMau(appId) {
|
|
1641
|
-
return this.get(`/app/${appId}/api/analytics/overview/mau`);
|
|
1642
|
-
}
|
|
1643
|
-
async getAnalyticsOverviewGrowth(appId, params) {
|
|
1644
|
-
return this.get(`/app/${appId}/api/analytics/overview/growth`, params);
|
|
1645
|
-
}
|
|
1646
|
-
async getAnalyticsDailyActive(appId, params) {
|
|
1647
|
-
return this.get(`/app/${appId}/api/analytics/daily-active`, params);
|
|
1539
|
+
async getAnalyticsDailyActive(appId, params) {
|
|
1540
|
+
return this.get(`/app/${appId}/api/analytics/daily-active`, params);
|
|
1648
1541
|
}
|
|
1649
1542
|
async getAnalyticsRollingActive(appId, params) {
|
|
1650
1543
|
return this.get(`/app/${appId}/api/analytics/rolling-active`, params);
|
|
@@ -1670,9 +1563,6 @@ export class ApiClient {
|
|
|
1670
1563
|
async getAnalyticsErrorsGroups(appId, params) {
|
|
1671
1564
|
return this.get(`/app/${appId}/api/analytics/errors/groups`, params);
|
|
1672
1565
|
}
|
|
1673
|
-
async getAnalyticsTopWorkflows(appId, params) {
|
|
1674
|
-
return this.get(`/app/${appId}/api/analytics/workflows/top`, params);
|
|
1675
|
-
}
|
|
1676
1566
|
async getAnalyticsTopPrompts(appId, params) {
|
|
1677
1567
|
return this.get(`/app/${appId}/api/analytics/prompts/top`, params);
|
|
1678
1568
|
}
|
|
@@ -1890,43 +1780,6 @@ export class ApiClient {
|
|
|
1890
1780
|
async deleteDatabase(appId, databaseId) {
|
|
1891
1781
|
return this.delete(`/app/${appId}/api/databases/${databaseId}`);
|
|
1892
1782
|
}
|
|
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
1783
|
// ============================================
|
|
1931
1784
|
// DATABASE PERMISSIONS
|
|
1932
1785
|
// ============================================
|
|
@@ -2021,72 +1874,6 @@ export class ApiClient {
|
|
|
2021
1874
|
});
|
|
2022
1875
|
}
|
|
2023
1876
|
// ============================================
|
|
2024
|
-
// DATABASE OPERATIONS (registered operations)
|
|
2025
|
-
// ============================================
|
|
2026
|
-
async listDatabaseOperations(appId, databaseId) {
|
|
2027
|
-
return this.get(`/app/${appId}/api/databases/${databaseId}/operations`);
|
|
2028
|
-
}
|
|
2029
|
-
/**
|
|
2030
|
-
* Execute a registered operation and, when the result is a query page,
|
|
2031
|
-
* normalize it to the CLI's one list envelope (issue #2440).
|
|
2032
|
-
*
|
|
2033
|
-
* The registered operation decides the response shape, so the normalization
|
|
2034
|
-
* is conditional — see {@link normalizeDatabaseOperationResult}. Everything
|
|
2035
|
-
* else (a `count`, an `aggregate`, a mutation, a `returnField: "all"`
|
|
2036
|
-
* pipeline) is returned exactly as the server sent it.
|
|
2037
|
-
*/
|
|
2038
|
-
async executeDatabaseOperation(appId, databaseId, operationName, data, token, options) {
|
|
2039
|
-
const path = `/app/${appId}/api/databases/${databaseId}/operations/${encodeURIComponent(operationName)}/execute`;
|
|
2040
|
-
const extraHeaders = {};
|
|
2041
|
-
if (options?.timing) {
|
|
2042
|
-
extraHeaders["X-Timing"] = "true";
|
|
2043
|
-
}
|
|
2044
|
-
if (token) {
|
|
2045
|
-
return normalizeDatabaseOperationResult(await this.requestWithToken(path, token, {
|
|
2046
|
-
method: "POST",
|
|
2047
|
-
body: JSON.stringify(data || {}),
|
|
2048
|
-
headers: extraHeaders,
|
|
2049
|
-
}));
|
|
2050
|
-
}
|
|
2051
|
-
return normalizeDatabaseOperationResult(await this.request(path, {
|
|
2052
|
-
method: "POST",
|
|
2053
|
-
body: JSON.stringify(data || {}),
|
|
2054
|
-
headers: extraHeaders,
|
|
2055
|
-
}));
|
|
2056
|
-
}
|
|
2057
|
-
/**
|
|
2058
|
-
* Execute a registered batch (bulk) database operation. Posts a chunk of
|
|
2059
|
-
* items to the canonical `operations/:name/batch` endpoint (the same one the
|
|
2060
|
-
* client library's `executeBatch` uses — NOT the deprecated `import-bulk`
|
|
2061
|
-
* alias). Used by `databases import-csv`.
|
|
2062
|
-
*
|
|
2063
|
-
* @returns `{ imported, failed }` — DO-level write outcome counts. Per-item
|
|
2064
|
-
* validation/access failures abort the whole chunk with a 4xx (thrown).
|
|
2065
|
-
*/
|
|
2066
|
-
async executeBatch(appId, databaseId, operationName, batch) {
|
|
2067
|
-
return this.post(`/app/${appId}/api/databases/${databaseId}/operations/${encodeURIComponent(operationName)}/batch`, { batch });
|
|
2068
|
-
}
|
|
2069
|
-
/**
|
|
2070
|
-
* Make a request using a specific JWT token instead of the CLI's credentials.
|
|
2071
|
-
* Used for executing operations as a different user.
|
|
2072
|
-
*/
|
|
2073
|
-
async requestWithToken(path, token, options = {}) {
|
|
2074
|
-
const credentials = await this.ensureAuthenticated();
|
|
2075
|
-
const url = `${credentials.serverUrl}${path}`;
|
|
2076
|
-
const headers = {
|
|
2077
|
-
Authorization: `Bearer ${token}`,
|
|
2078
|
-
"Content-Type": "application/json",
|
|
2079
|
-
...(options.headers || {}),
|
|
2080
|
-
};
|
|
2081
|
-
const response = await fetchWithTLS(url, { ...options, headers });
|
|
2082
|
-
const text = await response.text();
|
|
2083
|
-
if (!response.ok) {
|
|
2084
|
-
const parsed = parseErrorResponse(response, text, path);
|
|
2085
|
-
throw new ApiError(parsed.message, response.status, parsed.code, parsed.details);
|
|
2086
|
-
}
|
|
2087
|
-
return text ? JSON.parse(text) : null;
|
|
2088
|
-
}
|
|
2089
|
-
// ============================================
|
|
2090
1877
|
// DATABASE INDEXES
|
|
2091
1878
|
// ============================================
|
|
2092
1879
|
async listDatabaseIndexes(appId, databaseId, modelName) {
|
|
@@ -2119,7 +1906,7 @@ export class ApiClient {
|
|
|
2119
1906
|
// Issue #1336 (codex round-1 P2): `dryRun` runs every create-time
|
|
2120
1907
|
// validation (inline CEL rule lint, manifest shape, schema TOML parse)
|
|
2121
1908
|
// WITHOUT persisting, so a fresh-type `config push --dry-run` surfaces a
|
|
2122
|
-
// malformed
|
|
1909
|
+
// malformed rule or manifest the same way the real POST would.
|
|
2123
1910
|
options) {
|
|
2124
1911
|
const query = options?.dryRun ? "?dryRun=true" : "";
|
|
2125
1912
|
return this.post(`/app/${appId}/api/databases/types${query}`, data);
|
|
@@ -2140,105 +1927,14 @@ export class ApiClient {
|
|
|
2140
1927
|
return this.delete(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}${query}`);
|
|
2141
1928
|
}
|
|
2142
1929
|
/**
|
|
2143
|
-
* Issue #666 Phase 3: ask the server to scaffold a TOML schema from
|
|
2144
|
-
*
|
|
1930
|
+
* Issue #666 Phase 3: ask the server to scaffold a TOML schema from the
|
|
1931
|
+
* type's databases (the models each holds, and their fields). Read-only —
|
|
1932
|
+
* does NOT persist.
|
|
2145
1933
|
*/
|
|
2146
1934
|
async scaffoldDatabaseTypeSchema(appId, databaseType) {
|
|
2147
1935
|
return this.post(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/schema:scaffold`, {});
|
|
2148
1936
|
}
|
|
2149
1937
|
// ============================================
|
|
2150
|
-
// DATABASE TYPE OPERATIONS
|
|
2151
|
-
// ============================================
|
|
2152
|
-
async listDatabaseTypeOperations(appId, databaseType) {
|
|
2153
|
-
return this.get(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/operations`);
|
|
2154
|
-
}
|
|
2155
|
-
async getDatabaseTypeOperation(appId, databaseType, name) {
|
|
2156
|
-
return this.get(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/operations/${encodeURIComponent(name)}`);
|
|
2157
|
-
}
|
|
2158
|
-
async createDatabaseTypeOperation(appId, databaseType, data,
|
|
2159
|
-
// Issue #813 (1A): when `dryRun` is set the server runs the op-edit gate
|
|
2160
|
-
// without persisting — used by `config push --dry-run` to surface gate
|
|
2161
|
-
// failures the real push would hit. `schemaOverride` lets the gate run
|
|
2162
|
-
// against the schema the SAME push is about to land (so an op that depends
|
|
2163
|
-
// on a new field isn't falsely rejected against the stale stored schema).
|
|
2164
|
-
//
|
|
2165
|
-
// Issue #915 (defect a, follow-up): for a FRESH db-type the parent config
|
|
2166
|
-
// does not exist on the server yet, so the server's "access required"
|
|
2167
|
-
// check has no `defaultAccess` to fall back on. Thread the TOML-declared
|
|
2168
|
-
// type-level `defaultAccess` so an op that omits `access` (intending to
|
|
2169
|
-
// inherit it after the type is created) isn't falsely rejected during the
|
|
2170
|
-
// dry-run. Server only trusts this on the fresh-type dry-run path.
|
|
2171
|
-
options) {
|
|
2172
|
-
const query = options?.dryRun ? "?dryRun=true" : "";
|
|
2173
|
-
let body = data;
|
|
2174
|
-
if (options?.dryRun && options.schemaOverride !== undefined) {
|
|
2175
|
-
body = { ...body, schemaOverride: options.schemaOverride };
|
|
2176
|
-
}
|
|
2177
|
-
if (options?.dryRun && options.defaultAccess !== undefined) {
|
|
2178
|
-
body = { ...body, defaultAccess: options.defaultAccess };
|
|
2179
|
-
}
|
|
2180
|
-
if (options?.dryRun && options.metadataManifestOverride !== undefined) {
|
|
2181
|
-
body = { ...body, metadataManifestOverride: options.metadataManifestOverride };
|
|
2182
|
-
}
|
|
2183
|
-
return this.post(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/operations${query}`, body);
|
|
2184
|
-
}
|
|
2185
|
-
async updateDatabaseTypeOperation(appId, databaseType, name, data, expectedModifiedAt,
|
|
2186
|
-
// Issue #813 (1A): dry-run runs the op-edit gate without persisting.
|
|
2187
|
-
// `schemaOverride` gates against the schema the same push is landing.
|
|
2188
|
-
// Issue #1336: `metadataManifestOverride` gates the op-access lint against
|
|
2189
|
-
// the manifest the same push is landing (see `createDatabaseTypeOperation`).
|
|
2190
|
-
options) {
|
|
2191
|
-
let body = expectedModifiedAt ? { ...data, expectedModifiedAt } : { ...data };
|
|
2192
|
-
if (options?.dryRun && options.schemaOverride !== undefined) {
|
|
2193
|
-
body = { ...body, schemaOverride: options.schemaOverride };
|
|
2194
|
-
}
|
|
2195
|
-
if (options?.dryRun && options.metadataManifestOverride !== undefined) {
|
|
2196
|
-
body = { ...body, metadataManifestOverride: options.metadataManifestOverride };
|
|
2197
|
-
}
|
|
2198
|
-
const query = options?.dryRun ? "?dryRun=true" : "";
|
|
2199
|
-
return this.patch(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/operations/${encodeURIComponent(name)}${query}`, body);
|
|
2200
|
-
}
|
|
2201
|
-
async deleteDatabaseTypeOperation(appId, databaseType, name) {
|
|
2202
|
-
return this.delete(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/operations/${encodeURIComponent(name)}`);
|
|
2203
|
-
}
|
|
2204
|
-
// ============================================
|
|
2205
|
-
// DATABASE TYPE SUBSCRIPTIONS (#740 / #803)
|
|
2206
|
-
// ============================================
|
|
2207
|
-
//
|
|
2208
|
-
// Transparent passthroughs against the server's type-scoped subscription
|
|
2209
|
-
// routes (GET/POST/PUT/DELETE `/databases/types/:type/subscriptions[/:key]`).
|
|
2210
|
-
// The wire format is authoritative in the controller
|
|
2211
|
-
// (`database-type-subscriptions-controller.ts`): create requires
|
|
2212
|
-
// `subscriptionKey`, `displayName`, `modelName`, `filter` (CEL), `access`
|
|
2213
|
-
// (CEL); optional `description`, `select` (string[]), `emit`
|
|
2214
|
-
// (enter/update/leave subset), `params` (object, ≤5 entries), `status`.
|
|
2215
|
-
//
|
|
2216
|
-
// The list endpoint wraps results in `{ items: [...] }` (unlike operations,
|
|
2217
|
-
// which returns a bare array). We unwrap here so callers get an array,
|
|
2218
|
-
// matching `listDatabaseTypeOperations`.
|
|
2219
|
-
async listDatabaseTypeSubscriptions(appId, databaseType) {
|
|
2220
|
-
const result = await this.get(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/subscriptions`);
|
|
2221
|
-
if (Array.isArray(result))
|
|
2222
|
-
return result;
|
|
2223
|
-
return Array.isArray(result?.items) ? result.items : [];
|
|
2224
|
-
}
|
|
2225
|
-
async getDatabaseTypeSubscription(appId, databaseType, subscriptionKey) {
|
|
2226
|
-
return this.get(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/subscriptions/${encodeURIComponent(subscriptionKey)}`);
|
|
2227
|
-
}
|
|
2228
|
-
async createDatabaseTypeSubscription(appId, databaseType, data) {
|
|
2229
|
-
return this.post(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/subscriptions`, data);
|
|
2230
|
-
}
|
|
2231
|
-
async updateDatabaseTypeSubscription(appId, databaseType, subscriptionKey, data,
|
|
2232
|
-
// #2731 B10 — optimistic concurrency, as the sibling operation update has
|
|
2233
|
-
// had. Omitted (by `--force`, and by every caller predating it) means the
|
|
2234
|
-
// write stays unconditional.
|
|
2235
|
-
expectedModifiedAt) {
|
|
2236
|
-
return this.put(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/subscriptions/${encodeURIComponent(subscriptionKey)}`, expectedModifiedAt ? { ...data, expectedModifiedAt } : data);
|
|
2237
|
-
}
|
|
2238
|
-
async deleteDatabaseTypeSubscription(appId, databaseType, subscriptionKey) {
|
|
2239
|
-
return this.delete(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/subscriptions/${encodeURIComponent(subscriptionKey)}`);
|
|
2240
|
-
}
|
|
2241
|
-
// ============================================
|
|
2242
1938
|
// GROUPS
|
|
2243
1939
|
// ============================================
|
|
2244
1940
|
async listGroups(appId, params) {
|
|
@@ -2250,31 +1946,55 @@ export class ApiClient {
|
|
|
2250
1946
|
hasMore: result?.hasMore ?? nextCursor != null,
|
|
2251
1947
|
};
|
|
2252
1948
|
}
|
|
1949
|
+
/** One page of one custodian's groups (`app` or `user:<userId>`), through the admin API. */
|
|
1950
|
+
async listGroupsByCustodian(appId, custodian, params) {
|
|
1951
|
+
const result = await this.get(`/admin/api/apps/${appId}/groups`, { custodian, ...(params ?? {}) });
|
|
1952
|
+
const nextCursor = result?.nextCursor;
|
|
1953
|
+
return {
|
|
1954
|
+
items: result?.items ?? [],
|
|
1955
|
+
nextCursor,
|
|
1956
|
+
hasMore: result?.hasMore ?? nextCursor != null,
|
|
1957
|
+
};
|
|
1958
|
+
}
|
|
1959
|
+
/** Transfer a group to `to` (`app` or `user:<userId>`) through the admin API. */
|
|
1960
|
+
async transferGroup(appId, groupId, to) {
|
|
1961
|
+
return this.post(`/admin/api/apps/${appId}/groups/${encodeURIComponent(groupId)}/transfer`, { to });
|
|
1962
|
+
}
|
|
1963
|
+
/**
|
|
1964
|
+
* Create a group through the admin API; `custodian` is `app` or
|
|
1965
|
+
* `user:<userId>`. The server mints the group's id and answers it as
|
|
1966
|
+
* `groupId`.
|
|
1967
|
+
*/
|
|
2253
1968
|
async createGroup(appId, data) {
|
|
2254
|
-
return this.post(`/
|
|
1969
|
+
return this.post(`/admin/api/apps/${appId}/groups`, data);
|
|
2255
1970
|
}
|
|
2256
|
-
|
|
2257
|
-
|
|
1971
|
+
/**
|
|
1972
|
+
* A group by id. `groupType` looks a group written before groups were
|
|
1973
|
+
* addressed by id up by its type first (the routes' deprecated
|
|
1974
|
+
* `?groupType=`), as an exported collection's grants still name them.
|
|
1975
|
+
*/
|
|
1976
|
+
async getGroup(appId, groupId, options) {
|
|
1977
|
+
return this.get(`/app/${appId}/api/groups/${encodeURIComponent(groupId)}`, options?.groupType ? { groupType: options.groupType } : undefined);
|
|
2258
1978
|
}
|
|
2259
|
-
async updateGroup(appId,
|
|
2260
|
-
return this.patch(`/app/${appId}/api/groups/${
|
|
1979
|
+
async updateGroup(appId, groupId, data) {
|
|
1980
|
+
return this.patch(`/app/${appId}/api/groups/${encodeURIComponent(groupId)}`, data);
|
|
2261
1981
|
}
|
|
2262
|
-
async deleteGroup(appId,
|
|
2263
|
-
return this.delete(`/app/${appId}/api/groups/${
|
|
1982
|
+
async deleteGroup(appId, groupId) {
|
|
1983
|
+
return this.delete(`/app/${appId}/api/groups/${encodeURIComponent(groupId)}`);
|
|
2264
1984
|
}
|
|
2265
1985
|
// ============================================
|
|
2266
1986
|
// GROUP MEMBERS
|
|
2267
1987
|
// ============================================
|
|
2268
1988
|
/**
|
|
2269
|
-
* One page of a group's members.
|
|
1989
|
+
* One page of a group's members, with their roles.
|
|
2270
1990
|
*
|
|
2271
1991
|
* The route paginates cursor-style (no default limit, cap 200) and reports
|
|
2272
1992
|
* the boundary, so `groups members list` carries `--limit`/`--cursor` and
|
|
2273
1993
|
* forwards them (#3646) rather than printing a first page that claims to be
|
|
2274
1994
|
* the whole membership.
|
|
2275
1995
|
*/
|
|
2276
|
-
async listGroupMembers(appId,
|
|
2277
|
-
const result = await this.get(`/app/${appId}/api/groups/${
|
|
1996
|
+
async listGroupMembers(appId, groupId, options) {
|
|
1997
|
+
const result = await this.get(`/app/${appId}/api/groups/${encodeURIComponent(groupId)}/members`, {
|
|
2278
1998
|
...(options?.include ? { include: options.include } : {}),
|
|
2279
1999
|
...(options?.limit !== undefined ? { limit: options.limit } : {}),
|
|
2280
2000
|
...(options?.cursor ? { cursor: options.cursor } : {}),
|
|
@@ -2286,17 +2006,23 @@ export class ApiClient {
|
|
|
2286
2006
|
hasMore: result?.hasMore ?? nextCursor != null,
|
|
2287
2007
|
};
|
|
2288
2008
|
}
|
|
2289
|
-
|
|
2290
|
-
|
|
2009
|
+
/** Add a member through the admin API; `role: "owner"` restores the custodian's owner row. */
|
|
2010
|
+
async addGroupMember(appId, groupId, data) {
|
|
2011
|
+
return this.post(`/admin/api/apps/${appId}/groups/${encodeURIComponent(groupId)}/members`, data);
|
|
2291
2012
|
}
|
|
2292
|
-
|
|
2293
|
-
|
|
2013
|
+
/** Change a member's role through the admin API, the owner row's included. */
|
|
2014
|
+
async setGroupMemberRole(appId, groupId, userId, role) {
|
|
2015
|
+
return this.patch(`/admin/api/apps/${appId}/groups/${encodeURIComponent(groupId)}/members/${encodeURIComponent(userId)}`, { role });
|
|
2016
|
+
}
|
|
2017
|
+
/** Remove a member through the admin API, the owner row included. */
|
|
2018
|
+
async removeGroupMember(appId, groupId, userId) {
|
|
2019
|
+
return this.delete(`/admin/api/apps/${appId}/groups/${encodeURIComponent(groupId)}/members/${encodeURIComponent(userId)}`);
|
|
2294
2020
|
}
|
|
2295
2021
|
// ============================================
|
|
2296
2022
|
// GROUP RESOURCE LISTINGS
|
|
2297
2023
|
// ============================================
|
|
2298
|
-
async listGroupDocuments(appId,
|
|
2299
|
-
return this.get(`/app/${appId}/api/groups/${
|
|
2024
|
+
async listGroupDocuments(appId, groupId) {
|
|
2025
|
+
return this.get(`/app/${appId}/api/groups/${encodeURIComponent(groupId)}/documents`);
|
|
2300
2026
|
}
|
|
2301
2027
|
// ============================================
|
|
2302
2028
|
// USER MEMBERSHIPS
|
|
@@ -2350,6 +2076,33 @@ export class ApiClient {
|
|
|
2350
2076
|
async createCollection(appId, data) {
|
|
2351
2077
|
return this.post(`/app/${appId}/api/collections`, data);
|
|
2352
2078
|
}
|
|
2079
|
+
/** One page of one custodian's collections (`app` or `user:<userId>`), through the admin API. */
|
|
2080
|
+
async listCollectionsByCustodian(appId, custodian, params) {
|
|
2081
|
+
const query = { custodian };
|
|
2082
|
+
if (params?.limit !== undefined)
|
|
2083
|
+
query.limit = params.limit;
|
|
2084
|
+
if (params?.cursor)
|
|
2085
|
+
query.cursor = params.cursor;
|
|
2086
|
+
const result = await this.get(`/admin/api/apps/${appId}/collections`, query);
|
|
2087
|
+
const nextCursor = result?.nextCursor;
|
|
2088
|
+
return {
|
|
2089
|
+
items: result?.items ?? [],
|
|
2090
|
+
nextCursor,
|
|
2091
|
+
hasMore: result?.hasMore ?? nextCursor != null,
|
|
2092
|
+
};
|
|
2093
|
+
}
|
|
2094
|
+
/** Transfer a collection to `to` (`app` or `user:<userId>`) through the admin API. */
|
|
2095
|
+
async transferCollection(appId, collectionId, to) {
|
|
2096
|
+
return this.post(`/admin/api/apps/${appId}/collections/${encodeURIComponent(collectionId)}/transfer`, { to });
|
|
2097
|
+
}
|
|
2098
|
+
/**
|
|
2099
|
+
* Create a collection through the admin API, for `custodian` (`app` or
|
|
2100
|
+
* `user:<userId>`; a user custodian holds the owner row). Spends no
|
|
2101
|
+
* create budget.
|
|
2102
|
+
*/
|
|
2103
|
+
async createCollectionAsAdmin(appId, data) {
|
|
2104
|
+
return this.post(`/admin/api/apps/${appId}/collections`, data);
|
|
2105
|
+
}
|
|
2353
2106
|
async getCollection(appId, collectionId) {
|
|
2354
2107
|
return this.get(`/app/${appId}/api/collections/${collectionId}`);
|
|
2355
2108
|
}
|
|
@@ -2396,41 +2149,30 @@ export class ApiClient {
|
|
|
2396
2149
|
};
|
|
2397
2150
|
}
|
|
2398
2151
|
// ============================================
|
|
2399
|
-
// COLLECTION
|
|
2152
|
+
// COLLECTION MEMBERS (USERS + GROUPS)
|
|
2400
2153
|
// ============================================
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
return this.delete(`/app/${appId}/api/collections/${collectionId}/group-permissions/${groupType}/${groupId}`);
|
|
2154
|
+
/**
|
|
2155
|
+
* Every member of a collection, users and groups as principals with their
|
|
2156
|
+
* roles, in one answer.
|
|
2157
|
+
*/
|
|
2158
|
+
async listCollectionMembers(appId, collectionId) {
|
|
2159
|
+
const result = await this.get(`/app/${appId}/api/collections/${encodeURIComponent(collectionId)}/members`);
|
|
2160
|
+
return { items: result?.items ?? [], hasMore: Boolean(result?.hasMore) };
|
|
2409
2161
|
}
|
|
2162
|
+
/**
|
|
2163
|
+
* Add a user or a group to a collection through the admin API, with a role;
|
|
2164
|
+
* `role: "owner"` restores the custodian's owner row.
|
|
2165
|
+
*/
|
|
2410
2166
|
async addCollectionMember(appId, collectionId, data) {
|
|
2411
|
-
return this.post(`/
|
|
2412
|
-
}
|
|
2413
|
-
async removeCollectionMember(appId, collectionId, userId) {
|
|
2414
|
-
return this.delete(`/app/${appId}/api/collections/${collectionId}/members/${userId}`);
|
|
2167
|
+
return this.post(`/admin/api/apps/${appId}/collections/${encodeURIComponent(collectionId)}/members`, data);
|
|
2415
2168
|
}
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
async listCollectionTypeConfigs(appId) {
|
|
2420
|
-
return this.get(`/app/${appId}/api/collection-type-configs`);
|
|
2421
|
-
}
|
|
2422
|
-
async getCollectionTypeConfig(appId, collectionType) {
|
|
2423
|
-
return this.get(`/app/${appId}/api/collection-type-configs/${collectionType}`);
|
|
2424
|
-
}
|
|
2425
|
-
async createCollectionTypeConfig(appId, data) {
|
|
2426
|
-
return this.post(`/app/${appId}/api/collection-type-configs`, data);
|
|
2427
|
-
}
|
|
2428
|
-
async updateCollectionTypeConfig(appId, collectionType, data, expectedModifiedAt) {
|
|
2429
|
-
const body = expectedModifiedAt ? { ...data, expectedModifiedAt } : data;
|
|
2430
|
-
return this.patch(`/app/${appId}/api/collection-type-configs/${collectionType}`, body);
|
|
2169
|
+
/** Change a member's role through the admin API, the owner row's included. */
|
|
2170
|
+
async setCollectionMemberRole(appId, collectionId, principal, role) {
|
|
2171
|
+
return this.patch(`/admin/api/apps/${appId}/collections/${encodeURIComponent(collectionId)}/members/${encodeURIComponent(principal)}`, { role });
|
|
2431
2172
|
}
|
|
2432
|
-
|
|
2433
|
-
|
|
2173
|
+
/** Remove a user or `group:<groupId>` through the admin API, the owner row included. */
|
|
2174
|
+
async removeCollectionMember(appId, collectionId, principal) {
|
|
2175
|
+
return this.delete(`/admin/api/apps/${appId}/collections/${encodeURIComponent(collectionId)}/members/${encodeURIComponent(principal)}`);
|
|
2434
2176
|
}
|
|
2435
2177
|
// ============================================
|
|
2436
2178
|
// METADATA CATEGORY CONFIGS (issue #1304, P-B)
|
|
@@ -2501,25 +2243,6 @@ export class ApiClient {
|
|
|
2501
2243
|
return this.post(`/app/${appId}/api/rule-sets/debug`, data);
|
|
2502
2244
|
}
|
|
2503
2245
|
// ============================================
|
|
2504
|
-
// GROUP TYPE CONFIGS
|
|
2505
|
-
// ============================================
|
|
2506
|
-
async listGroupTypeConfigs(appId) {
|
|
2507
|
-
return this.get(`/app/${appId}/api/group-type-configs`);
|
|
2508
|
-
}
|
|
2509
|
-
async getGroupTypeConfig(appId, groupType) {
|
|
2510
|
-
return this.get(`/app/${appId}/api/group-type-configs/${groupType}`);
|
|
2511
|
-
}
|
|
2512
|
-
async createGroupTypeConfig(appId, data) {
|
|
2513
|
-
return this.post(`/app/${appId}/api/group-type-configs`, data);
|
|
2514
|
-
}
|
|
2515
|
-
async updateGroupTypeConfig(appId, groupType, data, expectedModifiedAt) {
|
|
2516
|
-
const body = expectedModifiedAt ? { ...data, expectedModifiedAt } : data;
|
|
2517
|
-
return this.patch(`/app/${appId}/api/group-type-configs/${groupType}`, body);
|
|
2518
|
-
}
|
|
2519
|
-
async deleteGroupTypeConfig(appId, groupType) {
|
|
2520
|
-
return this.delete(`/app/${appId}/api/group-type-configs/${groupType}`);
|
|
2521
|
-
}
|
|
2522
|
-
// ============================================
|
|
2523
2246
|
// DOCUMENT EXPORT / IMPORT
|
|
2524
2247
|
// ============================================
|
|
2525
2248
|
async exportDocumentState(appId, documentId) {
|
|
@@ -2556,51 +2279,6 @@ export class ApiClient {
|
|
|
2556
2279
|
async importDocumentState(appId, documentId, stateBase64) {
|
|
2557
2280
|
return this.post(`/app/${appId}/api/documents/${documentId}/import/state`, { state: stateBase64 });
|
|
2558
2281
|
}
|
|
2559
|
-
/**
|
|
2560
|
-
* Upload one artifact of a large document's import chain (#2816,
|
|
2561
|
-
* behavior 20).
|
|
2562
|
-
*
|
|
2563
|
-
* The bytes travel raw and unmodified: the manifest's digest is over the
|
|
2564
|
-
* STORED bytes, so re-encoding a chunk here would fail its own verification
|
|
2565
|
-
* on install. The query says which artifact it is — the server derives the
|
|
2566
|
-
* key from the document it belongs to, so nothing here composes one.
|
|
2567
|
-
*/
|
|
2568
|
-
async uploadDocumentChainArtifact(appId, documentId, query, body) {
|
|
2569
|
-
const credentials = await this.ensureAuthenticated();
|
|
2570
|
-
const search = new URLSearchParams();
|
|
2571
|
-
for (const [key, value] of Object.entries(query)) {
|
|
2572
|
-
search.set(key, String(value));
|
|
2573
|
-
}
|
|
2574
|
-
const url = `${credentials.serverUrl}/app/${appId}/api/documents/${documentId}` +
|
|
2575
|
-
`/import/chain/artifact?${search.toString()}`;
|
|
2576
|
-
const headers = {
|
|
2577
|
-
Authorization: `Bearer ${credentials.accessToken}`,
|
|
2578
|
-
"Content-Type": "application/octet-stream",
|
|
2579
|
-
};
|
|
2580
|
-
if (credentials.globalAdminAppId) {
|
|
2581
|
-
headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
|
|
2582
|
-
}
|
|
2583
|
-
const response = await fetchWithTLS(url, {
|
|
2584
|
-
method: "POST",
|
|
2585
|
-
headers,
|
|
2586
|
-
body: body,
|
|
2587
|
-
});
|
|
2588
|
-
if (!response.ok) {
|
|
2589
|
-
// #3403 — through the shared parser: reading `error` alone dropped the
|
|
2590
|
-
// envelope's `code` and `details`, so the refusal reached the operator
|
|
2591
|
-
// (and `primitive documents import`) with nothing to branch on.
|
|
2592
|
-
throw await blobFailure(response, "Failed to upload import artifact: ");
|
|
2593
|
-
}
|
|
2594
|
-
return await response.json().catch(() => ({}));
|
|
2595
|
-
}
|
|
2596
|
-
/** Start installing an uploaded chain (#2816, behavior 20). */
|
|
2597
|
-
async installDocumentChain(appId, documentId, plan) {
|
|
2598
|
-
return this.post(`/app/${appId}/api/documents/${documentId}/import/chain`, plan);
|
|
2599
|
-
}
|
|
2600
|
-
/** How far a document's chain install has got (#2816, behavior 20). */
|
|
2601
|
-
async getDocumentChainInstall(appId, documentId) {
|
|
2602
|
-
return this.get(`/app/${appId}/api/documents/${documentId}/import/chain`);
|
|
2603
|
-
}
|
|
2604
2282
|
async getDocument(appId, documentId) {
|
|
2605
2283
|
return this.get(`/app/${appId}/api/documents/${documentId}`);
|
|
2606
2284
|
}
|
|
@@ -2640,11 +2318,17 @@ export class ApiClient {
|
|
|
2640
2318
|
* through the caller's document permission (reader+) or the app-admin arm.
|
|
2641
2319
|
* Returns the unified `{ items, hasMore, nextCursor? }` envelope. The cursor
|
|
2642
2320
|
* is an opaque token — feed `nextCursor` back as `cursor` for the next page.
|
|
2321
|
+
* `limit` is 100 records when omitted and at most 10000; it is forwarded
|
|
2322
|
+
* unparsed (a string from the command line) so the server validates it.
|
|
2643
2323
|
*/
|
|
2644
2324
|
async queryDocumentRecords(appId, documentId, modelName, queryOptions) {
|
|
2645
2325
|
const query = {};
|
|
2646
2326
|
if (queryOptions?.filter)
|
|
2647
2327
|
query.filter = JSON.stringify(queryOptions.filter);
|
|
2328
|
+
if (queryOptions?.sort !== undefined)
|
|
2329
|
+
query.sort = JSON.stringify(queryOptions.sort);
|
|
2330
|
+
if (queryOptions?.include !== undefined)
|
|
2331
|
+
query.include = JSON.stringify(queryOptions.include);
|
|
2648
2332
|
if (queryOptions?.documentFormat !== undefined) {
|
|
2649
2333
|
query.documentFormat = queryOptions.documentFormat;
|
|
2650
2334
|
}
|
|
@@ -2673,12 +2357,14 @@ export class ApiClient {
|
|
|
2673
2357
|
/**
|
|
2674
2358
|
* Aggregate records in a document model (issue #2437) — the documents twin
|
|
2675
2359
|
* of {@link aggregateDatabaseRecords}. `POST documents/:id/records/:model/aggregate`
|
|
2676
|
-
* with `{ options: { operations, groupBy?, filter? } }`,
|
|
2677
|
-
* in the same shape as the database endpoint.
|
|
2360
|
+
* with `{ options: { operations, groupBy?, filter?, sort?, limit? } }`,
|
|
2361
|
+
* answering `{ result }` in the same shape as the database endpoint.
|
|
2678
2362
|
*
|
|
2679
|
-
* `groupBy` takes plain field names
|
|
2680
|
-
*
|
|
2681
|
-
*
|
|
2363
|
+
* `groupBy` takes plain field names and period levels `{ field, period }`
|
|
2364
|
+
* (keyed `<field>_<period>` in a row's `group`). On a db3 (a format-3
|
|
2365
|
+
* document) a StringSet field name is a facet, `{ field, contains }` a
|
|
2366
|
+
* membership, and `sort` and `limit` order and bound the groups; a format-1
|
|
2367
|
+
* document refuses those four with a 400.
|
|
2682
2368
|
*/
|
|
2683
2369
|
async aggregateDocumentRecords(appId, documentId, modelName, options, extra) {
|
|
2684
2370
|
return this.post(`/app/${appId}/api/documents/${documentId}/records/${encodeURIComponent(modelName)}/aggregate` +
|
|
@@ -2691,6 +2377,23 @@ export class ApiClient {
|
|
|
2691
2377
|
async getDocumentStats(appId, documentId) {
|
|
2692
2378
|
return this.get(`/app/${appId}/api/documents/${documentId}/stats`);
|
|
2693
2379
|
}
|
|
2380
|
+
/**
|
|
2381
|
+
* A record-log document's room (#4306): `GET documents/:id/room` →
|
|
2382
|
+
* `{ ok, appId, documentId, documentFormat, room }`, `room` null for a
|
|
2383
|
+
* format that has no record room. With `refused` (#4310), the answer also
|
|
2384
|
+
* carries a page of that bulk load's refused rows. App admins only.
|
|
2385
|
+
*/
|
|
2386
|
+
async getDocumentRoom(appId, documentId, query = {}) {
|
|
2387
|
+
const params = new URLSearchParams();
|
|
2388
|
+
if (query.refused !== undefined)
|
|
2389
|
+
params.set("refused", query.refused);
|
|
2390
|
+
if (query.limit !== undefined)
|
|
2391
|
+
params.set("limit", String(query.limit));
|
|
2392
|
+
if (query.cursor !== undefined)
|
|
2393
|
+
params.set("cursor", query.cursor);
|
|
2394
|
+
const search = params.toString();
|
|
2395
|
+
return this.get(`/app/${appId}/api/documents/${documentId}/room${search ? `?${search}` : ""}`);
|
|
2396
|
+
}
|
|
2694
2397
|
/**
|
|
2695
2398
|
* A large document's snapshot builds, newest first, one page (#3432).
|
|
2696
2399
|
* `GET documents/:id/snapshots` → `{ items, hasMore, nextCursor? }`.
|
|
@@ -2724,9 +2427,13 @@ export class ApiClient {
|
|
|
2724
2427
|
async getDocumentIngest(appId, documentId, sessionId) {
|
|
2725
2428
|
return this.get(`/app/${appId}/api/documents/${documentId}/ingest/${encodeURIComponent(sessionId)}`);
|
|
2726
2429
|
}
|
|
2727
|
-
/**
|
|
2728
|
-
|
|
2729
|
-
|
|
2430
|
+
/**
|
|
2431
|
+
* Open a bulk-load session on a large document (#3435, criterion 9). A
|
|
2432
|
+
* record-log (format 3) document's load takes `reserveEpoch`, `mode` and a
|
|
2433
|
+
* refresh's `models` (#4310); `{}` keeps every format's defaults.
|
|
2434
|
+
*/
|
|
2435
|
+
async createDocumentIngest(appId, documentId, options = {}) {
|
|
2436
|
+
return this.post(`/app/${appId}/api/documents/${documentId}/ingest`, options);
|
|
2730
2437
|
}
|
|
2731
2438
|
/**
|
|
2732
2439
|
* Upload one chunk of a bulk-load session's artifact (#3435).
|
|
@@ -2737,6 +2444,10 @@ export class ApiClient {
|
|
|
2737
2444
|
* would name one the body does not use.
|
|
2738
2445
|
*/
|
|
2739
2446
|
async uploadDocumentIngestChunk(appId, documentId, sessionId, chunk) {
|
|
2447
|
+
return this.uploadIngestChunkAt(`/app/${appId}/api/documents/${documentId}`, sessionId, chunk);
|
|
2448
|
+
}
|
|
2449
|
+
/** A chunk upload to `<base>/ingest/<session>/chunks`: the documents route or its db3 mount. */
|
|
2450
|
+
async uploadIngestChunkAt(base, sessionId, chunk) {
|
|
2740
2451
|
const credentials = await this.ensureAuthenticated();
|
|
2741
2452
|
const form = new FormData();
|
|
2742
2453
|
form.set("model", chunk.model);
|
|
@@ -2748,18 +2459,15 @@ export class ApiClient {
|
|
|
2748
2459
|
form.set("firstId", chunk.firstId);
|
|
2749
2460
|
form.set("lastId", chunk.lastId);
|
|
2750
2461
|
form.set("chunk", new Blob([chunk.body]), `${chunk.index}.ndjson.gz`);
|
|
2751
|
-
const
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
method: "POST",
|
|
2761
|
-
headers,
|
|
2762
|
-
body: form,
|
|
2462
|
+
const url = `${credentials.serverUrl}${base}/ingest/${encodeURIComponent(sessionId)}/chunks`;
|
|
2463
|
+
const response = await this.fetchWithStepUp(url, url, credentials.accessToken, (bearer) => {
|
|
2464
|
+
const headers = {
|
|
2465
|
+
Authorization: `Bearer ${bearer}`,
|
|
2466
|
+
};
|
|
2467
|
+
if (credentials.globalAdminAppId) {
|
|
2468
|
+
headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
|
|
2469
|
+
}
|
|
2470
|
+
return { method: "POST", headers, body: form };
|
|
2763
2471
|
});
|
|
2764
2472
|
const text = await response.text();
|
|
2765
2473
|
let result = {};
|
|
@@ -2835,6 +2543,246 @@ export class ApiClient {
|
|
|
2835
2543
|
return this.post(`/app/${appId}/api/documents/${documentId}/records/bulk` +
|
|
2836
2544
|
this.documentFormatQuery(extra?.documentFormat), { operations });
|
|
2837
2545
|
}
|
|
2546
|
+
// ============================================
|
|
2547
|
+
// DB3 (format-3 documents)
|
|
2548
|
+
// ============================================
|
|
2549
|
+
/** `/app/<app>/api/db3/<id>`, the id encoded: every `db3.*` route's prefix. */
|
|
2550
|
+
db3Path(appId, db3Id) {
|
|
2551
|
+
return `/app/${appId}/api/db3/${encodeURIComponent(db3Id)}`;
|
|
2552
|
+
}
|
|
2553
|
+
/** `GET db3/:id/indexes` → `{ indexes }`, sorted by name. */
|
|
2554
|
+
async listDb3Indexes(appId, db3Id) {
|
|
2555
|
+
return this.get(`${this.db3Path(appId, db3Id)}/indexes`);
|
|
2556
|
+
}
|
|
2557
|
+
/**
|
|
2558
|
+
* `POST db3/:id/indexes` with `{ model, field | fields, unique?, name? }` →
|
|
2559
|
+
* `{ index }`. The route applies the registration rules.
|
|
2560
|
+
*/
|
|
2561
|
+
async registerDb3Index(appId, db3Id, body) {
|
|
2562
|
+
return this.post(`${this.db3Path(appId, db3Id)}/indexes`, body);
|
|
2563
|
+
}
|
|
2564
|
+
/** `DELETE db3/:id/indexes/:name` → `{ ok, name }`. */
|
|
2565
|
+
async dropDb3Index(appId, db3Id, name) {
|
|
2566
|
+
return this.delete(`${this.db3Path(appId, db3Id)}/indexes/${encodeURIComponent(name)}`);
|
|
2567
|
+
}
|
|
2568
|
+
/** The db3 mount of {@link queryDocumentRecords}: `GET db3/:id/records/:model`. */
|
|
2569
|
+
async queryDb3Records(appId, db3Id, modelName, queryOptions) {
|
|
2570
|
+
const query = {};
|
|
2571
|
+
if (queryOptions?.filter)
|
|
2572
|
+
query.filter = JSON.stringify(queryOptions.filter);
|
|
2573
|
+
if (queryOptions?.sort !== undefined)
|
|
2574
|
+
query.sort = JSON.stringify(queryOptions.sort);
|
|
2575
|
+
if (queryOptions?.include !== undefined)
|
|
2576
|
+
query.include = JSON.stringify(queryOptions.include);
|
|
2577
|
+
if (queryOptions?.limit !== undefined)
|
|
2578
|
+
query.limit = queryOptions.limit;
|
|
2579
|
+
if (queryOptions?.cursor)
|
|
2580
|
+
query.cursor = queryOptions.cursor;
|
|
2581
|
+
const raw = await this.get(`${this.db3Path(appId, db3Id)}/records/${encodeURIComponent(modelName)}`, query);
|
|
2582
|
+
return normalizeCliListEnvelope(raw);
|
|
2583
|
+
}
|
|
2584
|
+
/** The db3 mount of {@link countDocumentRecords}: `GET db3/:id/records/:model/count`. */
|
|
2585
|
+
async countDb3Records(appId, db3Id, modelName, queryOptions) {
|
|
2586
|
+
const query = {};
|
|
2587
|
+
if (queryOptions?.filter)
|
|
2588
|
+
query.filter = JSON.stringify(queryOptions.filter);
|
|
2589
|
+
return this.get(`${this.db3Path(appId, db3Id)}/records/${encodeURIComponent(modelName)}/count`, query);
|
|
2590
|
+
}
|
|
2591
|
+
/** The db3 mount of {@link aggregateDocumentRecords}: `POST db3/:id/records/:model/aggregate`. */
|
|
2592
|
+
async aggregateDb3Records(appId, db3Id, modelName, options) {
|
|
2593
|
+
return this.post(`${this.db3Path(appId, db3Id)}/records/${encodeURIComponent(modelName)}/aggregate`, { options });
|
|
2594
|
+
}
|
|
2595
|
+
/** The db3 mount of {@link saveDocumentRecord}: `POST db3/:id/records/:model`. */
|
|
2596
|
+
async saveDb3Record(appId, db3Id, modelName, body) {
|
|
2597
|
+
return this.post(`${this.db3Path(appId, db3Id)}/records/${encodeURIComponent(modelName)}`, body);
|
|
2598
|
+
}
|
|
2599
|
+
/** The db3 mount of {@link patchDocumentRecord}: `PATCH db3/:id/records/:model/:recordId`. */
|
|
2600
|
+
async patchDb3Record(appId, db3Id, modelName, recordId, body) {
|
|
2601
|
+
return this.patch(`${this.db3Path(appId, db3Id)}/records/${encodeURIComponent(modelName)}/${encodeURIComponent(recordId)}`, body);
|
|
2602
|
+
}
|
|
2603
|
+
/** The db3 mount of {@link deleteDocumentRecord}: `DELETE db3/:id/records/:model/:recordId`. */
|
|
2604
|
+
async deleteDb3Record(appId, db3Id, modelName, recordId) {
|
|
2605
|
+
return this.delete(`${this.db3Path(appId, db3Id)}/records/${encodeURIComponent(modelName)}/${encodeURIComponent(recordId)}`);
|
|
2606
|
+
}
|
|
2607
|
+
/** The db3 mount of {@link bulkDocumentRecords}: `POST db3/:id/records/bulk`. */
|
|
2608
|
+
async bulkDb3Records(appId, db3Id, operations) {
|
|
2609
|
+
return this.post(`${this.db3Path(appId, db3Id)}/records/bulk`, { operations });
|
|
2610
|
+
}
|
|
2611
|
+
/** The db3 mount of {@link getDocumentSchema}: `GET db3/:id/schema`. */
|
|
2612
|
+
async getDb3Schema(appId, db3Id) {
|
|
2613
|
+
return this.get(`${this.db3Path(appId, db3Id)}/schema`);
|
|
2614
|
+
}
|
|
2615
|
+
/** `?limit=&cursor=` for a listing. */
|
|
2616
|
+
pageQuery(options = {}) {
|
|
2617
|
+
const params = new URLSearchParams();
|
|
2618
|
+
if (options.limit !== undefined)
|
|
2619
|
+
params.set("limit", String(options.limit));
|
|
2620
|
+
if (options.cursor)
|
|
2621
|
+
params.set("cursor", options.cursor);
|
|
2622
|
+
const query = params.toString();
|
|
2623
|
+
return query ? `?${query}` : "";
|
|
2624
|
+
}
|
|
2625
|
+
/** `GET db3/:id/ingest` (#4889): a db3's bulk-load sessions, newest first. */
|
|
2626
|
+
async listDb3Ingests(appId, db3Id, options = {}) {
|
|
2627
|
+
return this.get(`${this.db3Path(appId, db3Id)}/ingest${this.pageQuery(options)}`);
|
|
2628
|
+
}
|
|
2629
|
+
/** `GET db3/:id/ingest/:sessionId` (#4889). */
|
|
2630
|
+
async getDb3Ingest(appId, db3Id, sessionId) {
|
|
2631
|
+
return this.get(`${this.db3Path(appId, db3Id)}/ingest/${encodeURIComponent(sessionId)}`);
|
|
2632
|
+
}
|
|
2633
|
+
/** `POST db3/:id/ingest` (#4889): open a bulk-load session; `{}` keeps the defaults. */
|
|
2634
|
+
async createDb3Ingest(appId, db3Id, options = {}) {
|
|
2635
|
+
return this.post(`${this.db3Path(appId, db3Id)}/ingest`, options);
|
|
2636
|
+
}
|
|
2637
|
+
/** `POST db3/:id/ingest/:sessionId/chunks` (#4889): one chunk, multipart, as the documents upload sends it. */
|
|
2638
|
+
async uploadDb3IngestChunk(appId, db3Id, sessionId, chunk) {
|
|
2639
|
+
return this.uploadIngestChunkAt(this.db3Path(appId, db3Id), sessionId, chunk);
|
|
2640
|
+
}
|
|
2641
|
+
/** `POST db3/:id/ingest/:sessionId/commit` (#4889). */
|
|
2642
|
+
async commitDb3Ingest(appId, db3Id, sessionId) {
|
|
2643
|
+
return this.post(`${this.db3Path(appId, db3Id)}/ingest/${encodeURIComponent(sessionId)}/commit`, {});
|
|
2644
|
+
}
|
|
2645
|
+
/** `POST db3/:id/ingest/:sessionId/abort` (#4889). */
|
|
2646
|
+
async abortDb3Ingest(appId, db3Id, sessionId) {
|
|
2647
|
+
return this.post(`${this.db3Path(appId, db3Id)}/ingest/${encodeURIComponent(sessionId)}/abort`, {});
|
|
2648
|
+
}
|
|
2649
|
+
/** `GET db3/:id/snapshots` (#4889): a db3's segment exports, newest first. */
|
|
2650
|
+
async listDb3Snapshots(appId, db3Id, options = {}) {
|
|
2651
|
+
return this.get(`${this.db3Path(appId, db3Id)}/snapshots${this.pageQuery(options)}`);
|
|
2652
|
+
}
|
|
2653
|
+
/** `GET db3/:id/snapshots/:segmentId` (#4889). */
|
|
2654
|
+
async getDb3Snapshot(appId, db3Id, segmentId) {
|
|
2655
|
+
return this.get(`${this.db3Path(appId, db3Id)}/snapshots/${encodeURIComponent(segmentId)}`);
|
|
2656
|
+
}
|
|
2657
|
+
/** `POST db3/:id/snapshots` (#4889): start a segment export. */
|
|
2658
|
+
async requestDb3Snapshot(appId, db3Id) {
|
|
2659
|
+
return this.post(`${this.db3Path(appId, db3Id)}/snapshots`, {});
|
|
2660
|
+
}
|
|
2661
|
+
/** `GET db3/:id/export/chain` (#4889): the current segment, signed for download. */
|
|
2662
|
+
async exportDb3Chain(appId, db3Id) {
|
|
2663
|
+
return this.get(`${this.db3Path(appId, db3Id)}/export/chain`);
|
|
2664
|
+
}
|
|
2665
|
+
/** `GET db3/:id/blobs` (#4889): one page of a db3's blobs. */
|
|
2666
|
+
async listDb3Blobs(appId, db3Id, params) {
|
|
2667
|
+
return this.get(`${this.db3Path(appId, db3Id)}/blobs`, params);
|
|
2668
|
+
}
|
|
2669
|
+
/** `GET db3/:id/blobs/:blobId/download` (#4889): a blob's bytes. */
|
|
2670
|
+
async downloadDb3Blob(appId, db3Id, blobId) {
|
|
2671
|
+
return this.downloadBlobAt(this.db3Path(appId, db3Id), blobId);
|
|
2672
|
+
}
|
|
2673
|
+
/** `PUT db3/:id/blobs/:blobId` (#4889): a blob under its own id, as the documents upload sends it. */
|
|
2674
|
+
async uploadDb3Blob(appId, db3Id, blobId, data, meta) {
|
|
2675
|
+
return this.uploadBlobAt(this.db3Path(appId, db3Id), blobId, data, meta);
|
|
2676
|
+
}
|
|
2677
|
+
/** The db3 mount of {@link getDocumentRoom}: `GET db3/:id/room`. App admins only. */
|
|
2678
|
+
async getDb3Room(appId, db3Id, query = {}) {
|
|
2679
|
+
const params = new URLSearchParams();
|
|
2680
|
+
if (query.refused !== undefined)
|
|
2681
|
+
params.set("refused", query.refused);
|
|
2682
|
+
if (query.limit !== undefined)
|
|
2683
|
+
params.set("limit", String(query.limit));
|
|
2684
|
+
if (query.cursor !== undefined)
|
|
2685
|
+
params.set("cursor", query.cursor);
|
|
2686
|
+
const search = params.toString();
|
|
2687
|
+
return this.get(`${this.db3Path(appId, db3Id)}/room${search ? `?${search}` : ""}`);
|
|
2688
|
+
}
|
|
2689
|
+
/**
|
|
2690
|
+
* `POST db3` (#4804): create a db3 and answer it, `{ id, title, createdBy,
|
|
2691
|
+
* createdAt, modifiedAt, custodian, sync, type, tags }`. Without `custodian`
|
|
2692
|
+
* an admin token's create is held by the app. `id` keeps a caller-chosen id
|
|
2693
|
+
* (#4921), refused `DB3_ID_EXISTS` when the app holds it.
|
|
2694
|
+
*/
|
|
2695
|
+
async createDb3(appId, body) {
|
|
2696
|
+
return this.post(`/app/${appId}/api/db3`, body);
|
|
2697
|
+
}
|
|
2698
|
+
/** `GET db3/:id` (#4804): the db3, with the caller's grant when it reads it through one. */
|
|
2699
|
+
async getDb3(appId, db3Id) {
|
|
2700
|
+
return this.get(this.db3Path(appId, db3Id));
|
|
2701
|
+
}
|
|
2702
|
+
/** `DELETE db3/:id` (#4804): the documents delete for a db3. */
|
|
2703
|
+
async deleteDb3(appId, db3Id) {
|
|
2704
|
+
return this.delete(this.db3Path(appId, db3Id));
|
|
2705
|
+
}
|
|
2706
|
+
/** The db3 mount of {@link getDocumentStats}: `GET db3/:id/stats`. */
|
|
2707
|
+
async getDb3Stats(appId, db3Id) {
|
|
2708
|
+
return this.get(`${this.db3Path(appId, db3Id)}/stats`);
|
|
2709
|
+
}
|
|
2710
|
+
/**
|
|
2711
|
+
* `POST db3/:id/records/:model/:recordId/increment` (#4797): add each of
|
|
2712
|
+
* `fields` to the record's field, answering `{ record, values }`.
|
|
2713
|
+
*/
|
|
2714
|
+
async incrementDb3Record(appId, db3Id, modelName, recordId, body) {
|
|
2715
|
+
return this.post(`${this.db3Path(appId, db3Id)}/records/${encodeURIComponent(modelName)}/${encodeURIComponent(recordId)}/increment`, body);
|
|
2716
|
+
}
|
|
2717
|
+
/** `GET db3/:id/sync` (#4799): `{ id, sync }`. */
|
|
2718
|
+
async getDb3Sync(appId, db3Id) {
|
|
2719
|
+
return this.get(`${this.db3Path(appId, db3Id)}/sync`);
|
|
2720
|
+
}
|
|
2721
|
+
/** `PUT db3/:id/sync` (#4799): `{ id, sync, changed }`. */
|
|
2722
|
+
async setDb3Sync(appId, db3Id, sync) {
|
|
2723
|
+
return this.put(`${this.db3Path(appId, db3Id)}/sync`, { sync });
|
|
2724
|
+
}
|
|
2725
|
+
/** `GET db3/:id/app-access` (#4801): `{ id, level }`, the level every user of the app holds, or null. */
|
|
2726
|
+
async getDb3AppAccess(appId, db3Id) {
|
|
2727
|
+
return this.get(`${this.db3Path(appId, db3Id)}/app-access`);
|
|
2728
|
+
}
|
|
2729
|
+
/** `PUT db3/:id/app-access` (#4801): grant the db3 to every user of the app, or remove the grant with null. */
|
|
2730
|
+
async setDb3AppAccess(appId, db3Id, level) {
|
|
2731
|
+
return this.put(`${this.db3Path(appId, db3Id)}/app-access`, { level });
|
|
2732
|
+
}
|
|
2733
|
+
/** One custodian's db3s through the admin API (#4800): `custodian` is `app` or `user:<userId>`. */
|
|
2734
|
+
async listDb3ByCustodian(appId, custodian, params) {
|
|
2735
|
+
const result = await this.get(`/admin/api/apps/${appId}/db3`, { custodian, ...(params ?? {}) });
|
|
2736
|
+
const nextCursor = result?.nextCursor;
|
|
2737
|
+
return { items: result?.items ?? [], nextCursor, hasMore: result?.hasMore ?? nextCursor != null };
|
|
2738
|
+
}
|
|
2739
|
+
/** Transfer a db3 to `to` (`app` or `user:<userId>`) through the admin API (#4804). */
|
|
2740
|
+
async transferDb3(appId, db3Id, to) {
|
|
2741
|
+
return this.post(`/admin/api/apps/${appId}/db3/${encodeURIComponent(db3Id)}/transfer`, { to });
|
|
2742
|
+
}
|
|
2743
|
+
/** Remove or restore a db3's owner row through the admin API (#4804): `{ id, custodian, ownerRow, changed }`. */
|
|
2744
|
+
async setDb3OwnerRow(appId, db3Id, present) {
|
|
2745
|
+
return this.put(`/admin/api/apps/${appId}/db3/${encodeURIComponent(db3Id)}/owner-row`, { present });
|
|
2746
|
+
}
|
|
2747
|
+
/** `GET db3/:id/type` (#4802): the type the db3's row names, its revision, and what its room holds. */
|
|
2748
|
+
async getDb3Type(appId, db3Id) {
|
|
2749
|
+
return this.get(`${this.db3Path(appId, db3Id)}/type`);
|
|
2750
|
+
}
|
|
2751
|
+
/** `PUT db3/:id/type` (#4802): attach a type, installing its current revision whole or refusing it whole. */
|
|
2752
|
+
async attachDb3Type(appId, db3Id, type) {
|
|
2753
|
+
return this.put(`${this.db3Path(appId, db3Id)}/type`, { type });
|
|
2754
|
+
}
|
|
2755
|
+
/** `/app/<app>/api/db3-types/<name>`, the name encoded (#4802). */
|
|
2756
|
+
db3TypePath(appId, name) {
|
|
2757
|
+
return `/app/${appId}/api/db3-types/${encodeURIComponent(name)}`;
|
|
2758
|
+
}
|
|
2759
|
+
/** `GET db3-types` (#4802): the app's db3 types, each with its revision. */
|
|
2760
|
+
async listDb3Types(appId) {
|
|
2761
|
+
return this.get(`/app/${appId}/api/db3-types`);
|
|
2762
|
+
}
|
|
2763
|
+
/** `GET db3-types/:name` (#4802). */
|
|
2764
|
+
async getDb3TypeConfig(appId, name) {
|
|
2765
|
+
return this.get(this.db3TypePath(appId, name));
|
|
2766
|
+
}
|
|
2767
|
+
/**
|
|
2768
|
+
* `PUT db3-types/:name` (#4802): create or update the type, and push its new
|
|
2769
|
+
* revision to every db3 of it. `dryRun` judges the body and persists nothing.
|
|
2770
|
+
*/
|
|
2771
|
+
async pushDb3Type(appId, name, data, options) {
|
|
2772
|
+
return this.put(`${this.db3TypePath(appId, name)}${options?.dryRun ? "?dryRun=true" : ""}`, data);
|
|
2773
|
+
}
|
|
2774
|
+
/** `DELETE db3-types/:name` (#4802): refused while a db3 is of the type. */
|
|
2775
|
+
async deleteDb3Type(appId, name) {
|
|
2776
|
+
return this.delete(this.db3TypePath(appId, name));
|
|
2777
|
+
}
|
|
2778
|
+
/** `GET db3-types/:name/push-status` (#4802): a push's outcome per db3; the latest without `runId`. */
|
|
2779
|
+
async getDb3TypePushStatus(appId, name, runId) {
|
|
2780
|
+
return this.get(`${this.db3TypePath(appId, name)}/push-status${runId ? `?runId=${encodeURIComponent(runId)}` : ""}`);
|
|
2781
|
+
}
|
|
2782
|
+
/** `POST db3-types/:name/push?runId=` (#4802): push the current revision again to a run's refused and failed db3s. */
|
|
2783
|
+
async retryDb3TypePush(appId, name, runId) {
|
|
2784
|
+
return this.post(`${this.db3TypePath(appId, name)}/push?runId=${encodeURIComponent(runId)}`, {});
|
|
2785
|
+
}
|
|
2838
2786
|
async createDocument(appId, data) {
|
|
2839
2787
|
return this.post(`/app/${appId}/api/documents`, data);
|
|
2840
2788
|
}
|
|
@@ -2890,12 +2838,21 @@ export class ApiClient {
|
|
|
2890
2838
|
async listDocumentPendingInvitations(appId, documentId) {
|
|
2891
2839
|
return this.get(`/app/${appId}/api/documents/${documentId}/pending-invitations`);
|
|
2892
2840
|
}
|
|
2893
|
-
|
|
2894
|
-
|
|
2841
|
+
/**
|
|
2842
|
+
* One page of a document's uploaded blobs (the `{ items, hasMore,
|
|
2843
|
+
* nextCursor? }` envelope); pass the previous page's `nextCursor` for the
|
|
2844
|
+
* next.
|
|
2845
|
+
*/
|
|
2846
|
+
async listDocumentBlobs(appId, documentId, params) {
|
|
2847
|
+
return this.get(`/app/${appId}/api/documents/${documentId}/blobs`, params);
|
|
2895
2848
|
}
|
|
2896
2849
|
async downloadBlob(appId, documentId, blobId) {
|
|
2850
|
+
return this.downloadBlobAt(`/app/${appId}/api/documents/${documentId}`, blobId);
|
|
2851
|
+
}
|
|
2852
|
+
/** A blob's bytes from `<base>/blobs/<id>/download`: the documents route or its db3 mount. */
|
|
2853
|
+
async downloadBlobAt(base, blobId) {
|
|
2897
2854
|
const credentials = await this.ensureAuthenticated();
|
|
2898
|
-
const url = `${credentials.serverUrl}
|
|
2855
|
+
const url = `${credentials.serverUrl}${base}/blobs/${blobId}/download`;
|
|
2899
2856
|
const headers = {
|
|
2900
2857
|
Authorization: `Bearer ${credentials.accessToken}`,
|
|
2901
2858
|
};
|
|
@@ -2910,8 +2867,37 @@ export class ApiClient {
|
|
|
2910
2867
|
return Buffer.from(arrayBuffer);
|
|
2911
2868
|
}
|
|
2912
2869
|
async uploadBlob(appId, documentId, blobId, data, meta) {
|
|
2870
|
+
return this.uploadBlobAt(`/app/${appId}/api/documents/${documentId}`, blobId, data, meta);
|
|
2871
|
+
}
|
|
2872
|
+
/** A blob PUT to `<base>/blobs/<id>`: the documents route or its db3 mount. */
|
|
2873
|
+
async uploadBlobAt(base, blobId, data, meta) {
|
|
2913
2874
|
const credentials = await this.ensureAuthenticated();
|
|
2914
|
-
const url = `${credentials.serverUrl}
|
|
2875
|
+
const url = `${credentials.serverUrl}${base}/blobs/${blobId}`;
|
|
2876
|
+
const response = await this.fetchWithStepUp(url, url, credentials.accessToken, (bearer) => {
|
|
2877
|
+
const headers = {
|
|
2878
|
+
Authorization: `Bearer ${bearer}`,
|
|
2879
|
+
"Content-Type": meta.contentType,
|
|
2880
|
+
"X-Blob-Filename": encodeURIComponent(meta.filename),
|
|
2881
|
+
"X-Blob-Size": String(data.length),
|
|
2882
|
+
"X-Blob-Sha256": meta.sha256,
|
|
2883
|
+
};
|
|
2884
|
+
if (credentials.globalAdminAppId) {
|
|
2885
|
+
headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
|
|
2886
|
+
}
|
|
2887
|
+
return { method: "PUT", headers, body: data };
|
|
2888
|
+
});
|
|
2889
|
+
if (!response.ok) {
|
|
2890
|
+
throw await blobFailure(response, "Failed to upload blob: ");
|
|
2891
|
+
}
|
|
2892
|
+
return response.json();
|
|
2893
|
+
}
|
|
2894
|
+
/**
|
|
2895
|
+
* Upload a file to an agent session by `sessionId` (#4585): the document
|
|
2896
|
+
* blob upload's headers and body, on the session's attachment route.
|
|
2897
|
+
*/
|
|
2898
|
+
async uploadAgentSessionAttachment(appId, sessionId, blobId, data, meta) {
|
|
2899
|
+
const credentials = await this.ensureAuthenticated();
|
|
2900
|
+
const url = `${credentials.serverUrl}/app/${appId}/api/agent-sessions/${encodeURIComponent(sessionId)}/attachments/${encodeURIComponent(blobId)}`;
|
|
2915
2901
|
const headers = {
|
|
2916
2902
|
Authorization: `Bearer ${credentials.accessToken}`,
|
|
2917
2903
|
"Content-Type": meta.contentType,
|
|
@@ -2922,19 +2908,64 @@ export class ApiClient {
|
|
|
2922
2908
|
if (credentials.globalAdminAppId) {
|
|
2923
2909
|
headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
|
|
2924
2910
|
}
|
|
2925
|
-
const response = await fetchWithTLS(url, {
|
|
2926
|
-
method: "PUT",
|
|
2927
|
-
headers,
|
|
2928
|
-
body: data,
|
|
2929
|
-
});
|
|
2911
|
+
const response = await fetchWithTLS(url, { method: "PUT", headers, body: data });
|
|
2930
2912
|
if (!response.ok) {
|
|
2931
|
-
throw await blobFailure(response, "Failed to upload
|
|
2913
|
+
throw await blobFailure(response, "Failed to upload attachment: ");
|
|
2932
2914
|
}
|
|
2933
2915
|
return response.json();
|
|
2934
2916
|
}
|
|
2917
|
+
/** Download an agent session's file by `sessionId` (#4585). */
|
|
2918
|
+
async downloadAgentSessionAttachment(appId, sessionId, blobId) {
|
|
2919
|
+
const response = await this.agentSessionAttachmentRead("GET", appId, sessionId, blobId);
|
|
2920
|
+
if (!response.ok) {
|
|
2921
|
+
throw await blobFailure(response, "Failed to download attachment: ");
|
|
2922
|
+
}
|
|
2923
|
+
return Buffer.from(await response.arrayBuffer());
|
|
2924
|
+
}
|
|
2925
|
+
/**
|
|
2926
|
+
* An agent session file's download headers (lower-cased names) by
|
|
2927
|
+
* `sessionId` (#4585). A refusal has no body, so it fails with the status
|
|
2928
|
+
* alone and no code.
|
|
2929
|
+
*/
|
|
2930
|
+
async headAgentSessionAttachment(appId, sessionId, blobId) {
|
|
2931
|
+
const response = await this.agentSessionAttachmentRead("HEAD", appId, sessionId, blobId);
|
|
2932
|
+
if (!response.ok && response.status !== 304) {
|
|
2933
|
+
throw new ApiError(`Failed to read attachment headers: HTTP ${response.status}`, response.status);
|
|
2934
|
+
}
|
|
2935
|
+
const headers = {};
|
|
2936
|
+
response.headers.forEach((value, name) => {
|
|
2937
|
+
headers[name.toLowerCase()] = value;
|
|
2938
|
+
});
|
|
2939
|
+
return { status: response.status, headers };
|
|
2940
|
+
}
|
|
2941
|
+
async agentSessionAttachmentRead(method, appId, sessionId, blobId) {
|
|
2942
|
+
const credentials = await this.ensureAuthenticated();
|
|
2943
|
+
const url = `${credentials.serverUrl}/app/${appId}/api/agent-sessions/${encodeURIComponent(sessionId)}/attachments/${encodeURIComponent(blobId)}/download`;
|
|
2944
|
+
const headers = {
|
|
2945
|
+
Authorization: `Bearer ${credentials.accessToken}`,
|
|
2946
|
+
};
|
|
2947
|
+
if (credentials.globalAdminAppId) {
|
|
2948
|
+
headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
|
|
2949
|
+
}
|
|
2950
|
+
return fetchWithTLS(url, { method, headers });
|
|
2951
|
+
}
|
|
2935
2952
|
async listDocumentAliases(appId, documentId) {
|
|
2936
2953
|
return this.get(`/app/${appId}/api/documents/${documentId}/aliases`);
|
|
2937
2954
|
}
|
|
2955
|
+
/**
|
|
2956
|
+
* The document a user's alias points at, or null when the user holds no
|
|
2957
|
+
* alias under that key (#4148).
|
|
2958
|
+
*/
|
|
2959
|
+
async getDocumentAlias(appId, aliasScope, aliasKey, userId) {
|
|
2960
|
+
try {
|
|
2961
|
+
return await this.get(`/app/${appId}/api/document-aliases/${aliasScope}/${encodeURIComponent(aliasKey)}`, { userId });
|
|
2962
|
+
}
|
|
2963
|
+
catch (error) {
|
|
2964
|
+
if (error instanceof ApiError && error.statusCode === 404)
|
|
2965
|
+
return null;
|
|
2966
|
+
throw error;
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2938
2969
|
async setDocumentAlias(appId, aliasScope, aliasKey, documentId, ownerUserId, mustNotExist) {
|
|
2939
2970
|
return this.put(`/app/${appId}/api/document-aliases/${aliasScope}/${encodeURIComponent(aliasKey)}`, {
|
|
2940
2971
|
documentId,
|
|
@@ -3079,21 +3110,19 @@ export class ApiClient {
|
|
|
3079
3110
|
async uploadBucketBlob(appId, bucketIdOrKey, data, meta) {
|
|
3080
3111
|
const credentials = await this.ensureAuthenticated();
|
|
3081
3112
|
const url = `${credentials.serverUrl}/app/${appId}/api/blob-buckets/${encodeURIComponent(bucketIdOrKey)}/blobs`;
|
|
3082
|
-
const
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
method: "POST",
|
|
3095
|
-
headers,
|
|
3096
|
-
body: data,
|
|
3113
|
+
const response = await this.fetchWithStepUp(url, url, credentials.accessToken, (bearer) => {
|
|
3114
|
+
const headers = {
|
|
3115
|
+
Authorization: `Bearer ${bearer}`,
|
|
3116
|
+
"Content-Type": meta.contentType,
|
|
3117
|
+
"X-Blob-Filename": encodeURIComponent(meta.filename),
|
|
3118
|
+
};
|
|
3119
|
+
if (meta.tags && meta.tags.length > 0) {
|
|
3120
|
+
headers["X-Blob-Tags"] = JSON.stringify(meta.tags);
|
|
3121
|
+
}
|
|
3122
|
+
if (credentials.globalAdminAppId) {
|
|
3123
|
+
headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
|
|
3124
|
+
}
|
|
3125
|
+
return { method: "POST", headers, body: data };
|
|
3097
3126
|
});
|
|
3098
3127
|
if (!response.ok) {
|
|
3099
3128
|
throw await blobFailure(response, "Failed to upload blob: ");
|