@stigmer/server 3.25.0 → 3.27.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/evaluator.d.ts +4 -1
- package/dist/authorization/evaluator.d.ts.map +1 -1
- package/dist/authorization/evaluator.js +8 -0
- package/dist/authorization/evaluator.js.map +1 -1
- package/dist/authorization/model/agent.d.ts.map +1 -1
- package/dist/authorization/model/agent.js +4 -2
- package/dist/authorization/model/agent.js.map +1 -1
- package/dist/authorization/model/agent_channel.d.ts.map +1 -1
- package/dist/authorization/model/agent_channel.js +5 -4
- package/dist/authorization/model/agent_channel.js.map +1 -1
- package/dist/authorization/model/agent_instance.d.ts.map +1 -1
- package/dist/authorization/model/agent_instance.js +3 -2
- package/dist/authorization/model/agent_instance.js.map +1 -1
- package/dist/authorization/model/mcp_server.d.ts.map +1 -1
- package/dist/authorization/model/mcp_server.js +3 -2
- package/dist/authorization/model/mcp_server.js.map +1 -1
- package/dist/authorization/model/organization.d.ts.map +1 -1
- package/dist/authorization/model/organization.js +3 -0
- package/dist/authorization/model/organization.js.map +1 -1
- package/dist/authorization/model/plugin.d.ts.map +1 -1
- package/dist/authorization/model/plugin.js +4 -2
- package/dist/authorization/model/plugin.js.map +1 -1
- package/dist/authorization/model/rewrite.d.ts +25 -7
- package/dist/authorization/model/rewrite.d.ts.map +1 -1
- package/dist/authorization/model/rewrite.js +40 -1
- package/dist/authorization/model/rewrite.js.map +1 -1
- package/dist/authorization/model/schedule.d.ts.map +1 -1
- package/dist/authorization/model/schedule.js +8 -3
- package/dist/authorization/model/schedule.js.map +1 -1
- package/dist/authorization/model/skill.d.ts.map +1 -1
- package/dist/authorization/model/skill.js +3 -2
- package/dist/authorization/model/skill.js.map +1 -1
- package/dist/authorization/model/workflow.d.ts.map +1 -1
- package/dist/authorization/model/workflow.js +3 -2
- package/dist/authorization/model/workflow.js.map +1 -1
- package/dist/authorization/model/workflow_execution.d.ts.map +1 -1
- package/dist/authorization/model/workflow_execution.js +4 -3
- package/dist/authorization/model/workflow_execution.js.map +1 -1
- package/dist/authorization/model/workflow_instance.d.ts.map +1 -1
- package/dist/authorization/model/workflow_instance.js +5 -1
- package/dist/authorization/model/workflow_instance.js.map +1 -1
- package/dist/boot/compose.d.ts.map +1 -1
- package/dist/boot/compose.js +13 -8
- 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/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/access-lists.d.ts +5 -3
- package/dist/domain/iampolicy/access-lists.d.ts.map +1 -1
- package/dist/domain/iampolicy/access-lists.js +72 -19
- package/dist/domain/iampolicy/access-lists.js.map +1 -1
- package/dist/domain/iampolicy/constants.d.ts +32 -6
- package/dist/domain/iampolicy/constants.d.ts.map +1 -1
- package/dist/domain/iampolicy/constants.js +41 -6
- package/dist/domain/iampolicy/constants.js.map +1 -1
- package/dist/domain/iampolicy/controller.d.ts +6 -0
- package/dist/domain/iampolicy/controller.d.ts.map +1 -1
- package/dist/domain/iampolicy/controller.js +16 -7
- package/dist/domain/iampolicy/controller.js.map +1 -1
- package/dist/domain/iampolicy/resource-store.d.ts.map +1 -1
- package/dist/domain/iampolicy/resource-store.js +2 -6
- package/dist/domain/iampolicy/resource-store.js.map +1 -1
- package/dist/domain/iampolicy/steps.d.ts.map +1 -1
- package/dist/domain/iampolicy/steps.js +49 -7
- package/dist/domain/iampolicy/steps.js.map +1 -1
- 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/workflow/registry/data/task-kind-registry.json +1 -0
- 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/extensions/drivers.d.ts +12 -0
- package/dist/extensions/drivers.d.ts.map +1 -1
- package/dist/extensions/gate-slots.d.ts +10 -1
- package/dist/extensions/gate-slots.d.ts.map +1 -1
- package/dist/extensions/gate-slots.js +1 -0
- package/dist/extensions/gate-slots.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/principal-display.d.ts +34 -0
- package/dist/extensions/principal-display.d.ts.map +1 -0
- package/dist/extensions/principal-display.js +2 -0
- package/dist/extensions/principal-display.js.map +1 -0
- package/dist/extensions/registry.d.ts +6 -0
- package/dist/extensions/registry.d.ts.map +1 -1
- package/dist/extensions/registry.js +10 -0
- package/dist/extensions/registry.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/pipeline/apiresource-meta.d.ts +10 -0
- package/dist/pipeline/apiresource-meta.d.ts.map +1 -1
- package/dist/pipeline/apiresource-meta.js +16 -0
- package/dist/pipeline/apiresource-meta.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/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/__tests__/drivers.ts +6 -1
- package/src/authorization/__tests__/evaluator.test.ts +127 -2
- package/src/authorization/evaluator.ts +12 -1
- package/src/authorization/model/__tests__/registry.test.ts +64 -2
- package/src/authorization/model/agent.ts +4 -1
- package/src/authorization/model/agent_channel.ts +11 -3
- package/src/authorization/model/agent_instance.ts +3 -1
- package/src/authorization/model/mcp_server.ts +3 -1
- package/src/authorization/model/organization.ts +3 -0
- package/src/authorization/model/plugin.ts +4 -1
- package/src/authorization/model/rewrite.ts +65 -8
- package/src/authorization/model/schedule.ts +11 -2
- package/src/authorization/model/skill.ts +3 -1
- package/src/authorization/model/workflow.ts +3 -1
- package/src/authorization/model/workflow_execution.ts +4 -2
- package/src/authorization/model/workflow_instance.ts +5 -0
- package/src/boot/__tests__/list-indexes.test.ts +65 -0
- package/src/boot/compose.ts +25 -13
- package/src/boot/list-indexes.ts +25 -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__/access-lists.test.ts +120 -6
- package/src/domain/iampolicy/__tests__/constants.test.ts +26 -4
- package/src/domain/iampolicy/__tests__/controller.test.ts +55 -1
- package/src/domain/iampolicy/__tests__/grant-path.test.ts +3 -3
- package/src/domain/iampolicy/__tests__/steps.test.ts +123 -1
- package/src/domain/iampolicy/access-lists.ts +98 -23
- package/src/domain/iampolicy/constants.ts +50 -6
- package/src/domain/iampolicy/controller.ts +30 -7
- package/src/domain/iampolicy/resource-store.ts +3 -6
- package/src/domain/iampolicy/steps.ts +89 -7
- package/src/domain/session/list-index.ts +28 -0
- package/src/domain/session/steps.ts +108 -123
- package/src/domain/workflow/registry/data/task-kind-registry.json +1 -0
- 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/extensions/__tests__/iam-policy-points.test.ts +30 -0
- package/src/extensions/__tests__/registry.test.ts +3 -0
- package/src/extensions/drivers.ts +12 -0
- package/src/extensions/gate-slots.ts +10 -0
- package/src/extensions/list-read-scope.ts +9 -12
- package/src/extensions/principal-display.ts +37 -0
- package/src/extensions/registry.ts +19 -0
- package/src/index.ts +19 -1
- package/src/pipeline/apiresource-meta.ts +21 -0
- package/src/pipeline/steps/__tests__/list-page.test.ts +186 -0
- package/src/pipeline/steps/list-page.ts +284 -0
- package/src/query/activity/handler.ts +57 -78
- 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
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Versioned schema migrations — ports the inline chain in
|
|
3
3
|
* backend/libs/go/store/sqlite/store.go (v1–v6, DDL character-faithful),
|
|
4
|
-
* adds v7, the OD-3 consolidation (D2 §3),
|
|
5
|
-
* oauth_grant index (the channel teardown's query pattern, C3 Stage 6)
|
|
4
|
+
* adds v7, the OD-3 consolidation (D2 §3), v8, the by-resource
|
|
5
|
+
* oauth_grant index (the channel teardown's query pattern, C3 Stage 6),
|
|
6
|
+
* and the later steps each named at its constant below.
|
|
6
7
|
*
|
|
7
8
|
* Schema continuity across cutover is the design point: a database the Go
|
|
8
9
|
* server created at any version migrates forward through the SAME steps Go
|
|
@@ -45,10 +46,15 @@ export declare const SCHEMA_VERSION_8 = 8;
|
|
|
45
46
|
export declare const SCHEMA_VERSION_9 = 9;
|
|
46
47
|
/** v10: every row holding the retired public visibility level moved to org. */
|
|
47
48
|
export declare const SCHEMA_VERSION_10 = 10;
|
|
49
|
+
/** v11: the list index's columns, key table and indexes (DDL only). */
|
|
50
|
+
export declare const SCHEMA_VERSION_11 = 11;
|
|
48
51
|
/** Target version for new databases. */
|
|
49
|
-
export declare const CURRENT_SCHEMA_VERSION =
|
|
50
|
-
/**
|
|
51
|
-
|
|
52
|
+
export declare const CURRENT_SCHEMA_VERSION = 11;
|
|
53
|
+
/**
|
|
54
|
+
* Applies every pending migration up to `targetVersion` in order — all of
|
|
55
|
+
* them unless a test builds the database a given step starts from.
|
|
56
|
+
*/
|
|
57
|
+
export declare function runMigrations(db: DatabaseSync, targetVersion?: number): void;
|
|
52
58
|
/** Current schema version; 0 when none has been recorded yet. */
|
|
53
59
|
export declare function getSchemaVersion(db: DatabaseSync): number;
|
|
54
60
|
//# sourceMappingURL=migrations.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../../src/store/sqlite/migrations.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../../src/store/sqlite/migrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAOhD,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAClC,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAClC,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAClC,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAClC,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAClC,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAClC,gFAAgF;AAChF,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAClC,2EAA2E;AAC3E,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAClC,wDAAwD;AACxD,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAClC,+EAA+E;AAC/E,eAAO,MAAM,iBAAiB,KAAK,CAAC;AACpC,uEAAuE;AACvE,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAEpC,wCAAwC;AACxC,eAAO,MAAM,sBAAsB,KAAoB,CAAC;AAExD;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,EAAE,EAAE,YAAY,EAChB,aAAa,GAAE,MAA+B,GAC7C,IAAI,CA6BN;AAED,iEAAiE;AACjE,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,YAAY,GAAG,MAAM,CAKzD"}
|
|
@@ -13,10 +13,15 @@ export const SCHEMA_VERSION_8 = 8;
|
|
|
13
13
|
export const SCHEMA_VERSION_9 = 9;
|
|
14
14
|
/** v10: every row holding the retired public visibility level moved to org. */
|
|
15
15
|
export const SCHEMA_VERSION_10 = 10;
|
|
16
|
+
/** v11: the list index's columns, key table and indexes (DDL only). */
|
|
17
|
+
export const SCHEMA_VERSION_11 = 11;
|
|
16
18
|
/** Target version for new databases. */
|
|
17
|
-
export const CURRENT_SCHEMA_VERSION =
|
|
18
|
-
/**
|
|
19
|
-
|
|
19
|
+
export const CURRENT_SCHEMA_VERSION = SCHEMA_VERSION_11;
|
|
20
|
+
/**
|
|
21
|
+
* Applies every pending migration up to `targetVersion` in order — all of
|
|
22
|
+
* them unless a test builds the database a given step starts from.
|
|
23
|
+
*/
|
|
24
|
+
export function runMigrations(db, targetVersion = CURRENT_SCHEMA_VERSION) {
|
|
20
25
|
db.exec(`
|
|
21
26
|
CREATE TABLE IF NOT EXISTS schema_version (
|
|
22
27
|
version INTEGER PRIMARY KEY,
|
|
@@ -35,9 +40,10 @@ export function runMigrations(db) {
|
|
|
35
40
|
[SCHEMA_VERSION_8, migrateToV8],
|
|
36
41
|
[SCHEMA_VERSION_9, migrateToV9],
|
|
37
42
|
[SCHEMA_VERSION_10, migrateToV10],
|
|
43
|
+
[SCHEMA_VERSION_11, migrateToV11],
|
|
38
44
|
];
|
|
39
45
|
for (const [version, migrate] of chain) {
|
|
40
|
-
if (currentVersion < version) {
|
|
46
|
+
if (currentVersion < version && version <= targetVersion) {
|
|
41
47
|
applyInTransaction(db, version, migrate);
|
|
42
48
|
}
|
|
43
49
|
}
|
|
@@ -344,4 +350,44 @@ function migrateToV10(db) {
|
|
|
344
350
|
}
|
|
345
351
|
}
|
|
346
352
|
}
|
|
353
|
+
/**
|
|
354
|
+
* v11: the list index (../list-index.ts) — schema only, the Postgres
|
|
355
|
+
* driver's v6 in this engine's terms (postgres/migrations.ts gives the
|
|
356
|
+
* reasons for every column and index). Existing rows, a Go-era database's
|
|
357
|
+
* included, arrive unproven and are derived by the store's reconciliation
|
|
358
|
+
* at open. sqlite compares text as bytes (BINARY) by default, the order
|
|
359
|
+
* list-index.ts merges in, so no collation is spelled here. The store now
|
|
360
|
+
* writes `updated_at` to the millisecond and the stamp from the same
|
|
361
|
+
* expression in the same statement; a writer that does not know the index
|
|
362
|
+
* writes `datetime('now')`, whole seconds, which can never equal a stamp.
|
|
363
|
+
*/
|
|
364
|
+
function migrateToV11(db) {
|
|
365
|
+
db.exec(`
|
|
366
|
+
ALTER TABLE resources ADD COLUMN list_org TEXT;
|
|
367
|
+
ALTER TABLE resources ADD COLUMN list_created_at TEXT;
|
|
368
|
+
ALTER TABLE resources ADD COLUMN list_index_revision INTEGER;
|
|
369
|
+
ALTER TABLE resources ADD COLUMN list_indexed_at TEXT;
|
|
370
|
+
|
|
371
|
+
CREATE INDEX idx_resources_list_org
|
|
372
|
+
ON resources (kind, list_org, list_created_at, id);
|
|
373
|
+
CREATE INDEX idx_resources_list_created
|
|
374
|
+
ON resources (kind, list_created_at, id);
|
|
375
|
+
CREATE INDEX idx_resources_list_revision
|
|
376
|
+
ON resources (kind, list_index_revision);
|
|
377
|
+
CREATE INDEX idx_resources_list_unproven
|
|
378
|
+
ON resources (kind, id) WHERE list_indexed_at IS NOT updated_at;
|
|
379
|
+
|
|
380
|
+
CREATE TABLE resource_list_keys (
|
|
381
|
+
kind TEXT NOT NULL,
|
|
382
|
+
id TEXT NOT NULL,
|
|
383
|
+
key TEXT NOT NULL,
|
|
384
|
+
value TEXT NOT NULL,
|
|
385
|
+
created_at TEXT NOT NULL,
|
|
386
|
+
PRIMARY KEY (kind, id, key)
|
|
387
|
+
) WITHOUT ROWID;
|
|
388
|
+
|
|
389
|
+
CREATE INDEX idx_resource_list_keys_lookup
|
|
390
|
+
ON resource_list_keys (kind, key, value, created_at, id);
|
|
391
|
+
`);
|
|
392
|
+
}
|
|
347
393
|
//# sourceMappingURL=migrations.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrations.js","sourceRoot":"","sources":["../../../src/store/sqlite/migrations.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"migrations.js","sourceRoot":"","sources":["../../../src/store/sqlite/migrations.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { DescMessage, MessageShape } from "@bufbuild/protobuf";
|
|
2
2
|
import type { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
3
|
-
import type { AuditRecord, BootstrapStateStore, OAuthGrantStore, PendingOAuthStateStore, RawResourceDocument, ScheduleRunRecord, SearchIndexEntry, SearchIndexQuery, SearchIndexQueryResult, SignalDedupeStore, Store, WorkflowExecutionEventRecord } from "../interface.js";
|
|
3
|
+
import type { AuditRecord, BootstrapStateStore, OAuthGrantStore, PendingOAuthStateStore, RawResourceDocument, ScheduleRunRecord, SearchIndexEntry, SearchIndexQuery, SearchIndexQueryResult, SignalDedupeStore, Store, StoreOpenOptions, WorkflowExecutionEventRecord } from "../interface.js";
|
|
4
|
+
import type { ListIndexDeclaration, ListIndexQuery, ListIndexRow } from "../list-index.js";
|
|
4
5
|
import type { StoreLogger } from "../logger.js";
|
|
5
6
|
export type { StoreLogger } from "../logger.js";
|
|
6
7
|
export declare class SqliteStore implements Store {
|
|
@@ -11,18 +12,21 @@ export declare class SqliteStore implements Store {
|
|
|
11
12
|
private db;
|
|
12
13
|
private readonly dbPath;
|
|
13
14
|
private readonly logger;
|
|
15
|
+
private readonly listIndexes;
|
|
14
16
|
private constructor();
|
|
15
17
|
/**
|
|
16
18
|
* Opens (or creates) the database at dbPath, applies the pragmas in Go's
|
|
17
|
-
* order,
|
|
19
|
+
* order, runs migrations — Go NewStore — and reconciles the list index
|
|
20
|
+
* (derives the facts of every unproven row of a declared kind).
|
|
18
21
|
*/
|
|
19
|
-
static open(dbPath: string, logger?: StoreLogger): SqliteStore;
|
|
22
|
+
static open(dbPath: string, logger?: StoreLogger, options?: StoreOpenOptions): SqliteStore;
|
|
20
23
|
/** Filesystem path of the database file (Go Store.Path()). */
|
|
21
24
|
path(): string;
|
|
22
25
|
saveResource<Desc extends DescMessage>(kind: ApiResourceKind, id: string, schema: Desc, msg: MessageShape<Desc>): Promise<void>;
|
|
23
26
|
getResource<Desc extends DescMessage>(kind: ApiResourceKind, id: string, schema: Desc): Promise<MessageShape<Desc>>;
|
|
24
27
|
updateResource<Desc extends DescMessage>(kind: ApiResourceKind, id: string, schema: Desc, modify: (msg: MessageShape<Desc>) => void): Promise<MessageShape<Desc>>;
|
|
25
28
|
listResources(kind: ApiResourceKind): Promise<Uint8Array[]>;
|
|
29
|
+
queryResources<K extends string>(declaration: ListIndexDeclaration<K>, query: ListIndexQuery<K>): Promise<ListIndexRow[]>;
|
|
26
30
|
deleteResource(kind: ApiResourceKind, id: string): Promise<void>;
|
|
27
31
|
findByField<Desc extends DescMessage>(kind: ApiResourceKind, fieldPath: string, value: string, schema: Desc): Promise<MessageShape<Desc>>;
|
|
28
32
|
findAllByField(kind: ApiResourceKind, fieldPath: string, value: string): Promise<Uint8Array[]>;
|
|
@@ -31,6 +35,17 @@ export declare class SqliteStore implements Store {
|
|
|
31
35
|
replaceResourceDataIfUnchanged(kind: ApiResourceKind, id: string, expectedData: Uint8Array, newData: Uint8Array): Promise<boolean>;
|
|
32
36
|
deleteResourcesByKind(kind: ApiResourceKind): Promise<number>;
|
|
33
37
|
deleteResourcesByIdPrefix(kind: ApiResourceKind, idPrefix: string): Promise<number>;
|
|
38
|
+
/**
|
|
39
|
+
* An unproven row's facts, derived from its bytes, and the row repaired
|
|
40
|
+
* when those bytes are still the stored ones. Undefined when the bytes
|
|
41
|
+
* do not decode; the row stays unproven and is skipped.
|
|
42
|
+
*/
|
|
43
|
+
private deriveAndRepair;
|
|
44
|
+
/**
|
|
45
|
+
* Derives every unproven row of each declared kind and stamps every other
|
|
46
|
+
* kind's unstamped rows, so the unproven set is empty once open returns.
|
|
47
|
+
*/
|
|
48
|
+
private reconcileListIndexes;
|
|
34
49
|
saveAudit<Desc extends DescMessage>(kind: ApiResourceKind, resourceId: string, schema: Desc, msg: MessageShape<Desc>, versionHash: string, tag: string): Promise<void>;
|
|
35
50
|
getAuditByHash<Desc extends DescMessage>(kind: ApiResourceKind, resourceId: string, versionHash: string, schema: Desc): Promise<MessageShape<Desc>>;
|
|
36
51
|
getAuditByTag<Desc extends DescMessage>(kind: ApiResourceKind, resourceId: string, tag: string, schema: Desc): Promise<MessageShape<Desc>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/store/sqlite/store.ts"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qFAAqF,CAAC;AAQ3H,OAAO,KAAK,EACV,WAAW,EACX,mBAAmB,EAGnB,eAAe,EAEf,sBAAsB,EACtB,mBAAmB,EACnB,iBAAiB,EACjB,gBAAgB,EAEhB,gBAAgB,EAChB,sBAAsB,EAGtB,iBAAiB,EACjB,KAAK,EACL,4BAA4B,EAC7B,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/store/sqlite/store.ts"],"names":[],"mappings":"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"}
|
|
@@ -31,11 +31,60 @@ import path from "node:path";
|
|
|
31
31
|
import { DatabaseSync } from "node:sqlite";
|
|
32
32
|
import { fromBinary, toBinary } from "@bufbuild/protobuf";
|
|
33
33
|
import { AuditNotFoundError, DELIVERED_SIGNAL_DEDUPE_TTL_MS, PENDING_OAUTH_STATE_TTL_MS, ResourceNotFoundError, } from "../interface.js";
|
|
34
|
+
import { ListIndexRegistry, assertListIndexLimit, listIndexFactsOf, matchesListIndexQuery, mergeListIndexRows, sameListKeyRows, } from "../list-index.js";
|
|
34
35
|
import { NOOP_STORE_LOGGER } from "../logger.js";
|
|
35
36
|
import { apiResourceKindName, filterRowsByLabel, scanForFieldMatch, } from "../proto-fields.js";
|
|
36
37
|
import { rfc3339Seconds } from "../rfc3339.js";
|
|
37
38
|
import { normalizeBm25Score, renderFts5MatchExpression } from "./fts5.js";
|
|
38
39
|
import { runMigrations } from "./migrations.js";
|
|
40
|
+
/**
|
|
41
|
+
* `updated_at` and the list index's stamp, to the millisecond. Evaluated
|
|
42
|
+
* twice in one statement it yields one value (sqlite fixes 'now' per
|
|
43
|
+
* statement step), so a row this driver writes is stamped with exactly its
|
|
44
|
+
* `updated_at`; a writer that does not know the index writes
|
|
45
|
+
* `datetime('now')`, whole seconds, which never equals a stamp.
|
|
46
|
+
*/
|
|
47
|
+
const NOW = `strftime('%Y-%m-%d %H:%M:%f', 'now')`;
|
|
48
|
+
/** The one upsert every resource write goes through (postgres/store.ts's twin). */
|
|
49
|
+
const UPSERT_RESOURCE_SQL = `INSERT OR REPLACE INTO resources
|
|
50
|
+
(kind, id, data, updated_at, list_org, list_created_at, list_index_revision, list_indexed_at)
|
|
51
|
+
VALUES (?, ?, ?, ${NOW}, ?, ?, ?, ${NOW})`;
|
|
52
|
+
/**
|
|
53
|
+
* The rows of a kind whose facts are not proven current under a revision
|
|
54
|
+
* (list-index.ts; postgres/store.ts's twin). A row's list columns sit
|
|
55
|
+
* after its blob in the record and sqlite reaches them only by walking the
|
|
56
|
+
* blob's overflow pages, so any predicate checked per row of the kind
|
|
57
|
+
* costs a read of every blob (measured: 11 ms, and 72 ms for an OR over
|
|
58
|
+
* the revision, across 1,279 rows of 37 KB). Every arm is therefore one
|
|
59
|
+
* index range that fetches a row only when it matches, and names its
|
|
60
|
+
* index: without statistics the planner prefers the primary key for
|
|
61
|
+
* `kind = ?`, and `INDEXED BY` fails the statement rather than fall back
|
|
62
|
+
* to a scan. The arms overlap (an unstamped row has no revision either),
|
|
63
|
+
* so the caller keeps one row per id. Parameters: kind, kind, kind,
|
|
64
|
+
* revision, kind, revision.
|
|
65
|
+
*/
|
|
66
|
+
const UNPROVEN_ROWS_SQL = `
|
|
67
|
+
SELECT id, data FROM resources INDEXED BY idx_resources_list_unproven
|
|
68
|
+
WHERE kind = ? AND list_indexed_at IS NOT updated_at
|
|
69
|
+
UNION ALL
|
|
70
|
+
SELECT id, data FROM resources INDEXED BY idx_resources_list_revision
|
|
71
|
+
WHERE kind = ? AND list_index_revision IS NULL
|
|
72
|
+
UNION ALL
|
|
73
|
+
SELECT id, data FROM resources INDEXED BY idx_resources_list_revision
|
|
74
|
+
WHERE kind = ? AND list_index_revision < ?
|
|
75
|
+
UNION ALL
|
|
76
|
+
SELECT id, data FROM resources INDEXED BY idx_resources_list_revision
|
|
77
|
+
WHERE kind = ? AND list_index_revision > ?`;
|
|
78
|
+
function unprovenRowsParams(kindName, revision) {
|
|
79
|
+
return [kindName, kindName, kindName, revision, kindName, revision];
|
|
80
|
+
}
|
|
81
|
+
/** Keeps the first row per id; the unproven arms overlap. */
|
|
82
|
+
function oneRowPerId(rows) {
|
|
83
|
+
const seen = new Set();
|
|
84
|
+
return rows.filter((row) => seen.has(row.id) ? false : (seen.add(row.id), true));
|
|
85
|
+
}
|
|
86
|
+
/** How many unproven rows the reconciliation at open decodes per statement. */
|
|
87
|
+
const RECONCILE_BATCH = 500;
|
|
39
88
|
export class SqliteStore {
|
|
40
89
|
bootstrapState;
|
|
41
90
|
signalDedupe;
|
|
@@ -44,10 +93,12 @@ export class SqliteStore {
|
|
|
44
93
|
db;
|
|
45
94
|
dbPath;
|
|
46
95
|
logger;
|
|
47
|
-
|
|
96
|
+
listIndexes;
|
|
97
|
+
constructor(db, dbPath, logger, listIndexes) {
|
|
48
98
|
this.db = db;
|
|
49
99
|
this.dbPath = dbPath;
|
|
50
100
|
this.logger = logger;
|
|
101
|
+
this.listIndexes = listIndexes;
|
|
51
102
|
this.bootstrapState = new SqliteBootstrapStateStore(() => this.open());
|
|
52
103
|
this.signalDedupe = new SqliteSignalDedupeStore(() => this.open(), logger);
|
|
53
104
|
this.oauthGrants = new SqliteOAuthGrantStore(() => this.open());
|
|
@@ -55,9 +106,11 @@ export class SqliteStore {
|
|
|
55
106
|
}
|
|
56
107
|
/**
|
|
57
108
|
* Opens (or creates) the database at dbPath, applies the pragmas in Go's
|
|
58
|
-
* order,
|
|
109
|
+
* order, runs migrations — Go NewStore — and reconciles the list index
|
|
110
|
+
* (derives the facts of every unproven row of a declared kind).
|
|
59
111
|
*/
|
|
60
|
-
static open(dbPath, logger = NOOP_STORE_LOGGER) {
|
|
112
|
+
static open(dbPath, logger = NOOP_STORE_LOGGER, options = {}) {
|
|
113
|
+
const listIndexes = new ListIndexRegistry(options.listIndexes ?? []);
|
|
61
114
|
const dir = path.dirname(dbPath);
|
|
62
115
|
if (dir !== "" && dir !== ".") {
|
|
63
116
|
mkdirSync(dir, { recursive: true });
|
|
@@ -77,7 +130,15 @@ export class SqliteStore {
|
|
|
77
130
|
db.close();
|
|
78
131
|
throw error;
|
|
79
132
|
}
|
|
80
|
-
|
|
133
|
+
const store = new SqliteStore(db, dbPath, logger, listIndexes);
|
|
134
|
+
try {
|
|
135
|
+
store.reconcileListIndexes();
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
db.close();
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
return store;
|
|
81
142
|
}
|
|
82
143
|
/** Filesystem path of the database file (Go Store.Path()). */
|
|
83
144
|
path() {
|
|
@@ -89,7 +150,22 @@ export class SqliteStore {
|
|
|
89
150
|
async saveResource(kind, id, schema, msg) {
|
|
90
151
|
const db = this.open();
|
|
91
152
|
const data = toBinary(schema, msg);
|
|
92
|
-
|
|
153
|
+
const kindName = apiResourceKindName(kind);
|
|
154
|
+
const facts = this.listIndexes.factsOf(kind, msg);
|
|
155
|
+
if (facts === undefined) {
|
|
156
|
+
db.prepare(UPSERT_RESOURCE_SQL).run(kindName, id, data, null, null, null);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
db.exec("BEGIN");
|
|
160
|
+
try {
|
|
161
|
+
db.prepare(UPSERT_RESOURCE_SQL).run(kindName, id, data, facts.org, facts.createdAt, facts.revision);
|
|
162
|
+
replaceListKeys(db, kindName, id, facts);
|
|
163
|
+
db.exec("COMMIT");
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
db.exec("ROLLBACK");
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
93
169
|
}
|
|
94
170
|
async getResource(kind, id, schema) {
|
|
95
171
|
const db = this.open();
|
|
@@ -113,15 +189,25 @@ export class SqliteStore {
|
|
|
113
189
|
db.exec("BEGIN IMMEDIATE");
|
|
114
190
|
try {
|
|
115
191
|
const row = db
|
|
116
|
-
.prepare(`SELECT data
|
|
192
|
+
.prepare(`SELECT data, list_indexed_at IS updated_at AS stamped, list_index_revision
|
|
193
|
+
FROM resources WHERE kind = ? AND id = ?`)
|
|
117
194
|
.get(kindName, id);
|
|
118
195
|
if (row === undefined) {
|
|
119
196
|
throw new ResourceNotFoundError(`${kindName}/${id}`);
|
|
120
197
|
}
|
|
121
198
|
const msg = fromBinary(schema, row.data);
|
|
199
|
+
const before = this.listIndexes.factsOf(kind, msg);
|
|
122
200
|
modify(msg);
|
|
201
|
+
const after = this.listIndexes.factsOf(kind, msg);
|
|
123
202
|
const data = toBinary(schema, msg);
|
|
124
|
-
db.prepare(
|
|
203
|
+
db.prepare(UPSERT_RESOURCE_SQL).run(kindName, id, data, after?.org ?? null, after?.createdAt ?? null, after?.revision ?? null);
|
|
204
|
+
if (after !== undefined &&
|
|
205
|
+
(row.stamped !== 1 ||
|
|
206
|
+
row.list_index_revision !== after.revision ||
|
|
207
|
+
before === undefined ||
|
|
208
|
+
!sameListKeyRows(before, after))) {
|
|
209
|
+
replaceListKeys(db, kindName, id, after);
|
|
210
|
+
}
|
|
125
211
|
db.exec("COMMIT");
|
|
126
212
|
return msg;
|
|
127
213
|
}
|
|
@@ -139,9 +225,52 @@ export class SqliteStore {
|
|
|
139
225
|
// copy is needed (Go copied because database/sql may reuse buffers).
|
|
140
226
|
return rows.map((row) => row.data);
|
|
141
227
|
}
|
|
228
|
+
async queryResources(declaration, query) {
|
|
229
|
+
this.listIndexes.require(declaration);
|
|
230
|
+
assertListIndexLimit(query.limit);
|
|
231
|
+
if (query.anyKey !== undefined && query.anyKey.length === 0) {
|
|
232
|
+
return [];
|
|
233
|
+
}
|
|
234
|
+
const db = this.open();
|
|
235
|
+
const kindName = apiResourceKindName(declaration.kind);
|
|
236
|
+
// A key read is driven from the key table (CROSS JOIN fixes sqlite's
|
|
237
|
+
// join order), so only the rows the key names are ever read; several
|
|
238
|
+
// keys are one such read each, merged below (postgres/store.ts's twin).
|
|
239
|
+
const reads = query.anyKey === undefined
|
|
240
|
+
? [provenReadSql(kindName, declaration.revision, query, undefined)]
|
|
241
|
+
: query.anyKey.map((key) => provenReadSql(kindName, declaration.revision, query, key));
|
|
242
|
+
const proven = [];
|
|
243
|
+
for (const read of reads) {
|
|
244
|
+
const rows = db.prepare(read.sql).all(...read.params);
|
|
245
|
+
for (const row of rows) {
|
|
246
|
+
proven.push({
|
|
247
|
+
id: row.id,
|
|
248
|
+
data: row.data,
|
|
249
|
+
cursor: { createdAt: row.created_at, id: row.id },
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
const unproven = oneRowPerId(db
|
|
254
|
+
.prepare(UNPROVEN_ROWS_SQL)
|
|
255
|
+
.all(...unprovenRowsParams(kindName, declaration.revision)));
|
|
256
|
+
const unprovenRows = [];
|
|
257
|
+
for (const row of unproven) {
|
|
258
|
+
const facts = this.deriveAndRepair(declaration, row.id, row.data);
|
|
259
|
+
if (facts !== undefined && matchesListIndexQuery(row.id, facts, query)) {
|
|
260
|
+
unprovenRows.push({
|
|
261
|
+
id: row.id,
|
|
262
|
+
data: row.data,
|
|
263
|
+
cursor: { createdAt: facts.createdAt, id: row.id },
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return mergeListIndexRows(proven, unprovenRows, query.limit);
|
|
268
|
+
}
|
|
142
269
|
async deleteResource(kind, id) {
|
|
143
270
|
const db = this.open();
|
|
144
|
-
|
|
271
|
+
const kindName = apiResourceKindName(kind);
|
|
272
|
+
db.prepare(`DELETE FROM resources WHERE kind = ? AND id = ?`).run(kindName, id);
|
|
273
|
+
db.prepare(`DELETE FROM resource_list_keys WHERE kind = ? AND id = ?`).run(kindName, id);
|
|
145
274
|
}
|
|
146
275
|
async findByField(kind, fieldPath, value, schema) {
|
|
147
276
|
const db = this.open();
|
|
@@ -185,28 +314,139 @@ export class SqliteStore {
|
|
|
185
314
|
const db = this.open();
|
|
186
315
|
// BLOB equality in the WHERE clause makes the compare-and-swap one
|
|
187
316
|
// atomic statement: zero rows changed means the row moved on (or was
|
|
188
|
-
// deleted) since the read — a lost swap, never an upsert.
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
317
|
+
// deleted) since the read — a lost swap, never an upsert. The new bytes'
|
|
318
|
+
// list facts ride the same statement; bytes a declared kind's schema
|
|
319
|
+
// cannot decode leave the row unproven (stamp NULL), for a read to skip.
|
|
320
|
+
const kindName = apiResourceKindName(kind);
|
|
321
|
+
const declaration = this.listIndexes.declarationOf(kind);
|
|
322
|
+
const facts = declaration === undefined
|
|
323
|
+
? undefined
|
|
324
|
+
: factsOfBytes(declaration, newData);
|
|
325
|
+
const swap = () => Number(db
|
|
326
|
+
.prepare(`UPDATE resources SET data = ?, updated_at = ${NOW},
|
|
327
|
+
list_org = ?, list_created_at = ?, list_index_revision = ?,
|
|
328
|
+
list_indexed_at = CASE WHEN ? THEN ${NOW} ELSE NULL END
|
|
329
|
+
WHERE kind = ? AND id = ? AND data = ?`)
|
|
330
|
+
.run(newData, facts?.org ?? null, facts?.createdAt ?? null, facts?.revision ?? null, declaration === undefined || facts !== undefined ? 1 : 0, kindName, id, expectedData).changes) === 1;
|
|
331
|
+
if (facts === undefined) {
|
|
332
|
+
return swap();
|
|
333
|
+
}
|
|
334
|
+
db.exec("BEGIN");
|
|
335
|
+
try {
|
|
336
|
+
const swapped = swap();
|
|
337
|
+
if (swapped) {
|
|
338
|
+
replaceListKeys(db, kindName, id, facts);
|
|
339
|
+
}
|
|
340
|
+
db.exec("COMMIT");
|
|
341
|
+
return swapped;
|
|
342
|
+
}
|
|
343
|
+
catch (error) {
|
|
344
|
+
db.exec("ROLLBACK");
|
|
345
|
+
throw error;
|
|
346
|
+
}
|
|
193
347
|
}
|
|
194
348
|
async deleteResourcesByKind(kind) {
|
|
195
349
|
const db = this.open();
|
|
350
|
+
const kindName = apiResourceKindName(kind);
|
|
196
351
|
const result = db
|
|
197
352
|
.prepare(`DELETE FROM resources WHERE kind = ?`)
|
|
198
|
-
.run(
|
|
353
|
+
.run(kindName);
|
|
354
|
+
db.prepare(`DELETE FROM resource_list_keys WHERE kind = ?`).run(kindName);
|
|
199
355
|
return Number(result.changes);
|
|
200
356
|
}
|
|
201
357
|
async deleteResourcesByIdPrefix(kind, idPrefix) {
|
|
202
358
|
const db = this.open();
|
|
359
|
+
const kindName = apiResourceKindName(kind);
|
|
203
360
|
// GLOB 'prefix*' uses the (kind, id) index where LIKE would not (Go).
|
|
204
361
|
const result = db
|
|
205
362
|
.prepare(`DELETE FROM resources WHERE kind = ? AND id GLOB ?`)
|
|
206
|
-
.run(
|
|
363
|
+
.run(kindName, `${idPrefix}*`);
|
|
364
|
+
db.prepare(`DELETE FROM resource_list_keys WHERE kind = ? AND id GLOB ?`).run(kindName, `${idPrefix}*`);
|
|
207
365
|
return Number(result.changes);
|
|
208
366
|
}
|
|
209
367
|
// ---------------------------------------------------------------------------
|
|
368
|
+
// The list index's repair (list-index.ts; postgres/store.ts's twin)
|
|
369
|
+
// ---------------------------------------------------------------------------
|
|
370
|
+
/**
|
|
371
|
+
* An unproven row's facts, derived from its bytes, and the row repaired
|
|
372
|
+
* when those bytes are still the stored ones. Undefined when the bytes
|
|
373
|
+
* do not decode; the row stays unproven and is skipped.
|
|
374
|
+
*/
|
|
375
|
+
deriveAndRepair(declaration, id, data) {
|
|
376
|
+
const kindName = apiResourceKindName(declaration.kind);
|
|
377
|
+
const facts = factsOfBytes(declaration, data);
|
|
378
|
+
if (facts === undefined) {
|
|
379
|
+
this.logger.warn("list index: an unproven row does not decode, skipping", {
|
|
380
|
+
kind: kindName,
|
|
381
|
+
id,
|
|
382
|
+
});
|
|
383
|
+
return undefined;
|
|
384
|
+
}
|
|
385
|
+
const db = this.open();
|
|
386
|
+
db.exec("BEGIN");
|
|
387
|
+
try {
|
|
388
|
+
const repaired = db
|
|
389
|
+
.prepare(`UPDATE resources SET list_org = ?, list_created_at = ?,
|
|
390
|
+
list_index_revision = ?, list_indexed_at = updated_at
|
|
391
|
+
WHERE kind = ? AND id = ? AND data = ?`)
|
|
392
|
+
.run(facts.org, facts.createdAt, facts.revision, kindName, id, data);
|
|
393
|
+
if (Number(repaired.changes) === 1) {
|
|
394
|
+
replaceListKeys(db, kindName, id, facts);
|
|
395
|
+
}
|
|
396
|
+
db.exec("COMMIT");
|
|
397
|
+
}
|
|
398
|
+
catch (error) {
|
|
399
|
+
db.exec("ROLLBACK");
|
|
400
|
+
this.logger.warn("list index: repairing a row failed", {
|
|
401
|
+
kind: kindName,
|
|
402
|
+
id,
|
|
403
|
+
error: error instanceof Error ? error.message : String(error),
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
return facts;
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Derives every unproven row of each declared kind and stamps every other
|
|
410
|
+
* kind's unstamped rows, so the unproven set is empty once open returns.
|
|
411
|
+
*/
|
|
412
|
+
reconcileListIndexes() {
|
|
413
|
+
const db = this.open();
|
|
414
|
+
const declarations = this.listIndexes.declarations();
|
|
415
|
+
for (const declaration of declarations) {
|
|
416
|
+
const kindName = apiResourceKindName(declaration.kind);
|
|
417
|
+
let after = "";
|
|
418
|
+
let derived = 0;
|
|
419
|
+
for (;;) {
|
|
420
|
+
// Duplicates from overlapping arms sit side by side in id order and
|
|
421
|
+
// the next batch starts after the last id, so none is repaired twice
|
|
422
|
+
// across batches and the count still ends the loop.
|
|
423
|
+
const rows = db
|
|
424
|
+
.prepare(`SELECT id, data FROM (${UNPROVEN_ROWS_SQL}) WHERE id > ?
|
|
425
|
+
ORDER BY id LIMIT ?`)
|
|
426
|
+
.all(...unprovenRowsParams(kindName, declaration.revision), after, RECONCILE_BATCH);
|
|
427
|
+
for (const row of oneRowPerId(rows)) {
|
|
428
|
+
if (this.deriveAndRepair(declaration, row.id, row.data) !== undefined) {
|
|
429
|
+
derived += 1;
|
|
430
|
+
}
|
|
431
|
+
after = row.id;
|
|
432
|
+
}
|
|
433
|
+
if (rows.length < RECONCILE_BATCH) {
|
|
434
|
+
break;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
if (derived > 0) {
|
|
438
|
+
this.logger.info("list index reconciled", {
|
|
439
|
+
kind: kindName,
|
|
440
|
+
rows: derived,
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
const declared = declarations.map((d) => apiResourceKindName(d.kind));
|
|
445
|
+
db.prepare(`UPDATE resources SET list_indexed_at = updated_at
|
|
446
|
+
WHERE list_indexed_at IS NOT updated_at
|
|
447
|
+
AND kind NOT IN (${declared.map(() => "?").join(", ") || "''"})`).run(...declared);
|
|
448
|
+
}
|
|
449
|
+
// ---------------------------------------------------------------------------
|
|
210
450
|
// Audit operations
|
|
211
451
|
// ---------------------------------------------------------------------------
|
|
212
452
|
async saveAudit(kind, resourceId, schema, msg, versionHash, tag) {
|
|
@@ -718,6 +958,72 @@ class SqliteSignalDedupeStore {
|
|
|
718
958
|
}
|
|
719
959
|
}
|
|
720
960
|
/** Composite dedupe key: "{org}:{idempotency_key}" (Go buildDedupeKey). */
|
|
961
|
+
/**
|
|
962
|
+
* A row's key rows made equal to its facts, on the caller's open
|
|
963
|
+
* transaction (postgres/store.ts's twin).
|
|
964
|
+
*/
|
|
965
|
+
function replaceListKeys(db, kindName, id, facts) {
|
|
966
|
+
db.prepare(`DELETE FROM resource_list_keys WHERE kind = ? AND id = ?`).run(kindName, id);
|
|
967
|
+
const insert = db.prepare(`INSERT INTO resource_list_keys (kind, id, key, value, created_at) VALUES (?, ?, ?, ?, ?)`);
|
|
968
|
+
for (const entry of facts.keys) {
|
|
969
|
+
insert.run(kindName, id, entry.key, entry.value, facts.createdAt);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* One proven read of `queryResources` (postgres/store.ts's twin): through
|
|
974
|
+
* one key's index range when `key` is given, else through the organization
|
|
975
|
+
* index; only rows whose facts are proven current.
|
|
976
|
+
*/
|
|
977
|
+
function provenReadSql(kindName, revision, query, key) {
|
|
978
|
+
const params = [];
|
|
979
|
+
const created = key === undefined ? "r.list_created_at" : "k.created_at";
|
|
980
|
+
const id = key === undefined ? "r.id" : "k.id";
|
|
981
|
+
const where = [];
|
|
982
|
+
let from = `resources AS r`;
|
|
983
|
+
if (key === undefined) {
|
|
984
|
+
where.push(`r.kind = ?`);
|
|
985
|
+
params.push(kindName);
|
|
986
|
+
}
|
|
987
|
+
else {
|
|
988
|
+
from = `resource_list_keys AS k CROSS JOIN resources AS r`;
|
|
989
|
+
where.push(`k.kind = ?`, `k.key = ?`, `k.value = ?`, `r.kind = k.kind`, `r.id = k.id`);
|
|
990
|
+
params.push(kindName, key.name, key.value);
|
|
991
|
+
}
|
|
992
|
+
where.push(`r.list_indexed_at = r.updated_at`, `r.list_index_revision = ?`);
|
|
993
|
+
params.push(revision);
|
|
994
|
+
if (query.org !== undefined && query.org !== "") {
|
|
995
|
+
where.push(`r.list_org = ?`);
|
|
996
|
+
params.push(query.org);
|
|
997
|
+
}
|
|
998
|
+
if (query.createdAtOrAfter !== undefined) {
|
|
999
|
+
where.push(`(${created} >= ? OR ${created} = '')`);
|
|
1000
|
+
params.push(query.createdAtOrAfter);
|
|
1001
|
+
}
|
|
1002
|
+
if (query.after !== undefined) {
|
|
1003
|
+
where.push(`(${created} < ? OR (${created} = ? AND ${id} < ?))`);
|
|
1004
|
+
params.push(query.after.createdAt, query.after.createdAt, query.after.id);
|
|
1005
|
+
}
|
|
1006
|
+
let limit = "";
|
|
1007
|
+
if (query.limit !== undefined) {
|
|
1008
|
+
limit = ` LIMIT ?`;
|
|
1009
|
+
params.push(query.limit);
|
|
1010
|
+
}
|
|
1011
|
+
return {
|
|
1012
|
+
sql: `SELECT r.id, r.data, ${created} AS created_at FROM ${from}
|
|
1013
|
+
WHERE ${where.join(" AND ")}
|
|
1014
|
+
ORDER BY ${created} DESC, ${id} DESC${limit}`,
|
|
1015
|
+
params,
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
1018
|
+
/** A declared kind's facts from stored bytes; undefined when they do not decode. */
|
|
1019
|
+
function factsOfBytes(declaration, data) {
|
|
1020
|
+
try {
|
|
1021
|
+
return listIndexFactsOf(declaration, fromBinary(declaration.schema, data));
|
|
1022
|
+
}
|
|
1023
|
+
catch {
|
|
1024
|
+
return undefined;
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
721
1027
|
function buildDedupeKey(org, idempotencyKey) {
|
|
722
1028
|
return `${org}:${idempotencyKey}`;
|
|
723
1029
|
}
|