primitive-admin 1.0.61 → 1.0.63
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 +125 -69
- package/assets/skill/skills/primitive-platform/SKILL.md +336 -37
- package/dist/bin/primitive.js +25 -14
- package/dist/bin/primitive.js.map +1 -1
- package/dist/src/commands/analytics.js +36 -16
- package/dist/src/commands/analytics.js.map +1 -1
- package/dist/src/commands/apps.js +52 -188
- package/dist/src/commands/apps.js.map +1 -1
- package/dist/src/commands/blob-buckets.js +13 -153
- package/dist/src/commands/blob-buckets.js.map +1 -1
- package/dist/src/commands/collection-type-configs.js +14 -100
- package/dist/src/commands/collection-type-configs.js.map +1 -1
- package/dist/src/commands/collections.js +47 -8
- package/dist/src/commands/collections.js.map +1 -1
- package/dist/src/commands/config.d.ts +46 -0
- package/dist/src/commands/config.js +479 -0
- package/dist/src/commands/config.js.map +1 -0
- package/dist/src/commands/connections.js +12 -7
- package/dist/src/commands/connections.js.map +1 -1
- package/dist/src/commands/cron-triggers.js +84 -176
- package/dist/src/commands/cron-triggers.js.map +1 -1
- package/dist/src/commands/database-type-configs.d.ts +2 -0
- package/dist/src/commands/database-type-configs.js +171 -0
- package/dist/src/commands/database-type-configs.js.map +1 -0
- package/dist/src/commands/database-types.js +4 -4
- package/dist/src/commands/database-types.js.map +1 -1
- package/dist/src/commands/databases.d.ts +63 -0
- package/dist/src/commands/databases.js +649 -87
- package/dist/src/commands/databases.js.map +1 -1
- package/dist/src/commands/documents.js +869 -12
- package/dist/src/commands/documents.js.map +1 -1
- package/dist/src/commands/email-templates.js +21 -128
- package/dist/src/commands/email-templates.js.map +1 -1
- package/dist/src/commands/env.d.ts +12 -1
- package/dist/src/commands/env.js +115 -26
- package/dist/src/commands/env.js.map +1 -1
- package/dist/src/commands/feature-flags.d.ts +14 -0
- package/dist/src/commands/feature-flags.js +116 -0
- package/dist/src/commands/feature-flags.js.map +1 -0
- package/dist/src/commands/group-type-configs.js +14 -109
- package/dist/src/commands/group-type-configs.js.map +1 -1
- package/dist/src/commands/init.d.ts +24 -1
- package/dist/src/commands/init.js +1194 -279
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/integrations.js +166 -435
- package/dist/src/commands/integrations.js.map +1 -1
- package/dist/src/commands/metadata-category-configs.d.ts +12 -0
- package/dist/src/commands/metadata-category-configs.js +112 -0
- package/dist/src/commands/metadata-category-configs.js.map +1 -0
- package/dist/src/commands/metadata.js +33 -141
- package/dist/src/commands/metadata.js.map +1 -1
- package/dist/src/commands/prompts.js +206 -563
- package/dist/src/commands/prompts.js.map +1 -1
- package/dist/src/commands/rule-sets.d.ts +1 -1
- package/dist/src/commands/rule-sets.js +16 -132
- package/dist/src/commands/rule-sets.js.map +1 -1
- package/dist/src/commands/scripts.d.ts +10 -0
- package/dist/src/commands/scripts.js +203 -241
- package/dist/src/commands/scripts.js.map +1 -1
- package/dist/src/commands/sessions.js +17 -5
- package/dist/src/commands/sessions.js.map +1 -1
- package/dist/src/commands/sync-app-settings.d.ts +113 -70
- package/dist/src/commands/sync-app-settings.js +193 -242
- package/dist/src/commands/sync-app-settings.js.map +1 -1
- package/dist/src/commands/sync.d.ts +1904 -51
- package/dist/src/commands/sync.js +9468 -1790
- package/dist/src/commands/sync.js.map +1 -1
- package/dist/src/commands/tokens.js +8 -2
- package/dist/src/commands/tokens.js.map +1 -1
- package/dist/src/commands/users.js +92 -1
- package/dist/src/commands/users.js.map +1 -1
- package/dist/src/commands/vars.js +44 -58
- package/dist/src/commands/vars.js.map +1 -1
- package/dist/src/commands/webhooks.js +318 -156
- package/dist/src/commands/webhooks.js.map +1 -1
- package/dist/src/commands/workflows.d.ts +49 -12
- package/dist/src/commands/workflows.js +652 -1086
- package/dist/src/commands/workflows.js.map +1 -1
- package/dist/src/lib/access-rule-display.d.ts +21 -0
- package/dist/src/lib/access-rule-display.js +34 -0
- package/dist/src/lib/access-rule-display.js.map +1 -0
- package/dist/src/lib/api-client.d.ts +541 -61
- package/dist/src/lib/api-client.js +559 -102
- package/dist/src/lib/api-client.js.map +1 -1
- package/dist/src/lib/app-settings-descriptor.d.ts +180 -25
- package/dist/src/lib/app-settings-descriptor.js +403 -82
- package/dist/src/lib/app-settings-descriptor.js.map +1 -1
- package/dist/src/lib/canonical-json.d.ts +12 -0
- package/dist/src/lib/canonical-json.js +35 -0
- package/dist/src/lib/canonical-json.js.map +1 -0
- package/dist/src/lib/cli-manifest.d.ts +8 -0
- package/dist/src/lib/cli-manifest.js +1 -0
- package/dist/src/lib/cli-manifest.js.map +1 -1
- package/dist/src/lib/codegen-shared/generatedFiles.d.ts +6 -2
- package/dist/src/lib/codegen-shared/generatedFiles.js +9 -2
- package/dist/src/lib/codegen-shared/generatedFiles.js.map +1 -1
- package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.d.ts +6 -6
- package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js +8 -8
- package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js.map +1 -1
- package/dist/src/lib/config-object-descriptor.d.ts +127 -0
- package/dist/src/lib/config-object-descriptor.js +658 -0
- package/dist/src/lib/config-object-descriptor.js.map +1 -0
- package/dist/src/lib/config-payload.d.ts +85 -0
- package/dist/src/lib/config-payload.js +116 -0
- package/dist/src/lib/config-payload.js.map +1 -0
- package/dist/src/lib/config-surface.d.ts +130 -0
- package/dist/src/lib/config-surface.js +300 -0
- package/dist/src/lib/config-surface.js.map +1 -0
- package/dist/src/lib/credentials-store.d.ts +10 -0
- package/dist/src/lib/credentials-store.js +23 -0
- package/dist/src/lib/credentials-store.js.map +1 -1
- package/dist/src/lib/data-input.d.ts +23 -0
- package/dist/src/lib/data-input.js +50 -0
- package/dist/src/lib/data-input.js.map +1 -0
- package/dist/src/lib/db-codegen/dbTemplates.js +1 -1
- package/dist/src/lib/db-codegen/dbTemplates.js.map +1 -1
- package/dist/src/lib/db-codegen/dbTypeIR.d.ts +1 -1
- package/dist/src/lib/env-resolver-core.d.ts +187 -0
- package/dist/src/lib/env-resolver-core.js +325 -0
- package/dist/src/lib/env-resolver-core.js.map +1 -0
- package/dist/src/lib/env-resolver.d.ts +28 -6
- package/dist/src/lib/env-resolver.js +44 -32
- package/dist/src/lib/env-resolver.js.map +1 -1
- package/dist/src/lib/generated-allowlist.js +12 -0
- package/dist/src/lib/generated-allowlist.js.map +1 -1
- package/dist/src/lib/generated-config-surfaces.d.ts +682 -0
- package/dist/src/lib/generated-config-surfaces.js +4058 -0
- package/dist/src/lib/generated-config-surfaces.js.map +1 -0
- package/dist/src/lib/generated-template-lint.d.ts +212 -0
- package/dist/src/lib/generated-template-lint.js +624 -0
- package/dist/src/lib/generated-template-lint.js.map +1 -0
- package/dist/src/lib/init-adopt.d.ts +16 -0
- package/dist/src/lib/init-adopt.js +34 -0
- package/dist/src/lib/init-adopt.js.map +1 -0
- package/dist/src/lib/init-assets.d.ts +39 -0
- package/dist/src/lib/init-assets.js +97 -0
- package/dist/src/lib/init-assets.js.map +1 -0
- package/dist/src/lib/init-client-platforms.d.ts +14 -0
- package/dist/src/lib/init-client-platforms.js +71 -0
- package/dist/src/lib/init-client-platforms.js.map +1 -0
- package/dist/src/lib/init-config.d.ts +44 -5
- package/dist/src/lib/init-config.js +80 -7
- package/dist/src/lib/init-config.js.map +1 -1
- package/dist/src/lib/init-email-redirect-uris.d.ts +37 -0
- package/dist/src/lib/init-email-redirect-uris.js +46 -0
- package/dist/src/lib/init-email-redirect-uris.js.map +1 -0
- package/dist/src/lib/init-ios-links.d.ts +90 -0
- package/dist/src/lib/init-ios-links.js +218 -0
- package/dist/src/lib/init-ios-links.js.map +1 -0
- package/dist/src/lib/init-plan.d.ts +80 -0
- package/dist/src/lib/init-plan.js +95 -0
- package/dist/src/lib/init-plan.js.map +1 -0
- package/dist/src/lib/init-production-env.d.ts +48 -0
- package/dist/src/lib/init-production-env.js +59 -0
- package/dist/src/lib/init-production-env.js.map +1 -0
- package/dist/src/lib/init-schema.d.ts +74 -0
- package/dist/src/lib/init-schema.js +358 -0
- package/dist/src/lib/init-schema.js.map +1 -0
- package/dist/src/lib/init-xcode.d.ts +33 -0
- package/dist/src/lib/init-xcode.js +114 -0
- package/dist/src/lib/init-xcode.js.map +1 -0
- package/dist/src/lib/integration-request-config.d.ts +30 -0
- package/dist/src/lib/integration-request-config.js +145 -0
- package/dist/src/lib/integration-request-config.js.map +1 -0
- package/dist/src/lib/integration-selector.d.ts +42 -0
- package/dist/src/lib/integration-selector.js +46 -0
- package/dist/src/lib/integration-selector.js.map +1 -0
- package/dist/src/lib/local-state.d.ts +55 -0
- package/dist/src/lib/local-state.js +167 -0
- package/dist/src/lib/local-state.js.map +1 -0
- package/dist/src/lib/local-test-cases.d.ts +63 -0
- package/dist/src/lib/local-test-cases.js +135 -0
- package/dist/src/lib/local-test-cases.js.map +1 -0
- package/dist/src/lib/log-inspection.d.ts +568 -0
- package/dist/src/lib/log-inspection.js +639 -0
- package/dist/src/lib/log-inspection.js.map +1 -0
- package/dist/src/lib/migration-nag.d.ts +2 -2
- package/dist/src/lib/migration-nag.js +3 -3
- package/dist/src/lib/object-status-filter.d.ts +22 -0
- package/dist/src/lib/object-status-filter.js +45 -0
- package/dist/src/lib/object-status-filter.js.map +1 -0
- package/dist/src/lib/output.d.ts +24 -1
- package/dist/src/lib/output.js +42 -1
- package/dist/src/lib/output.js.map +1 -1
- package/dist/src/lib/package-manager.d.ts +140 -0
- package/dist/src/lib/package-manager.js +305 -0
- package/dist/src/lib/package-manager.js.map +1 -0
- package/dist/src/lib/paginate.d.ts +50 -0
- package/dist/src/lib/paginate.js +53 -0
- package/dist/src/lib/paginate.js.map +1 -1
- package/dist/src/lib/platform-owned.d.ts +29 -3
- package/dist/src/lib/platform-owned.js +42 -3
- package/dist/src/lib/platform-owned.js.map +1 -1
- package/dist/src/lib/project-config.d.ts +8 -0
- package/dist/src/lib/project-config.js +30 -5
- package/dist/src/lib/project-config.js.map +1 -1
- package/dist/src/lib/query-operators.d.ts +1 -1
- package/dist/src/lib/query-operators.js +1 -1
- package/dist/src/lib/record-filter.d.ts +18 -0
- package/dist/src/lib/record-filter.js +55 -0
- package/dist/src/lib/record-filter.js.map +1 -0
- package/dist/src/lib/resolve-init-dev-port.d.ts +56 -0
- package/dist/src/lib/resolve-init-dev-port.js +55 -0
- package/dist/src/lib/resolve-init-dev-port.js.map +1 -0
- package/dist/src/lib/resolve-init-server.d.ts +64 -0
- package/dist/src/lib/resolve-init-server.js +77 -0
- package/dist/src/lib/resolve-init-server.js.map +1 -0
- package/dist/src/lib/resolve-platform.d.ts +43 -14
- package/dist/src/lib/resolve-platform.js +74 -12
- package/dist/src/lib/resolve-platform.js.map +1 -1
- package/dist/src/lib/run-status.d.ts +19 -0
- package/dist/src/lib/run-status.generated.d.ts +39 -0
- package/dist/src/lib/run-status.generated.js +66 -0
- package/dist/src/lib/run-status.generated.js.map +1 -0
- package/dist/src/lib/run-status.js +19 -0
- package/dist/src/lib/run-status.js.map +1 -0
- package/dist/src/lib/server-text-normalization.d.ts +51 -0
- package/dist/src/lib/server-text-normalization.js +90 -0
- package/dist/src/lib/server-text-normalization.js.map +1 -0
- package/dist/src/lib/server-url.d.ts +22 -0
- package/dist/src/lib/server-url.js +33 -0
- package/dist/src/lib/server-url.js.map +1 -0
- package/dist/src/lib/signing-secret-status.d.ts +81 -0
- package/dist/src/lib/signing-secret-status.js +116 -0
- package/dist/src/lib/signing-secret-status.js.map +1 -0
- package/dist/src/lib/skill-installer.js +6 -3
- package/dist/src/lib/skill-installer.js.map +1 -1
- package/dist/src/lib/snapshots.d.ts +2 -2
- package/dist/src/lib/snapshots.js +2 -2
- package/dist/src/lib/swift-codegen/dbGenerator.d.ts +15 -2
- package/dist/src/lib/swift-codegen/dbGenerator.js +250 -18
- package/dist/src/lib/swift-codegen/dbGenerator.js.map +1 -1
- package/dist/src/lib/swift-codegen/generator.d.ts +11 -2
- package/dist/src/lib/swift-codegen/generator.js +181 -7
- package/dist/src/lib/swift-codegen/generator.js.map +1 -1
- package/dist/src/lib/swift-codegen/siblingSymbols.d.ts +7 -3
- package/dist/src/lib/swift-codegen/siblingSymbols.js +4 -1
- package/dist/src/lib/swift-codegen/siblingSymbols.js.map +1 -1
- package/dist/src/lib/sync-dir-selector.d.ts +21 -0
- package/dist/src/lib/sync-dir-selector.js +30 -0
- package/dist/src/lib/sync-dir-selector.js.map +1 -0
- package/dist/src/lib/sync-paths.d.ts +39 -0
- package/dist/src/lib/sync-paths.js +70 -2
- package/dist/src/lib/sync-paths.js.map +1 -1
- package/dist/src/lib/sync-resource-types.d.ts +138 -31
- package/dist/src/lib/sync-resource-types.js +241 -47
- package/dist/src/lib/sync-resource-types.js.map +1 -1
- package/dist/src/lib/sync-selectors.d.ts +95 -0
- package/dist/src/lib/sync-selectors.js +228 -0
- package/dist/src/lib/sync-selectors.js.map +1 -0
- package/dist/src/lib/template.d.ts +62 -4
- package/dist/src/lib/template.js +147 -39
- package/dist/src/lib/template.js.map +1 -1
- package/dist/src/lib/test-case-file-names.d.ts +40 -0
- package/dist/src/lib/test-case-file-names.js +91 -0
- package/dist/src/lib/test-case-file-names.js.map +1 -0
- package/dist/src/lib/test-case-keys.d.ts +29 -0
- package/dist/src/lib/test-case-keys.js +55 -0
- package/dist/src/lib/test-case-keys.js.map +1 -0
- package/dist/src/lib/test-case-variables.d.ts +15 -0
- package/dist/src/lib/test-case-variables.js +29 -0
- package/dist/src/lib/test-case-variables.js.map +1 -0
- package/dist/src/lib/toml-database-config.d.ts +4 -4
- package/dist/src/lib/toml-database-config.js +27 -34
- package/dist/src/lib/toml-database-config.js.map +1 -1
- package/dist/src/lib/toml-metadata-config.d.ts +9 -5
- package/dist/src/lib/toml-metadata-config.js +40 -45
- package/dist/src/lib/toml-metadata-config.js.map +1 -1
- package/dist/src/lib/toml-native-form.d.ts +1 -1
- package/dist/src/lib/toml-native-form.js +1 -1
- package/dist/src/lib/toml-params-validator.d.ts +1 -1
- package/dist/src/lib/toml-params-validator.js +1 -1
- package/dist/src/lib/toml-scalar-edit.d.ts +43 -0
- package/dist/src/lib/toml-scalar-edit.js +283 -0
- package/dist/src/lib/toml-scalar-edit.js.map +1 -0
- package/dist/src/lib/user-selector.d.ts +24 -0
- package/dist/src/lib/user-selector.js +33 -0
- package/dist/src/lib/user-selector.js.map +1 -0
- package/dist/src/lib/version-check.d.ts +25 -0
- package/dist/src/lib/version-check.js +77 -8
- package/dist/src/lib/version-check.js.map +1 -1
- package/dist/src/lib/watch.d.ts +121 -0
- package/dist/src/lib/watch.js +169 -0
- package/dist/src/lib/watch.js.map +1 -0
- package/dist/src/lib/web-url.d.ts +40 -0
- package/dist/src/lib/web-url.js +76 -0
- package/dist/src/lib/web-url.js.map +1 -0
- package/dist/src/lib/workflow-apply.d.ts +69 -45
- package/dist/src/lib/workflow-apply.js +99 -72
- package/dist/src/lib/workflow-apply.js.map +1 -1
- package/dist/src/lib/workflow-codegen/generator.js +4 -4
- package/dist/src/lib/workflow-codegen/generator.js.map +1 -1
- package/dist/src/lib/workflow-codegen/invokerIR.d.ts +11 -0
- package/dist/src/lib/workflow-codegen/invokerIR.js +5 -0
- package/dist/src/lib/workflow-codegen/invokerIR.js.map +1 -1
- package/dist/src/lib/workflow-config-apply.d.ts +70 -0
- package/dist/src/lib/workflow-config-apply.js +137 -0
- package/dist/src/lib/workflow-config-apply.js.map +1 -0
- package/dist/src/lib/workflow-config-sidecar.d.ts +63 -0
- package/dist/src/lib/workflow-config-sidecar.js +96 -0
- package/dist/src/lib/workflow-config-sidecar.js.map +1 -0
- package/dist/src/lib/workflow-defaults.d.ts +29 -0
- package/dist/src/lib/workflow-defaults.js +41 -0
- package/dist/src/lib/workflow-defaults.js.map +1 -0
- package/dist/src/lib/workflow-include-preserve.d.ts +76 -0
- package/dist/src/lib/workflow-include-preserve.js +286 -0
- package/dist/src/lib/workflow-include-preserve.js.map +1 -0
- package/dist/src/lib/workflow-payload.d.ts +31 -20
- package/dist/src/lib/workflow-payload.js +100 -110
- package/dist/src/lib/workflow-payload.js.map +1 -1
- package/dist/src/lib/workflow-toml-validator.d.ts +37 -16
- package/dist/src/lib/workflow-toml-validator.js +141 -16
- package/dist/src/lib/workflow-toml-validator.js.map +1 -1
- package/dist/src/types/index.d.ts +78 -22
- package/dist/src/validators.d.ts +7 -7
- package/dist/src/validators.js +6 -6
- package/package.json +11 -6
- package/dist/src/commands/settings.d.ts +0 -15
- package/dist/src/commands/settings.js +0 -102
- package/dist/src/commands/settings.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { loadCredentials, saveCredentials, isTokenExpiringSoon, } from "./config.js";
|
|
2
2
|
import { fetchWithTLS } from "./fetch.js";
|
|
3
|
-
import { paginateAll } from "./paginate.js";
|
|
3
|
+
import { paginateAll, normalizeCliListEnvelope } from "./paginate.js";
|
|
4
4
|
import { RefreshError, refreshAdminCredentials, } from "./refresh-admin-credentials.js";
|
|
5
5
|
export class ApiError extends Error {
|
|
6
6
|
statusCode;
|
|
@@ -162,6 +162,45 @@ export class OpsExistError extends ApiError {
|
|
|
162
162
|
return typeof d?.opCount === "number" ? d.opCount : 0;
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* Normalize a `databases operations execute` result to the CLI's one list
|
|
167
|
+
* envelope — but only when the result is a Durable Object query page
|
|
168
|
+
* (issue #2440).
|
|
169
|
+
*
|
|
170
|
+
* A registered operation is a list only sometimes. Of the shapes the server's
|
|
171
|
+
* operation dispatch can return, exactly two carry a top-level `data` array: a
|
|
172
|
+
* bare `query` and a `pipeline` whose `returnField` names a query step. A
|
|
173
|
+
* `count` (`{ count }`), an `aggregate` (`{ result }`), a mutation
|
|
174
|
+
* (`{ results }`), an `applyToQuery` (`{ matched, affected, failed, … }`) and a
|
|
175
|
+
* `returnField: "all"` pipeline (`{ steps: { … } }`) do not, so blanket
|
|
176
|
+
* normalization would corrupt them. This helper therefore tests the shape
|
|
177
|
+
* first and passes everything else through byte-for-byte — including the
|
|
178
|
+
* nested steps of a `returnField: "all"` pipeline, which are operation-defined
|
|
179
|
+
* objects the CLI must not reach into.
|
|
180
|
+
*
|
|
181
|
+
* Deliberately narrow, not a general-purpose predicate: the contract is
|
|
182
|
+
* specifically "an operations-execute result may be a DO query page".
|
|
183
|
+
* `executeDatabaseOperation()` is the only caller.
|
|
184
|
+
*
|
|
185
|
+
* The conversion itself is delegated to `normalizeCliListEnvelope()` so cursor
|
|
186
|
+
* handling, `prevCursor` dropping and `hasMore` derivation have exactly one
|
|
187
|
+
* implementation. That helper throws on an unrecognized shape — correct when
|
|
188
|
+
* the payload is supposed to be a list, wrong here, where the payload is the
|
|
189
|
+
* caller's own result — which is why the shape test has to run first. `_timing`
|
|
190
|
+
* (`--timing`) is re-attached afterwards, since it rides alongside the envelope
|
|
191
|
+
* rather than inside it.
|
|
192
|
+
*/
|
|
193
|
+
export function normalizeDatabaseOperationResult(raw) {
|
|
194
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw))
|
|
195
|
+
return raw;
|
|
196
|
+
const page = raw;
|
|
197
|
+
if (!Array.isArray(page.data))
|
|
198
|
+
return raw;
|
|
199
|
+
const envelope = normalizeCliListEnvelope(page);
|
|
200
|
+
if (page._timing !== undefined)
|
|
201
|
+
envelope._timing = page._timing;
|
|
202
|
+
return envelope;
|
|
203
|
+
}
|
|
165
204
|
export class ApiClient {
|
|
166
205
|
credentials = null;
|
|
167
206
|
constructor() {
|
|
@@ -378,6 +417,40 @@ export class ApiClient {
|
|
|
378
417
|
async removeUser(appId, userId) {
|
|
379
418
|
return this.delete(`/app/${appId}/api/users/${userId}`);
|
|
380
419
|
}
|
|
420
|
+
/**
|
|
421
|
+
* #2803 — take a user's access away, or give it back. Distinct from
|
|
422
|
+
* `removeUser`, which detaches the membership: this blocks the person's auth
|
|
423
|
+
* in the app, revokes their sessions and tokens, and is reversible. Both
|
|
424
|
+
* routes already existed; nothing on the CLI reached them.
|
|
425
|
+
*/
|
|
426
|
+
async setUserAvailability(appId, userId, action) {
|
|
427
|
+
return this.put(`/admin/api/apps/${appId}/users/${userId}/${action}`, {});
|
|
428
|
+
}
|
|
429
|
+
// ============================================
|
|
430
|
+
// FEATURE FLAGS (super-admin)
|
|
431
|
+
// ============================================
|
|
432
|
+
/**
|
|
433
|
+
* #2803 — the platform's feature flags. A server-owned admin toggle with a
|
|
434
|
+
* console but, until now, no CLI: the same "one control, both surfaces" rule
|
|
435
|
+
* the carrying types get.
|
|
436
|
+
*/
|
|
437
|
+
async listFeatureFlags() {
|
|
438
|
+
return this.get(`/admin/api/flags`);
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* The flag itself, not the `{ flag }` envelope the route answers with —
|
|
442
|
+
* `feature-flags get` prints the key, the enabled state and the per-app
|
|
443
|
+
* overrides, and reading them off the envelope printed a blank row.
|
|
444
|
+
*/
|
|
445
|
+
async getFeatureFlag(flagKey) {
|
|
446
|
+
const result = await this.get(`/admin/api/flags/${flagKey}`);
|
|
447
|
+
return result?.flag ?? result;
|
|
448
|
+
}
|
|
449
|
+
/** Same envelope, same unwrap: `enable`/`disable` print the updated flag. */
|
|
450
|
+
async setFeatureFlagEnabled(flagKey, enabled) {
|
|
451
|
+
const result = await this.put(`/admin/api/flags/${flagKey}`, { enabled });
|
|
452
|
+
return result?.flag ?? result;
|
|
453
|
+
}
|
|
381
454
|
async updateUserRole(appId, userId, role) {
|
|
382
455
|
return this.put(`/app/${appId}/api/users/${userId}/role`, { role });
|
|
383
456
|
}
|
|
@@ -467,9 +540,19 @@ export class ApiClient {
|
|
|
467
540
|
nextCursor: result?.nextCursor ?? null,
|
|
468
541
|
};
|
|
469
542
|
}
|
|
543
|
+
/** Integration detail (#2631: `accessRule` is on the detail, not the list summary). */
|
|
470
544
|
async getIntegration(appId, integrationId) {
|
|
471
545
|
return this.get(`/admin/api/apps/${appId}/integrations/${integrationId}`);
|
|
472
546
|
}
|
|
547
|
+
/**
|
|
548
|
+
* An integration's named configs. Test cases pin a config by NAME so the
|
|
549
|
+
* sidecar is portable across apps (#2769); this is the map pull and push
|
|
550
|
+
* resolve that name through, the same way prompts/workflows/scripts do.
|
|
551
|
+
*/
|
|
552
|
+
async listIntegrationConfigs(appId, integrationId) {
|
|
553
|
+
const result = await this.get(`/admin/api/apps/${appId}/integrations/${integrationId}/configs`);
|
|
554
|
+
return { items: result?.items ?? result ?? [] };
|
|
555
|
+
}
|
|
473
556
|
async createIntegration(appId, payload) {
|
|
474
557
|
return this.post(`/admin/api/apps/${appId}/integrations`, payload);
|
|
475
558
|
}
|
|
@@ -477,8 +560,15 @@ export class ApiClient {
|
|
|
477
560
|
const body = expectedModifiedAt ? { ...payload, expectedModifiedAt } : payload;
|
|
478
561
|
return this.patch(`/admin/api/apps/${appId}/integrations/${integrationId}`, body);
|
|
479
562
|
}
|
|
563
|
+
/**
|
|
564
|
+
* Delete an integration. Archives it by default; `{ hard: true }` destroys
|
|
565
|
+
* the row instead, which is what `config push --prune` passes (#2803) so a
|
|
566
|
+
* pruned integration stops holding its key. The positional boolean is still
|
|
567
|
+
* accepted for the existing callers.
|
|
568
|
+
*/
|
|
480
569
|
async deleteIntegration(appId, integrationId, hard) {
|
|
481
|
-
const
|
|
570
|
+
const isHard = typeof hard === "object" ? hard?.hard === true : hard === true;
|
|
571
|
+
const path = isHard
|
|
482
572
|
? `/admin/api/apps/${appId}/integrations/${integrationId}?hard=true`
|
|
483
573
|
: `/admin/api/apps/${appId}/integrations/${integrationId}`;
|
|
484
574
|
return this.delete(path);
|
|
@@ -494,19 +584,6 @@ export class ApiClient {
|
|
|
494
584
|
const result = await this.get(`/admin/api/apps/${appId}/workflows/runs/${runId}/integration-logs`, params);
|
|
495
585
|
return result?.items ?? [];
|
|
496
586
|
}
|
|
497
|
-
async listIntegrationSecrets(appId, integrationId, params) {
|
|
498
|
-
const result = await this.get(`/admin/api/apps/${appId}/integrations/${integrationId}/secrets`, params);
|
|
499
|
-
return result?.items ?? [];
|
|
500
|
-
}
|
|
501
|
-
async addIntegrationSecret(appId, integrationId, payload) {
|
|
502
|
-
const result = await this.post(`/admin/api/apps/${appId}/integrations/${integrationId}/secrets`, payload);
|
|
503
|
-
return result?.secret ?? null;
|
|
504
|
-
}
|
|
505
|
-
async archiveIntegrationSecret(appId, integrationId, secretId) {
|
|
506
|
-
return this.patch(`/admin/api/apps/${appId}/integrations/${integrationId}/secrets/${secretId}`, {
|
|
507
|
-
status: "inactive",
|
|
508
|
-
});
|
|
509
|
-
}
|
|
510
587
|
// ============================================
|
|
511
588
|
// APP SECRETS
|
|
512
589
|
// ============================================
|
|
@@ -583,15 +660,45 @@ export class ApiClient {
|
|
|
583
660
|
const body = expectedModifiedAt ? { ...payload, expectedModifiedAt } : payload;
|
|
584
661
|
return this.patch(`/admin/api/apps/${appId}/webhooks/${webhookId}`, body);
|
|
585
662
|
}
|
|
586
|
-
|
|
587
|
-
|
|
663
|
+
/**
|
|
664
|
+
* Delete a webhook. Archives it by default; `hard` hard-deletes the row
|
|
665
|
+
* instead (#2232), which is what frees a slot under the per-app webhook cap
|
|
666
|
+
* and releases the webhook key for reuse. Same `?hard=true` verb the admin
|
|
667
|
+
* integrations delete uses.
|
|
668
|
+
*/
|
|
669
|
+
async deleteWebhook(appId, webhookId, options) {
|
|
670
|
+
const query = options?.hard ? "?hard=true" : "";
|
|
671
|
+
return this.delete(`/admin/api/apps/${appId}/webhooks/${webhookId}${query}`);
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* #2803 — the operational verb pair for a webhook. It posts to the
|
|
675
|
+
* enable/disable route rather than PATCHing `status`, which is server-owned
|
|
676
|
+
* and refused on every configuration write path.
|
|
677
|
+
*/
|
|
678
|
+
async setWebhookAvailability(appId, webhookId, action) {
|
|
679
|
+
return this.post(`/admin/api/apps/${appId}/webhooks/${webhookId}/${action}`, {});
|
|
680
|
+
}
|
|
681
|
+
/** #2803 — the operational verb pair for a prompt (admin API). */
|
|
682
|
+
async setPromptAvailability(appId, promptId, action) {
|
|
683
|
+
return this.post(`/admin/api/apps/${appId}/prompts/${promptId}/${action}`, {});
|
|
684
|
+
}
|
|
685
|
+
/** #2803 — the operational verb pair for an integration (admin API). */
|
|
686
|
+
async setIntegrationAvailability(appId, integrationId, action) {
|
|
687
|
+
return this.post(`/admin/api/apps/${appId}/integrations/${integrationId}/${action}`, {});
|
|
588
688
|
}
|
|
589
689
|
async rotateWebhookSecret(appId, webhookId, payload) {
|
|
590
690
|
return this.post(`/admin/api/apps/${appId}/webhooks/${webhookId}/rotate-secret`, payload);
|
|
591
691
|
}
|
|
592
692
|
async listWebhookEvents(appId, webhookId, params) {
|
|
593
693
|
const result = await this.get(`/admin/api/apps/${appId}/webhooks/${webhookId}/events`, params);
|
|
594
|
-
|
|
694
|
+
// #1316: prefer `nextCursor`; fall back to the deprecated `cursor` alias.
|
|
695
|
+
const nextCursor = result?.nextCursor ?? result?.cursor ?? null;
|
|
696
|
+
return {
|
|
697
|
+
items: result?.items ?? [],
|
|
698
|
+
hasMore: result?.hasMore ?? !!nextCursor,
|
|
699
|
+
nextCursor,
|
|
700
|
+
cursor: nextCursor,
|
|
701
|
+
};
|
|
595
702
|
}
|
|
596
703
|
// ============================================
|
|
597
704
|
// CONNECTIONS INSPECTION (#1968)
|
|
@@ -626,6 +733,17 @@ export class ApiClient {
|
|
|
626
733
|
async testWebhook(appId, webhookId, payload) {
|
|
627
734
|
return this.post(`/admin/api/apps/${appId}/webhooks/${webhookId}/test`, payload || {});
|
|
628
735
|
}
|
|
736
|
+
/**
|
|
737
|
+
* Run a webhook's configured verifier over headers and a body captured from
|
|
738
|
+
* a real delivery, and report whether that signature checks out (#2445).
|
|
739
|
+
*
|
|
740
|
+
* `headers` is sent as ordered `[name, value]` pairs so a capture carrying a
|
|
741
|
+
* field name on more than one line survives the round trip. Nothing is
|
|
742
|
+
* delivered: no delivery record, no workflow run.
|
|
743
|
+
*/
|
|
744
|
+
async verifyWebhook(appId, webhookId, payload) {
|
|
745
|
+
return this.post(`/admin/api/apps/${appId}/webhooks/${webhookId}/verify`, payload);
|
|
746
|
+
}
|
|
629
747
|
// ============================================
|
|
630
748
|
// NAMED LOCKS (#1518)
|
|
631
749
|
// ============================================
|
|
@@ -661,14 +779,21 @@ export class ApiClient {
|
|
|
661
779
|
async updateCronTrigger(appId, triggerId, payload) {
|
|
662
780
|
return this.put(`/app/${appId}/api/cron-triggers/${triggerId}`, payload);
|
|
663
781
|
}
|
|
664
|
-
|
|
665
|
-
|
|
782
|
+
/**
|
|
783
|
+
* Retire a cron trigger. The default soft-deletes (writes the `archived`
|
|
784
|
+
* tombstone); `{ hard: true }` destroys the row, which is what
|
|
785
|
+
* `config push --prune` passes (#2803) so a pruned trigger stops consuming
|
|
786
|
+
* the per-app cap and holding its key.
|
|
787
|
+
*/
|
|
788
|
+
async deleteCronTrigger(appId, triggerId, options = {}) {
|
|
789
|
+
const query = options.hard ? "?hard=true" : "";
|
|
790
|
+
return this.delete(`/app/${appId}/api/cron-triggers/${triggerId}${query}`);
|
|
666
791
|
}
|
|
667
|
-
async
|
|
668
|
-
return this.post(`/app/${appId}/api/cron-triggers/${triggerId}/
|
|
792
|
+
async disableCronTrigger(appId, triggerId) {
|
|
793
|
+
return this.post(`/app/${appId}/api/cron-triggers/${triggerId}/disable`, {});
|
|
669
794
|
}
|
|
670
|
-
async
|
|
671
|
-
return this.post(`/app/${appId}/api/cron-triggers/${triggerId}/
|
|
795
|
+
async enableCronTrigger(appId, triggerId) {
|
|
796
|
+
return this.post(`/app/${appId}/api/cron-triggers/${triggerId}/enable`, {});
|
|
672
797
|
}
|
|
673
798
|
async testCronTrigger(appId, triggerId) {
|
|
674
799
|
return this.post(`/app/${appId}/api/cron-triggers/${triggerId}/test`, {});
|
|
@@ -716,6 +841,20 @@ export class ApiClient {
|
|
|
716
841
|
async deleteResourceMetadata(appId, resourceType, resourceId, category) {
|
|
717
842
|
return this.delete(`/app/${appId}/api/resources/${encodeURIComponent(resourceType)}/${encodeURIComponent(resourceId)}/metadata/${encodeURIComponent(category)}`);
|
|
718
843
|
}
|
|
844
|
+
/**
|
|
845
|
+
* Reverse-resolve a resource by a category's unique metadata value (issue
|
|
846
|
+
* #2137). A miss is a success with `resourceId: null`, never an error. The
|
|
847
|
+
* CLI authenticates as a console admin, so the app-level owner/admin bypass
|
|
848
|
+
* skips readRule evaluation.
|
|
849
|
+
*/
|
|
850
|
+
async resolveResourceMetadata(appId, resourceType, category, key, value) {
|
|
851
|
+
return this.post(`/app/${appId}/api/metadata/resolve`, {
|
|
852
|
+
resourceType,
|
|
853
|
+
category,
|
|
854
|
+
key,
|
|
855
|
+
value,
|
|
856
|
+
});
|
|
857
|
+
}
|
|
719
858
|
// ============================================
|
|
720
859
|
// PROMPTS
|
|
721
860
|
// ============================================
|
|
@@ -736,6 +875,12 @@ export class ApiClient {
|
|
|
736
875
|
const body = expectedModifiedAt ? { ...payload, expectedModifiedAt } : payload;
|
|
737
876
|
return this.patch(`/admin/api/apps/${appId}/prompts/${promptId}`, body);
|
|
738
877
|
}
|
|
878
|
+
/**
|
|
879
|
+
* Retire a prompt, or destroy it.
|
|
880
|
+
*
|
|
881
|
+
* `hard` was always sent by both first-party clients and always ignored by
|
|
882
|
+
* the server; #2887 made it mean something — the plain call now archives.
|
|
883
|
+
*/
|
|
739
884
|
async deletePrompt(appId, promptId, hard) {
|
|
740
885
|
const path = hard
|
|
741
886
|
? `/admin/api/apps/${appId}/prompts/${promptId}?hard=true`
|
|
@@ -775,7 +920,7 @@ export class ApiClient {
|
|
|
775
920
|
// The `Script` model is the authoring surface for `script` workflow
|
|
776
921
|
// steps. A `Script` is a HEADER (name/description/activeConfigId); the
|
|
777
922
|
// Rhai body lives on versioned `ScriptConfig` rows resolved LIVE at run
|
|
778
|
-
// time. CLI `
|
|
923
|
+
// time. CLI `config push` reads `transforms/*.rhai` files and reconciles
|
|
779
924
|
// them: a new file → create script (mints a default config); a changed
|
|
780
925
|
// file → create a new config + activate it (zero fan-out — referencing
|
|
781
926
|
// workflows pick up the new body on their next run).
|
|
@@ -808,10 +953,28 @@ export class ApiClient {
|
|
|
808
953
|
* workflows pick up the new body on their next run with no fan-out. The
|
|
809
954
|
* config name is unique per script, so we mint a timestamped name.
|
|
810
955
|
*/
|
|
811
|
-
async pushScriptBody(appId, scriptId, body
|
|
956
|
+
async pushScriptBody(appId, scriptId, body,
|
|
957
|
+
/**
|
|
958
|
+
* The active state the caller believes holds right now (#2731 B10).
|
|
959
|
+
* Creating an inactive config races nothing, so the guard sits on the
|
|
960
|
+
* ACTIVATION step: if the active config has moved since the caller read it,
|
|
961
|
+
* the server rejects with a 409 instead of overwriting a concurrent script
|
|
962
|
+
* change. It takes BOTH halves of "what is active", because a config's body
|
|
963
|
+
* can be edited in place without the active id changing — the id alone
|
|
964
|
+
* would let such an edit through. Omitted (e.g. `--force`) means activate
|
|
965
|
+
* unconditionally.
|
|
966
|
+
*/
|
|
967
|
+
expectedActive) {
|
|
812
968
|
const configName = `sync-${Date.now()}`;
|
|
813
969
|
const config = await this.post(`/admin/api/apps/${appId}/scripts/${scriptId}/configs`, { configName, body });
|
|
814
|
-
|
|
970
|
+
const activateBody = {};
|
|
971
|
+
if (expectedActive?.configId) {
|
|
972
|
+
activateBody.expectedActiveConfigId = expectedActive.configId;
|
|
973
|
+
}
|
|
974
|
+
if (expectedActive?.modifiedAt) {
|
|
975
|
+
activateBody.expectedActiveModifiedAt = expectedActive.modifiedAt;
|
|
976
|
+
}
|
|
977
|
+
await this.post(`/admin/api/apps/${appId}/scripts/${scriptId}/configs/${config.configId}/activate`, activateBody);
|
|
815
978
|
return config;
|
|
816
979
|
}
|
|
817
980
|
/**
|
|
@@ -831,30 +994,54 @@ export class ApiClient {
|
|
|
831
994
|
// ============================================
|
|
832
995
|
// BLOCK TEST CASES (Prompts, Integrations, Workflows)
|
|
833
996
|
// ============================================
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
997
|
+
/**
|
|
998
|
+
* One page of a block's test cases. The endpoint paginates (default 50, cap
|
|
999
|
+
* 100) and reports `nextCursor`; callers that reconcile the sync tree must
|
|
1000
|
+
* drain every page before deleting or classifying anything (#2769).
|
|
1001
|
+
*/
|
|
1002
|
+
async listTestCases(appId, blockType, blockId, params = {}) {
|
|
1003
|
+
const query = new URLSearchParams();
|
|
1004
|
+
if (params.limit !== undefined)
|
|
1005
|
+
query.set("limit", String(params.limit));
|
|
1006
|
+
if (params.cursor)
|
|
1007
|
+
query.set("cursor", params.cursor);
|
|
1008
|
+
const suffix = query.toString() ? `?${query.toString()}` : "";
|
|
1009
|
+
const result = await this.get(`/admin/api/apps/${appId}/blocks/${blockType}/${blockId}/test-cases${suffix}`);
|
|
1010
|
+
return {
|
|
1011
|
+
items: result?.items ?? result ?? [],
|
|
1012
|
+
nextCursor: result?.nextCursor ?? undefined,
|
|
1013
|
+
};
|
|
837
1014
|
}
|
|
838
1015
|
async getTestCase(appId, blockType, blockId, testCaseId) {
|
|
839
1016
|
return this.get(`/admin/api/apps/${appId}/blocks/${blockType}/${blockId}/test-cases/${testCaseId}`);
|
|
840
1017
|
}
|
|
841
1018
|
async createTestCase(appId, blockType, blockId, payload) {
|
|
842
|
-
//
|
|
1019
|
+
// #2769 — the JSON fields travel PARSED. The handler stringifies them for
|
|
1020
|
+
// storage (`createBlockTestCase`) and `serializeBlockTestCase` parses them
|
|
1021
|
+
// on the way back out, so a client that stringifies first stores a JSON
|
|
1022
|
+
// string of a JSON string and reads back a quoted blob.
|
|
843
1023
|
const serverPayload = {
|
|
844
1024
|
name: payload.name,
|
|
845
|
-
inputVariables:
|
|
1025
|
+
inputVariables: payload.inputVariables,
|
|
846
1026
|
configId: payload.configId,
|
|
847
1027
|
evaluatorPromptId: payload.evaluatorPromptId,
|
|
848
1028
|
evaluatorConfigId: payload.evaluatorConfigId,
|
|
849
1029
|
};
|
|
1030
|
+
// #2896 — only when the caller has one: an older server rejects the key
|
|
1031
|
+
// outright, and push reads that 400 to learn what it is talking to.
|
|
1032
|
+
if (payload.key !== undefined)
|
|
1033
|
+
serverPayload.key = payload.key;
|
|
1034
|
+
if (payload.description !== undefined) {
|
|
1035
|
+
serverPayload.description = payload.description;
|
|
1036
|
+
}
|
|
850
1037
|
if (payload.expectedOutputPattern) {
|
|
851
1038
|
serverPayload.expectedOutputPattern = payload.expectedOutputPattern;
|
|
852
1039
|
}
|
|
853
1040
|
if (payload.expectedOutputContains) {
|
|
854
|
-
serverPayload.expectedOutputContains =
|
|
1041
|
+
serverPayload.expectedOutputContains = payload.expectedOutputContains;
|
|
855
1042
|
}
|
|
856
1043
|
if (payload.expectedJsonSubset) {
|
|
857
|
-
serverPayload.expectedJsonSubset =
|
|
1044
|
+
serverPayload.expectedJsonSubset = payload.expectedJsonSubset;
|
|
858
1045
|
}
|
|
859
1046
|
return this.post(`/admin/api/apps/${appId}/blocks/${blockType}/${blockId}/test-cases`, serverPayload);
|
|
860
1047
|
}
|
|
@@ -862,21 +1049,23 @@ export class ApiClient {
|
|
|
862
1049
|
const serverPayload = {};
|
|
863
1050
|
if (payload.name !== undefined)
|
|
864
1051
|
serverPayload.name = payload.name;
|
|
1052
|
+
if (payload.description !== undefined) {
|
|
1053
|
+
serverPayload.description = payload.description;
|
|
1054
|
+
}
|
|
1055
|
+
// #2769 — parsed on the wire (see `createTestCase`), and an explicit `null`
|
|
1056
|
+
// is how the authored file clears a field: the handler only touches keys
|
|
1057
|
+
// that are present, so an omitted key retains the previous value.
|
|
865
1058
|
if (payload.inputVariables !== undefined) {
|
|
866
|
-
serverPayload.inputVariables =
|
|
1059
|
+
serverPayload.inputVariables = payload.inputVariables;
|
|
867
1060
|
}
|
|
868
1061
|
if (payload.expectedOutputPattern !== undefined) {
|
|
869
1062
|
serverPayload.expectedOutputPattern = payload.expectedOutputPattern;
|
|
870
1063
|
}
|
|
871
1064
|
if (payload.expectedOutputContains !== undefined) {
|
|
872
|
-
serverPayload.expectedOutputContains = payload.expectedOutputContains
|
|
873
|
-
? JSON.stringify(payload.expectedOutputContains)
|
|
874
|
-
: null;
|
|
1065
|
+
serverPayload.expectedOutputContains = payload.expectedOutputContains;
|
|
875
1066
|
}
|
|
876
1067
|
if (payload.expectedJsonSubset !== undefined) {
|
|
877
|
-
serverPayload.expectedJsonSubset = payload.expectedJsonSubset
|
|
878
|
-
? JSON.stringify(payload.expectedJsonSubset)
|
|
879
|
-
: null;
|
|
1068
|
+
serverPayload.expectedJsonSubset = payload.expectedJsonSubset;
|
|
880
1069
|
}
|
|
881
1070
|
if (payload.configId !== undefined)
|
|
882
1071
|
serverPayload.configId = payload.configId;
|
|
@@ -886,6 +1075,8 @@ export class ApiClient {
|
|
|
886
1075
|
if (payload.evaluatorConfigId !== undefined) {
|
|
887
1076
|
serverPayload.evaluatorConfigId = payload.evaluatorConfigId;
|
|
888
1077
|
}
|
|
1078
|
+
if (payload.key !== undefined)
|
|
1079
|
+
serverPayload.key = payload.key;
|
|
889
1080
|
return this.patch(`/admin/api/apps/${appId}/blocks/${blockType}/${blockId}/test-cases/${testCaseId}`, serverPayload);
|
|
890
1081
|
}
|
|
891
1082
|
async deleteTestCase(appId, blockType, blockId, testCaseId) {
|
|
@@ -982,15 +1173,33 @@ export class ApiClient {
|
|
|
982
1173
|
const body = expectedModifiedAt ? { ...payload, expectedModifiedAt } : payload;
|
|
983
1174
|
return this.patch(`/admin/api/apps/${appId}/workflows/${workflowId}`, body);
|
|
984
1175
|
}
|
|
985
|
-
|
|
986
|
-
|
|
1176
|
+
/**
|
|
1177
|
+
* Retire a workflow, or destroy it (#2887).
|
|
1178
|
+
*
|
|
1179
|
+
* The plain call ARCHIVES: the definition row stays, so its runs, revisions
|
|
1180
|
+
* and test cases keep resolving. `{ hard: true }` performs the full cascade
|
|
1181
|
+
* and is what releases the `workflowKey` — the flag `config push --prune`
|
|
1182
|
+
* sends, for the same reason it sends it for webhooks and integrations.
|
|
1183
|
+
*/
|
|
1184
|
+
async deleteWorkflow(appId, workflowId, options) {
|
|
1185
|
+
const path = `/admin/api/apps/${appId}/workflows/${workflowId}`;
|
|
1186
|
+
return this.delete(options?.hard ? `${path}?hard=true` : path);
|
|
1187
|
+
}
|
|
1188
|
+
/**
|
|
1189
|
+
* #2645 criterion 9 — the operational toggle. Deliberately NOT the config
|
|
1190
|
+
* PATCH: `config push` owns every configuration field, and taking a workflow
|
|
1191
|
+
* out of service must not touch one, or an operator's action would show up as
|
|
1192
|
+
* drift against the repo. Same route shape as `cron-triggers pause|resume`.
|
|
1193
|
+
*/
|
|
1194
|
+
async disableWorkflow(appId, workflowId) {
|
|
1195
|
+
return this.post(`/app/${appId}/api/workflows/${workflowId}/disable`, {});
|
|
1196
|
+
}
|
|
1197
|
+
async enableWorkflow(appId, workflowId) {
|
|
1198
|
+
return this.post(`/app/${appId}/api/workflows/${workflowId}/enable`, {});
|
|
987
1199
|
}
|
|
988
1200
|
async updateWorkflowDraft(appId, workflowId, payload) {
|
|
989
1201
|
return this.put(`/admin/api/apps/${appId}/workflows/${workflowId}/draft`, payload);
|
|
990
1202
|
}
|
|
991
|
-
async publishWorkflow(appId, workflowId) {
|
|
992
|
-
return this.post(`/admin/api/apps/${appId}/workflows/${workflowId}/publish`, {});
|
|
993
|
-
}
|
|
994
1203
|
async previewWorkflow(appId, workflowId, payload) {
|
|
995
1204
|
return this.post(`/admin/api/apps/${appId}/workflows/${workflowId}/preview`, payload);
|
|
996
1205
|
}
|
|
@@ -1003,8 +1212,27 @@ export class ApiClient {
|
|
|
1003
1212
|
const nextCursor = result?.nextCursor ?? result?.cursor ?? null;
|
|
1004
1213
|
return {
|
|
1005
1214
|
items: result?.items ?? [],
|
|
1215
|
+
hasMore: result?.hasMore,
|
|
1006
1216
|
nextCursor,
|
|
1007
1217
|
cursor: nextCursor,
|
|
1218
|
+
resumeAfter: result?.resumeAfter,
|
|
1219
|
+
scanned: typeof result?.scanned === "number" ? result.scanned : undefined,
|
|
1220
|
+
};
|
|
1221
|
+
}
|
|
1222
|
+
/**
|
|
1223
|
+
* Workflow runs started by one app user, across every workflow (#1967).
|
|
1224
|
+
* Backed by `GET /admin/api/apps/{appId}/users/{userId}/workflow-runs`,
|
|
1225
|
+
* which resolves the app user before querying the `runsByUser` index.
|
|
1226
|
+
*/
|
|
1227
|
+
async listUserWorkflowRuns(appId, userId, params) {
|
|
1228
|
+
const result = await this.get(`/admin/api/apps/${appId}/users/${encodeURIComponent(userId)}/workflow-runs`, params);
|
|
1229
|
+
const nextCursor = result?.nextCursor ?? result?.cursor ?? null;
|
|
1230
|
+
return {
|
|
1231
|
+
items: result?.items ?? [],
|
|
1232
|
+
hasMore: result?.hasMore,
|
|
1233
|
+
nextCursor,
|
|
1234
|
+
cursor: nextCursor,
|
|
1235
|
+
scanned: typeof result?.scanned === "number" ? result.scanned : undefined,
|
|
1008
1236
|
};
|
|
1009
1237
|
}
|
|
1010
1238
|
async getWorkflowRunStatus(appId, workflowId, runId) {
|
|
@@ -1014,12 +1242,9 @@ export class ApiClient {
|
|
|
1014
1242
|
const result = await this.get(`/admin/api/apps/${appId}/workflows/${workflowId}/runs/${runId}/steps`);
|
|
1015
1243
|
return { items: result?.items ?? [] };
|
|
1016
1244
|
}
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
async getTopWorkflows(appId, params) {
|
|
1021
|
-
return this.get(`/app/${appId}/api/analytics/workflows/top`, params);
|
|
1022
|
-
}
|
|
1245
|
+
// Workflow analytics have one client path, `getAnalyticsTopWorkflows`
|
|
1246
|
+
// (issue #2766): `getTopWorkflows` was a second name for the same endpoint,
|
|
1247
|
+
// and `getWorkflowAnalytics` called a route the server never registered.
|
|
1023
1248
|
// ============================================
|
|
1024
1249
|
// WORKFLOW CONFIGURATIONS
|
|
1025
1250
|
// ============================================
|
|
@@ -1177,21 +1402,6 @@ export class ApiClient {
|
|
|
1177
1402
|
return this.delete(`/admin/api/catalog/integrations/${catalogId}`);
|
|
1178
1403
|
}
|
|
1179
1404
|
// ============================================
|
|
1180
|
-
// LLM UTILITIES
|
|
1181
|
-
// ============================================
|
|
1182
|
-
async listLlmModels(provider = "openrouter") {
|
|
1183
|
-
return this.get("/admin/api/llm/models", { provider });
|
|
1184
|
-
}
|
|
1185
|
-
async generatePrompt(payload) {
|
|
1186
|
-
return this.post("/admin/api/llm/generate-prompt", payload);
|
|
1187
|
-
}
|
|
1188
|
-
async generateEvaluator(payload) {
|
|
1189
|
-
return this.post("/admin/api/llm/generate-evaluator", payload);
|
|
1190
|
-
}
|
|
1191
|
-
async generateWorkflowEvaluator(payload) {
|
|
1192
|
-
return this.post("/admin/api/llm/generate-evaluator-workflow", payload);
|
|
1193
|
-
}
|
|
1194
|
-
// ============================================
|
|
1195
1405
|
// BATCH TEST EXECUTION
|
|
1196
1406
|
// ============================================
|
|
1197
1407
|
async startBatchTests(appId, blockType, blockId, payload) {
|
|
@@ -1218,8 +1428,17 @@ export class ApiClient {
|
|
|
1218
1428
|
async getEmailTemplate(appId, emailType) {
|
|
1219
1429
|
return this.get(`/admin/api/apps/${appId}/email-templates/${emailType}`);
|
|
1220
1430
|
}
|
|
1221
|
-
|
|
1222
|
-
|
|
1431
|
+
/**
|
|
1432
|
+
* Create or replace an email-template override.
|
|
1433
|
+
*
|
|
1434
|
+
* `expectedModifiedAt` is the optimistic-concurrency token every sibling
|
|
1435
|
+
* config endpoint already takes (#2731 B10). Optional and additive: when
|
|
1436
|
+
* omitted the write is unconditional, which is how `--force` and every
|
|
1437
|
+
* pre-existing caller keep working; when supplied and stale, the server
|
|
1438
|
+
* answers 409 rather than silently overwriting a concurrent edit.
|
|
1439
|
+
*/
|
|
1440
|
+
async upsertEmailTemplate(appId, emailType, payload, expectedModifiedAt) {
|
|
1441
|
+
return this.put(`/admin/api/apps/${appId}/email-templates/${emailType}`, expectedModifiedAt ? { ...payload, expectedModifiedAt } : payload);
|
|
1223
1442
|
}
|
|
1224
1443
|
async deleteEmailTemplate(appId, emailType) {
|
|
1225
1444
|
return this.delete(`/admin/api/apps/${appId}/email-templates/${emailType}`);
|
|
@@ -1246,8 +1465,42 @@ export class ApiClient {
|
|
|
1246
1465
|
// ============================================
|
|
1247
1466
|
// DATABASES
|
|
1248
1467
|
// ============================================
|
|
1249
|
-
|
|
1250
|
-
|
|
1468
|
+
/**
|
|
1469
|
+
* List an app's databases, following the cursor to the end.
|
|
1470
|
+
*
|
|
1471
|
+
* The endpoint returns the `{ items, hasMore, nextCursor }` envelope and
|
|
1472
|
+
* caps a page at 100 (#1958), so `databases list` needs the follow-up pages
|
|
1473
|
+
* to show every database. `paginateAll` carries the repeat-cursor and
|
|
1474
|
+
* max-page guards, so a server that hands out a non-advancing cursor fails
|
|
1475
|
+
* loudly instead of looping forever.
|
|
1476
|
+
*
|
|
1477
|
+
* `options.owner` narrows the result to the databases that user created; the
|
|
1478
|
+
* server resolves it through the `databasesByCreator` index (#1965).
|
|
1479
|
+
*/
|
|
1480
|
+
async listDatabases(appId, options) {
|
|
1481
|
+
return paginateAll(async (cursor) => {
|
|
1482
|
+
const params = new URLSearchParams();
|
|
1483
|
+
if (options?.owner)
|
|
1484
|
+
params.set("owner", options.owner);
|
|
1485
|
+
if (cursor)
|
|
1486
|
+
params.set("cursor", cursor);
|
|
1487
|
+
const qs = params.toString() ? `?${params.toString()}` : "";
|
|
1488
|
+
const resp = await this.get(`/app/${appId}/api/databases${qs}`);
|
|
1489
|
+
// Tolerant read: a server older than #1958 answers with a bare array.
|
|
1490
|
+
// Without this a published CLI pointed at a not-yet-upgraded server
|
|
1491
|
+
// would print nothing at all. Such a server also predates the `owner`
|
|
1492
|
+
// filter (#1965) and ignores the unknown query parameter, so apply the
|
|
1493
|
+
// filter here — the legacy rows carry `createdBy`, so this reproduces
|
|
1494
|
+
// the server's own post-join filter (#2245).
|
|
1495
|
+
if (Array.isArray(resp)) {
|
|
1496
|
+
return {
|
|
1497
|
+
items: options?.owner
|
|
1498
|
+
? resp.filter((db) => db?.createdBy === options.owner)
|
|
1499
|
+
: resp,
|
|
1500
|
+
};
|
|
1501
|
+
}
|
|
1502
|
+
return { items: resp?.items ?? [], nextCursor: resp?.nextCursor };
|
|
1503
|
+
});
|
|
1251
1504
|
}
|
|
1252
1505
|
async createDatabase(appId, data) {
|
|
1253
1506
|
return this.post(`/app/${appId}/api/databases`, data);
|
|
@@ -1314,6 +1567,14 @@ export class ApiClient {
|
|
|
1314
1567
|
async describeDatabaseModel(appId, databaseId, modelName) {
|
|
1315
1568
|
return this.get(`/app/${appId}/api/databases/${databaseId}/records/describe`, { modelName });
|
|
1316
1569
|
}
|
|
1570
|
+
/**
|
|
1571
|
+
* Query records in a database model. The Durable Object answers with its own
|
|
1572
|
+
* `{ data, hasMore, nextCursor?, prevCursor? }` envelope; that wire shape is
|
|
1573
|
+
* shared with the JS client, the Swift client and web-admin, so the CLI
|
|
1574
|
+
* normalizes it at its own boundary instead (issue #2357) and hands callers
|
|
1575
|
+
* the same `{ items, hasMore, nextCursor? }` envelope as
|
|
1576
|
+
* {@link queryDocumentRecords}.
|
|
1577
|
+
*/
|
|
1317
1578
|
async queryDatabaseRecords(appId, databaseId, modelName, queryOptions) {
|
|
1318
1579
|
const body = { modelName };
|
|
1319
1580
|
if (queryOptions?.filter)
|
|
@@ -1325,7 +1586,55 @@ export class ApiClient {
|
|
|
1325
1586
|
options.uniqueStartKey = queryOptions.cursor;
|
|
1326
1587
|
if (Object.keys(options).length > 0)
|
|
1327
1588
|
body.options = options;
|
|
1328
|
-
|
|
1589
|
+
const raw = await this.post(`/app/${appId}/api/databases/${databaseId}/records/query`, body);
|
|
1590
|
+
return normalizeCliListEnvelope(raw);
|
|
1591
|
+
}
|
|
1592
|
+
async countDatabaseRecords(appId, databaseId, modelName, filter) {
|
|
1593
|
+
const body = { modelName };
|
|
1594
|
+
if (filter)
|
|
1595
|
+
body.filter = filter;
|
|
1596
|
+
return this.post(`/app/${appId}/api/databases/${databaseId}/records/count`, body);
|
|
1597
|
+
}
|
|
1598
|
+
async aggregateDatabaseRecords(appId, databaseId, modelName, options) {
|
|
1599
|
+
return this.post(`/app/${appId}/api/databases/${databaseId}/records/aggregate`, {
|
|
1600
|
+
modelName,
|
|
1601
|
+
options,
|
|
1602
|
+
});
|
|
1603
|
+
}
|
|
1604
|
+
/**
|
|
1605
|
+
* Upsert one record via `records/save`: creates the record when the id is
|
|
1606
|
+
* new, and merges the supplied fields into it when it already exists (the DO
|
|
1607
|
+
* writes with `ON CONFLICT … json_patch`, so fields you leave out survive).
|
|
1608
|
+
* `patchDatabaseRecord` is the same merge but requires the record to exist.
|
|
1609
|
+
*
|
|
1610
|
+
* `id` is optional: when omitted the body carries no `id`, which the
|
|
1611
|
+
* DatabaseDO only accepts alongside an `upsertOn` field — callers that want a
|
|
1612
|
+
* plain create must supply an id themselves (`databases records save` mints a
|
|
1613
|
+
* ULID, matching the CSV import path).
|
|
1614
|
+
*
|
|
1615
|
+
* Returns the endpoint's flat `{ success, id, appliedFields }` — not the
|
|
1616
|
+
* saved record.
|
|
1617
|
+
*/
|
|
1618
|
+
async saveDatabaseRecord(appId, databaseId, modelName, id, data) {
|
|
1619
|
+
const body = { modelName, data };
|
|
1620
|
+
if (id !== undefined)
|
|
1621
|
+
body.id = id;
|
|
1622
|
+
return this.post(`/app/${appId}/api/databases/${databaseId}/records/save`, body);
|
|
1623
|
+
}
|
|
1624
|
+
/**
|
|
1625
|
+
* Merge the supplied fields into an existing record via `records/patch`.
|
|
1626
|
+
*
|
|
1627
|
+
* Fields absent from `data` are left as they are. Unlike `saveDatabaseRecord`
|
|
1628
|
+
* the record must already exist — the DatabaseDO answers 404 for an unknown
|
|
1629
|
+
* id rather than creating one. Returns the flat
|
|
1630
|
+
* `{ success, id, appliedFields }`.
|
|
1631
|
+
*/
|
|
1632
|
+
async patchDatabaseRecord(appId, databaseId, modelName, id, data) {
|
|
1633
|
+
return this.post(`/app/${appId}/api/databases/${databaseId}/records/patch`, {
|
|
1634
|
+
modelName,
|
|
1635
|
+
id,
|
|
1636
|
+
data,
|
|
1637
|
+
});
|
|
1329
1638
|
}
|
|
1330
1639
|
// ============================================
|
|
1331
1640
|
// DATABASE OPERATIONS (registered operations)
|
|
@@ -1333,6 +1642,15 @@ export class ApiClient {
|
|
|
1333
1642
|
async listDatabaseOperations(appId, databaseId) {
|
|
1334
1643
|
return this.get(`/app/${appId}/api/databases/${databaseId}/operations`);
|
|
1335
1644
|
}
|
|
1645
|
+
/**
|
|
1646
|
+
* Execute a registered operation and, when the result is a query page,
|
|
1647
|
+
* normalize it to the CLI's one list envelope (issue #2440).
|
|
1648
|
+
*
|
|
1649
|
+
* The registered operation decides the response shape, so the normalization
|
|
1650
|
+
* is conditional — see {@link normalizeDatabaseOperationResult}. Everything
|
|
1651
|
+
* else (a `count`, an `aggregate`, a mutation, a `returnField: "all"`
|
|
1652
|
+
* pipeline) is returned exactly as the server sent it.
|
|
1653
|
+
*/
|
|
1336
1654
|
async executeDatabaseOperation(appId, databaseId, operationName, data, token, options) {
|
|
1337
1655
|
const path = `/app/${appId}/api/databases/${databaseId}/operations/${encodeURIComponent(operationName)}/execute`;
|
|
1338
1656
|
const extraHeaders = {};
|
|
@@ -1340,17 +1658,17 @@ export class ApiClient {
|
|
|
1340
1658
|
extraHeaders["X-Timing"] = "true";
|
|
1341
1659
|
}
|
|
1342
1660
|
if (token) {
|
|
1343
|
-
return this.requestWithToken(path, token, {
|
|
1661
|
+
return normalizeDatabaseOperationResult(await this.requestWithToken(path, token, {
|
|
1344
1662
|
method: "POST",
|
|
1345
1663
|
body: JSON.stringify(data || {}),
|
|
1346
1664
|
headers: extraHeaders,
|
|
1347
|
-
});
|
|
1665
|
+
}));
|
|
1348
1666
|
}
|
|
1349
|
-
return this.request(path, {
|
|
1667
|
+
return normalizeDatabaseOperationResult(await this.request(path, {
|
|
1350
1668
|
method: "POST",
|
|
1351
1669
|
body: JSON.stringify(data || {}),
|
|
1352
1670
|
headers: extraHeaders,
|
|
1353
|
-
});
|
|
1671
|
+
}));
|
|
1354
1672
|
}
|
|
1355
1673
|
/**
|
|
1356
1674
|
* Execute a registered batch (bulk) database operation. Posts a chunk of
|
|
@@ -1416,7 +1734,7 @@ export class ApiClient {
|
|
|
1416
1734
|
async createDatabaseTypeConfig(appId, data,
|
|
1417
1735
|
// Issue #1336 (codex round-1 P2): `dryRun` runs every create-time
|
|
1418
1736
|
// validation (inline CEL rule lint, manifest shape, schema TOML parse)
|
|
1419
|
-
// WITHOUT persisting, so a fresh-type `
|
|
1737
|
+
// WITHOUT persisting, so a fresh-type `config push --dry-run` surfaces a
|
|
1420
1738
|
// malformed `defaultAccess`/manifest the same way the real POST would.
|
|
1421
1739
|
options) {
|
|
1422
1740
|
const query = options?.dryRun ? "?dryRun=true" : "";
|
|
@@ -1455,7 +1773,7 @@ export class ApiClient {
|
|
|
1455
1773
|
}
|
|
1456
1774
|
async createDatabaseTypeOperation(appId, databaseType, data,
|
|
1457
1775
|
// Issue #813 (1A): when `dryRun` is set the server runs the op-edit gate
|
|
1458
|
-
// without persisting — used by `
|
|
1776
|
+
// without persisting — used by `config push --dry-run` to surface gate
|
|
1459
1777
|
// failures the real push would hit. `schemaOverride` lets the gate run
|
|
1460
1778
|
// against the schema the SAME push is about to land (so an op that depends
|
|
1461
1779
|
// on a new field isn't falsely rejected against the stale stored schema).
|
|
@@ -1526,8 +1844,12 @@ export class ApiClient {
|
|
|
1526
1844
|
async createDatabaseTypeSubscription(appId, databaseType, data) {
|
|
1527
1845
|
return this.post(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/subscriptions`, data);
|
|
1528
1846
|
}
|
|
1529
|
-
async updateDatabaseTypeSubscription(appId, databaseType, subscriptionKey, data
|
|
1530
|
-
|
|
1847
|
+
async updateDatabaseTypeSubscription(appId, databaseType, subscriptionKey, data,
|
|
1848
|
+
// #2731 B10 — optimistic concurrency, as the sibling operation update has
|
|
1849
|
+
// had. Omitted (by `--force`, and by every caller predating it) means the
|
|
1850
|
+
// write stays unconditional.
|
|
1851
|
+
expectedModifiedAt) {
|
|
1852
|
+
return this.put(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/subscriptions/${encodeURIComponent(subscriptionKey)}`, expectedModifiedAt ? { ...data, expectedModifiedAt } : data);
|
|
1531
1853
|
}
|
|
1532
1854
|
async deleteDatabaseTypeSubscription(appId, databaseType, subscriptionKey) {
|
|
1533
1855
|
return this.delete(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/subscriptions/${encodeURIComponent(subscriptionKey)}`);
|
|
@@ -1738,8 +2060,10 @@ export class ApiClient {
|
|
|
1738
2060
|
* path-addressed PUT route). `resourceType` / `category` identify the config;
|
|
1739
2061
|
* the body carries `schema` / `readRule` / `writeRule` / `description`.
|
|
1740
2062
|
*/
|
|
1741
|
-
async upsertMetadataCategoryConfig(appId, resourceType, category, data
|
|
1742
|
-
|
|
2063
|
+
async upsertMetadataCategoryConfig(appId, resourceType, category, data,
|
|
2064
|
+
/** Optimistic-concurrency token; see `upsertEmailTemplate` (#2731 B10). */
|
|
2065
|
+
expectedModifiedAt) {
|
|
2066
|
+
return this.put(`/app/${appId}/api/metadata-categories/${encodeURIComponent(resourceType)}/${encodeURIComponent(category)}`, expectedModifiedAt ? { ...data, expectedModifiedAt } : data);
|
|
1743
2067
|
}
|
|
1744
2068
|
/**
|
|
1745
2069
|
* Delete a metadata category config (issue #1426, admin-gated route shipped
|
|
@@ -1815,6 +2139,112 @@ export class ApiClient {
|
|
|
1815
2139
|
async getDocument(appId, documentId) {
|
|
1816
2140
|
return this.get(`/app/${appId}/api/documents/${documentId}`);
|
|
1817
2141
|
}
|
|
2142
|
+
/**
|
|
2143
|
+
* Delete a document and everything hanging off it (#2756). The server route
|
|
2144
|
+
* runs the same cascade the client SDK's `documents.delete()` triggers —
|
|
2145
|
+
* Yjs state and update history, blob rows and objects, aliases, user/group
|
|
2146
|
+
* permissions, invitations, collection memberships — and returns
|
|
2147
|
+
* `{ success, message }`, which callers pass through under `--json`.
|
|
2148
|
+
*/
|
|
2149
|
+
async deleteDocument(appId, documentId) {
|
|
2150
|
+
return this.delete(`/app/${appId}/api/documents/${documentId}`);
|
|
2151
|
+
}
|
|
2152
|
+
/**
|
|
2153
|
+
* Introspect a document's Yjs schema (model names, per-model fields and
|
|
2154
|
+
* indexes). Backs `documents records models` and `documents records
|
|
2155
|
+
* describe`. Reuses the existing `GET documents/:id/schema` endpoint.
|
|
2156
|
+
*/
|
|
2157
|
+
async getDocumentSchema(appId, documentId) {
|
|
2158
|
+
return this.get(`/app/${appId}/api/documents/${documentId}/schema`);
|
|
2159
|
+
}
|
|
2160
|
+
/**
|
|
2161
|
+
* Query records in a document model (issue #1964 Phase 3). Reuses the
|
|
2162
|
+
* server-side `GET documents/:id/records/:model` endpoint, which runs
|
|
2163
|
+
* through the caller's document permission (reader+) or the app-admin arm.
|
|
2164
|
+
* Returns the unified `{ items, hasMore, nextCursor? }` envelope. The cursor
|
|
2165
|
+
* is an opaque token — feed `nextCursor` back as `cursor` for the next page.
|
|
2166
|
+
* The server also dual-emits a deprecated `cursor` alias (#1316); the
|
|
2167
|
+
* normalizer drops it so CLI output is already on `nextCursor` when #1982
|
|
2168
|
+
* removes it.
|
|
2169
|
+
*/
|
|
2170
|
+
async queryDocumentRecords(appId, documentId, modelName, queryOptions) {
|
|
2171
|
+
const query = {};
|
|
2172
|
+
if (queryOptions?.filter)
|
|
2173
|
+
query.filter = JSON.stringify(queryOptions.filter);
|
|
2174
|
+
// Forward `limit` whenever the caller supplied one (including an invalid
|
|
2175
|
+
// 0/negative) so the server validates and returns a clean 400 rather than
|
|
2176
|
+
// silently applying the default.
|
|
2177
|
+
if (queryOptions?.limit !== undefined)
|
|
2178
|
+
query.limit = queryOptions.limit;
|
|
2179
|
+
if (queryOptions?.cursor)
|
|
2180
|
+
query.cursor = queryOptions.cursor;
|
|
2181
|
+
const raw = await this.get(`/app/${appId}/api/documents/${documentId}/records/${encodeURIComponent(modelName)}`, query);
|
|
2182
|
+
return normalizeCliListEnvelope(raw);
|
|
2183
|
+
}
|
|
2184
|
+
/**
|
|
2185
|
+
* Count records in a document model (issue #1964 Phase 3). Returns `{ count }`.
|
|
2186
|
+
*/
|
|
2187
|
+
async countDocumentRecords(appId, documentId, modelName, queryOptions) {
|
|
2188
|
+
const query = {};
|
|
2189
|
+
if (queryOptions?.filter)
|
|
2190
|
+
query.filter = JSON.stringify(queryOptions.filter);
|
|
2191
|
+
return this.get(`/app/${appId}/api/documents/${documentId}/records/${encodeURIComponent(modelName)}/count`, query);
|
|
2192
|
+
}
|
|
2193
|
+
/**
|
|
2194
|
+
* Aggregate records in a document model (issue #2437) — the documents twin
|
|
2195
|
+
* of {@link aggregateDatabaseRecords}. `POST documents/:id/records/:model/aggregate`
|
|
2196
|
+
* with `{ options: { operations, groupBy?, filter? } }`, answering `{ result }`
|
|
2197
|
+
* in the same shape as the database endpoint.
|
|
2198
|
+
*
|
|
2199
|
+
* `groupBy` takes plain field names only: StringSet facet grouping and
|
|
2200
|
+
* `{ field, contains }` membership grouping are database-only, which is why
|
|
2201
|
+
* this signature is narrower than the databases one.
|
|
2202
|
+
*/
|
|
2203
|
+
async aggregateDocumentRecords(appId, documentId, modelName, options) {
|
|
2204
|
+
return this.post(`/app/${appId}/api/documents/${documentId}/records/${encodeURIComponent(modelName)}/aggregate`, { options });
|
|
2205
|
+
}
|
|
2206
|
+
/**
|
|
2207
|
+
* Fetch platform-vocabulary document statistics (issue #1964 Phase 3):
|
|
2208
|
+
* record/model/blob counts, approximate size, and last-modified timestamp.
|
|
2209
|
+
*/
|
|
2210
|
+
async getDocumentStats(appId, documentId) {
|
|
2211
|
+
return this.get(`/app/${appId}/api/documents/${documentId}/stats`);
|
|
2212
|
+
}
|
|
2213
|
+
/**
|
|
2214
|
+
* Create or replace a record in a document model (issue #1964 Phase 4).
|
|
2215
|
+
* `POST documents/:id/records/:model` with `{ id, data, options? }` —
|
|
2216
|
+
* server-side write through the document facade (Yjs-first, CRDT-safe,
|
|
2217
|
+
* broadcasts to connected clients). Requires read-write+ on the document
|
|
2218
|
+
* or the app-admin arm. Returns `{ record }`.
|
|
2219
|
+
*/
|
|
2220
|
+
async saveDocumentRecord(appId, documentId, modelName, body) {
|
|
2221
|
+
return this.post(`/app/${appId}/api/documents/${documentId}/records/${encodeURIComponent(modelName)}`, body);
|
|
2222
|
+
}
|
|
2223
|
+
/**
|
|
2224
|
+
* Merge fields into an existing record (issue #1964 Phase 4).
|
|
2225
|
+
* `PATCH documents/:id/records/:model/:recordId` with `{ data, options? }`.
|
|
2226
|
+
* Returns `{ record }`.
|
|
2227
|
+
*/
|
|
2228
|
+
async patchDocumentRecord(appId, documentId, modelName, recordId, body) {
|
|
2229
|
+
return this.patch(`/app/${appId}/api/documents/${documentId}/records/${encodeURIComponent(modelName)}/${encodeURIComponent(recordId)}`, body);
|
|
2230
|
+
}
|
|
2231
|
+
/**
|
|
2232
|
+
* Delete a record from a document model (issue #1964 Phase 4).
|
|
2233
|
+
* `DELETE documents/:id/records/:model/:recordId`. A missing record id is
|
|
2234
|
+
* a silent no-op per the facade contract. Returns `{ deleted: true }`.
|
|
2235
|
+
*/
|
|
2236
|
+
async deleteDocumentRecord(appId, documentId, modelName, recordId) {
|
|
2237
|
+
return this.delete(`/app/${appId}/api/documents/${documentId}/records/${encodeURIComponent(modelName)}/${encodeURIComponent(recordId)}`);
|
|
2238
|
+
}
|
|
2239
|
+
/**
|
|
2240
|
+
* Apply an ordered multi-model create/patch/delete blob atomically (issue
|
|
2241
|
+
* #1964 Phase 4, wrapping the #1517 `bulkUpdate` facade). All-or-nothing:
|
|
2242
|
+
* a validation failure commits nothing. Returns the facade's
|
|
2243
|
+
* `{ applied, added, updated, deleted }`.
|
|
2244
|
+
*/
|
|
2245
|
+
async bulkDocumentRecords(appId, documentId, operations) {
|
|
2246
|
+
return this.post(`/app/${appId}/api/documents/${documentId}/records/bulk`, { operations });
|
|
2247
|
+
}
|
|
1818
2248
|
async createDocument(appId, data) {
|
|
1819
2249
|
return this.post(`/app/${appId}/api/documents`, data);
|
|
1820
2250
|
}
|
|
@@ -1824,6 +2254,24 @@ export class ApiClient {
|
|
|
1824
2254
|
async grantDocumentPermission(appId, documentId, permissions) {
|
|
1825
2255
|
return this.put(`/app/${appId}/api/documents/${documentId}/permissions`, { permissions });
|
|
1826
2256
|
}
|
|
2257
|
+
/**
|
|
2258
|
+
* Revoke a user's permission on a document. Mirrors the published client's
|
|
2259
|
+
* `documents.removePermission` wire behavior (`src/client/api/documentsApi.ts`):
|
|
2260
|
+
* a userId targets the path route `DELETE .../permissions/:userId`, while an
|
|
2261
|
+
* email targets the query-param route `DELETE .../permissions?email=...`
|
|
2262
|
+
* (unified email removal, #452). This duplication of the client's wire shape
|
|
2263
|
+
* is deliberate and parity-tested (issue #1964 amendment 8).
|
|
2264
|
+
*/
|
|
2265
|
+
async revokeDocumentPermission(appId, documentId, target) {
|
|
2266
|
+
if (target.email) {
|
|
2267
|
+
const email = encodeURIComponent(target.email);
|
|
2268
|
+
return this.delete(`/app/${appId}/api/documents/${documentId}/permissions?email=${email}`);
|
|
2269
|
+
}
|
|
2270
|
+
if (!target.userId) {
|
|
2271
|
+
throw new Error("revokeDocumentPermission: userId or email is required");
|
|
2272
|
+
}
|
|
2273
|
+
return this.delete(`/app/${appId}/api/documents/${documentId}/permissions/${target.userId}`);
|
|
2274
|
+
}
|
|
1827
2275
|
async getDocumentLinkAccess(appId, documentId) {
|
|
1828
2276
|
return this.get(`/app/${appId}/api/documents/${documentId}/link-access`);
|
|
1829
2277
|
}
|
|
@@ -1889,15 +2337,16 @@ export class ApiClient {
|
|
|
1889
2337
|
mustNotExist,
|
|
1890
2338
|
});
|
|
1891
2339
|
}
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
2340
|
+
/**
|
|
2341
|
+
* Resolve an app user by exact email — the single email → userId lookup in
|
|
2342
|
+
* the CLI (issue #2763). It reads the member-accessible `users/lookup`
|
|
2343
|
+
* endpoint rather than the admin-only `GET /users` list, and returns the
|
|
2344
|
+
* server's `{ exists, user? }` envelope as-is: a 403 or a transport failure
|
|
2345
|
+
* rejects instead of being flattened into "no such user", which is exactly
|
|
2346
|
+
* how the deleted admin-list helper misreported permission problems.
|
|
2347
|
+
*/
|
|
2348
|
+
async lookupUserByEmail(appId, email) {
|
|
2349
|
+
return this.get(`/app/${appId}/api/users/lookup`, { email });
|
|
1901
2350
|
}
|
|
1902
2351
|
async listAdminDocuments(appId, userId) {
|
|
1903
2352
|
const result = await this.get(`/admin/api/apps/${appId}/documents`, { userId });
|
|
@@ -1906,21 +2355,29 @@ export class ApiClient {
|
|
|
1906
2355
|
// ============================================
|
|
1907
2356
|
// DATABASE EXPORT / IMPORT
|
|
1908
2357
|
// ============================================
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
2358
|
+
// `saveDatabaseRecord` lives with the other record verbs under
|
|
2359
|
+
// "DATABASE RECORDS & SCHEMA" above.
|
|
2360
|
+
/**
|
|
2361
|
+
* Apply an ordered batch of record operations via `records/batch`.
|
|
2362
|
+
*
|
|
2363
|
+
* `atomic` (default off, matching the DatabaseDO) makes the whole batch
|
|
2364
|
+
* all-or-nothing: the DO rolls every write back at the first failed
|
|
2365
|
+
* operation and answers with that operation's status. `databases records
|
|
2366
|
+
* bulk` sends `atomic: true` so it matches the documents twin's
|
|
2367
|
+
* all-or-nothing contract (#2437); the CSV/import paths keep the default
|
|
2368
|
+
* partial-success behavior.
|
|
2369
|
+
*/
|
|
2370
|
+
async batchDatabaseRecords(appId, databaseId, operations, options) {
|
|
2371
|
+
const body = { operations };
|
|
2372
|
+
if (options?.atomic)
|
|
2373
|
+
body.atomic = true;
|
|
2374
|
+
return this.post(`/app/${appId}/api/databases/${databaseId}/records/batch`, body);
|
|
1918
2375
|
}
|
|
1919
2376
|
async deleteDatabaseRecord(appId, databaseId, modelName, id) {
|
|
1920
|
-
return this.post(`/app/${appId}/api/databases/${databaseId}/
|
|
2377
|
+
return this.post(`/app/${appId}/api/databases/${databaseId}/records/delete`, { modelName, id });
|
|
1921
2378
|
}
|
|
1922
2379
|
async batchDeleteDatabaseRecords(appId, databaseId, operations) {
|
|
1923
|
-
return this.post(`/app/${appId}/api/databases/${databaseId}/
|
|
2380
|
+
return this.post(`/app/${appId}/api/databases/${databaseId}/records/batch`, { operations });
|
|
1924
2381
|
}
|
|
1925
2382
|
async listDatabaseUniqueConstraints(appId, databaseId) {
|
|
1926
2383
|
const result = await this.get(`/app/${appId}/api/databases/${databaseId}/records/unique-constraints`);
|