@powerhousedao/reactor 6.0.0-dev.84 → 6.0.0-dev.89
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/src/admin/document-integrity-service.d.ts +17 -0
- package/dist/src/admin/document-integrity-service.d.ts.map +1 -0
- package/dist/src/admin/types.d.ts +30 -0
- package/dist/src/admin/types.d.ts.map +1 -0
- package/dist/src/cache/collection-membership-cache.d.ts +1 -0
- package/dist/src/cache/collection-membership-cache.d.ts.map +1 -1
- package/dist/src/cache/document-meta-cache.d.ts +1 -0
- package/dist/src/cache/document-meta-cache.d.ts.map +1 -1
- package/dist/src/cache/kysely-operation-index.d.ts +5 -1
- package/dist/src/cache/kysely-operation-index.d.ts.map +1 -1
- package/dist/src/cache/kysely-write-cache.d.ts +1 -0
- package/dist/src/cache/kysely-write-cache.d.ts.map +1 -1
- package/dist/src/core/reactor-builder.d.ts.map +1 -1
- package/dist/src/core/reactor-client-builder.d.ts +3 -0
- package/dist/src/core/reactor-client-builder.d.ts.map +1 -1
- package/dist/src/executor/document-action-handler.d.ts +3 -10
- package/dist/src/executor/document-action-handler.d.ts.map +1 -1
- package/dist/src/executor/execution-scope.d.ts +44 -0
- package/dist/src/executor/execution-scope.d.ts.map +1 -0
- package/dist/src/executor/simple-job-executor.d.ts +3 -1
- package/dist/src/executor/simple-job-executor.d.ts.map +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +498 -205
- package/dist/src/processors/index.d.ts +0 -1
- package/dist/src/processors/index.d.ts.map +1 -1
- package/dist/src/storage/interfaces.d.ts +14 -0
- package/dist/src/storage/interfaces.d.ts.map +1 -1
- package/dist/src/storage/kysely/keyframe-store.d.ts +10 -1
- package/dist/src/storage/kysely/keyframe-store.d.ts.map +1 -1
- package/dist/src/storage/kysely/store.d.ts +5 -1
- package/dist/src/storage/kysely/store.d.ts.map +1 -1
- package/package.json +6 -6
- package/dist/src/processors/relational/types.d.ts +0 -2
- package/dist/src/processors/relational/types.d.ts.map +0 -1
- package/dist/src/processors/relational/utils.d.ts +0 -2
- package/dist/src/processors/relational/utils.d.ts.map +0 -1
package/dist/src/index.js
CHANGED
|
@@ -11080,6 +11080,11 @@ class CollectionMembershipCache {
|
|
|
11080
11080
|
constructor(operationIndex) {
|
|
11081
11081
|
this.operationIndex = operationIndex;
|
|
11082
11082
|
}
|
|
11083
|
+
withScopedIndex(operationIndex) {
|
|
11084
|
+
const scoped = new CollectionMembershipCache(operationIndex);
|
|
11085
|
+
scoped.cache = this.cache;
|
|
11086
|
+
return scoped;
|
|
11087
|
+
}
|
|
11083
11088
|
async getCollectionsForDocuments(documentIds) {
|
|
11084
11089
|
const result = {};
|
|
11085
11090
|
const missing = [];
|
|
@@ -11435,6 +11440,12 @@ class DocumentMetaCache {
|
|
|
11435
11440
|
this.cache = new Map;
|
|
11436
11441
|
this.lruTracker = new LRUTracker;
|
|
11437
11442
|
}
|
|
11443
|
+
withScopedStore(operationStore) {
|
|
11444
|
+
const scoped = new DocumentMetaCache(operationStore, this.config);
|
|
11445
|
+
scoped.cache = this.cache;
|
|
11446
|
+
scoped.lruTracker = this.lruTracker;
|
|
11447
|
+
return scoped;
|
|
11448
|
+
}
|
|
11438
11449
|
async startup() {
|
|
11439
11450
|
return Promise.resolve();
|
|
11440
11451
|
}
|
|
@@ -11585,9 +11596,18 @@ class KyselyOperationIndexTxn {
|
|
|
11585
11596
|
|
|
11586
11597
|
class KyselyOperationIndex {
|
|
11587
11598
|
db;
|
|
11599
|
+
trx;
|
|
11588
11600
|
constructor(db) {
|
|
11589
11601
|
this.db = db;
|
|
11590
11602
|
}
|
|
11603
|
+
get queryExecutor() {
|
|
11604
|
+
return this.trx ?? this.db;
|
|
11605
|
+
}
|
|
11606
|
+
withTransaction(trx) {
|
|
11607
|
+
const instance = new KyselyOperationIndex(this.db);
|
|
11608
|
+
instance.trx = trx;
|
|
11609
|
+
return instance;
|
|
11610
|
+
}
|
|
11591
11611
|
start() {
|
|
11592
11612
|
return new KyselyOperationIndexTxn;
|
|
11593
11613
|
}
|
|
@@ -11596,70 +11616,76 @@ class KyselyOperationIndex {
|
|
|
11596
11616
|
throw new Error("Operation aborted");
|
|
11597
11617
|
}
|
|
11598
11618
|
const kyselyTxn = txn;
|
|
11619
|
+
if (this.trx) {
|
|
11620
|
+
return this.executeCommit(this.trx, kyselyTxn);
|
|
11621
|
+
}
|
|
11622
|
+
let resultOrdinals = [];
|
|
11623
|
+
await this.db.transaction().execute(async (trx) => {
|
|
11624
|
+
resultOrdinals = await this.executeCommit(trx, kyselyTxn);
|
|
11625
|
+
});
|
|
11626
|
+
return resultOrdinals;
|
|
11627
|
+
}
|
|
11628
|
+
async executeCommit(trx, kyselyTxn) {
|
|
11599
11629
|
const collections = kyselyTxn.getCollections();
|
|
11600
11630
|
const memberships = kyselyTxn.getCollectionMembershipRecords();
|
|
11601
11631
|
const removals = kyselyTxn.getCollectionRemovals();
|
|
11602
11632
|
const operations = kyselyTxn.getOperations();
|
|
11603
|
-
|
|
11604
|
-
|
|
11605
|
-
|
|
11606
|
-
|
|
11607
|
-
|
|
11608
|
-
|
|
11609
|
-
|
|
11633
|
+
if (collections.length > 0) {
|
|
11634
|
+
const collectionRows = collections.map((collectionId) => ({
|
|
11635
|
+
documentId: collectionId,
|
|
11636
|
+
collectionId,
|
|
11637
|
+
joinedOrdinal: BigInt(0),
|
|
11638
|
+
leftOrdinal: null
|
|
11639
|
+
}));
|
|
11640
|
+
await trx.insertInto("document_collections").values(collectionRows).onConflict((oc) => oc.doNothing()).execute();
|
|
11641
|
+
}
|
|
11642
|
+
let operationOrdinals = [];
|
|
11643
|
+
if (operations.length > 0) {
|
|
11644
|
+
const operationRows = operations.map((op) => ({
|
|
11645
|
+
opId: op.id || "",
|
|
11646
|
+
documentId: op.documentId,
|
|
11647
|
+
documentType: op.documentType,
|
|
11648
|
+
scope: op.scope,
|
|
11649
|
+
branch: op.branch,
|
|
11650
|
+
timestampUtcMs: op.timestampUtcMs,
|
|
11651
|
+
index: op.index,
|
|
11652
|
+
skip: op.skip,
|
|
11653
|
+
hash: op.hash,
|
|
11654
|
+
action: op.action,
|
|
11655
|
+
sourceRemote: op.sourceRemote
|
|
11656
|
+
}));
|
|
11657
|
+
const insertedOps = await trx.insertInto("operation_index_operations").values(operationRows).returning("ordinal").execute();
|
|
11658
|
+
operationOrdinals = insertedOps.map((row) => row.ordinal);
|
|
11659
|
+
}
|
|
11660
|
+
if (memberships.length > 0) {
|
|
11661
|
+
for (const m of memberships) {
|
|
11662
|
+
const ordinal = operationOrdinals[m.operationIndex];
|
|
11663
|
+
await trx.insertInto("document_collections").values({
|
|
11664
|
+
documentId: m.documentId,
|
|
11665
|
+
collectionId: m.collectionId,
|
|
11666
|
+
joinedOrdinal: BigInt(ordinal),
|
|
11610
11667
|
leftOrdinal: null
|
|
11611
|
-
}))
|
|
11612
|
-
|
|
11613
|
-
|
|
11614
|
-
|
|
11615
|
-
if (operations.length > 0) {
|
|
11616
|
-
const operationRows = operations.map((op) => ({
|
|
11617
|
-
opId: op.id || "",
|
|
11618
|
-
documentId: op.documentId,
|
|
11619
|
-
documentType: op.documentType,
|
|
11620
|
-
scope: op.scope,
|
|
11621
|
-
branch: op.branch,
|
|
11622
|
-
timestampUtcMs: op.timestampUtcMs,
|
|
11623
|
-
index: op.index,
|
|
11624
|
-
skip: op.skip,
|
|
11625
|
-
hash: op.hash,
|
|
11626
|
-
action: op.action,
|
|
11627
|
-
sourceRemote: op.sourceRemote
|
|
11628
|
-
}));
|
|
11629
|
-
const insertedOps = await trx.insertInto("operation_index_operations").values(operationRows).returning("ordinal").execute();
|
|
11630
|
-
operationOrdinals = insertedOps.map((row) => row.ordinal);
|
|
11631
|
-
resultOrdinals = operationOrdinals;
|
|
11632
|
-
}
|
|
11633
|
-
if (memberships.length > 0) {
|
|
11634
|
-
for (const m of memberships) {
|
|
11635
|
-
const ordinal = operationOrdinals[m.operationIndex];
|
|
11636
|
-
await trx.insertInto("document_collections").values({
|
|
11637
|
-
documentId: m.documentId,
|
|
11638
|
-
collectionId: m.collectionId,
|
|
11639
|
-
joinedOrdinal: BigInt(ordinal),
|
|
11640
|
-
leftOrdinal: null
|
|
11641
|
-
}).onConflict((oc) => oc.columns(["documentId", "collectionId"]).doUpdateSet({
|
|
11642
|
-
joinedOrdinal: BigInt(ordinal),
|
|
11643
|
-
leftOrdinal: null
|
|
11644
|
-
})).execute();
|
|
11645
|
-
}
|
|
11668
|
+
}).onConflict((oc) => oc.columns(["documentId", "collectionId"]).doUpdateSet({
|
|
11669
|
+
joinedOrdinal: BigInt(ordinal),
|
|
11670
|
+
leftOrdinal: null
|
|
11671
|
+
})).execute();
|
|
11646
11672
|
}
|
|
11647
|
-
|
|
11648
|
-
|
|
11649
|
-
|
|
11650
|
-
|
|
11651
|
-
|
|
11652
|
-
|
|
11653
|
-
}
|
|
11673
|
+
}
|
|
11674
|
+
if (removals.length > 0) {
|
|
11675
|
+
for (const r of removals) {
|
|
11676
|
+
const ordinal = operationOrdinals[r.operationIndex];
|
|
11677
|
+
await trx.updateTable("document_collections").set({
|
|
11678
|
+
leftOrdinal: BigInt(ordinal)
|
|
11679
|
+
}).where("collectionId", "=", r.collectionId).where("documentId", "=", r.documentId).where("leftOrdinal", "is", null).execute();
|
|
11654
11680
|
}
|
|
11655
|
-
}
|
|
11656
|
-
return
|
|
11681
|
+
}
|
|
11682
|
+
return operationOrdinals;
|
|
11657
11683
|
}
|
|
11658
11684
|
async find(collectionId, cursor, view, paging, signal) {
|
|
11659
11685
|
if (signal?.aborted) {
|
|
11660
11686
|
throw new Error("Operation aborted");
|
|
11661
11687
|
}
|
|
11662
|
-
let query = this.
|
|
11688
|
+
let query = this.queryExecutor.selectFrom("operation_index_operations as oi").innerJoin("document_collections as dc", "oi.documentId", "dc.documentId").selectAll("oi").select(["dc.documentId", "dc.collectionId"]).where("dc.collectionId", "=", collectionId).where(sql`(dc."leftOrdinal" IS NULL OR oi.ordinal < dc."leftOrdinal")`).orderBy("oi.ordinal", "asc");
|
|
11663
11689
|
if (cursor !== undefined) {
|
|
11664
11690
|
query = query.where("oi.ordinal", ">", cursor);
|
|
11665
11691
|
}
|
|
@@ -11701,7 +11727,7 @@ class KyselyOperationIndex {
|
|
|
11701
11727
|
if (signal?.aborted) {
|
|
11702
11728
|
throw new Error("Operation aborted");
|
|
11703
11729
|
}
|
|
11704
|
-
let query = this.
|
|
11730
|
+
let query = this.queryExecutor.selectFrom("operation_index_operations").selectAll().where("documentId", "=", documentId).orderBy("ordinal", "asc");
|
|
11705
11731
|
if (view?.branch) {
|
|
11706
11732
|
query = query.where("branch", "=", view.branch);
|
|
11707
11733
|
}
|
|
@@ -11737,7 +11763,7 @@ class KyselyOperationIndex {
|
|
|
11737
11763
|
if (signal?.aborted) {
|
|
11738
11764
|
throw new Error("Operation aborted");
|
|
11739
11765
|
}
|
|
11740
|
-
let query = this.
|
|
11766
|
+
let query = this.queryExecutor.selectFrom("operation_index_operations").selectAll().where("ordinal", ">", ordinal).orderBy("ordinal", "asc");
|
|
11741
11767
|
if (paging?.cursor) {
|
|
11742
11768
|
const cursorOrdinal = Number.parseInt(paging.cursor, 10);
|
|
11743
11769
|
query = query.where("ordinal", ">", cursorOrdinal);
|
|
@@ -11802,14 +11828,14 @@ class KyselyOperationIndex {
|
|
|
11802
11828
|
if (signal?.aborted) {
|
|
11803
11829
|
throw new Error("Operation aborted");
|
|
11804
11830
|
}
|
|
11805
|
-
const result = await this.
|
|
11831
|
+
const result = await this.queryExecutor.selectFrom("operation_index_operations as oi").innerJoin("document_collections as dc", "oi.documentId", "dc.documentId").select("oi.timestampUtcMs").where("dc.collectionId", "=", collectionId).where(sql`(dc."leftOrdinal" IS NULL OR oi.ordinal < dc."leftOrdinal")`).orderBy("oi.ordinal", "desc").limit(1).executeTakeFirst();
|
|
11806
11832
|
return result?.timestampUtcMs ?? null;
|
|
11807
11833
|
}
|
|
11808
11834
|
async getCollectionsForDocuments(documentIds) {
|
|
11809
11835
|
if (documentIds.length === 0) {
|
|
11810
11836
|
return {};
|
|
11811
11837
|
}
|
|
11812
|
-
const rows = await this.
|
|
11838
|
+
const rows = await this.queryExecutor.selectFrom("document_collections").select(["documentId", "collectionId"]).where("documentId", "in", documentIds).where("leftOrdinal", "is", null).execute();
|
|
11813
11839
|
const result = {};
|
|
11814
11840
|
for (const row of rows) {
|
|
11815
11841
|
if (!(row.documentId in result)) {
|
|
@@ -11866,6 +11892,11 @@ class RingBuffer {
|
|
|
11866
11892
|
}
|
|
11867
11893
|
|
|
11868
11894
|
// src/cache/kysely-write-cache.ts
|
|
11895
|
+
function extractModuleVersion(doc) {
|
|
11896
|
+
const v = doc.state.document.version;
|
|
11897
|
+
return v === 0 ? undefined : v;
|
|
11898
|
+
}
|
|
11899
|
+
|
|
11869
11900
|
class KyselyWriteCache {
|
|
11870
11901
|
streams;
|
|
11871
11902
|
lruTracker;
|
|
@@ -11885,6 +11916,12 @@ class KyselyWriteCache {
|
|
|
11885
11916
|
this.streams = new Map;
|
|
11886
11917
|
this.lruTracker = new LRUTracker;
|
|
11887
11918
|
}
|
|
11919
|
+
withScopedStores(operationStore, keyframeStore) {
|
|
11920
|
+
const scoped = new KyselyWriteCache(keyframeStore, operationStore, this.registry, this.config);
|
|
11921
|
+
scoped.streams = this.streams;
|
|
11922
|
+
scoped.lruTracker = this.lruTracker;
|
|
11923
|
+
return scoped;
|
|
11924
|
+
}
|
|
11888
11925
|
async startup() {
|
|
11889
11926
|
return Promise.resolve();
|
|
11890
11927
|
}
|
|
@@ -12023,7 +12060,7 @@ class KyselyWriteCache {
|
|
|
12023
12060
|
throw new Error(`Failed to rebuild document ${documentId}: CREATE_DOCUMENT action missing model in input`);
|
|
12024
12061
|
}
|
|
12025
12062
|
document = createDocumentFromAction(documentCreateAction);
|
|
12026
|
-
|
|
12063
|
+
let docModule = this.registry.getModule(documentType, extractModuleVersion(document));
|
|
12027
12064
|
const docScopeOps = await this.operationStore.getSince(documentId, "document", branch, 0, undefined, undefined, signal);
|
|
12028
12065
|
for (const operation of docScopeOps.results) {
|
|
12029
12066
|
if (operation.index === 0) {
|
|
@@ -12032,6 +12069,7 @@ class KyselyWriteCache {
|
|
|
12032
12069
|
if (operation.action.type === "UPGRADE_DOCUMENT") {
|
|
12033
12070
|
const upgradeAction = operation.action;
|
|
12034
12071
|
document = applyUpgradeDocumentAction(document, upgradeAction);
|
|
12072
|
+
docModule = this.registry.getModule(documentType, extractModuleVersion(document));
|
|
12035
12073
|
} else if (operation.action.type === "DELETE_DOCUMENT") {
|
|
12036
12074
|
applyDeleteDocumentAction(document, operation.action);
|
|
12037
12075
|
} else {
|
|
@@ -12043,7 +12081,7 @@ class KyselyWriteCache {
|
|
|
12043
12081
|
}
|
|
12044
12082
|
}
|
|
12045
12083
|
}
|
|
12046
|
-
const module = this.registry.getModule(documentType);
|
|
12084
|
+
const module = this.registry.getModule(documentType, extractModuleVersion(document));
|
|
12047
12085
|
let cursor = undefined;
|
|
12048
12086
|
const pageSize = 100;
|
|
12049
12087
|
let hasMorePages;
|
|
@@ -12217,6 +12255,64 @@ class EventBus {
|
|
|
12217
12255
|
}
|
|
12218
12256
|
}
|
|
12219
12257
|
|
|
12258
|
+
// src/executor/execution-scope.ts
|
|
12259
|
+
class DefaultExecutionScope {
|
|
12260
|
+
operationStore;
|
|
12261
|
+
operationIndex;
|
|
12262
|
+
writeCache;
|
|
12263
|
+
documentMetaCache;
|
|
12264
|
+
collectionMembershipCache;
|
|
12265
|
+
constructor(operationStore, operationIndex, writeCache, documentMetaCache, collectionMembershipCache) {
|
|
12266
|
+
this.operationStore = operationStore;
|
|
12267
|
+
this.operationIndex = operationIndex;
|
|
12268
|
+
this.writeCache = writeCache;
|
|
12269
|
+
this.documentMetaCache = documentMetaCache;
|
|
12270
|
+
this.collectionMembershipCache = collectionMembershipCache;
|
|
12271
|
+
}
|
|
12272
|
+
async run(fn) {
|
|
12273
|
+
return fn({
|
|
12274
|
+
operationStore: this.operationStore,
|
|
12275
|
+
operationIndex: this.operationIndex,
|
|
12276
|
+
writeCache: this.writeCache,
|
|
12277
|
+
documentMetaCache: this.documentMetaCache,
|
|
12278
|
+
collectionMembershipCache: this.collectionMembershipCache
|
|
12279
|
+
});
|
|
12280
|
+
}
|
|
12281
|
+
}
|
|
12282
|
+
|
|
12283
|
+
class KyselyExecutionScope {
|
|
12284
|
+
db;
|
|
12285
|
+
operationStore;
|
|
12286
|
+
operationIndex;
|
|
12287
|
+
keyframeStore;
|
|
12288
|
+
writeCache;
|
|
12289
|
+
documentMetaCache;
|
|
12290
|
+
collectionMembershipCache;
|
|
12291
|
+
constructor(db, operationStore, operationIndex, keyframeStore, writeCache, documentMetaCache, collectionMembershipCache) {
|
|
12292
|
+
this.db = db;
|
|
12293
|
+
this.operationStore = operationStore;
|
|
12294
|
+
this.operationIndex = operationIndex;
|
|
12295
|
+
this.keyframeStore = keyframeStore;
|
|
12296
|
+
this.writeCache = writeCache;
|
|
12297
|
+
this.documentMetaCache = documentMetaCache;
|
|
12298
|
+
this.collectionMembershipCache = collectionMembershipCache;
|
|
12299
|
+
}
|
|
12300
|
+
async run(fn) {
|
|
12301
|
+
return this.db.transaction().execute(async (trx) => {
|
|
12302
|
+
const scopedOperationStore = this.operationStore.withTransaction(trx);
|
|
12303
|
+
const scopedOperationIndex = this.operationIndex.withTransaction(trx);
|
|
12304
|
+
const scopedKeyframeStore = this.keyframeStore.withTransaction(trx);
|
|
12305
|
+
return fn({
|
|
12306
|
+
operationStore: scopedOperationStore,
|
|
12307
|
+
operationIndex: scopedOperationIndex,
|
|
12308
|
+
writeCache: this.writeCache.withScopedStores(scopedOperationStore, scopedKeyframeStore),
|
|
12309
|
+
documentMetaCache: this.documentMetaCache.withScopedStore(scopedOperationStore),
|
|
12310
|
+
collectionMembershipCache: this.collectionMembershipCache.withScopedIndex(scopedOperationIndex)
|
|
12311
|
+
});
|
|
12312
|
+
});
|
|
12313
|
+
}
|
|
12314
|
+
}
|
|
12315
|
+
|
|
12220
12316
|
// src/queue/types.ts
|
|
12221
12317
|
var QueueEventTypes = {
|
|
12222
12318
|
JOB_AVAILABLE: 1e4
|
|
@@ -12766,37 +12862,29 @@ function driveCollectionId(branch, driveId) {
|
|
|
12766
12862
|
|
|
12767
12863
|
// src/executor/document-action-handler.ts
|
|
12768
12864
|
class DocumentActionHandler {
|
|
12769
|
-
writeCache;
|
|
12770
|
-
operationStore;
|
|
12771
|
-
documentMetaCache;
|
|
12772
|
-
collectionMembershipCache;
|
|
12773
12865
|
registry;
|
|
12774
12866
|
logger;
|
|
12775
|
-
constructor(
|
|
12776
|
-
this.writeCache = writeCache;
|
|
12777
|
-
this.operationStore = operationStore;
|
|
12778
|
-
this.documentMetaCache = documentMetaCache;
|
|
12779
|
-
this.collectionMembershipCache = collectionMembershipCache;
|
|
12867
|
+
constructor(registry, logger) {
|
|
12780
12868
|
this.registry = registry;
|
|
12781
12869
|
this.logger = logger;
|
|
12782
12870
|
}
|
|
12783
|
-
async execute(job, action, startTime, indexTxn, skip = 0, sourceRemote = "") {
|
|
12871
|
+
async execute(job, action, startTime, indexTxn, stores, skip = 0, sourceRemote = "") {
|
|
12784
12872
|
switch (action.type) {
|
|
12785
12873
|
case "CREATE_DOCUMENT":
|
|
12786
|
-
return this.executeCreate(job, action, startTime, indexTxn, skip, sourceRemote);
|
|
12874
|
+
return this.executeCreate(job, action, startTime, indexTxn, stores, skip, sourceRemote);
|
|
12787
12875
|
case "DELETE_DOCUMENT":
|
|
12788
|
-
return this.executeDelete(job, action, startTime, indexTxn, sourceRemote);
|
|
12876
|
+
return this.executeDelete(job, action, startTime, indexTxn, stores, sourceRemote);
|
|
12789
12877
|
case "UPGRADE_DOCUMENT":
|
|
12790
|
-
return this.executeUpgrade(job, action, startTime, indexTxn, skip, sourceRemote);
|
|
12878
|
+
return this.executeUpgrade(job, action, startTime, indexTxn, stores, skip, sourceRemote);
|
|
12791
12879
|
case "ADD_RELATIONSHIP":
|
|
12792
|
-
return this.executeAddRelationship(job, action, startTime, indexTxn, sourceRemote);
|
|
12880
|
+
return this.executeAddRelationship(job, action, startTime, indexTxn, stores, sourceRemote);
|
|
12793
12881
|
case "REMOVE_RELATIONSHIP":
|
|
12794
|
-
return this.executeRemoveRelationship(job, action, startTime, indexTxn, sourceRemote);
|
|
12882
|
+
return this.executeRemoveRelationship(job, action, startTime, indexTxn, stores, sourceRemote);
|
|
12795
12883
|
default:
|
|
12796
12884
|
return buildErrorResult(job, new Error(`Unknown document action type: ${action.type}`), startTime);
|
|
12797
12885
|
}
|
|
12798
12886
|
}
|
|
12799
|
-
async executeCreate(job, action, startTime, indexTxn, skip = 0, sourceRemote = "") {
|
|
12887
|
+
async executeCreate(job, action, startTime, indexTxn, stores, skip = 0, sourceRemote = "") {
|
|
12800
12888
|
if (job.scope !== "document") {
|
|
12801
12889
|
return {
|
|
12802
12890
|
job,
|
|
@@ -12816,12 +12904,12 @@ class DocumentActionHandler {
|
|
|
12816
12904
|
...document.state
|
|
12817
12905
|
};
|
|
12818
12906
|
const resultingState = JSON.stringify(resultingStateObj);
|
|
12819
|
-
const writeError = await this.writeOperationToStore(document.header.id, document.header.documentType, job.scope, job.branch, operation, job, startTime);
|
|
12907
|
+
const writeError = await this.writeOperationToStore(document.header.id, document.header.documentType, job.scope, job.branch, operation, job, startTime, stores);
|
|
12820
12908
|
if (writeError !== null) {
|
|
12821
12909
|
return writeError;
|
|
12822
12910
|
}
|
|
12823
12911
|
updateDocumentRevision(document, job.scope, operation.index);
|
|
12824
|
-
|
|
12912
|
+
stores.writeCache.putState(document.header.id, job.scope, job.branch, operation.index, document);
|
|
12825
12913
|
indexTxn.write([
|
|
12826
12914
|
{
|
|
12827
12915
|
...operation,
|
|
@@ -12837,14 +12925,14 @@ class DocumentActionHandler {
|
|
|
12837
12925
|
indexTxn.createCollection(collectionId);
|
|
12838
12926
|
indexTxn.addToCollection(collectionId, document.header.id);
|
|
12839
12927
|
}
|
|
12840
|
-
|
|
12928
|
+
stores.documentMetaCache.putDocumentMeta(document.header.id, job.branch, {
|
|
12841
12929
|
state: document.state.document,
|
|
12842
12930
|
documentType: document.header.documentType,
|
|
12843
12931
|
documentScopeRevision: 1
|
|
12844
12932
|
});
|
|
12845
12933
|
return buildSuccessResult(job, operation, document.header.id, document.header.documentType, resultingState, startTime);
|
|
12846
12934
|
}
|
|
12847
|
-
async executeDelete(job, action, startTime, indexTxn, sourceRemote = "") {
|
|
12935
|
+
async executeDelete(job, action, startTime, indexTxn, stores, sourceRemote = "") {
|
|
12848
12936
|
const input = action.input;
|
|
12849
12937
|
if (!input.documentId) {
|
|
12850
12938
|
return buildErrorResult(job, new Error("DELETE_DOCUMENT action requires a documentId in input"), startTime);
|
|
@@ -12852,7 +12940,7 @@ class DocumentActionHandler {
|
|
|
12852
12940
|
const documentId = input.documentId;
|
|
12853
12941
|
let document;
|
|
12854
12942
|
try {
|
|
12855
|
-
document = await
|
|
12943
|
+
document = await stores.writeCache.getState(documentId, job.scope, job.branch);
|
|
12856
12944
|
} catch (error) {
|
|
12857
12945
|
return buildErrorResult(job, new Error(`Failed to fetch document before deletion: ${error instanceof Error ? error.message : String(error)}`), startTime);
|
|
12858
12946
|
}
|
|
@@ -12872,12 +12960,12 @@ class DocumentActionHandler {
|
|
|
12872
12960
|
document: document.state.document
|
|
12873
12961
|
};
|
|
12874
12962
|
const resultingState = JSON.stringify(resultingStateObj);
|
|
12875
|
-
const writeError = await this.writeOperationToStore(documentId, document.header.documentType, job.scope, job.branch, operation, job, startTime);
|
|
12963
|
+
const writeError = await this.writeOperationToStore(documentId, document.header.documentType, job.scope, job.branch, operation, job, startTime, stores);
|
|
12876
12964
|
if (writeError !== null) {
|
|
12877
12965
|
return writeError;
|
|
12878
12966
|
}
|
|
12879
12967
|
updateDocumentRevision(document, job.scope, operation.index);
|
|
12880
|
-
|
|
12968
|
+
stores.writeCache.putState(documentId, job.scope, job.branch, operation.index, document);
|
|
12881
12969
|
indexTxn.write([
|
|
12882
12970
|
{
|
|
12883
12971
|
...operation,
|
|
@@ -12888,14 +12976,14 @@ class DocumentActionHandler {
|
|
|
12888
12976
|
sourceRemote
|
|
12889
12977
|
}
|
|
12890
12978
|
]);
|
|
12891
|
-
|
|
12979
|
+
stores.documentMetaCache.putDocumentMeta(documentId, job.branch, {
|
|
12892
12980
|
state: document.state.document,
|
|
12893
12981
|
documentType: document.header.documentType,
|
|
12894
12982
|
documentScopeRevision: operation.index + 1
|
|
12895
12983
|
});
|
|
12896
12984
|
return buildSuccessResult(job, operation, documentId, document.header.documentType, resultingState, startTime);
|
|
12897
12985
|
}
|
|
12898
|
-
async executeUpgrade(job, action, startTime, indexTxn, skip = 0, sourceRemote = "") {
|
|
12986
|
+
async executeUpgrade(job, action, startTime, indexTxn, stores, skip = 0, sourceRemote = "") {
|
|
12899
12987
|
const input = action.input;
|
|
12900
12988
|
if (!input.documentId) {
|
|
12901
12989
|
return buildErrorResult(job, new Error("UPGRADE_DOCUMENT action requires a documentId in input"), startTime);
|
|
@@ -12905,7 +12993,7 @@ class DocumentActionHandler {
|
|
|
12905
12993
|
const toVersion = input.toVersion;
|
|
12906
12994
|
let document;
|
|
12907
12995
|
try {
|
|
12908
|
-
document = await
|
|
12996
|
+
document = await stores.writeCache.getState(documentId, job.scope, job.branch);
|
|
12909
12997
|
} catch (error) {
|
|
12910
12998
|
return buildErrorResult(job, new Error(`Failed to fetch document for upgrade: ${error instanceof Error ? error.message : String(error)}`), startTime);
|
|
12911
12999
|
}
|
|
@@ -12946,12 +13034,12 @@ class DocumentActionHandler {
|
|
|
12946
13034
|
...document.state
|
|
12947
13035
|
};
|
|
12948
13036
|
const resultingState = JSON.stringify(resultingStateObj);
|
|
12949
|
-
const writeError = await this.writeOperationToStore(documentId, document.header.documentType, job.scope, job.branch, operation, job, startTime);
|
|
13037
|
+
const writeError = await this.writeOperationToStore(documentId, document.header.documentType, job.scope, job.branch, operation, job, startTime, stores);
|
|
12950
13038
|
if (writeError !== null) {
|
|
12951
13039
|
return writeError;
|
|
12952
13040
|
}
|
|
12953
13041
|
updateDocumentRevision(document, job.scope, operation.index);
|
|
12954
|
-
|
|
13042
|
+
stores.writeCache.putState(documentId, job.scope, job.branch, operation.index, document);
|
|
12955
13043
|
indexTxn.write([
|
|
12956
13044
|
{
|
|
12957
13045
|
...operation,
|
|
@@ -12962,14 +13050,14 @@ class DocumentActionHandler {
|
|
|
12962
13050
|
sourceRemote
|
|
12963
13051
|
}
|
|
12964
13052
|
]);
|
|
12965
|
-
|
|
13053
|
+
stores.documentMetaCache.putDocumentMeta(documentId, job.branch, {
|
|
12966
13054
|
state: document.state.document,
|
|
12967
13055
|
documentType: document.header.documentType,
|
|
12968
13056
|
documentScopeRevision: operation.index + 1
|
|
12969
13057
|
});
|
|
12970
13058
|
return buildSuccessResult(job, operation, documentId, document.header.documentType, resultingState, startTime);
|
|
12971
13059
|
}
|
|
12972
|
-
async executeAddRelationship(job, action, startTime, indexTxn, sourceRemote = "") {
|
|
13060
|
+
async executeAddRelationship(job, action, startTime, indexTxn, stores, sourceRemote = "") {
|
|
12973
13061
|
if (job.scope !== "document") {
|
|
12974
13062
|
return buildErrorResult(job, new Error(`ADD_RELATIONSHIP must be in "document" scope, got "${job.scope}"`), startTime);
|
|
12975
13063
|
}
|
|
@@ -12982,7 +13070,7 @@ class DocumentActionHandler {
|
|
|
12982
13070
|
}
|
|
12983
13071
|
let sourceDoc;
|
|
12984
13072
|
try {
|
|
12985
|
-
sourceDoc = await
|
|
13073
|
+
sourceDoc = await stores.writeCache.getState(input.sourceId, "document", job.branch);
|
|
12986
13074
|
} catch (error) {
|
|
12987
13075
|
return buildErrorResult(job, new Error(`ADD_RELATIONSHIP: source document ${input.sourceId} not found: ${error instanceof Error ? error.message : String(error)}`), startTime);
|
|
12988
13076
|
}
|
|
@@ -12992,7 +13080,7 @@ class DocumentActionHandler {
|
|
|
12992
13080
|
scope: job.scope,
|
|
12993
13081
|
branch: job.branch
|
|
12994
13082
|
});
|
|
12995
|
-
const writeError = await this.writeOperationToStore(input.sourceId, sourceDoc.header.documentType, job.scope, job.branch, operation, job, startTime);
|
|
13083
|
+
const writeError = await this.writeOperationToStore(input.sourceId, sourceDoc.header.documentType, job.scope, job.branch, operation, job, startTime, stores);
|
|
12996
13084
|
if (writeError !== null) {
|
|
12997
13085
|
return writeError;
|
|
12998
13086
|
}
|
|
@@ -13008,7 +13096,7 @@ class DocumentActionHandler {
|
|
|
13008
13096
|
[job.scope]: scopeState === undefined ? {} : structuredClone(scopeState)
|
|
13009
13097
|
};
|
|
13010
13098
|
const resultingState = JSON.stringify(resultingStateObj);
|
|
13011
|
-
|
|
13099
|
+
stores.writeCache.putState(input.sourceId, job.scope, job.branch, operation.index, sourceDoc);
|
|
13012
13100
|
indexTxn.write([
|
|
13013
13101
|
{
|
|
13014
13102
|
...operation,
|
|
@@ -13022,16 +13110,16 @@ class DocumentActionHandler {
|
|
|
13022
13110
|
if (sourceDoc.header.documentType === "powerhouse/document-drive") {
|
|
13023
13111
|
const collectionId = driveCollectionId(job.branch, input.sourceId);
|
|
13024
13112
|
indexTxn.addToCollection(collectionId, input.targetId);
|
|
13025
|
-
|
|
13113
|
+
stores.collectionMembershipCache.invalidate(input.targetId);
|
|
13026
13114
|
}
|
|
13027
|
-
|
|
13115
|
+
stores.documentMetaCache.putDocumentMeta(input.sourceId, job.branch, {
|
|
13028
13116
|
state: sourceDoc.state.document,
|
|
13029
13117
|
documentType: sourceDoc.header.documentType,
|
|
13030
13118
|
documentScopeRevision: operation.index + 1
|
|
13031
13119
|
});
|
|
13032
13120
|
return buildSuccessResult(job, operation, input.sourceId, sourceDoc.header.documentType, resultingState, startTime);
|
|
13033
13121
|
}
|
|
13034
|
-
async executeRemoveRelationship(job, action, startTime, indexTxn, sourceRemote = "") {
|
|
13122
|
+
async executeRemoveRelationship(job, action, startTime, indexTxn, stores, sourceRemote = "") {
|
|
13035
13123
|
if (job.scope !== "document") {
|
|
13036
13124
|
return buildErrorResult(job, new Error(`REMOVE_RELATIONSHIP must be in "document" scope, got "${job.scope}"`), startTime);
|
|
13037
13125
|
}
|
|
@@ -13041,7 +13129,7 @@ class DocumentActionHandler {
|
|
|
13041
13129
|
}
|
|
13042
13130
|
let sourceDoc;
|
|
13043
13131
|
try {
|
|
13044
|
-
sourceDoc = await
|
|
13132
|
+
sourceDoc = await stores.writeCache.getState(input.sourceId, "document", job.branch);
|
|
13045
13133
|
} catch (error) {
|
|
13046
13134
|
return buildErrorResult(job, new Error(`REMOVE_RELATIONSHIP: source document ${input.sourceId} not found: ${error instanceof Error ? error.message : String(error)}`), startTime);
|
|
13047
13135
|
}
|
|
@@ -13051,7 +13139,7 @@ class DocumentActionHandler {
|
|
|
13051
13139
|
scope: job.scope,
|
|
13052
13140
|
branch: job.branch
|
|
13053
13141
|
});
|
|
13054
|
-
const writeError = await this.writeOperationToStore(input.sourceId, sourceDoc.header.documentType, job.scope, job.branch, operation, job, startTime);
|
|
13142
|
+
const writeError = await this.writeOperationToStore(input.sourceId, sourceDoc.header.documentType, job.scope, job.branch, operation, job, startTime, stores);
|
|
13055
13143
|
if (writeError !== null) {
|
|
13056
13144
|
return writeError;
|
|
13057
13145
|
}
|
|
@@ -13067,7 +13155,7 @@ class DocumentActionHandler {
|
|
|
13067
13155
|
[job.scope]: scopeState === undefined ? {} : structuredClone(scopeState)
|
|
13068
13156
|
};
|
|
13069
13157
|
const resultingState = JSON.stringify(resultingStateObj);
|
|
13070
|
-
|
|
13158
|
+
stores.writeCache.putState(input.sourceId, job.scope, job.branch, operation.index, sourceDoc);
|
|
13071
13159
|
indexTxn.write([
|
|
13072
13160
|
{
|
|
13073
13161
|
...operation,
|
|
@@ -13081,24 +13169,24 @@ class DocumentActionHandler {
|
|
|
13081
13169
|
if (sourceDoc.header.documentType === "powerhouse/document-drive") {
|
|
13082
13170
|
const collectionId = driveCollectionId(job.branch, input.sourceId);
|
|
13083
13171
|
indexTxn.removeFromCollection(collectionId, input.targetId);
|
|
13084
|
-
|
|
13172
|
+
stores.collectionMembershipCache.invalidate(input.targetId);
|
|
13085
13173
|
}
|
|
13086
|
-
|
|
13174
|
+
stores.documentMetaCache.putDocumentMeta(input.sourceId, job.branch, {
|
|
13087
13175
|
state: sourceDoc.state.document,
|
|
13088
13176
|
documentType: sourceDoc.header.documentType,
|
|
13089
13177
|
documentScopeRevision: operation.index + 1
|
|
13090
13178
|
});
|
|
13091
13179
|
return buildSuccessResult(job, operation, input.sourceId, sourceDoc.header.documentType, resultingState, startTime);
|
|
13092
13180
|
}
|
|
13093
|
-
async writeOperationToStore(documentId, documentType, scope, branch, operation, job, startTime) {
|
|
13181
|
+
async writeOperationToStore(documentId, documentType, scope, branch, operation, job, startTime, stores) {
|
|
13094
13182
|
try {
|
|
13095
|
-
await
|
|
13183
|
+
await stores.operationStore.apply(documentId, documentType, scope, branch, operation.index, (txn) => {
|
|
13096
13184
|
txn.addOperations(operation);
|
|
13097
13185
|
});
|
|
13098
13186
|
return null;
|
|
13099
13187
|
} catch (error) {
|
|
13100
13188
|
this.logger.error("Error writing @Operation to IOperationStore: @Error", operation, error);
|
|
13101
|
-
|
|
13189
|
+
stores.writeCache.invalidate(documentId, scope, branch);
|
|
13102
13190
|
return {
|
|
13103
13191
|
job,
|
|
13104
13192
|
success: false,
|
|
@@ -13199,7 +13287,8 @@ class SimpleJobExecutor {
|
|
|
13199
13287
|
config;
|
|
13200
13288
|
signatureVerifierModule;
|
|
13201
13289
|
documentActionHandler;
|
|
13202
|
-
|
|
13290
|
+
executionScope;
|
|
13291
|
+
constructor(logger, registry, operationStore, eventBus, writeCache, operationIndex, documentMetaCache, collectionMembershipCache, config, signatureVerifier, executionScope) {
|
|
13203
13292
|
this.logger = logger;
|
|
13204
13293
|
this.registry = registry;
|
|
13205
13294
|
this.operationStore = operationStore;
|
|
@@ -13216,71 +13305,101 @@ class SimpleJobExecutor {
|
|
|
13216
13305
|
retryMaxDelayMs: config.retryMaxDelayMs ?? 5000
|
|
13217
13306
|
};
|
|
13218
13307
|
this.signatureVerifierModule = new SignatureVerifier(signatureVerifier);
|
|
13219
|
-
this.documentActionHandler = new DocumentActionHandler(
|
|
13308
|
+
this.documentActionHandler = new DocumentActionHandler(registry, logger);
|
|
13309
|
+
this.executionScope = executionScope ?? new DefaultExecutionScope(operationStore, operationIndex, writeCache, documentMetaCache, collectionMembershipCache);
|
|
13220
13310
|
}
|
|
13221
13311
|
async executeJob(job) {
|
|
13222
13312
|
const startTime = Date.now();
|
|
13223
|
-
const
|
|
13224
|
-
|
|
13225
|
-
|
|
13226
|
-
|
|
13227
|
-
|
|
13228
|
-
|
|
13229
|
-
|
|
13313
|
+
const touchedCacheEntries = [];
|
|
13314
|
+
let pendingEvent;
|
|
13315
|
+
let result;
|
|
13316
|
+
try {
|
|
13317
|
+
result = await this.executionScope.run(async (stores) => {
|
|
13318
|
+
const indexTxn = stores.operationIndex.start();
|
|
13319
|
+
if (job.kind === "load") {
|
|
13320
|
+
const loadResult = await this.executeLoadJob(job, startTime, indexTxn, stores);
|
|
13321
|
+
if (loadResult.success && loadResult.operationsWithContext) {
|
|
13322
|
+
for (const owc of loadResult.operationsWithContext) {
|
|
13323
|
+
touchedCacheEntries.push({
|
|
13324
|
+
documentId: owc.context.documentId,
|
|
13325
|
+
scope: owc.context.scope,
|
|
13326
|
+
branch: owc.context.branch
|
|
13327
|
+
});
|
|
13328
|
+
}
|
|
13329
|
+
const ordinals2 = await stores.operationIndex.commit(indexTxn);
|
|
13330
|
+
for (let i = 0;i < loadResult.operationsWithContext.length; i++) {
|
|
13331
|
+
loadResult.operationsWithContext[i].context.ordinal = ordinals2[i];
|
|
13332
|
+
}
|
|
13333
|
+
const collectionMemberships = loadResult.operationsWithContext.length > 0 ? await this.getCollectionMembershipsForOperations(loadResult.operationsWithContext, stores) : {};
|
|
13334
|
+
pendingEvent = {
|
|
13335
|
+
jobId: job.id,
|
|
13336
|
+
operations: loadResult.operationsWithContext,
|
|
13337
|
+
jobMeta: job.meta,
|
|
13338
|
+
collectionMemberships
|
|
13339
|
+
};
|
|
13340
|
+
}
|
|
13341
|
+
return loadResult;
|
|
13230
13342
|
}
|
|
13231
|
-
const
|
|
13232
|
-
|
|
13233
|
-
|
|
13234
|
-
|
|
13235
|
-
|
|
13236
|
-
|
|
13343
|
+
const actionResult = await this.processActions(job, job.actions, startTime, indexTxn, stores);
|
|
13344
|
+
if (!actionResult.success) {
|
|
13345
|
+
return {
|
|
13346
|
+
job,
|
|
13347
|
+
success: false,
|
|
13348
|
+
error: actionResult.error,
|
|
13349
|
+
duration: Date.now() - startTime
|
|
13350
|
+
};
|
|
13351
|
+
}
|
|
13352
|
+
if (actionResult.operationsWithContext.length > 0) {
|
|
13353
|
+
for (const owc of actionResult.operationsWithContext) {
|
|
13354
|
+
touchedCacheEntries.push({
|
|
13355
|
+
documentId: owc.context.documentId,
|
|
13356
|
+
scope: owc.context.scope,
|
|
13357
|
+
branch: owc.context.branch
|
|
13358
|
+
});
|
|
13359
|
+
}
|
|
13360
|
+
}
|
|
13361
|
+
const ordinals = await stores.operationIndex.commit(indexTxn);
|
|
13362
|
+
if (actionResult.operationsWithContext.length > 0) {
|
|
13363
|
+
for (let i = 0;i < actionResult.operationsWithContext.length; i++) {
|
|
13364
|
+
actionResult.operationsWithContext[i].context.ordinal = ordinals[i];
|
|
13365
|
+
}
|
|
13366
|
+
const collectionMemberships = await this.getCollectionMembershipsForOperations(actionResult.operationsWithContext, stores);
|
|
13367
|
+
pendingEvent = {
|
|
13368
|
+
jobId: job.id,
|
|
13369
|
+
operations: actionResult.operationsWithContext,
|
|
13370
|
+
jobMeta: job.meta,
|
|
13371
|
+
collectionMemberships
|
|
13372
|
+
};
|
|
13373
|
+
}
|
|
13374
|
+
return {
|
|
13375
|
+
job,
|
|
13376
|
+
success: true,
|
|
13377
|
+
operations: actionResult.generatedOperations,
|
|
13378
|
+
operationsWithContext: actionResult.operationsWithContext,
|
|
13379
|
+
duration: Date.now() - startTime
|
|
13237
13380
|
};
|
|
13238
|
-
|
|
13239
|
-
|
|
13240
|
-
|
|
13381
|
+
});
|
|
13382
|
+
} catch (error) {
|
|
13383
|
+
for (const entry of touchedCacheEntries) {
|
|
13384
|
+
this.writeCache.invalidate(entry.documentId, entry.scope, entry.branch);
|
|
13385
|
+
this.documentMetaCache.invalidate(entry.documentId, entry.branch);
|
|
13241
13386
|
}
|
|
13242
|
-
|
|
13243
|
-
}
|
|
13244
|
-
const result = await this.processActions(job, job.actions, startTime, indexTxn);
|
|
13245
|
-
if (!result.success) {
|
|
13246
|
-
return {
|
|
13247
|
-
job,
|
|
13248
|
-
success: false,
|
|
13249
|
-
error: result.error,
|
|
13250
|
-
duration: Date.now() - startTime
|
|
13251
|
-
};
|
|
13387
|
+
throw error;
|
|
13252
13388
|
}
|
|
13253
|
-
|
|
13254
|
-
|
|
13255
|
-
|
|
13256
|
-
result.operationsWithContext[i].context.ordinal = ordinals[i];
|
|
13257
|
-
}
|
|
13258
|
-
const collectionMemberships = await this.getCollectionMembershipsForOperations(result.operationsWithContext);
|
|
13259
|
-
const event = {
|
|
13260
|
-
jobId: job.id,
|
|
13261
|
-
operations: result.operationsWithContext,
|
|
13262
|
-
jobMeta: job.meta,
|
|
13263
|
-
collectionMemberships
|
|
13264
|
-
};
|
|
13265
|
-
this.eventBus.emit(ReactorEventTypes.JOB_WRITE_READY, event).catch((error) => {
|
|
13266
|
-
this.logger.error("Failed to emit JOB_WRITE_READY event: @Event : @Error", event, error);
|
|
13389
|
+
if (pendingEvent) {
|
|
13390
|
+
this.eventBus.emit(ReactorEventTypes.JOB_WRITE_READY, pendingEvent).catch((error) => {
|
|
13391
|
+
this.logger.error("Failed to emit JOB_WRITE_READY event: @Event : @Error", pendingEvent, error);
|
|
13267
13392
|
});
|
|
13268
13393
|
}
|
|
13269
|
-
return
|
|
13270
|
-
job,
|
|
13271
|
-
success: true,
|
|
13272
|
-
operations: result.generatedOperations,
|
|
13273
|
-
operationsWithContext: result.operationsWithContext,
|
|
13274
|
-
duration: Date.now() - startTime
|
|
13275
|
-
};
|
|
13394
|
+
return result;
|
|
13276
13395
|
}
|
|
13277
|
-
async getCollectionMembershipsForOperations(operations) {
|
|
13396
|
+
async getCollectionMembershipsForOperations(operations, stores) {
|
|
13278
13397
|
const documentIds = [
|
|
13279
13398
|
...new Set(operations.map((op) => op.context.documentId))
|
|
13280
13399
|
];
|
|
13281
|
-
return
|
|
13400
|
+
return stores.collectionMembershipCache.getCollectionsForDocuments(documentIds);
|
|
13282
13401
|
}
|
|
13283
|
-
async processActions(job, actions2, startTime, indexTxn, skipValues, sourceOperations, sourceRemote = "") {
|
|
13402
|
+
async processActions(job, actions2, startTime, indexTxn, stores, skipValues, sourceOperations, sourceRemote = "") {
|
|
13284
13403
|
const generatedOperations = [];
|
|
13285
13404
|
const operationsWithContext = [];
|
|
13286
13405
|
try {
|
|
@@ -13298,7 +13417,7 @@ class SimpleJobExecutor {
|
|
|
13298
13417
|
const skip = skipValues?.[actionIndex] ?? 0;
|
|
13299
13418
|
const sourceOperation = sourceOperations?.[actionIndex];
|
|
13300
13419
|
const isDocumentAction = documentScopeActions.includes(action.type);
|
|
13301
|
-
const result = isDocumentAction ? await this.documentActionHandler.execute(job, action, startTime, indexTxn, skip, sourceRemote) : await this.executeRegularAction(job, action, startTime, indexTxn, skip, sourceOperation, sourceRemote);
|
|
13420
|
+
const result = isDocumentAction ? await this.documentActionHandler.execute(job, action, startTime, indexTxn, stores, skip, sourceRemote) : await this.executeRegularAction(job, action, startTime, indexTxn, stores, skip, sourceOperation, sourceRemote);
|
|
13302
13421
|
const error = this.accumulateResultOrReturnError(result, generatedOperations, operationsWithContext);
|
|
13303
13422
|
if (error !== null) {
|
|
13304
13423
|
return {
|
|
@@ -13315,10 +13434,10 @@ class SimpleJobExecutor {
|
|
|
13315
13434
|
operationsWithContext
|
|
13316
13435
|
};
|
|
13317
13436
|
}
|
|
13318
|
-
async executeRegularAction(job, action, startTime, indexTxn, skip = 0, sourceOperation, sourceRemote = "") {
|
|
13437
|
+
async executeRegularAction(job, action, startTime, indexTxn, stores, skip = 0, sourceOperation, sourceRemote = "") {
|
|
13319
13438
|
let docMeta;
|
|
13320
13439
|
try {
|
|
13321
|
-
docMeta = await
|
|
13440
|
+
docMeta = await stores.documentMetaCache.getDocumentMeta(job.documentId, job.branch);
|
|
13322
13441
|
} catch (error) {
|
|
13323
13442
|
return buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
|
|
13324
13443
|
}
|
|
@@ -13326,11 +13445,11 @@ class SimpleJobExecutor {
|
|
|
13326
13445
|
return buildErrorResult(job, new DocumentDeletedError(job.documentId, docMeta.state.deletedAtUtcIso), startTime);
|
|
13327
13446
|
}
|
|
13328
13447
|
if (isUndoRedo(action) || action.type === "PRUNE" || action.type === "NOOP" && skip > 0) {
|
|
13329
|
-
|
|
13448
|
+
stores.writeCache.invalidate(job.documentId, job.scope, job.branch);
|
|
13330
13449
|
}
|
|
13331
13450
|
let document;
|
|
13332
13451
|
try {
|
|
13333
|
-
document = await
|
|
13452
|
+
document = await stores.writeCache.getState(job.documentId, job.scope, job.branch);
|
|
13334
13453
|
} catch (error) {
|
|
13335
13454
|
return buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
|
|
13336
13455
|
}
|
|
@@ -13381,12 +13500,12 @@ ${error.stack}`;
|
|
|
13381
13500
|
header: updatedDocument.header
|
|
13382
13501
|
});
|
|
13383
13502
|
try {
|
|
13384
|
-
await
|
|
13503
|
+
await stores.operationStore.apply(job.documentId, document.header.documentType, scope, job.branch, newOperation.index, (txn) => {
|
|
13385
13504
|
txn.addOperations(newOperation);
|
|
13386
13505
|
});
|
|
13387
13506
|
} catch (error) {
|
|
13388
13507
|
this.logger.error("Error writing @Operation to IOperationStore: @Error", newOperation, error);
|
|
13389
|
-
|
|
13508
|
+
stores.writeCache.invalidate(job.documentId, scope, job.branch);
|
|
13390
13509
|
return {
|
|
13391
13510
|
job,
|
|
13392
13511
|
success: false,
|
|
@@ -13398,7 +13517,7 @@ ${error.stack}`;
|
|
|
13398
13517
|
...updatedDocument.header.revision,
|
|
13399
13518
|
[scope]: newOperation.index + 1
|
|
13400
13519
|
};
|
|
13401
|
-
|
|
13520
|
+
stores.writeCache.putState(job.documentId, scope, job.branch, newOperation.index, updatedDocument);
|
|
13402
13521
|
indexTxn.write([
|
|
13403
13522
|
{
|
|
13404
13523
|
...newOperation,
|
|
@@ -13429,13 +13548,13 @@ ${error.stack}`;
|
|
|
13429
13548
|
duration: Date.now() - startTime
|
|
13430
13549
|
};
|
|
13431
13550
|
}
|
|
13432
|
-
async executeLoadJob(job, startTime, indexTxn) {
|
|
13551
|
+
async executeLoadJob(job, startTime, indexTxn, stores) {
|
|
13433
13552
|
if (job.operations.length === 0) {
|
|
13434
13553
|
return buildErrorResult(job, new Error("Load job must include at least one operation"), startTime);
|
|
13435
13554
|
}
|
|
13436
13555
|
let docMeta;
|
|
13437
13556
|
try {
|
|
13438
|
-
docMeta = await
|
|
13557
|
+
docMeta = await stores.documentMetaCache.getDocumentMeta(job.documentId, job.branch);
|
|
13439
13558
|
} catch {}
|
|
13440
13559
|
if (docMeta?.state.isDeleted) {
|
|
13441
13560
|
return buildErrorResult(job, new DocumentDeletedError(job.documentId, docMeta.state.deletedAtUtcIso), startTime);
|
|
@@ -13443,7 +13562,7 @@ ${error.stack}`;
|
|
|
13443
13562
|
const scope = job.scope;
|
|
13444
13563
|
let latestRevision = 0;
|
|
13445
13564
|
try {
|
|
13446
|
-
const revisions = await
|
|
13565
|
+
const revisions = await stores.operationStore.getRevisions(job.documentId, job.branch);
|
|
13447
13566
|
latestRevision = revisions.revision[scope] ?? 0;
|
|
13448
13567
|
} catch {
|
|
13449
13568
|
latestRevision = 0;
|
|
@@ -13459,7 +13578,7 @@ ${error.stack}`;
|
|
|
13459
13578
|
}
|
|
13460
13579
|
let conflictingOps = [];
|
|
13461
13580
|
try {
|
|
13462
|
-
const conflictingResult = await
|
|
13581
|
+
const conflictingResult = await stores.operationStore.getConflicting(job.documentId, scope, job.branch, minIncomingTimestamp);
|
|
13463
13582
|
conflictingOps = conflictingResult.results;
|
|
13464
13583
|
} catch {
|
|
13465
13584
|
conflictingOps = [];
|
|
@@ -13468,7 +13587,7 @@ ${error.stack}`;
|
|
|
13468
13587
|
if (conflictingOps.length > 0) {
|
|
13469
13588
|
const minConflictingIndex = Math.min(...conflictingOps.map((op) => op.index));
|
|
13470
13589
|
try {
|
|
13471
|
-
const allOpsResult = await
|
|
13590
|
+
const allOpsResult = await stores.operationStore.getSince(job.documentId, scope, job.branch, minConflictingIndex - 1);
|
|
13472
13591
|
allOpsFromMinConflictingIndex = allOpsResult.results;
|
|
13473
13592
|
} catch {
|
|
13474
13593
|
allOpsFromMinConflictingIndex = conflictingOps;
|
|
@@ -13529,7 +13648,7 @@ ${error.stack}`;
|
|
|
13529
13648
|
const actions2 = reshuffledOperations.map((operation) => operation.action);
|
|
13530
13649
|
const skipValues = reshuffledOperations.map((operation) => operation.skip);
|
|
13531
13650
|
const effectiveSourceRemote = skipCount > 0 ? "" : job.meta.sourceRemote || "";
|
|
13532
|
-
const result = await this.processActions(job, actions2, startTime, indexTxn, skipValues, reshuffledOperations, effectiveSourceRemote);
|
|
13651
|
+
const result = await this.processActions(job, actions2, startTime, indexTxn, stores, skipValues, reshuffledOperations, effectiveSourceRemote);
|
|
13533
13652
|
if (!result.success) {
|
|
13534
13653
|
return {
|
|
13535
13654
|
job,
|
|
@@ -13538,9 +13657,9 @@ ${error.stack}`;
|
|
|
13538
13657
|
duration: Date.now() - startTime
|
|
13539
13658
|
};
|
|
13540
13659
|
}
|
|
13541
|
-
|
|
13660
|
+
stores.writeCache.invalidate(job.documentId, scope, job.branch);
|
|
13542
13661
|
if (scope === "document") {
|
|
13543
|
-
|
|
13662
|
+
stores.documentMetaCache.invalidate(job.documentId, job.branch);
|
|
13544
13663
|
}
|
|
13545
13664
|
return {
|
|
13546
13665
|
job,
|
|
@@ -15456,14 +15575,23 @@ class KyselyDocumentIndexer extends BaseReadModel {
|
|
|
15456
15575
|
// src/storage/kysely/keyframe-store.ts
|
|
15457
15576
|
class KyselyKeyframeStore {
|
|
15458
15577
|
db;
|
|
15578
|
+
trx;
|
|
15459
15579
|
constructor(db) {
|
|
15460
15580
|
this.db = db;
|
|
15461
15581
|
}
|
|
15582
|
+
get queryExecutor() {
|
|
15583
|
+
return this.trx ?? this.db;
|
|
15584
|
+
}
|
|
15585
|
+
withTransaction(trx) {
|
|
15586
|
+
const instance = new KyselyKeyframeStore(this.db);
|
|
15587
|
+
instance.trx = trx;
|
|
15588
|
+
return instance;
|
|
15589
|
+
}
|
|
15462
15590
|
async putKeyframe(documentId, scope, branch, revision, document, signal) {
|
|
15463
15591
|
if (signal?.aborted) {
|
|
15464
15592
|
throw new Error("Operation aborted");
|
|
15465
15593
|
}
|
|
15466
|
-
await this.
|
|
15594
|
+
await this.queryExecutor.insertInto("Keyframe").values({
|
|
15467
15595
|
documentId,
|
|
15468
15596
|
documentType: document.header.documentType,
|
|
15469
15597
|
scope,
|
|
@@ -15476,7 +15604,7 @@ class KyselyKeyframeStore {
|
|
|
15476
15604
|
if (signal?.aborted) {
|
|
15477
15605
|
throw new Error("Operation aborted");
|
|
15478
15606
|
}
|
|
15479
|
-
const row = await this.
|
|
15607
|
+
const row = await this.queryExecutor.selectFrom("Keyframe").selectAll().where("documentId", "=", documentId).where("scope", "=", scope).where("branch", "=", branch).where("revision", "<=", targetRevision).orderBy("revision", "desc").limit(1).executeTakeFirst();
|
|
15480
15608
|
if (!row) {
|
|
15481
15609
|
return;
|
|
15482
15610
|
}
|
|
@@ -15485,11 +15613,30 @@ class KyselyKeyframeStore {
|
|
|
15485
15613
|
document: row.document
|
|
15486
15614
|
};
|
|
15487
15615
|
}
|
|
15616
|
+
async listKeyframes(documentId, scope, branch, signal) {
|
|
15617
|
+
if (signal?.aborted) {
|
|
15618
|
+
throw new Error("Operation aborted");
|
|
15619
|
+
}
|
|
15620
|
+
let query = this.queryExecutor.selectFrom("Keyframe").selectAll().where("documentId", "=", documentId).orderBy("revision", "asc");
|
|
15621
|
+
if (scope !== undefined) {
|
|
15622
|
+
query = query.where("scope", "=", scope);
|
|
15623
|
+
}
|
|
15624
|
+
if (branch !== undefined) {
|
|
15625
|
+
query = query.where("branch", "=", branch);
|
|
15626
|
+
}
|
|
15627
|
+
const rows = await query.execute();
|
|
15628
|
+
return rows.map((row) => ({
|
|
15629
|
+
scope: row.scope,
|
|
15630
|
+
branch: row.branch,
|
|
15631
|
+
revision: row.revision,
|
|
15632
|
+
document: row.document
|
|
15633
|
+
}));
|
|
15634
|
+
}
|
|
15488
15635
|
async deleteKeyframes(documentId, scope, branch, signal) {
|
|
15489
15636
|
if (signal?.aborted) {
|
|
15490
15637
|
throw new Error("Operation aborted");
|
|
15491
15638
|
}
|
|
15492
|
-
let query = this.
|
|
15639
|
+
let query = this.queryExecutor.deleteFrom("Keyframe").where("documentId", "=", documentId);
|
|
15493
15640
|
if (scope !== undefined && branch !== undefined) {
|
|
15494
15641
|
query = query.where("scope", "=", scope).where("branch", "=", branch);
|
|
15495
15642
|
} else if (scope !== undefined) {
|
|
@@ -15564,48 +15711,64 @@ class AtomicTransaction {
|
|
|
15564
15711
|
// src/storage/kysely/store.ts
|
|
15565
15712
|
class KyselyOperationStore {
|
|
15566
15713
|
db;
|
|
15714
|
+
trx;
|
|
15567
15715
|
constructor(db) {
|
|
15568
15716
|
this.db = db;
|
|
15569
15717
|
}
|
|
15718
|
+
get queryExecutor() {
|
|
15719
|
+
return this.trx ?? this.db;
|
|
15720
|
+
}
|
|
15721
|
+
withTransaction(trx) {
|
|
15722
|
+
const instance = new KyselyOperationStore(this.db);
|
|
15723
|
+
instance.trx = trx;
|
|
15724
|
+
return instance;
|
|
15725
|
+
}
|
|
15570
15726
|
async apply(documentId, documentType, scope, branch, revision, fn, signal) {
|
|
15571
|
-
|
|
15572
|
-
|
|
15573
|
-
|
|
15727
|
+
if (this.trx) {
|
|
15728
|
+
await this.executeApply(this.trx, documentId, documentType, scope, branch, revision, fn, signal);
|
|
15729
|
+
} else {
|
|
15730
|
+
await this.db.transaction().execute(async (trx) => {
|
|
15731
|
+
await this.executeApply(trx, documentId, documentType, scope, branch, revision, fn, signal);
|
|
15732
|
+
});
|
|
15733
|
+
}
|
|
15734
|
+
}
|
|
15735
|
+
async executeApply(trx, documentId, documentType, scope, branch, revision, fn, signal) {
|
|
15736
|
+
if (signal?.aborted) {
|
|
15737
|
+
throw new Error("Operation aborted");
|
|
15738
|
+
}
|
|
15739
|
+
const latestOp = await trx.selectFrom("Operation").selectAll().where("documentId", "=", documentId).where("scope", "=", scope).where("branch", "=", branch).orderBy("index", "desc").limit(1).executeTakeFirst();
|
|
15740
|
+
const currentRevision = latestOp ? latestOp.index : -1;
|
|
15741
|
+
if (currentRevision !== revision - 1) {
|
|
15742
|
+
throw new RevisionMismatchError(currentRevision + 1, revision);
|
|
15743
|
+
}
|
|
15744
|
+
const atomicTxn = new AtomicTransaction(documentId, documentType, scope, branch, revision);
|
|
15745
|
+
await fn(atomicTxn);
|
|
15746
|
+
const operations = atomicTxn.getOperations();
|
|
15747
|
+
if (operations.length > 0) {
|
|
15748
|
+
let prevOpId = latestOp?.opId || "";
|
|
15749
|
+
for (const op of operations) {
|
|
15750
|
+
op.prevOpId = prevOpId;
|
|
15751
|
+
prevOpId = op.opId;
|
|
15574
15752
|
}
|
|
15575
|
-
|
|
15576
|
-
|
|
15577
|
-
|
|
15578
|
-
|
|
15579
|
-
|
|
15580
|
-
|
|
15581
|
-
|
|
15582
|
-
const operations = atomicTxn.getOperations();
|
|
15583
|
-
if (operations.length > 0) {
|
|
15584
|
-
let prevOpId = latestOp?.opId || "";
|
|
15585
|
-
for (const op of operations) {
|
|
15586
|
-
op.prevOpId = prevOpId;
|
|
15587
|
-
prevOpId = op.opId;
|
|
15588
|
-
}
|
|
15589
|
-
try {
|
|
15590
|
-
await trx.insertInto("Operation").values(operations).execute();
|
|
15591
|
-
} catch (error) {
|
|
15592
|
-
if (error instanceof Error) {
|
|
15593
|
-
if (error.message.includes("unique constraint")) {
|
|
15594
|
-
const op = operations[0];
|
|
15595
|
-
throw new DuplicateOperationError(`${op.opId} at index ${op.index} with skip ${op.skip}`);
|
|
15596
|
-
}
|
|
15597
|
-
throw error;
|
|
15753
|
+
try {
|
|
15754
|
+
await trx.insertInto("Operation").values(operations).execute();
|
|
15755
|
+
} catch (error) {
|
|
15756
|
+
if (error instanceof Error) {
|
|
15757
|
+
if (error.message.includes("unique constraint")) {
|
|
15758
|
+
const op = operations[0];
|
|
15759
|
+
throw new DuplicateOperationError(`${op.opId} at index ${op.index} with skip ${op.skip}`);
|
|
15598
15760
|
}
|
|
15599
15761
|
throw error;
|
|
15600
15762
|
}
|
|
15763
|
+
throw error;
|
|
15601
15764
|
}
|
|
15602
|
-
}
|
|
15765
|
+
}
|
|
15603
15766
|
}
|
|
15604
15767
|
async getSince(documentId, scope, branch, revision, filter, paging, signal) {
|
|
15605
15768
|
if (signal?.aborted) {
|
|
15606
15769
|
throw new Error("Operation aborted");
|
|
15607
15770
|
}
|
|
15608
|
-
let query = this.
|
|
15771
|
+
let query = this.queryExecutor.selectFrom("Operation").selectAll().where("documentId", "=", documentId).where("scope", "=", scope).where("branch", "=", branch).where("index", ">", revision).orderBy("index", "asc");
|
|
15609
15772
|
if (filter) {
|
|
15610
15773
|
if (filter.actionTypes && filter.actionTypes.length > 0) {
|
|
15611
15774
|
const actionTypesArray = filter.actionTypes.map((t) => `'${t.replace(/'/g, "''")}'`).join(",");
|
|
@@ -15652,7 +15815,7 @@ class KyselyOperationStore {
|
|
|
15652
15815
|
if (signal?.aborted) {
|
|
15653
15816
|
throw new Error("Operation aborted");
|
|
15654
15817
|
}
|
|
15655
|
-
let query = this.
|
|
15818
|
+
let query = this.queryExecutor.selectFrom("Operation").selectAll().where("id", ">", id).orderBy("id", "asc");
|
|
15656
15819
|
if (paging) {
|
|
15657
15820
|
const cursorValue = Number.parseInt(paging.cursor, 10);
|
|
15658
15821
|
if (cursorValue > 0) {
|
|
@@ -15684,7 +15847,7 @@ class KyselyOperationStore {
|
|
|
15684
15847
|
if (signal?.aborted) {
|
|
15685
15848
|
throw new Error("Operation aborted");
|
|
15686
15849
|
}
|
|
15687
|
-
let query = this.
|
|
15850
|
+
let query = this.queryExecutor.selectFrom("Operation").selectAll().where("documentId", "=", documentId).where("scope", "=", scope).where("branch", "=", branch).where("timestampUtcMs", ">=", new Date(minTimestamp)).orderBy("index", "asc");
|
|
15688
15851
|
if (paging) {
|
|
15689
15852
|
const cursorValue = Number.parseInt(paging.cursor, 10);
|
|
15690
15853
|
if (cursorValue > 0) {
|
|
@@ -15716,7 +15879,7 @@ class KyselyOperationStore {
|
|
|
15716
15879
|
if (signal?.aborted) {
|
|
15717
15880
|
throw new Error("Operation aborted");
|
|
15718
15881
|
}
|
|
15719
|
-
const scopeRevisions = await this.
|
|
15882
|
+
const scopeRevisions = await this.queryExecutor.selectFrom("Operation as o1").select(["o1.scope", "o1.index", "o1.timestampUtcMs"]).where("o1.documentId", "=", documentId).where("o1.branch", "=", branch).where((eb) => eb("o1.index", "=", eb.selectFrom("Operation as o2").select((eb2) => eb2.fn.max("o2.index").as("maxIndex")).where("o2.documentId", "=", eb.ref("o1.documentId")).where("o2.branch", "=", eb.ref("o1.branch")).where("o2.scope", "=", eb.ref("o1.scope")))).execute();
|
|
15720
15883
|
const revision = {};
|
|
15721
15884
|
let latestTimestamp = new Date(0).toISOString();
|
|
15722
15885
|
for (const row of scopeRevisions) {
|
|
@@ -19755,9 +19918,10 @@ class ReactorBuilder {
|
|
|
19755
19918
|
});
|
|
19756
19919
|
await documentMetaCache.startup();
|
|
19757
19920
|
const collectionMembershipCache = new CollectionMembershipCache(operationIndex);
|
|
19921
|
+
const executionScope = new KyselyExecutionScope(database, operationStore, operationIndex, keyframeStore, writeCache, documentMetaCache, collectionMembershipCache);
|
|
19758
19922
|
let executorManager = this.executorManager;
|
|
19759
19923
|
if (!executorManager) {
|
|
19760
|
-
executorManager = new SimpleJobExecutorManager(() => new SimpleJobExecutor(this.logger, documentModelRegistry, operationStore, eventBus, writeCache, operationIndex, documentMetaCache, collectionMembershipCache, this.executorConfig, this.signatureVerifier), eventBus, queue, jobTracker, this.logger, resolver);
|
|
19924
|
+
executorManager = new SimpleJobExecutorManager(() => new SimpleJobExecutor(this.logger, documentModelRegistry, operationStore, eventBus, writeCache, operationIndex, documentMetaCache, collectionMembershipCache, this.executorConfig, this.signatureVerifier, executionScope), eventBus, queue, jobTracker, this.logger, resolver);
|
|
19761
19925
|
}
|
|
19762
19926
|
await executorManager.start(this.executorConfig.maxConcurrency ?? 1);
|
|
19763
19927
|
const readModelInstances = Array.from(new Set([...this.readModels]));
|
|
@@ -19889,6 +20053,7 @@ class ReactorClientBuilder {
|
|
|
19889
20053
|
signatureVerifier;
|
|
19890
20054
|
subscriptionManager;
|
|
19891
20055
|
jobAwaiter;
|
|
20056
|
+
documentModelLoader;
|
|
19892
20057
|
withLogger(logger) {
|
|
19893
20058
|
this.logger = logger;
|
|
19894
20059
|
return this;
|
|
@@ -19927,6 +20092,10 @@ class ReactorClientBuilder {
|
|
|
19927
20092
|
this.jobAwaiter = jobAwaiter;
|
|
19928
20093
|
return this;
|
|
19929
20094
|
}
|
|
20095
|
+
withDocumentModelLoader(loader) {
|
|
20096
|
+
this.documentModelLoader = loader;
|
|
20097
|
+
return this;
|
|
20098
|
+
}
|
|
19930
20099
|
async build() {
|
|
19931
20100
|
const module = await this.buildModule();
|
|
19932
20101
|
return module.client;
|
|
@@ -19944,6 +20113,9 @@ class ReactorClientBuilder {
|
|
|
19944
20113
|
if (this.signatureVerifier) {
|
|
19945
20114
|
this.reactorBuilder.withSignatureVerifier(this.signatureVerifier);
|
|
19946
20115
|
}
|
|
20116
|
+
if (this.documentModelLoader) {
|
|
20117
|
+
this.reactorBuilder.withDocumentModelLoader(this.documentModelLoader);
|
|
20118
|
+
}
|
|
19947
20119
|
reactorModule = await this.reactorBuilder.buildModule();
|
|
19948
20120
|
reactor = reactorModule.reactor;
|
|
19949
20121
|
eventBus = reactorModule.eventBus;
|
|
@@ -20126,6 +20298,126 @@ class RelationalDbProcessor {
|
|
|
20126
20298
|
return relationalDbToQueryBuilder(this.relationalDb);
|
|
20127
20299
|
}
|
|
20128
20300
|
}
|
|
20301
|
+
// src/admin/document-integrity-service.ts
|
|
20302
|
+
import { hashDocumentStateForScope } from "document-model/core";
|
|
20303
|
+
var nullKeyframeStore = {
|
|
20304
|
+
putKeyframe: () => Promise.resolve(),
|
|
20305
|
+
findNearestKeyframe: () => Promise.resolve(undefined),
|
|
20306
|
+
listKeyframes: () => Promise.resolve([]),
|
|
20307
|
+
deleteKeyframes: () => Promise.resolve(0)
|
|
20308
|
+
};
|
|
20309
|
+
|
|
20310
|
+
class DocumentIntegrityService {
|
|
20311
|
+
keyframeStore;
|
|
20312
|
+
operationStore;
|
|
20313
|
+
writeCache;
|
|
20314
|
+
documentView;
|
|
20315
|
+
documentModelRegistry;
|
|
20316
|
+
constructor(keyframeStore, operationStore, writeCache, documentView, documentModelRegistry) {
|
|
20317
|
+
this.keyframeStore = keyframeStore;
|
|
20318
|
+
this.operationStore = operationStore;
|
|
20319
|
+
this.writeCache = writeCache;
|
|
20320
|
+
this.documentView = documentView;
|
|
20321
|
+
this.documentModelRegistry = documentModelRegistry;
|
|
20322
|
+
}
|
|
20323
|
+
async validateDocument(documentId, branch = "main", signal) {
|
|
20324
|
+
const keyframeIssues = [];
|
|
20325
|
+
const snapshotIssues = [];
|
|
20326
|
+
const replayCache = new KyselyWriteCache(nullKeyframeStore, this.operationStore, this.documentModelRegistry, {
|
|
20327
|
+
maxDocuments: 1,
|
|
20328
|
+
ringBufferSize: 1,
|
|
20329
|
+
keyframeInterval: Number.MAX_SAFE_INTEGER
|
|
20330
|
+
});
|
|
20331
|
+
const keyframes = await this.keyframeStore.listKeyframes(documentId, undefined, branch, signal);
|
|
20332
|
+
for (const keyframe of keyframes) {
|
|
20333
|
+
if (signal?.aborted) {
|
|
20334
|
+
throw new Error("Operation aborted");
|
|
20335
|
+
}
|
|
20336
|
+
replayCache.invalidate(documentId, keyframe.scope, branch);
|
|
20337
|
+
const replayedDoc = await replayCache.getState(documentId, keyframe.scope, branch, keyframe.revision, signal);
|
|
20338
|
+
const kfHash = hashDocumentStateForScope(keyframe.document, keyframe.scope);
|
|
20339
|
+
const replayHash = hashDocumentStateForScope(replayedDoc, keyframe.scope);
|
|
20340
|
+
if (kfHash !== replayHash) {
|
|
20341
|
+
keyframeIssues.push({
|
|
20342
|
+
scope: keyframe.scope,
|
|
20343
|
+
branch,
|
|
20344
|
+
revision: keyframe.revision,
|
|
20345
|
+
keyframeHash: kfHash,
|
|
20346
|
+
replayedHash: replayHash
|
|
20347
|
+
});
|
|
20348
|
+
}
|
|
20349
|
+
}
|
|
20350
|
+
let currentDoc;
|
|
20351
|
+
try {
|
|
20352
|
+
currentDoc = await this.documentView.get(documentId);
|
|
20353
|
+
} catch {
|
|
20354
|
+
return {
|
|
20355
|
+
documentId,
|
|
20356
|
+
isConsistent: keyframeIssues.length === 0,
|
|
20357
|
+
keyframeIssues,
|
|
20358
|
+
snapshotIssues
|
|
20359
|
+
};
|
|
20360
|
+
}
|
|
20361
|
+
const revisions = await this.operationStore.getRevisions(documentId, branch, signal);
|
|
20362
|
+
const allScopes = Object.keys(revisions.revision);
|
|
20363
|
+
for (const scope of allScopes) {
|
|
20364
|
+
if (scope === "document")
|
|
20365
|
+
continue;
|
|
20366
|
+
replayCache.invalidate(documentId, scope, branch);
|
|
20367
|
+
let replayedDoc;
|
|
20368
|
+
try {
|
|
20369
|
+
replayedDoc = await replayCache.getState(documentId, scope, branch, undefined, signal);
|
|
20370
|
+
} catch {
|
|
20371
|
+
if (signal?.aborted) {
|
|
20372
|
+
throw new Error("Operation aborted");
|
|
20373
|
+
}
|
|
20374
|
+
continue;
|
|
20375
|
+
}
|
|
20376
|
+
const snapshotHash = hashDocumentStateForScope(currentDoc, scope);
|
|
20377
|
+
const replayHash = hashDocumentStateForScope(replayedDoc, scope);
|
|
20378
|
+
if (snapshotHash !== replayHash) {
|
|
20379
|
+
snapshotIssues.push({
|
|
20380
|
+
scope,
|
|
20381
|
+
branch,
|
|
20382
|
+
snapshotHash,
|
|
20383
|
+
replayedHash: replayHash
|
|
20384
|
+
});
|
|
20385
|
+
}
|
|
20386
|
+
}
|
|
20387
|
+
return {
|
|
20388
|
+
documentId,
|
|
20389
|
+
isConsistent: keyframeIssues.length === 0 && snapshotIssues.length === 0,
|
|
20390
|
+
keyframeIssues,
|
|
20391
|
+
snapshotIssues
|
|
20392
|
+
};
|
|
20393
|
+
}
|
|
20394
|
+
async rebuildKeyframes(documentId, branch = "main", signal) {
|
|
20395
|
+
const deleted = await this.keyframeStore.deleteKeyframes(documentId, undefined, branch, signal);
|
|
20396
|
+
return {
|
|
20397
|
+
documentId,
|
|
20398
|
+
keyframesDeleted: deleted,
|
|
20399
|
+
scopesInvalidated: 0
|
|
20400
|
+
};
|
|
20401
|
+
}
|
|
20402
|
+
async rebuildSnapshots(documentId, branch = "main", signal) {
|
|
20403
|
+
const scopes = await this.discoverScopes(documentId, branch, signal);
|
|
20404
|
+
for (const scope of scopes) {
|
|
20405
|
+
if (signal?.aborted) {
|
|
20406
|
+
throw new Error("Operation aborted");
|
|
20407
|
+
}
|
|
20408
|
+
this.writeCache.invalidate(documentId, scope, branch);
|
|
20409
|
+
}
|
|
20410
|
+
return {
|
|
20411
|
+
documentId,
|
|
20412
|
+
keyframesDeleted: 0,
|
|
20413
|
+
scopesInvalidated: scopes.length
|
|
20414
|
+
};
|
|
20415
|
+
}
|
|
20416
|
+
async discoverScopes(documentId, branch, signal) {
|
|
20417
|
+
const revisions = await this.operationStore.getRevisions(documentId, branch, signal);
|
|
20418
|
+
return Object.keys(revisions.revision);
|
|
20419
|
+
}
|
|
20420
|
+
}
|
|
20129
20421
|
export {
|
|
20130
20422
|
upgradeDocumentAction,
|
|
20131
20423
|
trimMailboxFromAckOrdinal,
|
|
@@ -20197,6 +20489,7 @@ export {
|
|
|
20197
20489
|
DuplicateOperationError,
|
|
20198
20490
|
DuplicateModuleError,
|
|
20199
20491
|
DocumentModelRegistry,
|
|
20492
|
+
DocumentIntegrityService,
|
|
20200
20493
|
DocumentChangeType,
|
|
20201
20494
|
DefaultSubscriptionErrorHandler,
|
|
20202
20495
|
ConsoleLogger,
|