@stigmer/server 3.24.0 → 3.26.0
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/dist/authorization/authorizer.d.ts +10 -4
- package/dist/authorization/authorizer.d.ts.map +1 -1
- package/dist/authorization/authorizer.js +11 -4
- package/dist/authorization/authorizer.js.map +1 -1
- package/dist/authorization/model/index.d.ts +4 -4
- package/dist/authorization/model/index.d.ts.map +1 -1
- package/dist/authorization/model/index.js +2 -0
- package/dist/authorization/model/index.js.map +1 -1
- package/dist/authorization/model/oauth_app.d.ts.map +1 -1
- package/dist/authorization/model/oauth_app.js +3 -2
- package/dist/authorization/model/oauth_app.js.map +1 -1
- package/dist/authorization/model/platform_client.d.ts +2 -0
- package/dist/authorization/model/platform_client.d.ts.map +1 -0
- package/dist/authorization/model/platform_client.js +31 -0
- package/dist/authorization/model/platform_client.js.map +1 -0
- package/dist/boot/compose.d.ts.map +1 -1
- package/dist/boot/compose.js +99 -13
- package/dist/boot/compose.js.map +1 -1
- package/dist/boot/list-indexes.d.ts +3 -0
- package/dist/boot/list-indexes.d.ts.map +1 -0
- package/dist/boot/list-indexes.js +24 -0
- package/dist/boot/list-indexes.js.map +1 -0
- package/dist/boot/route-shadowing.d.ts +25 -0
- package/dist/boot/route-shadowing.d.ts.map +1 -0
- package/dist/boot/route-shadowing.js +17 -0
- package/dist/boot/route-shadowing.js.map +1 -0
- package/dist/domain/agentexecution/controller.d.ts.map +1 -1
- package/dist/domain/agentexecution/controller.js +6 -8
- package/dist/domain/agentexecution/controller.js.map +1 -1
- package/dist/domain/agentexecution/list-index.d.ts +2 -0
- package/dist/domain/agentexecution/list-index.d.ts.map +1 -0
- package/dist/domain/agentexecution/list-index.js +23 -0
- package/dist/domain/agentexecution/list-index.js.map +1 -0
- package/dist/domain/agentexecution/steps.d.ts +17 -33
- package/dist/domain/agentexecution/steps.d.ts.map +1 -1
- package/dist/domain/agentexecution/steps.js +94 -95
- package/dist/domain/agentexecution/steps.js.map +1 -1
- package/dist/domain/agentexecution/usage.d.ts.map +1 -1
- package/dist/domain/agentexecution/usage.js +37 -14
- package/dist/domain/agentexecution/usage.js.map +1 -1
- package/dist/domain/artifact/controller.d.ts.map +1 -1
- package/dist/domain/artifact/controller.js +27 -15
- package/dist/domain/artifact/controller.js.map +1 -1
- package/dist/domain/artifact/list-index.d.ts +2 -0
- package/dist/domain/artifact/list-index.d.ts.map +1 -0
- package/dist/domain/artifact/list-index.js +21 -0
- package/dist/domain/artifact/list-index.js.map +1 -0
- package/dist/domain/iampolicy/membership.d.ts +6 -1
- package/dist/domain/iampolicy/membership.d.ts.map +1 -1
- package/dist/domain/iampolicy/membership.js +8 -2
- package/dist/domain/iampolicy/membership.js.map +1 -1
- package/dist/domain/identityaccount/constants.d.ts +13 -0
- package/dist/domain/identityaccount/constants.d.ts.map +1 -1
- package/dist/domain/identityaccount/constants.js +17 -0
- package/dist/domain/identityaccount/constants.js.map +1 -1
- package/dist/domain/identityaccount/controller.d.ts +3 -1
- package/dist/domain/identityaccount/controller.d.ts.map +1 -1
- package/dist/domain/identityaccount/controller.js +15 -6
- package/dist/domain/identityaccount/controller.js.map +1 -1
- package/dist/domain/identityaccount/operator.d.ts.map +1 -1
- package/dist/domain/identityaccount/operator.js +1 -0
- package/dist/domain/identityaccount/operator.js.map +1 -1
- package/dist/domain/identityaccount/provisioning.d.ts +18 -0
- package/dist/domain/identityaccount/provisioning.d.ts.map +1 -1
- package/dist/domain/identityaccount/provisioning.js +1 -0
- package/dist/domain/identityaccount/provisioning.js.map +1 -1
- package/dist/domain/identityaccount/resource-store.js +9 -5
- package/dist/domain/identityaccount/resource-store.js.map +1 -1
- package/dist/domain/identityaccount/steps.d.ts +11 -2
- package/dist/domain/identityaccount/steps.d.ts.map +1 -1
- package/dist/domain/identityaccount/steps.js +42 -4
- package/dist/domain/identityaccount/steps.js.map +1 -1
- package/dist/domain/identityaccount/store-contract.d.ts.map +1 -1
- package/dist/domain/identityaccount/store-contract.js +33 -0
- package/dist/domain/identityaccount/store-contract.js.map +1 -1
- package/dist/domain/identityaccount/store.d.ts +7 -5
- package/dist/domain/identityaccount/store.d.ts.map +1 -1
- package/dist/domain/identityaccount/store.js.map +1 -1
- package/dist/domain/platform/controller.d.ts +10 -0
- package/dist/domain/platform/controller.d.ts.map +1 -1
- package/dist/domain/platform/controller.js +2 -1
- package/dist/domain/platform/controller.js.map +1 -1
- package/dist/domain/platformclient/constants.d.ts +60 -0
- package/dist/domain/platformclient/constants.d.ts.map +1 -0
- package/dist/domain/platformclient/constants.js +78 -0
- package/dist/domain/platformclient/constants.js.map +1 -0
- package/dist/domain/platformclient/controller.d.ts +59 -0
- package/dist/domain/platformclient/controller.d.ts.map +1 -0
- package/dist/domain/platformclient/controller.js +191 -0
- package/dist/domain/platformclient/controller.js.map +1 -0
- package/dist/domain/platformclient/credentials.d.ts +13 -0
- package/dist/domain/platformclient/credentials.d.ts.map +1 -0
- package/dist/domain/platformclient/credentials.js +56 -0
- package/dist/domain/platformclient/credentials.js.map +1 -0
- package/dist/domain/platformclient/mint.d.ts +23 -0
- package/dist/domain/platformclient/mint.d.ts.map +1 -0
- package/dist/domain/platformclient/mint.js +254 -0
- package/dist/domain/platformclient/mint.js.map +1 -0
- package/dist/domain/platformclient/origin-guard.d.ts +9 -0
- package/dist/domain/platformclient/origin-guard.d.ts.map +1 -0
- package/dist/domain/platformclient/origin-guard.js +81 -0
- package/dist/domain/platformclient/origin-guard.js.map +1 -0
- package/dist/domain/platformclient/resource-store.d.ts +4 -0
- package/dist/domain/platformclient/resource-store.d.ts.map +1 -0
- package/dist/domain/platformclient/resource-store.js +117 -0
- package/dist/domain/platformclient/resource-store.js.map +1 -0
- package/dist/domain/platformclient/steps.d.ts +51 -0
- package/dist/domain/platformclient/steps.d.ts.map +1 -0
- package/dist/domain/platformclient/steps.js +339 -0
- package/dist/domain/platformclient/steps.js.map +1 -0
- package/dist/domain/platformclient/store-contract.d.ts +8 -0
- package/dist/domain/platformclient/store-contract.d.ts.map +1 -0
- package/dist/domain/platformclient/store-contract.js +208 -0
- package/dist/domain/platformclient/store-contract.js.map +1 -0
- package/dist/domain/platformclient/store.d.ts +58 -0
- package/dist/domain/platformclient/store.d.ts.map +1 -0
- package/dist/domain/platformclient/store.js +13 -0
- package/dist/domain/platformclient/store.js.map +1 -0
- package/dist/domain/platformclient/system-managed.d.ts +18 -0
- package/dist/domain/platformclient/system-managed.d.ts.map +1 -0
- package/dist/domain/platformclient/system-managed.js +80 -0
- package/dist/domain/platformclient/system-managed.js.map +1 -0
- package/dist/domain/platformclient/token-controller.d.ts +11 -0
- package/dist/domain/platformclient/token-controller.d.ts.map +1 -0
- package/dist/domain/platformclient/token-controller.js +32 -0
- package/dist/domain/platformclient/token-controller.js.map +1 -0
- package/dist/domain/platformclient/verifier.d.ts +10 -0
- package/dist/domain/platformclient/verifier.d.ts.map +1 -0
- package/dist/domain/platformclient/verifier.js +76 -0
- package/dist/domain/platformclient/verifier.js.map +1 -0
- package/dist/domain/session/list-index.d.ts +4 -0
- package/dist/domain/session/list-index.d.ts.map +1 -0
- package/dist/domain/session/list-index.js +26 -0
- package/dist/domain/session/list-index.js.map +1 -0
- package/dist/domain/session/steps.d.ts +7 -8
- package/dist/domain/session/steps.d.ts.map +1 -1
- package/dist/domain/session/steps.js +76 -78
- package/dist/domain/session/steps.js.map +1 -1
- package/dist/domain/workflowexecution/controller.d.ts.map +1 -1
- package/dist/domain/workflowexecution/controller.js +53 -55
- package/dist/domain/workflowexecution/controller.js.map +1 -1
- package/dist/domain/workflowexecution/execution-filter.d.ts +4 -0
- package/dist/domain/workflowexecution/execution-filter.d.ts.map +1 -1
- package/dist/domain/workflowexecution/execution-filter.js +11 -5
- package/dist/domain/workflowexecution/execution-filter.js.map +1 -1
- package/dist/domain/workflowexecution/get-execution-summary.d.ts.map +1 -1
- package/dist/domain/workflowexecution/get-execution-summary.js +23 -17
- package/dist/domain/workflowexecution/get-execution-summary.js.map +1 -1
- package/dist/domain/workflowexecution/list-index.d.ts +2 -0
- package/dist/domain/workflowexecution/list-index.d.ts.map +1 -0
- package/dist/domain/workflowexecution/list-index.js +21 -0
- package/dist/domain/workflowexecution/list-index.js.map +1 -0
- package/dist/domain/workflowexecution/list-pending-approvals.d.ts.map +1 -1
- package/dist/domain/workflowexecution/list-pending-approvals.js +69 -32
- package/dist/domain/workflowexecution/list-pending-approvals.js.map +1 -1
- package/dist/domain/workflowexecution/queries.d.ts +30 -4
- package/dist/domain/workflowexecution/queries.d.ts.map +1 -1
- package/dist/domain/workflowexecution/queries.js +76 -22
- package/dist/domain/workflowexecution/queries.js.map +1 -1
- package/dist/encryption/key-manager.d.ts +20 -3
- package/dist/encryption/key-manager.d.ts.map +1 -1
- package/dist/encryption/key-manager.js +64 -42
- package/dist/encryption/key-manager.js.map +1 -1
- package/dist/extensions/caller-guards.d.ts +8 -3
- package/dist/extensions/caller-guards.d.ts.map +1 -1
- package/dist/extensions/drivers.d.ts +37 -0
- package/dist/extensions/drivers.d.ts.map +1 -1
- package/dist/extensions/guest-token-minting.d.ts +26 -0
- package/dist/extensions/guest-token-minting.d.ts.map +1 -0
- package/dist/extensions/guest-token-minting.js +2 -0
- package/dist/extensions/guest-token-minting.js.map +1 -0
- package/dist/extensions/identity.d.ts +6 -4
- package/dist/extensions/identity.d.ts.map +1 -1
- package/dist/extensions/identity.js.map +1 -1
- package/dist/extensions/list-read-scope.d.ts +9 -12
- package/dist/extensions/list-read-scope.d.ts.map +1 -1
- package/dist/extensions/list-read-scope.js.map +1 -1
- package/dist/extensions/registry.d.ts +30 -1
- package/dist/extensions/registry.d.ts.map +1 -1
- package/dist/extensions/registry.js +34 -1
- package/dist/extensions/registry.js.map +1 -1
- package/dist/index.d.ts +15 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/pipeline/interceptors/auth.d.ts +14 -0
- package/dist/pipeline/interceptors/auth.d.ts.map +1 -1
- package/dist/pipeline/interceptors/auth.js +24 -0
- package/dist/pipeline/interceptors/auth.js.map +1 -1
- package/dist/pipeline/steps/build-update-state.d.ts +11 -1
- package/dist/pipeline/steps/build-update-state.d.ts.map +1 -1
- package/dist/pipeline/steps/build-update-state.js +5 -2
- package/dist/pipeline/steps/build-update-state.js.map +1 -1
- package/dist/pipeline/steps/list-page.d.ts +63 -0
- package/dist/pipeline/steps/list-page.d.ts.map +1 -0
- package/dist/pipeline/steps/list-page.js +187 -0
- package/dist/pipeline/steps/list-page.js.map +1 -0
- package/dist/platformtoken/envelope.d.ts +100 -0
- package/dist/platformtoken/envelope.d.ts.map +1 -0
- package/dist/platformtoken/envelope.js +184 -0
- package/dist/platformtoken/envelope.js.map +1 -0
- package/dist/platformtoken/key-ring.d.ts +85 -0
- package/dist/platformtoken/key-ring.d.ts.map +1 -0
- package/dist/platformtoken/key-ring.js +164 -0
- package/dist/platformtoken/key-ring.js.map +1 -0
- package/dist/query/activity/handler.d.ts +6 -7
- package/dist/query/activity/handler.d.ts.map +1 -1
- package/dist/query/activity/handler.js +42 -59
- package/dist/query/activity/handler.js.map +1 -1
- package/dist/store/interface.d.ts +29 -0
- package/dist/store/interface.d.ts.map +1 -1
- package/dist/store/interface.js.map +1 -1
- package/dist/store/list-index.d.ts +123 -0
- package/dist/store/list-index.d.ts.map +1 -0
- package/dist/store/list-index.js +277 -0
- package/dist/store/list-index.js.map +1 -0
- package/dist/store/logger.d.ts +3 -0
- package/dist/store/logger.d.ts.map +1 -1
- package/dist/store/logger.js +5 -1
- package/dist/store/logger.js.map +1 -1
- package/dist/store/postgres/migrations.d.ts +10 -6
- package/dist/store/postgres/migrations.d.ts.map +1 -1
- package/dist/store/postgres/migrations.js +64 -6
- package/dist/store/postgres/migrations.js.map +1 -1
- package/dist/store/postgres/store.d.ts +26 -5
- package/dist/store/postgres/store.d.ts.map +1 -1
- package/dist/store/postgres/store.js +377 -22
- package/dist/store/postgres/store.js.map +1 -1
- package/dist/store/sqlite/migrations.d.ts +11 -5
- package/dist/store/sqlite/migrations.d.ts.map +1 -1
- package/dist/store/sqlite/migrations.js +50 -4
- package/dist/store/sqlite/migrations.js.map +1 -1
- package/dist/store/sqlite/store.d.ts +18 -3
- package/dist/store/sqlite/store.d.ts.map +1 -1
- package/dist/store/sqlite/store.js +321 -15
- package/dist/store/sqlite/store.js.map +1 -1
- package/package.json +6 -6
- package/src/authorization/README.md +1 -1
- package/src/authorization/__tests__/authorizer.test.ts +6 -2
- package/src/authorization/__tests__/drivers.ts +6 -1
- package/src/authorization/__tests__/wire-permissions.test.ts +1 -0
- package/src/authorization/authorizer.ts +11 -4
- package/src/authorization/model/__tests__/registry.test.ts +8 -1
- package/src/authorization/model/index.ts +6 -4
- package/src/authorization/model/oauth_app.ts +3 -2
- package/src/authorization/model/platform_client.ts +43 -0
- package/src/boot/__tests__/auth-enabled-composition.test.ts +2 -1
- package/src/boot/__tests__/list-indexes.test.ts +65 -0
- package/src/boot/__tests__/route-shadowing.test.ts +67 -0
- package/src/boot/compose.ts +113 -18
- package/src/boot/list-indexes.ts +25 -0
- package/src/boot/route-shadowing.ts +47 -0
- package/src/domain/agentexecution/__tests__/agentexecution.test.ts +85 -3
- package/src/domain/agentexecution/controller.ts +8 -10
- package/src/domain/agentexecution/list-index.ts +24 -0
- package/src/domain/agentexecution/steps.ts +118 -115
- package/src/domain/agentexecution/usage.ts +38 -17
- package/src/domain/artifact/controller.ts +28 -18
- package/src/domain/artifact/list-index.ts +22 -0
- package/src/domain/iampolicy/__tests__/membership.test.ts +7 -0
- package/src/domain/iampolicy/membership.ts +8 -2
- package/src/domain/identityaccount/__tests__/platform-client-accounts.test.ts +124 -0
- package/src/domain/identityaccount/__tests__/resource-store.test.ts +1 -0
- package/src/domain/identityaccount/constants.ts +20 -0
- package/src/domain/identityaccount/controller.ts +16 -4
- package/src/domain/identityaccount/operator.ts +1 -0
- package/src/domain/identityaccount/provisioning.ts +17 -0
- package/src/domain/identityaccount/resource-store.ts +9 -5
- package/src/domain/identityaccount/steps.ts +58 -3
- package/src/domain/identityaccount/store-contract.ts +51 -0
- package/src/domain/identityaccount/store.ts +7 -5
- package/src/domain/platform/__tests__/platform.test.ts +7 -1
- package/src/domain/platform/controller.ts +12 -1
- package/src/domain/platformclient/__tests__/controller.test.ts +397 -0
- package/src/domain/platformclient/__tests__/credentials.test.ts +46 -0
- package/src/domain/platformclient/__tests__/mint.test.ts +415 -0
- package/src/domain/platformclient/__tests__/resource-store.test.ts +140 -0
- package/src/domain/platformclient/__tests__/system-managed.test.ts +101 -0
- package/src/domain/platformclient/__tests__/verifier-and-guard.test.ts +207 -0
- package/src/domain/platformclient/constants.ts +120 -0
- package/src/domain/platformclient/controller.ts +449 -0
- package/src/domain/platformclient/credentials.ts +68 -0
- package/src/domain/platformclient/mint.ts +412 -0
- package/src/domain/platformclient/origin-guard.ts +117 -0
- package/src/domain/platformclient/resource-store.ts +150 -0
- package/src/domain/platformclient/steps.ts +472 -0
- package/src/domain/platformclient/store-contract.ts +318 -0
- package/src/domain/platformclient/store.ts +63 -0
- package/src/domain/platformclient/system-managed.ts +115 -0
- package/src/domain/platformclient/token-controller.ts +49 -0
- package/src/domain/platformclient/verifier.ts +106 -0
- package/src/domain/session/list-index.ts +28 -0
- package/src/domain/session/steps.ts +108 -123
- package/src/domain/workflowexecution/__tests__/list-read-scope-summaries.test.ts +155 -99
- package/src/domain/workflowexecution/__tests__/workflowexecution.test.ts +193 -31
- package/src/domain/workflowexecution/controller.ts +54 -82
- package/src/domain/workflowexecution/execution-filter.ts +20 -7
- package/src/domain/workflowexecution/get-execution-summary.ts +31 -24
- package/src/domain/workflowexecution/list-index.ts +22 -0
- package/src/domain/workflowexecution/list-pending-approvals.ts +82 -25
- package/src/domain/workflowexecution/queries.ts +134 -23
- package/src/encryption/key-manager.ts +87 -44
- package/src/extensions/__tests__/extension-composition.test.ts +3 -2
- package/src/extensions/__tests__/platform-client-composed.test.ts +288 -0
- package/src/extensions/__tests__/registry.test.ts +4 -1
- package/src/extensions/__tests__/tier-truthfulness.test.ts +8 -0
- package/src/extensions/caller-guards.ts +8 -3
- package/src/extensions/drivers.ts +37 -0
- package/src/extensions/guest-token-minting.ts +32 -0
- package/src/extensions/identity.ts +6 -4
- package/src/extensions/list-read-scope.ts +9 -12
- package/src/extensions/registry.ts +77 -3
- package/src/index.ts +65 -1
- package/src/pipeline/interceptors/auth.ts +27 -0
- package/src/pipeline/steps/__tests__/list-page.test.ts +186 -0
- package/src/pipeline/steps/__tests__/references.test.ts +2 -0
- package/src/pipeline/steps/build-update-state.ts +5 -2
- package/src/pipeline/steps/list-page.ts +284 -0
- package/src/platformtoken/__tests__/envelope.test.ts +277 -0
- package/src/platformtoken/__tests__/key-ring.test.ts +205 -0
- package/src/platformtoken/envelope.ts +276 -0
- package/src/platformtoken/key-ring.ts +266 -0
- package/src/query/activity/handler.ts +57 -78
- package/src/runnerauth/__tests__/runner-subject-composed.test.ts +9 -3
- package/src/store/README.md +51 -32
- package/src/store/__tests__/list-index.measure.test.ts +293 -0
- package/src/store/__tests__/list-index.test.ts +328 -0
- package/src/store/__tests__/store-contract.ts +346 -3
- package/src/store/interface.ts +39 -0
- package/src/store/list-index.ts +465 -0
- package/src/store/logger.ts +8 -1
- package/src/store/postgres/__tests__/list-index-repair.test.ts +133 -0
- package/src/store/postgres/__tests__/migrations.test.ts +27 -19
- package/src/store/postgres/__tests__/store-contract.test.ts +50 -3
- package/src/store/postgres/migrations.ts +69 -7
- package/src/store/postgres/store.ts +485 -29
- package/src/store/sqlite/__tests__/migrations.test.ts +51 -7
- package/src/store/sqlite/__tests__/store-contract.test.ts +70 -16
- package/src/store/sqlite/__tests__/support.ts +12 -3
- package/src/store/sqlite/migrations.ts +57 -6
- package/src/store/sqlite/store.ts +462 -21
|
@@ -59,8 +59,23 @@ import type {
|
|
|
59
59
|
SignalDedupeStatus,
|
|
60
60
|
SignalDedupeStore,
|
|
61
61
|
Store,
|
|
62
|
+
StoreOpenOptions,
|
|
62
63
|
WorkflowExecutionEventRecord,
|
|
63
64
|
} from "../interface.js";
|
|
65
|
+
import {
|
|
66
|
+
ListIndexRegistry,
|
|
67
|
+
assertListIndexLimit,
|
|
68
|
+
listIndexFactsOf,
|
|
69
|
+
matchesListIndexQuery,
|
|
70
|
+
mergeListIndexRows,
|
|
71
|
+
sameListKeyRows,
|
|
72
|
+
} from "../list-index.js";
|
|
73
|
+
import type {
|
|
74
|
+
ListIndexDeclaration,
|
|
75
|
+
ListIndexFacts,
|
|
76
|
+
ListIndexQuery,
|
|
77
|
+
ListIndexRow,
|
|
78
|
+
} from "../list-index.js";
|
|
64
79
|
import { NOOP_STORE_LOGGER } from "../logger.js";
|
|
65
80
|
import type { StoreLogger } from "../logger.js";
|
|
66
81
|
import {
|
|
@@ -77,6 +92,67 @@ import { runMigrations } from "./migrations.js";
|
|
|
77
92
|
// import path stays stable for existing consumers.
|
|
78
93
|
export type { StoreLogger } from "../logger.js";
|
|
79
94
|
|
|
95
|
+
/**
|
|
96
|
+
* `updated_at` and the list index's stamp, to the millisecond. Evaluated
|
|
97
|
+
* twice in one statement it yields one value (sqlite fixes 'now' per
|
|
98
|
+
* statement step), so a row this driver writes is stamped with exactly its
|
|
99
|
+
* `updated_at`; a writer that does not know the index writes
|
|
100
|
+
* `datetime('now')`, whole seconds, which never equals a stamp.
|
|
101
|
+
*/
|
|
102
|
+
const NOW = `strftime('%Y-%m-%d %H:%M:%f', 'now')`;
|
|
103
|
+
|
|
104
|
+
/** The one upsert every resource write goes through (postgres/store.ts's twin). */
|
|
105
|
+
const UPSERT_RESOURCE_SQL = `INSERT OR REPLACE INTO resources
|
|
106
|
+
(kind, id, data, updated_at, list_org, list_created_at, list_index_revision, list_indexed_at)
|
|
107
|
+
VALUES (?, ?, ?, ${NOW}, ?, ?, ?, ${NOW})`;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* The rows of a kind whose facts are not proven current under a revision
|
|
111
|
+
* (list-index.ts; postgres/store.ts's twin). A row's list columns sit
|
|
112
|
+
* after its blob in the record and sqlite reaches them only by walking the
|
|
113
|
+
* blob's overflow pages, so any predicate checked per row of the kind
|
|
114
|
+
* costs a read of every blob (measured: 11 ms, and 72 ms for an OR over
|
|
115
|
+
* the revision, across 1,279 rows of 37 KB). Every arm is therefore one
|
|
116
|
+
* index range that fetches a row only when it matches, and names its
|
|
117
|
+
* index: without statistics the planner prefers the primary key for
|
|
118
|
+
* `kind = ?`, and `INDEXED BY` fails the statement rather than fall back
|
|
119
|
+
* to a scan. The arms overlap (an unstamped row has no revision either),
|
|
120
|
+
* so the caller keeps one row per id. Parameters: kind, kind, kind,
|
|
121
|
+
* revision, kind, revision.
|
|
122
|
+
*/
|
|
123
|
+
const UNPROVEN_ROWS_SQL = `
|
|
124
|
+
SELECT id, data FROM resources INDEXED BY idx_resources_list_unproven
|
|
125
|
+
WHERE kind = ? AND list_indexed_at IS NOT updated_at
|
|
126
|
+
UNION ALL
|
|
127
|
+
SELECT id, data FROM resources INDEXED BY idx_resources_list_revision
|
|
128
|
+
WHERE kind = ? AND list_index_revision IS NULL
|
|
129
|
+
UNION ALL
|
|
130
|
+
SELECT id, data FROM resources INDEXED BY idx_resources_list_revision
|
|
131
|
+
WHERE kind = ? AND list_index_revision < ?
|
|
132
|
+
UNION ALL
|
|
133
|
+
SELECT id, data FROM resources INDEXED BY idx_resources_list_revision
|
|
134
|
+
WHERE kind = ? AND list_index_revision > ?`;
|
|
135
|
+
|
|
136
|
+
function unprovenRowsParams(
|
|
137
|
+
kindName: string,
|
|
138
|
+
revision: number,
|
|
139
|
+
): Array<string | number> {
|
|
140
|
+
return [kindName, kindName, kindName, revision, kindName, revision];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Keeps the first row per id; the unproven arms overlap. */
|
|
144
|
+
function oneRowPerId<T extends { readonly id: string }>(
|
|
145
|
+
rows: ReadonlyArray<T>,
|
|
146
|
+
): T[] {
|
|
147
|
+
const seen = new Set<string>();
|
|
148
|
+
return rows.filter((row) =>
|
|
149
|
+
seen.has(row.id) ? false : (seen.add(row.id), true),
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** How many unproven rows the reconciliation at open decodes per statement. */
|
|
154
|
+
const RECONCILE_BATCH = 500;
|
|
155
|
+
|
|
80
156
|
export class SqliteStore implements Store {
|
|
81
157
|
readonly bootstrapState: BootstrapStateStore;
|
|
82
158
|
readonly signalDedupe: SignalDedupeStore;
|
|
@@ -86,11 +162,18 @@ export class SqliteStore implements Store {
|
|
|
86
162
|
private db: DatabaseSync | undefined;
|
|
87
163
|
private readonly dbPath: string;
|
|
88
164
|
private readonly logger: StoreLogger;
|
|
165
|
+
private readonly listIndexes: ListIndexRegistry;
|
|
89
166
|
|
|
90
|
-
private constructor(
|
|
167
|
+
private constructor(
|
|
168
|
+
db: DatabaseSync,
|
|
169
|
+
dbPath: string,
|
|
170
|
+
logger: StoreLogger,
|
|
171
|
+
listIndexes: ListIndexRegistry,
|
|
172
|
+
) {
|
|
91
173
|
this.db = db;
|
|
92
174
|
this.dbPath = dbPath;
|
|
93
175
|
this.logger = logger;
|
|
176
|
+
this.listIndexes = listIndexes;
|
|
94
177
|
this.bootstrapState = new SqliteBootstrapStateStore(() => this.open());
|
|
95
178
|
this.signalDedupe = new SqliteSignalDedupeStore(() => this.open(), logger);
|
|
96
179
|
this.oauthGrants = new SqliteOAuthGrantStore(() => this.open());
|
|
@@ -101,12 +184,15 @@ export class SqliteStore implements Store {
|
|
|
101
184
|
|
|
102
185
|
/**
|
|
103
186
|
* Opens (or creates) the database at dbPath, applies the pragmas in Go's
|
|
104
|
-
* order,
|
|
187
|
+
* order, runs migrations — Go NewStore — and reconciles the list index
|
|
188
|
+
* (derives the facts of every unproven row of a declared kind).
|
|
105
189
|
*/
|
|
106
190
|
static open(
|
|
107
191
|
dbPath: string,
|
|
108
192
|
logger: StoreLogger = NOOP_STORE_LOGGER,
|
|
193
|
+
options: StoreOpenOptions = {},
|
|
109
194
|
): SqliteStore {
|
|
195
|
+
const listIndexes = new ListIndexRegistry(options.listIndexes ?? []);
|
|
110
196
|
const dir = path.dirname(dbPath);
|
|
111
197
|
if (dir !== "" && dir !== ".") {
|
|
112
198
|
mkdirSync(dir, { recursive: true });
|
|
@@ -129,7 +215,14 @@ export class SqliteStore implements Store {
|
|
|
129
215
|
throw error;
|
|
130
216
|
}
|
|
131
217
|
|
|
132
|
-
|
|
218
|
+
const store = new SqliteStore(db, dbPath, logger, listIndexes);
|
|
219
|
+
try {
|
|
220
|
+
store.reconcileListIndexes();
|
|
221
|
+
} catch (error) {
|
|
222
|
+
db.close();
|
|
223
|
+
throw error;
|
|
224
|
+
}
|
|
225
|
+
return store;
|
|
133
226
|
}
|
|
134
227
|
|
|
135
228
|
/** Filesystem path of the database file (Go Store.Path()). */
|
|
@@ -149,9 +242,28 @@ export class SqliteStore implements Store {
|
|
|
149
242
|
): Promise<void> {
|
|
150
243
|
const db = this.open();
|
|
151
244
|
const data = toBinary(schema, msg);
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
245
|
+
const kindName = apiResourceKindName(kind);
|
|
246
|
+
const facts = this.listIndexes.factsOf(kind, msg);
|
|
247
|
+
if (facts === undefined) {
|
|
248
|
+
db.prepare(UPSERT_RESOURCE_SQL).run(kindName, id, data, null, null, null);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
db.exec("BEGIN");
|
|
252
|
+
try {
|
|
253
|
+
db.prepare(UPSERT_RESOURCE_SQL).run(
|
|
254
|
+
kindName,
|
|
255
|
+
id,
|
|
256
|
+
data,
|
|
257
|
+
facts.org,
|
|
258
|
+
facts.createdAt,
|
|
259
|
+
facts.revision,
|
|
260
|
+
);
|
|
261
|
+
replaceListKeys(db, kindName, id, facts);
|
|
262
|
+
db.exec("COMMIT");
|
|
263
|
+
} catch (error) {
|
|
264
|
+
db.exec("ROLLBACK");
|
|
265
|
+
throw error;
|
|
266
|
+
}
|
|
155
267
|
}
|
|
156
268
|
|
|
157
269
|
async getResource<Desc extends DescMessage>(
|
|
@@ -187,19 +299,44 @@ export class SqliteStore implements Store {
|
|
|
187
299
|
db.exec("BEGIN IMMEDIATE");
|
|
188
300
|
try {
|
|
189
301
|
const row = db
|
|
190
|
-
.prepare(
|
|
191
|
-
|
|
302
|
+
.prepare(
|
|
303
|
+
`SELECT data, list_indexed_at IS updated_at AS stamped, list_index_revision
|
|
304
|
+
FROM resources WHERE kind = ? AND id = ?`,
|
|
305
|
+
)
|
|
306
|
+
.get(kindName, id) as
|
|
307
|
+
| {
|
|
308
|
+
data: Uint8Array;
|
|
309
|
+
stamped: number;
|
|
310
|
+
list_index_revision: number | null;
|
|
311
|
+
}
|
|
312
|
+
| undefined;
|
|
192
313
|
if (row === undefined) {
|
|
193
314
|
throw new ResourceNotFoundError(`${kindName}/${id}`);
|
|
194
315
|
}
|
|
195
316
|
|
|
196
317
|
const msg = fromBinary(schema, row.data);
|
|
318
|
+
const before = this.listIndexes.factsOf(kind, msg);
|
|
197
319
|
modify(msg);
|
|
320
|
+
const after = this.listIndexes.factsOf(kind, msg);
|
|
198
321
|
|
|
199
322
|
const data = toBinary(schema, msg);
|
|
200
|
-
db.prepare(
|
|
201
|
-
|
|
202
|
-
|
|
323
|
+
db.prepare(UPSERT_RESOURCE_SQL).run(
|
|
324
|
+
kindName,
|
|
325
|
+
id,
|
|
326
|
+
data,
|
|
327
|
+
after?.org ?? null,
|
|
328
|
+
after?.createdAt ?? null,
|
|
329
|
+
after?.revision ?? null,
|
|
330
|
+
);
|
|
331
|
+
if (
|
|
332
|
+
after !== undefined &&
|
|
333
|
+
(row.stamped !== 1 ||
|
|
334
|
+
row.list_index_revision !== after.revision ||
|
|
335
|
+
before === undefined ||
|
|
336
|
+
!sameListKeyRows(before, after))
|
|
337
|
+
) {
|
|
338
|
+
replaceListKeys(db, kindName, id, after);
|
|
339
|
+
}
|
|
203
340
|
|
|
204
341
|
db.exec("COMMIT");
|
|
205
342
|
return msg;
|
|
@@ -219,10 +356,74 @@ export class SqliteStore implements Store {
|
|
|
219
356
|
return rows.map((row) => row.data);
|
|
220
357
|
}
|
|
221
358
|
|
|
359
|
+
async queryResources<K extends string>(
|
|
360
|
+
declaration: ListIndexDeclaration<K>,
|
|
361
|
+
query: ListIndexQuery<K>,
|
|
362
|
+
): Promise<ListIndexRow[]> {
|
|
363
|
+
this.listIndexes.require(declaration);
|
|
364
|
+
assertListIndexLimit(query.limit);
|
|
365
|
+
if (query.anyKey !== undefined && query.anyKey.length === 0) {
|
|
366
|
+
return [];
|
|
367
|
+
}
|
|
368
|
+
const db = this.open();
|
|
369
|
+
const kindName = apiResourceKindName(declaration.kind);
|
|
370
|
+
|
|
371
|
+
// A key read is driven from the key table (CROSS JOIN fixes sqlite's
|
|
372
|
+
// join order), so only the rows the key names are ever read; several
|
|
373
|
+
// keys are one such read each, merged below (postgres/store.ts's twin).
|
|
374
|
+
const reads =
|
|
375
|
+
query.anyKey === undefined
|
|
376
|
+
? [provenReadSql(kindName, declaration.revision, query, undefined)]
|
|
377
|
+
: query.anyKey.map((key) =>
|
|
378
|
+
provenReadSql(kindName, declaration.revision, query, key),
|
|
379
|
+
);
|
|
380
|
+
const proven: ListIndexRow[] = [];
|
|
381
|
+
for (const read of reads) {
|
|
382
|
+
const rows = db.prepare(read.sql).all(...read.params) as Array<{
|
|
383
|
+
id: string;
|
|
384
|
+
data: Uint8Array;
|
|
385
|
+
created_at: string;
|
|
386
|
+
}>;
|
|
387
|
+
for (const row of rows) {
|
|
388
|
+
proven.push({
|
|
389
|
+
id: row.id,
|
|
390
|
+
data: row.data,
|
|
391
|
+
cursor: { createdAt: row.created_at, id: row.id },
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const unproven = oneRowPerId(
|
|
397
|
+
db
|
|
398
|
+
.prepare(UNPROVEN_ROWS_SQL)
|
|
399
|
+
.all(...unprovenRowsParams(kindName, declaration.revision)) as Array<{
|
|
400
|
+
id: string;
|
|
401
|
+
data: Uint8Array;
|
|
402
|
+
}>,
|
|
403
|
+
);
|
|
404
|
+
const unprovenRows: ListIndexRow[] = [];
|
|
405
|
+
for (const row of unproven) {
|
|
406
|
+
const facts = this.deriveAndRepair(declaration, row.id, row.data);
|
|
407
|
+
if (facts !== undefined && matchesListIndexQuery(row.id, facts, query)) {
|
|
408
|
+
unprovenRows.push({
|
|
409
|
+
id: row.id,
|
|
410
|
+
data: row.data,
|
|
411
|
+
cursor: { createdAt: facts.createdAt, id: row.id },
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
return mergeListIndexRows(proven, unprovenRows, query.limit);
|
|
416
|
+
}
|
|
417
|
+
|
|
222
418
|
async deleteResource(kind: ApiResourceKind, id: string): Promise<void> {
|
|
223
419
|
const db = this.open();
|
|
420
|
+
const kindName = apiResourceKindName(kind);
|
|
224
421
|
db.prepare(`DELETE FROM resources WHERE kind = ? AND id = ?`).run(
|
|
225
|
-
|
|
422
|
+
kindName,
|
|
423
|
+
id,
|
|
424
|
+
);
|
|
425
|
+
db.prepare(`DELETE FROM resource_list_keys WHERE kind = ? AND id = ?`).run(
|
|
426
|
+
kindName,
|
|
226
427
|
id,
|
|
227
428
|
);
|
|
228
429
|
}
|
|
@@ -315,20 +516,59 @@ export class SqliteStore implements Store {
|
|
|
315
516
|
const db = this.open();
|
|
316
517
|
// BLOB equality in the WHERE clause makes the compare-and-swap one
|
|
317
518
|
// atomic statement: zero rows changed means the row moved on (or was
|
|
318
|
-
// deleted) since the read — a lost swap, never an upsert.
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
519
|
+
// deleted) since the read — a lost swap, never an upsert. The new bytes'
|
|
520
|
+
// list facts ride the same statement; bytes a declared kind's schema
|
|
521
|
+
// cannot decode leave the row unproven (stamp NULL), for a read to skip.
|
|
522
|
+
const kindName = apiResourceKindName(kind);
|
|
523
|
+
const declaration = this.listIndexes.declarationOf(kind);
|
|
524
|
+
const facts =
|
|
525
|
+
declaration === undefined
|
|
526
|
+
? undefined
|
|
527
|
+
: factsOfBytes(declaration, newData);
|
|
528
|
+
const swap = (): boolean =>
|
|
529
|
+
Number(
|
|
530
|
+
db
|
|
531
|
+
.prepare(
|
|
532
|
+
`UPDATE resources SET data = ?, updated_at = ${NOW},
|
|
533
|
+
list_org = ?, list_created_at = ?, list_index_revision = ?,
|
|
534
|
+
list_indexed_at = CASE WHEN ? THEN ${NOW} ELSE NULL END
|
|
535
|
+
WHERE kind = ? AND id = ? AND data = ?`,
|
|
536
|
+
)
|
|
537
|
+
.run(
|
|
538
|
+
newData,
|
|
539
|
+
facts?.org ?? null,
|
|
540
|
+
facts?.createdAt ?? null,
|
|
541
|
+
facts?.revision ?? null,
|
|
542
|
+
declaration === undefined || facts !== undefined ? 1 : 0,
|
|
543
|
+
kindName,
|
|
544
|
+
id,
|
|
545
|
+
expectedData,
|
|
546
|
+
).changes,
|
|
547
|
+
) === 1;
|
|
548
|
+
if (facts === undefined) {
|
|
549
|
+
return swap();
|
|
550
|
+
}
|
|
551
|
+
db.exec("BEGIN");
|
|
552
|
+
try {
|
|
553
|
+
const swapped = swap();
|
|
554
|
+
if (swapped) {
|
|
555
|
+
replaceListKeys(db, kindName, id, facts);
|
|
556
|
+
}
|
|
557
|
+
db.exec("COMMIT");
|
|
558
|
+
return swapped;
|
|
559
|
+
} catch (error) {
|
|
560
|
+
db.exec("ROLLBACK");
|
|
561
|
+
throw error;
|
|
562
|
+
}
|
|
325
563
|
}
|
|
326
564
|
|
|
327
565
|
async deleteResourcesByKind(kind: ApiResourceKind): Promise<number> {
|
|
328
566
|
const db = this.open();
|
|
567
|
+
const kindName = apiResourceKindName(kind);
|
|
329
568
|
const result = db
|
|
330
569
|
.prepare(`DELETE FROM resources WHERE kind = ?`)
|
|
331
|
-
.run(
|
|
570
|
+
.run(kindName);
|
|
571
|
+
db.prepare(`DELETE FROM resource_list_keys WHERE kind = ?`).run(kindName);
|
|
332
572
|
return Number(result.changes);
|
|
333
573
|
}
|
|
334
574
|
|
|
@@ -337,13 +577,123 @@ export class SqliteStore implements Store {
|
|
|
337
577
|
idPrefix: string,
|
|
338
578
|
): Promise<number> {
|
|
339
579
|
const db = this.open();
|
|
580
|
+
const kindName = apiResourceKindName(kind);
|
|
340
581
|
// GLOB 'prefix*' uses the (kind, id) index where LIKE would not (Go).
|
|
341
582
|
const result = db
|
|
342
583
|
.prepare(`DELETE FROM resources WHERE kind = ? AND id GLOB ?`)
|
|
343
|
-
.run(
|
|
584
|
+
.run(kindName, `${idPrefix}*`);
|
|
585
|
+
db.prepare(
|
|
586
|
+
`DELETE FROM resource_list_keys WHERE kind = ? AND id GLOB ?`,
|
|
587
|
+
).run(kindName, `${idPrefix}*`);
|
|
344
588
|
return Number(result.changes);
|
|
345
589
|
}
|
|
346
590
|
|
|
591
|
+
// ---------------------------------------------------------------------------
|
|
592
|
+
// The list index's repair (list-index.ts; postgres/store.ts's twin)
|
|
593
|
+
// ---------------------------------------------------------------------------
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* An unproven row's facts, derived from its bytes, and the row repaired
|
|
597
|
+
* when those bytes are still the stored ones. Undefined when the bytes
|
|
598
|
+
* do not decode; the row stays unproven and is skipped.
|
|
599
|
+
*/
|
|
600
|
+
private deriveAndRepair(
|
|
601
|
+
declaration: ListIndexDeclaration,
|
|
602
|
+
id: string,
|
|
603
|
+
data: Uint8Array,
|
|
604
|
+
): ListIndexFacts | undefined {
|
|
605
|
+
const kindName = apiResourceKindName(declaration.kind);
|
|
606
|
+
const facts = factsOfBytes(declaration, data);
|
|
607
|
+
if (facts === undefined) {
|
|
608
|
+
this.logger.warn(
|
|
609
|
+
"list index: an unproven row does not decode, skipping",
|
|
610
|
+
{
|
|
611
|
+
kind: kindName,
|
|
612
|
+
id,
|
|
613
|
+
},
|
|
614
|
+
);
|
|
615
|
+
return undefined;
|
|
616
|
+
}
|
|
617
|
+
const db = this.open();
|
|
618
|
+
db.exec("BEGIN");
|
|
619
|
+
try {
|
|
620
|
+
const repaired = db
|
|
621
|
+
.prepare(
|
|
622
|
+
`UPDATE resources SET list_org = ?, list_created_at = ?,
|
|
623
|
+
list_index_revision = ?, list_indexed_at = updated_at
|
|
624
|
+
WHERE kind = ? AND id = ? AND data = ?`,
|
|
625
|
+
)
|
|
626
|
+
.run(facts.org, facts.createdAt, facts.revision, kindName, id, data);
|
|
627
|
+
if (Number(repaired.changes) === 1) {
|
|
628
|
+
replaceListKeys(db, kindName, id, facts);
|
|
629
|
+
}
|
|
630
|
+
db.exec("COMMIT");
|
|
631
|
+
} catch (error) {
|
|
632
|
+
db.exec("ROLLBACK");
|
|
633
|
+
this.logger.warn("list index: repairing a row failed", {
|
|
634
|
+
kind: kindName,
|
|
635
|
+
id,
|
|
636
|
+
error: error instanceof Error ? error.message : String(error),
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
return facts;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* Derives every unproven row of each declared kind and stamps every other
|
|
644
|
+
* kind's unstamped rows, so the unproven set is empty once open returns.
|
|
645
|
+
*/
|
|
646
|
+
private reconcileListIndexes(): void {
|
|
647
|
+
const db = this.open();
|
|
648
|
+
const declarations = this.listIndexes.declarations();
|
|
649
|
+
for (const declaration of declarations) {
|
|
650
|
+
const kindName = apiResourceKindName(declaration.kind);
|
|
651
|
+
let after = "";
|
|
652
|
+
let derived = 0;
|
|
653
|
+
for (;;) {
|
|
654
|
+
// Duplicates from overlapping arms sit side by side in id order and
|
|
655
|
+
// the next batch starts after the last id, so none is repaired twice
|
|
656
|
+
// across batches and the count still ends the loop.
|
|
657
|
+
const rows = db
|
|
658
|
+
.prepare(
|
|
659
|
+
`SELECT id, data FROM (${UNPROVEN_ROWS_SQL}) WHERE id > ?
|
|
660
|
+
ORDER BY id LIMIT ?`,
|
|
661
|
+
)
|
|
662
|
+
.all(
|
|
663
|
+
...unprovenRowsParams(kindName, declaration.revision),
|
|
664
|
+
after,
|
|
665
|
+
RECONCILE_BATCH,
|
|
666
|
+
) as Array<{
|
|
667
|
+
id: string;
|
|
668
|
+
data: Uint8Array;
|
|
669
|
+
}>;
|
|
670
|
+
for (const row of oneRowPerId(rows)) {
|
|
671
|
+
if (
|
|
672
|
+
this.deriveAndRepair(declaration, row.id, row.data) !== undefined
|
|
673
|
+
) {
|
|
674
|
+
derived += 1;
|
|
675
|
+
}
|
|
676
|
+
after = row.id;
|
|
677
|
+
}
|
|
678
|
+
if (rows.length < RECONCILE_BATCH) {
|
|
679
|
+
break;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
if (derived > 0) {
|
|
683
|
+
this.logger.info("list index reconciled", {
|
|
684
|
+
kind: kindName,
|
|
685
|
+
rows: derived,
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
const declared = declarations.map((d) => apiResourceKindName(d.kind));
|
|
690
|
+
db.prepare(
|
|
691
|
+
`UPDATE resources SET list_indexed_at = updated_at
|
|
692
|
+
WHERE list_indexed_at IS NOT updated_at
|
|
693
|
+
AND kind NOT IN (${declared.map(() => "?").join(", ") || "''"})`,
|
|
694
|
+
).run(...declared);
|
|
695
|
+
}
|
|
696
|
+
|
|
347
697
|
// ---------------------------------------------------------------------------
|
|
348
698
|
// Audit operations
|
|
349
699
|
// ---------------------------------------------------------------------------
|
|
@@ -1145,6 +1495,97 @@ class SqliteSignalDedupeStore implements SignalDedupeStore {
|
|
|
1145
1495
|
}
|
|
1146
1496
|
|
|
1147
1497
|
/** Composite dedupe key: "{org}:{idempotency_key}" (Go buildDedupeKey). */
|
|
1498
|
+
/**
|
|
1499
|
+
* A row's key rows made equal to its facts, on the caller's open
|
|
1500
|
+
* transaction (postgres/store.ts's twin).
|
|
1501
|
+
*/
|
|
1502
|
+
function replaceListKeys(
|
|
1503
|
+
db: DatabaseSync,
|
|
1504
|
+
kindName: string,
|
|
1505
|
+
id: string,
|
|
1506
|
+
facts: ListIndexFacts,
|
|
1507
|
+
): void {
|
|
1508
|
+
db.prepare(`DELETE FROM resource_list_keys WHERE kind = ? AND id = ?`).run(
|
|
1509
|
+
kindName,
|
|
1510
|
+
id,
|
|
1511
|
+
);
|
|
1512
|
+
const insert = db.prepare(
|
|
1513
|
+
`INSERT INTO resource_list_keys (kind, id, key, value, created_at) VALUES (?, ?, ?, ?, ?)`,
|
|
1514
|
+
);
|
|
1515
|
+
for (const entry of facts.keys) {
|
|
1516
|
+
insert.run(kindName, id, entry.key, entry.value, facts.createdAt);
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
/**
|
|
1521
|
+
* One proven read of `queryResources` (postgres/store.ts's twin): through
|
|
1522
|
+
* one key's index range when `key` is given, else through the organization
|
|
1523
|
+
* index; only rows whose facts are proven current.
|
|
1524
|
+
*/
|
|
1525
|
+
function provenReadSql(
|
|
1526
|
+
kindName: string,
|
|
1527
|
+
revision: number,
|
|
1528
|
+
query: ListIndexQuery,
|
|
1529
|
+
key: { readonly name: string; readonly value: string } | undefined,
|
|
1530
|
+
): { sql: string; params: Array<string | number> } {
|
|
1531
|
+
const params: Array<string | number> = [];
|
|
1532
|
+
const created = key === undefined ? "r.list_created_at" : "k.created_at";
|
|
1533
|
+
const id = key === undefined ? "r.id" : "k.id";
|
|
1534
|
+
const where: string[] = [];
|
|
1535
|
+
let from = `resources AS r`;
|
|
1536
|
+
if (key === undefined) {
|
|
1537
|
+
where.push(`r.kind = ?`);
|
|
1538
|
+
params.push(kindName);
|
|
1539
|
+
} else {
|
|
1540
|
+
from = `resource_list_keys AS k CROSS JOIN resources AS r`;
|
|
1541
|
+
where.push(
|
|
1542
|
+
`k.kind = ?`,
|
|
1543
|
+
`k.key = ?`,
|
|
1544
|
+
`k.value = ?`,
|
|
1545
|
+
`r.kind = k.kind`,
|
|
1546
|
+
`r.id = k.id`,
|
|
1547
|
+
);
|
|
1548
|
+
params.push(kindName, key.name, key.value);
|
|
1549
|
+
}
|
|
1550
|
+
where.push(`r.list_indexed_at = r.updated_at`, `r.list_index_revision = ?`);
|
|
1551
|
+
params.push(revision);
|
|
1552
|
+
if (query.org !== undefined && query.org !== "") {
|
|
1553
|
+
where.push(`r.list_org = ?`);
|
|
1554
|
+
params.push(query.org);
|
|
1555
|
+
}
|
|
1556
|
+
if (query.createdAtOrAfter !== undefined) {
|
|
1557
|
+
where.push(`(${created} >= ? OR ${created} = '')`);
|
|
1558
|
+
params.push(query.createdAtOrAfter);
|
|
1559
|
+
}
|
|
1560
|
+
if (query.after !== undefined) {
|
|
1561
|
+
where.push(`(${created} < ? OR (${created} = ? AND ${id} < ?))`);
|
|
1562
|
+
params.push(query.after.createdAt, query.after.createdAt, query.after.id);
|
|
1563
|
+
}
|
|
1564
|
+
let limit = "";
|
|
1565
|
+
if (query.limit !== undefined) {
|
|
1566
|
+
limit = ` LIMIT ?`;
|
|
1567
|
+
params.push(query.limit);
|
|
1568
|
+
}
|
|
1569
|
+
return {
|
|
1570
|
+
sql: `SELECT r.id, r.data, ${created} AS created_at FROM ${from}
|
|
1571
|
+
WHERE ${where.join(" AND ")}
|
|
1572
|
+
ORDER BY ${created} DESC, ${id} DESC${limit}`,
|
|
1573
|
+
params,
|
|
1574
|
+
};
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
/** A declared kind's facts from stored bytes; undefined when they do not decode. */
|
|
1578
|
+
function factsOfBytes(
|
|
1579
|
+
declaration: ListIndexDeclaration,
|
|
1580
|
+
data: Uint8Array,
|
|
1581
|
+
): ListIndexFacts | undefined {
|
|
1582
|
+
try {
|
|
1583
|
+
return listIndexFactsOf(declaration, fromBinary(declaration.schema, data));
|
|
1584
|
+
} catch {
|
|
1585
|
+
return undefined;
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1148
1589
|
function buildDedupeKey(org: string, idempotencyKey: string): string {
|
|
1149
1590
|
return `${org}:${idempotencyKey}`;
|
|
1150
1591
|
}
|