primitive-admin 1.2.0-alpha.1 → 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 +105 -134
- 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 +11 -12
- 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 +221 -457
- 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/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 +360 -610
- package/dist/src/commands/sync.js +3151 -6697
- package/dist/src/commands/sync.js.map +1 -1
- package/dist/src/commands/tokens.js +3 -3
- 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 -551
- package/dist/src/lib/api-client.js +880 -814
- 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 -290
- 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 -203
- package/dist/src/lib/generated-config-surfaces.js +2270 -2014
- 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
|
}
|
|
@@ -1984,72 +1874,6 @@ export class ApiClient {
|
|
|
1984
1874
|
});
|
|
1985
1875
|
}
|
|
1986
1876
|
// ============================================
|
|
1987
|
-
// DATABASE OPERATIONS (registered operations)
|
|
1988
|
-
// ============================================
|
|
1989
|
-
async listDatabaseOperations(appId, databaseId) {
|
|
1990
|
-
return this.get(`/app/${appId}/api/databases/${databaseId}/operations`);
|
|
1991
|
-
}
|
|
1992
|
-
/**
|
|
1993
|
-
* Execute a registered operation and, when the result is a query page,
|
|
1994
|
-
* normalize it to the CLI's one list envelope (issue #2440).
|
|
1995
|
-
*
|
|
1996
|
-
* The registered operation decides the response shape, so the normalization
|
|
1997
|
-
* is conditional — see {@link normalizeDatabaseOperationResult}. Everything
|
|
1998
|
-
* else (a `count`, an `aggregate`, a mutation, a `returnField: "all"`
|
|
1999
|
-
* pipeline) is returned exactly as the server sent it.
|
|
2000
|
-
*/
|
|
2001
|
-
async executeDatabaseOperation(appId, databaseId, operationName, data, token, options) {
|
|
2002
|
-
const path = `/app/${appId}/api/databases/${databaseId}/operations/${encodeURIComponent(operationName)}/execute`;
|
|
2003
|
-
const extraHeaders = {};
|
|
2004
|
-
if (options?.timing) {
|
|
2005
|
-
extraHeaders["X-Timing"] = "true";
|
|
2006
|
-
}
|
|
2007
|
-
if (token) {
|
|
2008
|
-
return normalizeDatabaseOperationResult(await this.requestWithToken(path, token, {
|
|
2009
|
-
method: "POST",
|
|
2010
|
-
body: JSON.stringify(data || {}),
|
|
2011
|
-
headers: extraHeaders,
|
|
2012
|
-
}));
|
|
2013
|
-
}
|
|
2014
|
-
return normalizeDatabaseOperationResult(await this.request(path, {
|
|
2015
|
-
method: "POST",
|
|
2016
|
-
body: JSON.stringify(data || {}),
|
|
2017
|
-
headers: extraHeaders,
|
|
2018
|
-
}));
|
|
2019
|
-
}
|
|
2020
|
-
/**
|
|
2021
|
-
* Execute a registered batch (bulk) database operation. Posts a chunk of
|
|
2022
|
-
* items to the canonical `operations/:name/batch` endpoint (the same one the
|
|
2023
|
-
* client library's `executeBatch` uses — NOT the deprecated `import-bulk`
|
|
2024
|
-
* alias). Used by `databases import-csv`.
|
|
2025
|
-
*
|
|
2026
|
-
* @returns `{ imported, failed }` — DO-level write outcome counts. Per-item
|
|
2027
|
-
* validation/access failures abort the whole chunk with a 4xx (thrown).
|
|
2028
|
-
*/
|
|
2029
|
-
async executeBatch(appId, databaseId, operationName, batch) {
|
|
2030
|
-
return this.post(`/app/${appId}/api/databases/${databaseId}/operations/${encodeURIComponent(operationName)}/batch`, { batch });
|
|
2031
|
-
}
|
|
2032
|
-
/**
|
|
2033
|
-
* Make a request using a specific JWT token instead of the CLI's credentials.
|
|
2034
|
-
* Used for executing operations as a different user.
|
|
2035
|
-
*/
|
|
2036
|
-
async requestWithToken(path, token, options = {}) {
|
|
2037
|
-
const credentials = await this.ensureAuthenticated();
|
|
2038
|
-
const url = `${credentials.serverUrl}${path}`;
|
|
2039
|
-
const headers = {
|
|
2040
|
-
Authorization: `Bearer ${token}`,
|
|
2041
|
-
"Content-Type": "application/json",
|
|
2042
|
-
...(options.headers || {}),
|
|
2043
|
-
};
|
|
2044
|
-
const response = await fetchWithTLS(url, { ...options, headers });
|
|
2045
|
-
const text = await response.text();
|
|
2046
|
-
if (!response.ok) {
|
|
2047
|
-
const parsed = parseErrorResponse(response, text, path);
|
|
2048
|
-
throw new ApiError(parsed.message, response.status, parsed.code, parsed.details);
|
|
2049
|
-
}
|
|
2050
|
-
return text ? JSON.parse(text) : null;
|
|
2051
|
-
}
|
|
2052
|
-
// ============================================
|
|
2053
1877
|
// DATABASE INDEXES
|
|
2054
1878
|
// ============================================
|
|
2055
1879
|
async listDatabaseIndexes(appId, databaseId, modelName) {
|
|
@@ -2082,7 +1906,7 @@ export class ApiClient {
|
|
|
2082
1906
|
// Issue #1336 (codex round-1 P2): `dryRun` runs every create-time
|
|
2083
1907
|
// validation (inline CEL rule lint, manifest shape, schema TOML parse)
|
|
2084
1908
|
// WITHOUT persisting, so a fresh-type `config push --dry-run` surfaces a
|
|
2085
|
-
// malformed
|
|
1909
|
+
// malformed rule or manifest the same way the real POST would.
|
|
2086
1910
|
options) {
|
|
2087
1911
|
const query = options?.dryRun ? "?dryRun=true" : "";
|
|
2088
1912
|
return this.post(`/app/${appId}/api/databases/types${query}`, data);
|
|
@@ -2103,105 +1927,14 @@ export class ApiClient {
|
|
|
2103
1927
|
return this.delete(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}${query}`);
|
|
2104
1928
|
}
|
|
2105
1929
|
/**
|
|
2106
|
-
* Issue #666 Phase 3: ask the server to scaffold a TOML schema from
|
|
2107
|
-
*
|
|
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.
|
|
2108
1933
|
*/
|
|
2109
1934
|
async scaffoldDatabaseTypeSchema(appId, databaseType) {
|
|
2110
1935
|
return this.post(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/schema:scaffold`, {});
|
|
2111
1936
|
}
|
|
2112
1937
|
// ============================================
|
|
2113
|
-
// DATABASE TYPE OPERATIONS
|
|
2114
|
-
// ============================================
|
|
2115
|
-
async listDatabaseTypeOperations(appId, databaseType) {
|
|
2116
|
-
return this.get(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/operations`);
|
|
2117
|
-
}
|
|
2118
|
-
async getDatabaseTypeOperation(appId, databaseType, name) {
|
|
2119
|
-
return this.get(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/operations/${encodeURIComponent(name)}`);
|
|
2120
|
-
}
|
|
2121
|
-
async createDatabaseTypeOperation(appId, databaseType, data,
|
|
2122
|
-
// Issue #813 (1A): when `dryRun` is set the server runs the op-edit gate
|
|
2123
|
-
// without persisting — used by `config push --dry-run` to surface gate
|
|
2124
|
-
// failures the real push would hit. `schemaOverride` lets the gate run
|
|
2125
|
-
// against the schema the SAME push is about to land (so an op that depends
|
|
2126
|
-
// on a new field isn't falsely rejected against the stale stored schema).
|
|
2127
|
-
//
|
|
2128
|
-
// Issue #915 (defect a, follow-up): for a FRESH db-type the parent config
|
|
2129
|
-
// does not exist on the server yet, so the server's "access required"
|
|
2130
|
-
// check has no `defaultAccess` to fall back on. Thread the TOML-declared
|
|
2131
|
-
// type-level `defaultAccess` so an op that omits `access` (intending to
|
|
2132
|
-
// inherit it after the type is created) isn't falsely rejected during the
|
|
2133
|
-
// dry-run. Server only trusts this on the fresh-type dry-run path.
|
|
2134
|
-
options) {
|
|
2135
|
-
const query = options?.dryRun ? "?dryRun=true" : "";
|
|
2136
|
-
let body = data;
|
|
2137
|
-
if (options?.dryRun && options.schemaOverride !== undefined) {
|
|
2138
|
-
body = { ...body, schemaOverride: options.schemaOverride };
|
|
2139
|
-
}
|
|
2140
|
-
if (options?.dryRun && options.defaultAccess !== undefined) {
|
|
2141
|
-
body = { ...body, defaultAccess: options.defaultAccess };
|
|
2142
|
-
}
|
|
2143
|
-
if (options?.dryRun && options.metadataManifestOverride !== undefined) {
|
|
2144
|
-
body = { ...body, metadataManifestOverride: options.metadataManifestOverride };
|
|
2145
|
-
}
|
|
2146
|
-
return this.post(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/operations${query}`, body);
|
|
2147
|
-
}
|
|
2148
|
-
async updateDatabaseTypeOperation(appId, databaseType, name, data, expectedModifiedAt,
|
|
2149
|
-
// Issue #813 (1A): dry-run runs the op-edit gate without persisting.
|
|
2150
|
-
// `schemaOverride` gates against the schema the same push is landing.
|
|
2151
|
-
// Issue #1336: `metadataManifestOverride` gates the op-access lint against
|
|
2152
|
-
// the manifest the same push is landing (see `createDatabaseTypeOperation`).
|
|
2153
|
-
options) {
|
|
2154
|
-
let body = expectedModifiedAt ? { ...data, expectedModifiedAt } : { ...data };
|
|
2155
|
-
if (options?.dryRun && options.schemaOverride !== undefined) {
|
|
2156
|
-
body = { ...body, schemaOverride: options.schemaOverride };
|
|
2157
|
-
}
|
|
2158
|
-
if (options?.dryRun && options.metadataManifestOverride !== undefined) {
|
|
2159
|
-
body = { ...body, metadataManifestOverride: options.metadataManifestOverride };
|
|
2160
|
-
}
|
|
2161
|
-
const query = options?.dryRun ? "?dryRun=true" : "";
|
|
2162
|
-
return this.patch(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/operations/${encodeURIComponent(name)}${query}`, body);
|
|
2163
|
-
}
|
|
2164
|
-
async deleteDatabaseTypeOperation(appId, databaseType, name) {
|
|
2165
|
-
return this.delete(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/operations/${encodeURIComponent(name)}`);
|
|
2166
|
-
}
|
|
2167
|
-
// ============================================
|
|
2168
|
-
// DATABASE TYPE SUBSCRIPTIONS (#740 / #803)
|
|
2169
|
-
// ============================================
|
|
2170
|
-
//
|
|
2171
|
-
// Transparent passthroughs against the server's type-scoped subscription
|
|
2172
|
-
// routes (GET/POST/PUT/DELETE `/databases/types/:type/subscriptions[/:key]`).
|
|
2173
|
-
// The wire format is authoritative in the controller
|
|
2174
|
-
// (`database-type-subscriptions-controller.ts`): create requires
|
|
2175
|
-
// `subscriptionKey`, `displayName`, `modelName`, `filter` (CEL), `access`
|
|
2176
|
-
// (CEL); optional `description`, `select` (string[]), `emit`
|
|
2177
|
-
// (enter/update/leave subset), `params` (object, ≤5 entries), `status`.
|
|
2178
|
-
//
|
|
2179
|
-
// The list endpoint wraps results in `{ items: [...] }` (unlike operations,
|
|
2180
|
-
// which returns a bare array). We unwrap here so callers get an array,
|
|
2181
|
-
// matching `listDatabaseTypeOperations`.
|
|
2182
|
-
async listDatabaseTypeSubscriptions(appId, databaseType) {
|
|
2183
|
-
const result = await this.get(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/subscriptions`);
|
|
2184
|
-
if (Array.isArray(result))
|
|
2185
|
-
return result;
|
|
2186
|
-
return Array.isArray(result?.items) ? result.items : [];
|
|
2187
|
-
}
|
|
2188
|
-
async getDatabaseTypeSubscription(appId, databaseType, subscriptionKey) {
|
|
2189
|
-
return this.get(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/subscriptions/${encodeURIComponent(subscriptionKey)}`);
|
|
2190
|
-
}
|
|
2191
|
-
async createDatabaseTypeSubscription(appId, databaseType, data) {
|
|
2192
|
-
return this.post(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/subscriptions`, data);
|
|
2193
|
-
}
|
|
2194
|
-
async updateDatabaseTypeSubscription(appId, databaseType, subscriptionKey, data,
|
|
2195
|
-
// #2731 B10 — optimistic concurrency, as the sibling operation update has
|
|
2196
|
-
// had. Omitted (by `--force`, and by every caller predating it) means the
|
|
2197
|
-
// write stays unconditional.
|
|
2198
|
-
expectedModifiedAt) {
|
|
2199
|
-
return this.put(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/subscriptions/${encodeURIComponent(subscriptionKey)}`, expectedModifiedAt ? { ...data, expectedModifiedAt } : data);
|
|
2200
|
-
}
|
|
2201
|
-
async deleteDatabaseTypeSubscription(appId, databaseType, subscriptionKey) {
|
|
2202
|
-
return this.delete(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/subscriptions/${encodeURIComponent(subscriptionKey)}`);
|
|
2203
|
-
}
|
|
2204
|
-
// ============================================
|
|
2205
1938
|
// GROUPS
|
|
2206
1939
|
// ============================================
|
|
2207
1940
|
async listGroups(appId, params) {
|
|
@@ -2213,31 +1946,55 @@ export class ApiClient {
|
|
|
2213
1946
|
hasMore: result?.hasMore ?? nextCursor != null,
|
|
2214
1947
|
};
|
|
2215
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
|
+
*/
|
|
2216
1968
|
async createGroup(appId, data) {
|
|
2217
|
-
return this.post(`/
|
|
1969
|
+
return this.post(`/admin/api/apps/${appId}/groups`, data);
|
|
2218
1970
|
}
|
|
2219
|
-
|
|
2220
|
-
|
|
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);
|
|
2221
1978
|
}
|
|
2222
|
-
async updateGroup(appId,
|
|
2223
|
-
return this.patch(`/app/${appId}/api/groups/${
|
|
1979
|
+
async updateGroup(appId, groupId, data) {
|
|
1980
|
+
return this.patch(`/app/${appId}/api/groups/${encodeURIComponent(groupId)}`, data);
|
|
2224
1981
|
}
|
|
2225
|
-
async deleteGroup(appId,
|
|
2226
|
-
return this.delete(`/app/${appId}/api/groups/${
|
|
1982
|
+
async deleteGroup(appId, groupId) {
|
|
1983
|
+
return this.delete(`/app/${appId}/api/groups/${encodeURIComponent(groupId)}`);
|
|
2227
1984
|
}
|
|
2228
1985
|
// ============================================
|
|
2229
1986
|
// GROUP MEMBERS
|
|
2230
1987
|
// ============================================
|
|
2231
1988
|
/**
|
|
2232
|
-
* One page of a group's members.
|
|
1989
|
+
* One page of a group's members, with their roles.
|
|
2233
1990
|
*
|
|
2234
1991
|
* The route paginates cursor-style (no default limit, cap 200) and reports
|
|
2235
1992
|
* the boundary, so `groups members list` carries `--limit`/`--cursor` and
|
|
2236
1993
|
* forwards them (#3646) rather than printing a first page that claims to be
|
|
2237
1994
|
* the whole membership.
|
|
2238
1995
|
*/
|
|
2239
|
-
async listGroupMembers(appId,
|
|
2240
|
-
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`, {
|
|
2241
1998
|
...(options?.include ? { include: options.include } : {}),
|
|
2242
1999
|
...(options?.limit !== undefined ? { limit: options.limit } : {}),
|
|
2243
2000
|
...(options?.cursor ? { cursor: options.cursor } : {}),
|
|
@@ -2249,17 +2006,23 @@ export class ApiClient {
|
|
|
2249
2006
|
hasMore: result?.hasMore ?? nextCursor != null,
|
|
2250
2007
|
};
|
|
2251
2008
|
}
|
|
2252
|
-
|
|
2253
|
-
|
|
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);
|
|
2012
|
+
}
|
|
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 });
|
|
2254
2016
|
}
|
|
2255
|
-
|
|
2256
|
-
|
|
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)}`);
|
|
2257
2020
|
}
|
|
2258
2021
|
// ============================================
|
|
2259
2022
|
// GROUP RESOURCE LISTINGS
|
|
2260
2023
|
// ============================================
|
|
2261
|
-
async listGroupDocuments(appId,
|
|
2262
|
-
return this.get(`/app/${appId}/api/groups/${
|
|
2024
|
+
async listGroupDocuments(appId, groupId) {
|
|
2025
|
+
return this.get(`/app/${appId}/api/groups/${encodeURIComponent(groupId)}/documents`);
|
|
2263
2026
|
}
|
|
2264
2027
|
// ============================================
|
|
2265
2028
|
// USER MEMBERSHIPS
|
|
@@ -2313,6 +2076,33 @@ export class ApiClient {
|
|
|
2313
2076
|
async createCollection(appId, data) {
|
|
2314
2077
|
return this.post(`/app/${appId}/api/collections`, data);
|
|
2315
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
|
+
}
|
|
2316
2106
|
async getCollection(appId, collectionId) {
|
|
2317
2107
|
return this.get(`/app/${appId}/api/collections/${collectionId}`);
|
|
2318
2108
|
}
|
|
@@ -2359,41 +2149,30 @@ export class ApiClient {
|
|
|
2359
2149
|
};
|
|
2360
2150
|
}
|
|
2361
2151
|
// ============================================
|
|
2362
|
-
// COLLECTION
|
|
2152
|
+
// COLLECTION MEMBERS (USERS + GROUPS)
|
|
2363
2153
|
// ============================================
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
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) };
|
|
2372
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
|
+
*/
|
|
2373
2166
|
async addCollectionMember(appId, collectionId, data) {
|
|
2374
|
-
return this.post(`/
|
|
2375
|
-
}
|
|
2376
|
-
async removeCollectionMember(appId, collectionId, userId) {
|
|
2377
|
-
return this.delete(`/app/${appId}/api/collections/${collectionId}/members/${userId}`);
|
|
2378
|
-
}
|
|
2379
|
-
// ============================================
|
|
2380
|
-
// COLLECTION TYPE CONFIGS
|
|
2381
|
-
// ============================================
|
|
2382
|
-
async listCollectionTypeConfigs(appId) {
|
|
2383
|
-
return this.get(`/app/${appId}/api/collection-type-configs`);
|
|
2167
|
+
return this.post(`/admin/api/apps/${appId}/collections/${encodeURIComponent(collectionId)}/members`, data);
|
|
2384
2168
|
}
|
|
2385
|
-
|
|
2386
|
-
|
|
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 });
|
|
2387
2172
|
}
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
async updateCollectionTypeConfig(appId, collectionType, data, expectedModifiedAt) {
|
|
2392
|
-
const body = expectedModifiedAt ? { ...data, expectedModifiedAt } : data;
|
|
2393
|
-
return this.patch(`/app/${appId}/api/collection-type-configs/${collectionType}`, body);
|
|
2394
|
-
}
|
|
2395
|
-
async deleteCollectionTypeConfig(appId, collectionType) {
|
|
2396
|
-
return this.delete(`/app/${appId}/api/collection-type-configs/${collectionType}`);
|
|
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)}`);
|
|
2397
2176
|
}
|
|
2398
2177
|
// ============================================
|
|
2399
2178
|
// METADATA CATEGORY CONFIGS (issue #1304, P-B)
|
|
@@ -2464,25 +2243,6 @@ export class ApiClient {
|
|
|
2464
2243
|
return this.post(`/app/${appId}/api/rule-sets/debug`, data);
|
|
2465
2244
|
}
|
|
2466
2245
|
// ============================================
|
|
2467
|
-
// GROUP TYPE CONFIGS
|
|
2468
|
-
// ============================================
|
|
2469
|
-
async listGroupTypeConfigs(appId) {
|
|
2470
|
-
return this.get(`/app/${appId}/api/group-type-configs`);
|
|
2471
|
-
}
|
|
2472
|
-
async getGroupTypeConfig(appId, groupType) {
|
|
2473
|
-
return this.get(`/app/${appId}/api/group-type-configs/${groupType}`);
|
|
2474
|
-
}
|
|
2475
|
-
async createGroupTypeConfig(appId, data) {
|
|
2476
|
-
return this.post(`/app/${appId}/api/group-type-configs`, data);
|
|
2477
|
-
}
|
|
2478
|
-
async updateGroupTypeConfig(appId, groupType, data, expectedModifiedAt) {
|
|
2479
|
-
const body = expectedModifiedAt ? { ...data, expectedModifiedAt } : data;
|
|
2480
|
-
return this.patch(`/app/${appId}/api/group-type-configs/${groupType}`, body);
|
|
2481
|
-
}
|
|
2482
|
-
async deleteGroupTypeConfig(appId, groupType) {
|
|
2483
|
-
return this.delete(`/app/${appId}/api/group-type-configs/${groupType}`);
|
|
2484
|
-
}
|
|
2485
|
-
// ============================================
|
|
2486
2246
|
// DOCUMENT EXPORT / IMPORT
|
|
2487
2247
|
// ============================================
|
|
2488
2248
|
async exportDocumentState(appId, documentId) {
|
|
@@ -2519,51 +2279,6 @@ export class ApiClient {
|
|
|
2519
2279
|
async importDocumentState(appId, documentId, stateBase64) {
|
|
2520
2280
|
return this.post(`/app/${appId}/api/documents/${documentId}/import/state`, { state: stateBase64 });
|
|
2521
2281
|
}
|
|
2522
|
-
/**
|
|
2523
|
-
* Upload one artifact of a large document's import chain (#2816,
|
|
2524
|
-
* behavior 20).
|
|
2525
|
-
*
|
|
2526
|
-
* The bytes travel raw and unmodified: the manifest's digest is over the
|
|
2527
|
-
* STORED bytes, so re-encoding a chunk here would fail its own verification
|
|
2528
|
-
* on install. The query says which artifact it is — the server derives the
|
|
2529
|
-
* key from the document it belongs to, so nothing here composes one.
|
|
2530
|
-
*/
|
|
2531
|
-
async uploadDocumentChainArtifact(appId, documentId, query, body) {
|
|
2532
|
-
const credentials = await this.ensureAuthenticated();
|
|
2533
|
-
const search = new URLSearchParams();
|
|
2534
|
-
for (const [key, value] of Object.entries(query)) {
|
|
2535
|
-
search.set(key, String(value));
|
|
2536
|
-
}
|
|
2537
|
-
const url = `${credentials.serverUrl}/app/${appId}/api/documents/${documentId}` +
|
|
2538
|
-
`/import/chain/artifact?${search.toString()}`;
|
|
2539
|
-
const headers = {
|
|
2540
|
-
Authorization: `Bearer ${credentials.accessToken}`,
|
|
2541
|
-
"Content-Type": "application/octet-stream",
|
|
2542
|
-
};
|
|
2543
|
-
if (credentials.globalAdminAppId) {
|
|
2544
|
-
headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
|
|
2545
|
-
}
|
|
2546
|
-
const response = await fetchWithTLS(url, {
|
|
2547
|
-
method: "POST",
|
|
2548
|
-
headers,
|
|
2549
|
-
body: body,
|
|
2550
|
-
});
|
|
2551
|
-
if (!response.ok) {
|
|
2552
|
-
// #3403 — through the shared parser: reading `error` alone dropped the
|
|
2553
|
-
// envelope's `code` and `details`, so the refusal reached the operator
|
|
2554
|
-
// (and `primitive documents import`) with nothing to branch on.
|
|
2555
|
-
throw await blobFailure(response, "Failed to upload import artifact: ");
|
|
2556
|
-
}
|
|
2557
|
-
return await response.json().catch(() => ({}));
|
|
2558
|
-
}
|
|
2559
|
-
/** Start installing an uploaded chain (#2816, behavior 20). */
|
|
2560
|
-
async installDocumentChain(appId, documentId, plan) {
|
|
2561
|
-
return this.post(`/app/${appId}/api/documents/${documentId}/import/chain`, plan);
|
|
2562
|
-
}
|
|
2563
|
-
/** How far a document's chain install has got (#2816, behavior 20). */
|
|
2564
|
-
async getDocumentChainInstall(appId, documentId) {
|
|
2565
|
-
return this.get(`/app/${appId}/api/documents/${documentId}/import/chain`);
|
|
2566
|
-
}
|
|
2567
2282
|
async getDocument(appId, documentId) {
|
|
2568
2283
|
return this.get(`/app/${appId}/api/documents/${documentId}`);
|
|
2569
2284
|
}
|
|
@@ -2603,11 +2318,17 @@ export class ApiClient {
|
|
|
2603
2318
|
* through the caller's document permission (reader+) or the app-admin arm.
|
|
2604
2319
|
* Returns the unified `{ items, hasMore, nextCursor? }` envelope. The cursor
|
|
2605
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.
|
|
2606
2323
|
*/
|
|
2607
2324
|
async queryDocumentRecords(appId, documentId, modelName, queryOptions) {
|
|
2608
2325
|
const query = {};
|
|
2609
2326
|
if (queryOptions?.filter)
|
|
2610
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);
|
|
2611
2332
|
if (queryOptions?.documentFormat !== undefined) {
|
|
2612
2333
|
query.documentFormat = queryOptions.documentFormat;
|
|
2613
2334
|
}
|
|
@@ -2636,12 +2357,14 @@ export class ApiClient {
|
|
|
2636
2357
|
/**
|
|
2637
2358
|
* Aggregate records in a document model (issue #2437) — the documents twin
|
|
2638
2359
|
* of {@link aggregateDatabaseRecords}. `POST documents/:id/records/:model/aggregate`
|
|
2639
|
-
* with `{ options: { operations, groupBy?, filter? } }`,
|
|
2640
|
-
* 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.
|
|
2641
2362
|
*
|
|
2642
|
-
* `groupBy` takes plain field names
|
|
2643
|
-
*
|
|
2644
|
-
*
|
|
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.
|
|
2645
2368
|
*/
|
|
2646
2369
|
async aggregateDocumentRecords(appId, documentId, modelName, options, extra) {
|
|
2647
2370
|
return this.post(`/app/${appId}/api/documents/${documentId}/records/${encodeURIComponent(modelName)}/aggregate` +
|
|
@@ -2654,6 +2377,23 @@ export class ApiClient {
|
|
|
2654
2377
|
async getDocumentStats(appId, documentId) {
|
|
2655
2378
|
return this.get(`/app/${appId}/api/documents/${documentId}/stats`);
|
|
2656
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
|
+
}
|
|
2657
2397
|
/**
|
|
2658
2398
|
* A large document's snapshot builds, newest first, one page (#3432).
|
|
2659
2399
|
* `GET documents/:id/snapshots` → `{ items, hasMore, nextCursor? }`.
|
|
@@ -2687,9 +2427,13 @@ export class ApiClient {
|
|
|
2687
2427
|
async getDocumentIngest(appId, documentId, sessionId) {
|
|
2688
2428
|
return this.get(`/app/${appId}/api/documents/${documentId}/ingest/${encodeURIComponent(sessionId)}`);
|
|
2689
2429
|
}
|
|
2690
|
-
/**
|
|
2691
|
-
|
|
2692
|
-
|
|
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);
|
|
2693
2437
|
}
|
|
2694
2438
|
/**
|
|
2695
2439
|
* Upload one chunk of a bulk-load session's artifact (#3435).
|
|
@@ -2700,6 +2444,10 @@ export class ApiClient {
|
|
|
2700
2444
|
* would name one the body does not use.
|
|
2701
2445
|
*/
|
|
2702
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) {
|
|
2703
2451
|
const credentials = await this.ensureAuthenticated();
|
|
2704
2452
|
const form = new FormData();
|
|
2705
2453
|
form.set("model", chunk.model);
|
|
@@ -2711,18 +2459,15 @@ export class ApiClient {
|
|
|
2711
2459
|
form.set("firstId", chunk.firstId);
|
|
2712
2460
|
form.set("lastId", chunk.lastId);
|
|
2713
2461
|
form.set("chunk", new Blob([chunk.body]), `${chunk.index}.ndjson.gz`);
|
|
2714
|
-
const
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
method: "POST",
|
|
2724
|
-
headers,
|
|
2725
|
-
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 };
|
|
2726
2471
|
});
|
|
2727
2472
|
const text = await response.text();
|
|
2728
2473
|
let result = {};
|
|
@@ -2798,6 +2543,246 @@ export class ApiClient {
|
|
|
2798
2543
|
return this.post(`/app/${appId}/api/documents/${documentId}/records/bulk` +
|
|
2799
2544
|
this.documentFormatQuery(extra?.documentFormat), { operations });
|
|
2800
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
|
+
}
|
|
2801
2786
|
async createDocument(appId, data) {
|
|
2802
2787
|
return this.post(`/app/${appId}/api/documents`, data);
|
|
2803
2788
|
}
|
|
@@ -2853,12 +2838,21 @@ export class ApiClient {
|
|
|
2853
2838
|
async listDocumentPendingInvitations(appId, documentId) {
|
|
2854
2839
|
return this.get(`/app/${appId}/api/documents/${documentId}/pending-invitations`);
|
|
2855
2840
|
}
|
|
2856
|
-
|
|
2857
|
-
|
|
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);
|
|
2858
2848
|
}
|
|
2859
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) {
|
|
2860
2854
|
const credentials = await this.ensureAuthenticated();
|
|
2861
|
-
const url = `${credentials.serverUrl}
|
|
2855
|
+
const url = `${credentials.serverUrl}${base}/blobs/${blobId}/download`;
|
|
2862
2856
|
const headers = {
|
|
2863
2857
|
Authorization: `Bearer ${credentials.accessToken}`,
|
|
2864
2858
|
};
|
|
@@ -2873,8 +2867,37 @@ export class ApiClient {
|
|
|
2873
2867
|
return Buffer.from(arrayBuffer);
|
|
2874
2868
|
}
|
|
2875
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) {
|
|
2874
|
+
const credentials = await this.ensureAuthenticated();
|
|
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) {
|
|
2876
2899
|
const credentials = await this.ensureAuthenticated();
|
|
2877
|
-
const url = `${credentials.serverUrl}/app/${appId}/api/
|
|
2900
|
+
const url = `${credentials.serverUrl}/app/${appId}/api/agent-sessions/${encodeURIComponent(sessionId)}/attachments/${encodeURIComponent(blobId)}`;
|
|
2878
2901
|
const headers = {
|
|
2879
2902
|
Authorization: `Bearer ${credentials.accessToken}`,
|
|
2880
2903
|
"Content-Type": meta.contentType,
|
|
@@ -2885,19 +2908,64 @@ export class ApiClient {
|
|
|
2885
2908
|
if (credentials.globalAdminAppId) {
|
|
2886
2909
|
headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
|
|
2887
2910
|
}
|
|
2888
|
-
const response = await fetchWithTLS(url, {
|
|
2889
|
-
method: "PUT",
|
|
2890
|
-
headers,
|
|
2891
|
-
body: data,
|
|
2892
|
-
});
|
|
2911
|
+
const response = await fetchWithTLS(url, { method: "PUT", headers, body: data });
|
|
2893
2912
|
if (!response.ok) {
|
|
2894
|
-
throw await blobFailure(response, "Failed to upload
|
|
2913
|
+
throw await blobFailure(response, "Failed to upload attachment: ");
|
|
2895
2914
|
}
|
|
2896
2915
|
return response.json();
|
|
2897
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
|
+
}
|
|
2898
2952
|
async listDocumentAliases(appId, documentId) {
|
|
2899
2953
|
return this.get(`/app/${appId}/api/documents/${documentId}/aliases`);
|
|
2900
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
|
+
}
|
|
2901
2969
|
async setDocumentAlias(appId, aliasScope, aliasKey, documentId, ownerUserId, mustNotExist) {
|
|
2902
2970
|
return this.put(`/app/${appId}/api/document-aliases/${aliasScope}/${encodeURIComponent(aliasKey)}`, {
|
|
2903
2971
|
documentId,
|
|
@@ -3042,21 +3110,19 @@ export class ApiClient {
|
|
|
3042
3110
|
async uploadBucketBlob(appId, bucketIdOrKey, data, meta) {
|
|
3043
3111
|
const credentials = await this.ensureAuthenticated();
|
|
3044
3112
|
const url = `${credentials.serverUrl}/app/${appId}/api/blob-buckets/${encodeURIComponent(bucketIdOrKey)}/blobs`;
|
|
3045
|
-
const
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
method: "POST",
|
|
3058
|
-
headers,
|
|
3059
|
-
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 };
|
|
3060
3126
|
});
|
|
3061
3127
|
if (!response.ok) {
|
|
3062
3128
|
throw await blobFailure(response, "Failed to upload blob: ");
|