@powerhousedao/reactor 6.0.0-dev.83 → 6.0.0-dev.88
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/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.js +350 -203
- package/dist/src/processors/index.d.ts +0 -1
- package/dist/src/processors/index.d.ts.map +1 -1
- package/dist/src/storage/kysely/keyframe-store.d.ts +4 -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)) {
|
|
@@ -11885,6 +11911,12 @@ class KyselyWriteCache {
|
|
|
11885
11911
|
this.streams = new Map;
|
|
11886
11912
|
this.lruTracker = new LRUTracker;
|
|
11887
11913
|
}
|
|
11914
|
+
withScopedStores(operationStore, keyframeStore) {
|
|
11915
|
+
const scoped = new KyselyWriteCache(keyframeStore, operationStore, this.registry, this.config);
|
|
11916
|
+
scoped.streams = this.streams;
|
|
11917
|
+
scoped.lruTracker = this.lruTracker;
|
|
11918
|
+
return scoped;
|
|
11919
|
+
}
|
|
11888
11920
|
async startup() {
|
|
11889
11921
|
return Promise.resolve();
|
|
11890
11922
|
}
|
|
@@ -12217,6 +12249,64 @@ class EventBus {
|
|
|
12217
12249
|
}
|
|
12218
12250
|
}
|
|
12219
12251
|
|
|
12252
|
+
// src/executor/execution-scope.ts
|
|
12253
|
+
class DefaultExecutionScope {
|
|
12254
|
+
operationStore;
|
|
12255
|
+
operationIndex;
|
|
12256
|
+
writeCache;
|
|
12257
|
+
documentMetaCache;
|
|
12258
|
+
collectionMembershipCache;
|
|
12259
|
+
constructor(operationStore, operationIndex, writeCache, documentMetaCache, collectionMembershipCache) {
|
|
12260
|
+
this.operationStore = operationStore;
|
|
12261
|
+
this.operationIndex = operationIndex;
|
|
12262
|
+
this.writeCache = writeCache;
|
|
12263
|
+
this.documentMetaCache = documentMetaCache;
|
|
12264
|
+
this.collectionMembershipCache = collectionMembershipCache;
|
|
12265
|
+
}
|
|
12266
|
+
async run(fn) {
|
|
12267
|
+
return fn({
|
|
12268
|
+
operationStore: this.operationStore,
|
|
12269
|
+
operationIndex: this.operationIndex,
|
|
12270
|
+
writeCache: this.writeCache,
|
|
12271
|
+
documentMetaCache: this.documentMetaCache,
|
|
12272
|
+
collectionMembershipCache: this.collectionMembershipCache
|
|
12273
|
+
});
|
|
12274
|
+
}
|
|
12275
|
+
}
|
|
12276
|
+
|
|
12277
|
+
class KyselyExecutionScope {
|
|
12278
|
+
db;
|
|
12279
|
+
operationStore;
|
|
12280
|
+
operationIndex;
|
|
12281
|
+
keyframeStore;
|
|
12282
|
+
writeCache;
|
|
12283
|
+
documentMetaCache;
|
|
12284
|
+
collectionMembershipCache;
|
|
12285
|
+
constructor(db, operationStore, operationIndex, keyframeStore, writeCache, documentMetaCache, collectionMembershipCache) {
|
|
12286
|
+
this.db = db;
|
|
12287
|
+
this.operationStore = operationStore;
|
|
12288
|
+
this.operationIndex = operationIndex;
|
|
12289
|
+
this.keyframeStore = keyframeStore;
|
|
12290
|
+
this.writeCache = writeCache;
|
|
12291
|
+
this.documentMetaCache = documentMetaCache;
|
|
12292
|
+
this.collectionMembershipCache = collectionMembershipCache;
|
|
12293
|
+
}
|
|
12294
|
+
async run(fn) {
|
|
12295
|
+
return this.db.transaction().execute(async (trx) => {
|
|
12296
|
+
const scopedOperationStore = this.operationStore.withTransaction(trx);
|
|
12297
|
+
const scopedOperationIndex = this.operationIndex.withTransaction(trx);
|
|
12298
|
+
const scopedKeyframeStore = this.keyframeStore.withTransaction(trx);
|
|
12299
|
+
return fn({
|
|
12300
|
+
operationStore: scopedOperationStore,
|
|
12301
|
+
operationIndex: scopedOperationIndex,
|
|
12302
|
+
writeCache: this.writeCache.withScopedStores(scopedOperationStore, scopedKeyframeStore),
|
|
12303
|
+
documentMetaCache: this.documentMetaCache.withScopedStore(scopedOperationStore),
|
|
12304
|
+
collectionMembershipCache: this.collectionMembershipCache.withScopedIndex(scopedOperationIndex)
|
|
12305
|
+
});
|
|
12306
|
+
});
|
|
12307
|
+
}
|
|
12308
|
+
}
|
|
12309
|
+
|
|
12220
12310
|
// src/queue/types.ts
|
|
12221
12311
|
var QueueEventTypes = {
|
|
12222
12312
|
JOB_AVAILABLE: 1e4
|
|
@@ -12766,37 +12856,29 @@ function driveCollectionId(branch, driveId) {
|
|
|
12766
12856
|
|
|
12767
12857
|
// src/executor/document-action-handler.ts
|
|
12768
12858
|
class DocumentActionHandler {
|
|
12769
|
-
writeCache;
|
|
12770
|
-
operationStore;
|
|
12771
|
-
documentMetaCache;
|
|
12772
|
-
collectionMembershipCache;
|
|
12773
12859
|
registry;
|
|
12774
12860
|
logger;
|
|
12775
|
-
constructor(
|
|
12776
|
-
this.writeCache = writeCache;
|
|
12777
|
-
this.operationStore = operationStore;
|
|
12778
|
-
this.documentMetaCache = documentMetaCache;
|
|
12779
|
-
this.collectionMembershipCache = collectionMembershipCache;
|
|
12861
|
+
constructor(registry, logger) {
|
|
12780
12862
|
this.registry = registry;
|
|
12781
12863
|
this.logger = logger;
|
|
12782
12864
|
}
|
|
12783
|
-
async execute(job, action, startTime, indexTxn, skip = 0, sourceRemote = "") {
|
|
12865
|
+
async execute(job, action, startTime, indexTxn, stores, skip = 0, sourceRemote = "") {
|
|
12784
12866
|
switch (action.type) {
|
|
12785
12867
|
case "CREATE_DOCUMENT":
|
|
12786
|
-
return this.executeCreate(job, action, startTime, indexTxn, skip, sourceRemote);
|
|
12868
|
+
return this.executeCreate(job, action, startTime, indexTxn, stores, skip, sourceRemote);
|
|
12787
12869
|
case "DELETE_DOCUMENT":
|
|
12788
|
-
return this.executeDelete(job, action, startTime, indexTxn, sourceRemote);
|
|
12870
|
+
return this.executeDelete(job, action, startTime, indexTxn, stores, sourceRemote);
|
|
12789
12871
|
case "UPGRADE_DOCUMENT":
|
|
12790
|
-
return this.executeUpgrade(job, action, startTime, indexTxn, skip, sourceRemote);
|
|
12872
|
+
return this.executeUpgrade(job, action, startTime, indexTxn, stores, skip, sourceRemote);
|
|
12791
12873
|
case "ADD_RELATIONSHIP":
|
|
12792
|
-
return this.executeAddRelationship(job, action, startTime, indexTxn, sourceRemote);
|
|
12874
|
+
return this.executeAddRelationship(job, action, startTime, indexTxn, stores, sourceRemote);
|
|
12793
12875
|
case "REMOVE_RELATIONSHIP":
|
|
12794
|
-
return this.executeRemoveRelationship(job, action, startTime, indexTxn, sourceRemote);
|
|
12876
|
+
return this.executeRemoveRelationship(job, action, startTime, indexTxn, stores, sourceRemote);
|
|
12795
12877
|
default:
|
|
12796
12878
|
return buildErrorResult(job, new Error(`Unknown document action type: ${action.type}`), startTime);
|
|
12797
12879
|
}
|
|
12798
12880
|
}
|
|
12799
|
-
async executeCreate(job, action, startTime, indexTxn, skip = 0, sourceRemote = "") {
|
|
12881
|
+
async executeCreate(job, action, startTime, indexTxn, stores, skip = 0, sourceRemote = "") {
|
|
12800
12882
|
if (job.scope !== "document") {
|
|
12801
12883
|
return {
|
|
12802
12884
|
job,
|
|
@@ -12816,12 +12898,12 @@ class DocumentActionHandler {
|
|
|
12816
12898
|
...document.state
|
|
12817
12899
|
};
|
|
12818
12900
|
const resultingState = JSON.stringify(resultingStateObj);
|
|
12819
|
-
const writeError = await this.writeOperationToStore(document.header.id, document.header.documentType, job.scope, job.branch, operation, job, startTime);
|
|
12901
|
+
const writeError = await this.writeOperationToStore(document.header.id, document.header.documentType, job.scope, job.branch, operation, job, startTime, stores);
|
|
12820
12902
|
if (writeError !== null) {
|
|
12821
12903
|
return writeError;
|
|
12822
12904
|
}
|
|
12823
12905
|
updateDocumentRevision(document, job.scope, operation.index);
|
|
12824
|
-
|
|
12906
|
+
stores.writeCache.putState(document.header.id, job.scope, job.branch, operation.index, document);
|
|
12825
12907
|
indexTxn.write([
|
|
12826
12908
|
{
|
|
12827
12909
|
...operation,
|
|
@@ -12837,14 +12919,14 @@ class DocumentActionHandler {
|
|
|
12837
12919
|
indexTxn.createCollection(collectionId);
|
|
12838
12920
|
indexTxn.addToCollection(collectionId, document.header.id);
|
|
12839
12921
|
}
|
|
12840
|
-
|
|
12922
|
+
stores.documentMetaCache.putDocumentMeta(document.header.id, job.branch, {
|
|
12841
12923
|
state: document.state.document,
|
|
12842
12924
|
documentType: document.header.documentType,
|
|
12843
12925
|
documentScopeRevision: 1
|
|
12844
12926
|
});
|
|
12845
12927
|
return buildSuccessResult(job, operation, document.header.id, document.header.documentType, resultingState, startTime);
|
|
12846
12928
|
}
|
|
12847
|
-
async executeDelete(job, action, startTime, indexTxn, sourceRemote = "") {
|
|
12929
|
+
async executeDelete(job, action, startTime, indexTxn, stores, sourceRemote = "") {
|
|
12848
12930
|
const input = action.input;
|
|
12849
12931
|
if (!input.documentId) {
|
|
12850
12932
|
return buildErrorResult(job, new Error("DELETE_DOCUMENT action requires a documentId in input"), startTime);
|
|
@@ -12852,7 +12934,7 @@ class DocumentActionHandler {
|
|
|
12852
12934
|
const documentId = input.documentId;
|
|
12853
12935
|
let document;
|
|
12854
12936
|
try {
|
|
12855
|
-
document = await
|
|
12937
|
+
document = await stores.writeCache.getState(documentId, job.scope, job.branch);
|
|
12856
12938
|
} catch (error) {
|
|
12857
12939
|
return buildErrorResult(job, new Error(`Failed to fetch document before deletion: ${error instanceof Error ? error.message : String(error)}`), startTime);
|
|
12858
12940
|
}
|
|
@@ -12872,12 +12954,12 @@ class DocumentActionHandler {
|
|
|
12872
12954
|
document: document.state.document
|
|
12873
12955
|
};
|
|
12874
12956
|
const resultingState = JSON.stringify(resultingStateObj);
|
|
12875
|
-
const writeError = await this.writeOperationToStore(documentId, document.header.documentType, job.scope, job.branch, operation, job, startTime);
|
|
12957
|
+
const writeError = await this.writeOperationToStore(documentId, document.header.documentType, job.scope, job.branch, operation, job, startTime, stores);
|
|
12876
12958
|
if (writeError !== null) {
|
|
12877
12959
|
return writeError;
|
|
12878
12960
|
}
|
|
12879
12961
|
updateDocumentRevision(document, job.scope, operation.index);
|
|
12880
|
-
|
|
12962
|
+
stores.writeCache.putState(documentId, job.scope, job.branch, operation.index, document);
|
|
12881
12963
|
indexTxn.write([
|
|
12882
12964
|
{
|
|
12883
12965
|
...operation,
|
|
@@ -12888,14 +12970,14 @@ class DocumentActionHandler {
|
|
|
12888
12970
|
sourceRemote
|
|
12889
12971
|
}
|
|
12890
12972
|
]);
|
|
12891
|
-
|
|
12973
|
+
stores.documentMetaCache.putDocumentMeta(documentId, job.branch, {
|
|
12892
12974
|
state: document.state.document,
|
|
12893
12975
|
documentType: document.header.documentType,
|
|
12894
12976
|
documentScopeRevision: operation.index + 1
|
|
12895
12977
|
});
|
|
12896
12978
|
return buildSuccessResult(job, operation, documentId, document.header.documentType, resultingState, startTime);
|
|
12897
12979
|
}
|
|
12898
|
-
async executeUpgrade(job, action, startTime, indexTxn, skip = 0, sourceRemote = "") {
|
|
12980
|
+
async executeUpgrade(job, action, startTime, indexTxn, stores, skip = 0, sourceRemote = "") {
|
|
12899
12981
|
const input = action.input;
|
|
12900
12982
|
if (!input.documentId) {
|
|
12901
12983
|
return buildErrorResult(job, new Error("UPGRADE_DOCUMENT action requires a documentId in input"), startTime);
|
|
@@ -12905,7 +12987,7 @@ class DocumentActionHandler {
|
|
|
12905
12987
|
const toVersion = input.toVersion;
|
|
12906
12988
|
let document;
|
|
12907
12989
|
try {
|
|
12908
|
-
document = await
|
|
12990
|
+
document = await stores.writeCache.getState(documentId, job.scope, job.branch);
|
|
12909
12991
|
} catch (error) {
|
|
12910
12992
|
return buildErrorResult(job, new Error(`Failed to fetch document for upgrade: ${error instanceof Error ? error.message : String(error)}`), startTime);
|
|
12911
12993
|
}
|
|
@@ -12946,12 +13028,12 @@ class DocumentActionHandler {
|
|
|
12946
13028
|
...document.state
|
|
12947
13029
|
};
|
|
12948
13030
|
const resultingState = JSON.stringify(resultingStateObj);
|
|
12949
|
-
const writeError = await this.writeOperationToStore(documentId, document.header.documentType, job.scope, job.branch, operation, job, startTime);
|
|
13031
|
+
const writeError = await this.writeOperationToStore(documentId, document.header.documentType, job.scope, job.branch, operation, job, startTime, stores);
|
|
12950
13032
|
if (writeError !== null) {
|
|
12951
13033
|
return writeError;
|
|
12952
13034
|
}
|
|
12953
13035
|
updateDocumentRevision(document, job.scope, operation.index);
|
|
12954
|
-
|
|
13036
|
+
stores.writeCache.putState(documentId, job.scope, job.branch, operation.index, document);
|
|
12955
13037
|
indexTxn.write([
|
|
12956
13038
|
{
|
|
12957
13039
|
...operation,
|
|
@@ -12962,14 +13044,14 @@ class DocumentActionHandler {
|
|
|
12962
13044
|
sourceRemote
|
|
12963
13045
|
}
|
|
12964
13046
|
]);
|
|
12965
|
-
|
|
13047
|
+
stores.documentMetaCache.putDocumentMeta(documentId, job.branch, {
|
|
12966
13048
|
state: document.state.document,
|
|
12967
13049
|
documentType: document.header.documentType,
|
|
12968
13050
|
documentScopeRevision: operation.index + 1
|
|
12969
13051
|
});
|
|
12970
13052
|
return buildSuccessResult(job, operation, documentId, document.header.documentType, resultingState, startTime);
|
|
12971
13053
|
}
|
|
12972
|
-
async executeAddRelationship(job, action, startTime, indexTxn, sourceRemote = "") {
|
|
13054
|
+
async executeAddRelationship(job, action, startTime, indexTxn, stores, sourceRemote = "") {
|
|
12973
13055
|
if (job.scope !== "document") {
|
|
12974
13056
|
return buildErrorResult(job, new Error(`ADD_RELATIONSHIP must be in "document" scope, got "${job.scope}"`), startTime);
|
|
12975
13057
|
}
|
|
@@ -12982,7 +13064,7 @@ class DocumentActionHandler {
|
|
|
12982
13064
|
}
|
|
12983
13065
|
let sourceDoc;
|
|
12984
13066
|
try {
|
|
12985
|
-
sourceDoc = await
|
|
13067
|
+
sourceDoc = await stores.writeCache.getState(input.sourceId, "document", job.branch);
|
|
12986
13068
|
} catch (error) {
|
|
12987
13069
|
return buildErrorResult(job, new Error(`ADD_RELATIONSHIP: source document ${input.sourceId} not found: ${error instanceof Error ? error.message : String(error)}`), startTime);
|
|
12988
13070
|
}
|
|
@@ -12992,7 +13074,7 @@ class DocumentActionHandler {
|
|
|
12992
13074
|
scope: job.scope,
|
|
12993
13075
|
branch: job.branch
|
|
12994
13076
|
});
|
|
12995
|
-
const writeError = await this.writeOperationToStore(input.sourceId, sourceDoc.header.documentType, job.scope, job.branch, operation, job, startTime);
|
|
13077
|
+
const writeError = await this.writeOperationToStore(input.sourceId, sourceDoc.header.documentType, job.scope, job.branch, operation, job, startTime, stores);
|
|
12996
13078
|
if (writeError !== null) {
|
|
12997
13079
|
return writeError;
|
|
12998
13080
|
}
|
|
@@ -13008,7 +13090,7 @@ class DocumentActionHandler {
|
|
|
13008
13090
|
[job.scope]: scopeState === undefined ? {} : structuredClone(scopeState)
|
|
13009
13091
|
};
|
|
13010
13092
|
const resultingState = JSON.stringify(resultingStateObj);
|
|
13011
|
-
|
|
13093
|
+
stores.writeCache.putState(input.sourceId, job.scope, job.branch, operation.index, sourceDoc);
|
|
13012
13094
|
indexTxn.write([
|
|
13013
13095
|
{
|
|
13014
13096
|
...operation,
|
|
@@ -13022,16 +13104,16 @@ class DocumentActionHandler {
|
|
|
13022
13104
|
if (sourceDoc.header.documentType === "powerhouse/document-drive") {
|
|
13023
13105
|
const collectionId = driveCollectionId(job.branch, input.sourceId);
|
|
13024
13106
|
indexTxn.addToCollection(collectionId, input.targetId);
|
|
13025
|
-
|
|
13107
|
+
stores.collectionMembershipCache.invalidate(input.targetId);
|
|
13026
13108
|
}
|
|
13027
|
-
|
|
13109
|
+
stores.documentMetaCache.putDocumentMeta(input.sourceId, job.branch, {
|
|
13028
13110
|
state: sourceDoc.state.document,
|
|
13029
13111
|
documentType: sourceDoc.header.documentType,
|
|
13030
13112
|
documentScopeRevision: operation.index + 1
|
|
13031
13113
|
});
|
|
13032
13114
|
return buildSuccessResult(job, operation, input.sourceId, sourceDoc.header.documentType, resultingState, startTime);
|
|
13033
13115
|
}
|
|
13034
|
-
async executeRemoveRelationship(job, action, startTime, indexTxn, sourceRemote = "") {
|
|
13116
|
+
async executeRemoveRelationship(job, action, startTime, indexTxn, stores, sourceRemote = "") {
|
|
13035
13117
|
if (job.scope !== "document") {
|
|
13036
13118
|
return buildErrorResult(job, new Error(`REMOVE_RELATIONSHIP must be in "document" scope, got "${job.scope}"`), startTime);
|
|
13037
13119
|
}
|
|
@@ -13041,7 +13123,7 @@ class DocumentActionHandler {
|
|
|
13041
13123
|
}
|
|
13042
13124
|
let sourceDoc;
|
|
13043
13125
|
try {
|
|
13044
|
-
sourceDoc = await
|
|
13126
|
+
sourceDoc = await stores.writeCache.getState(input.sourceId, "document", job.branch);
|
|
13045
13127
|
} catch (error) {
|
|
13046
13128
|
return buildErrorResult(job, new Error(`REMOVE_RELATIONSHIP: source document ${input.sourceId} not found: ${error instanceof Error ? error.message : String(error)}`), startTime);
|
|
13047
13129
|
}
|
|
@@ -13051,7 +13133,7 @@ class DocumentActionHandler {
|
|
|
13051
13133
|
scope: job.scope,
|
|
13052
13134
|
branch: job.branch
|
|
13053
13135
|
});
|
|
13054
|
-
const writeError = await this.writeOperationToStore(input.sourceId, sourceDoc.header.documentType, job.scope, job.branch, operation, job, startTime);
|
|
13136
|
+
const writeError = await this.writeOperationToStore(input.sourceId, sourceDoc.header.documentType, job.scope, job.branch, operation, job, startTime, stores);
|
|
13055
13137
|
if (writeError !== null) {
|
|
13056
13138
|
return writeError;
|
|
13057
13139
|
}
|
|
@@ -13067,7 +13149,7 @@ class DocumentActionHandler {
|
|
|
13067
13149
|
[job.scope]: scopeState === undefined ? {} : structuredClone(scopeState)
|
|
13068
13150
|
};
|
|
13069
13151
|
const resultingState = JSON.stringify(resultingStateObj);
|
|
13070
|
-
|
|
13152
|
+
stores.writeCache.putState(input.sourceId, job.scope, job.branch, operation.index, sourceDoc);
|
|
13071
13153
|
indexTxn.write([
|
|
13072
13154
|
{
|
|
13073
13155
|
...operation,
|
|
@@ -13081,24 +13163,24 @@ class DocumentActionHandler {
|
|
|
13081
13163
|
if (sourceDoc.header.documentType === "powerhouse/document-drive") {
|
|
13082
13164
|
const collectionId = driveCollectionId(job.branch, input.sourceId);
|
|
13083
13165
|
indexTxn.removeFromCollection(collectionId, input.targetId);
|
|
13084
|
-
|
|
13166
|
+
stores.collectionMembershipCache.invalidate(input.targetId);
|
|
13085
13167
|
}
|
|
13086
|
-
|
|
13168
|
+
stores.documentMetaCache.putDocumentMeta(input.sourceId, job.branch, {
|
|
13087
13169
|
state: sourceDoc.state.document,
|
|
13088
13170
|
documentType: sourceDoc.header.documentType,
|
|
13089
13171
|
documentScopeRevision: operation.index + 1
|
|
13090
13172
|
});
|
|
13091
13173
|
return buildSuccessResult(job, operation, input.sourceId, sourceDoc.header.documentType, resultingState, startTime);
|
|
13092
13174
|
}
|
|
13093
|
-
async writeOperationToStore(documentId, documentType, scope, branch, operation, job, startTime) {
|
|
13175
|
+
async writeOperationToStore(documentId, documentType, scope, branch, operation, job, startTime, stores) {
|
|
13094
13176
|
try {
|
|
13095
|
-
await
|
|
13177
|
+
await stores.operationStore.apply(documentId, documentType, scope, branch, operation.index, (txn) => {
|
|
13096
13178
|
txn.addOperations(operation);
|
|
13097
13179
|
});
|
|
13098
13180
|
return null;
|
|
13099
13181
|
} catch (error) {
|
|
13100
13182
|
this.logger.error("Error writing @Operation to IOperationStore: @Error", operation, error);
|
|
13101
|
-
|
|
13183
|
+
stores.writeCache.invalidate(documentId, scope, branch);
|
|
13102
13184
|
return {
|
|
13103
13185
|
job,
|
|
13104
13186
|
success: false,
|
|
@@ -13199,7 +13281,8 @@ class SimpleJobExecutor {
|
|
|
13199
13281
|
config;
|
|
13200
13282
|
signatureVerifierModule;
|
|
13201
13283
|
documentActionHandler;
|
|
13202
|
-
|
|
13284
|
+
executionScope;
|
|
13285
|
+
constructor(logger, registry, operationStore, eventBus, writeCache, operationIndex, documentMetaCache, collectionMembershipCache, config, signatureVerifier, executionScope) {
|
|
13203
13286
|
this.logger = logger;
|
|
13204
13287
|
this.registry = registry;
|
|
13205
13288
|
this.operationStore = operationStore;
|
|
@@ -13216,71 +13299,101 @@ class SimpleJobExecutor {
|
|
|
13216
13299
|
retryMaxDelayMs: config.retryMaxDelayMs ?? 5000
|
|
13217
13300
|
};
|
|
13218
13301
|
this.signatureVerifierModule = new SignatureVerifier(signatureVerifier);
|
|
13219
|
-
this.documentActionHandler = new DocumentActionHandler(
|
|
13302
|
+
this.documentActionHandler = new DocumentActionHandler(registry, logger);
|
|
13303
|
+
this.executionScope = executionScope ?? new DefaultExecutionScope(operationStore, operationIndex, writeCache, documentMetaCache, collectionMembershipCache);
|
|
13220
13304
|
}
|
|
13221
13305
|
async executeJob(job) {
|
|
13222
13306
|
const startTime = Date.now();
|
|
13223
|
-
const
|
|
13224
|
-
|
|
13225
|
-
|
|
13226
|
-
|
|
13227
|
-
|
|
13228
|
-
|
|
13229
|
-
|
|
13307
|
+
const touchedCacheEntries = [];
|
|
13308
|
+
let pendingEvent;
|
|
13309
|
+
let result;
|
|
13310
|
+
try {
|
|
13311
|
+
result = await this.executionScope.run(async (stores) => {
|
|
13312
|
+
const indexTxn = stores.operationIndex.start();
|
|
13313
|
+
if (job.kind === "load") {
|
|
13314
|
+
const loadResult = await this.executeLoadJob(job, startTime, indexTxn, stores);
|
|
13315
|
+
if (loadResult.success && loadResult.operationsWithContext) {
|
|
13316
|
+
for (const owc of loadResult.operationsWithContext) {
|
|
13317
|
+
touchedCacheEntries.push({
|
|
13318
|
+
documentId: owc.context.documentId,
|
|
13319
|
+
scope: owc.context.scope,
|
|
13320
|
+
branch: owc.context.branch
|
|
13321
|
+
});
|
|
13322
|
+
}
|
|
13323
|
+
const ordinals2 = await stores.operationIndex.commit(indexTxn);
|
|
13324
|
+
for (let i = 0;i < loadResult.operationsWithContext.length; i++) {
|
|
13325
|
+
loadResult.operationsWithContext[i].context.ordinal = ordinals2[i];
|
|
13326
|
+
}
|
|
13327
|
+
const collectionMemberships = loadResult.operationsWithContext.length > 0 ? await this.getCollectionMembershipsForOperations(loadResult.operationsWithContext, stores) : {};
|
|
13328
|
+
pendingEvent = {
|
|
13329
|
+
jobId: job.id,
|
|
13330
|
+
operations: loadResult.operationsWithContext,
|
|
13331
|
+
jobMeta: job.meta,
|
|
13332
|
+
collectionMemberships
|
|
13333
|
+
};
|
|
13334
|
+
}
|
|
13335
|
+
return loadResult;
|
|
13230
13336
|
}
|
|
13231
|
-
const
|
|
13232
|
-
|
|
13233
|
-
|
|
13234
|
-
|
|
13235
|
-
|
|
13236
|
-
|
|
13337
|
+
const actionResult = await this.processActions(job, job.actions, startTime, indexTxn, stores);
|
|
13338
|
+
if (!actionResult.success) {
|
|
13339
|
+
return {
|
|
13340
|
+
job,
|
|
13341
|
+
success: false,
|
|
13342
|
+
error: actionResult.error,
|
|
13343
|
+
duration: Date.now() - startTime
|
|
13344
|
+
};
|
|
13345
|
+
}
|
|
13346
|
+
if (actionResult.operationsWithContext.length > 0) {
|
|
13347
|
+
for (const owc of actionResult.operationsWithContext) {
|
|
13348
|
+
touchedCacheEntries.push({
|
|
13349
|
+
documentId: owc.context.documentId,
|
|
13350
|
+
scope: owc.context.scope,
|
|
13351
|
+
branch: owc.context.branch
|
|
13352
|
+
});
|
|
13353
|
+
}
|
|
13354
|
+
}
|
|
13355
|
+
const ordinals = await stores.operationIndex.commit(indexTxn);
|
|
13356
|
+
if (actionResult.operationsWithContext.length > 0) {
|
|
13357
|
+
for (let i = 0;i < actionResult.operationsWithContext.length; i++) {
|
|
13358
|
+
actionResult.operationsWithContext[i].context.ordinal = ordinals[i];
|
|
13359
|
+
}
|
|
13360
|
+
const collectionMemberships = await this.getCollectionMembershipsForOperations(actionResult.operationsWithContext, stores);
|
|
13361
|
+
pendingEvent = {
|
|
13362
|
+
jobId: job.id,
|
|
13363
|
+
operations: actionResult.operationsWithContext,
|
|
13364
|
+
jobMeta: job.meta,
|
|
13365
|
+
collectionMemberships
|
|
13366
|
+
};
|
|
13367
|
+
}
|
|
13368
|
+
return {
|
|
13369
|
+
job,
|
|
13370
|
+
success: true,
|
|
13371
|
+
operations: actionResult.generatedOperations,
|
|
13372
|
+
operationsWithContext: actionResult.operationsWithContext,
|
|
13373
|
+
duration: Date.now() - startTime
|
|
13237
13374
|
};
|
|
13238
|
-
|
|
13239
|
-
|
|
13240
|
-
|
|
13375
|
+
});
|
|
13376
|
+
} catch (error) {
|
|
13377
|
+
for (const entry of touchedCacheEntries) {
|
|
13378
|
+
this.writeCache.invalidate(entry.documentId, entry.scope, entry.branch);
|
|
13379
|
+
this.documentMetaCache.invalidate(entry.documentId, entry.branch);
|
|
13241
13380
|
}
|
|
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
|
-
};
|
|
13381
|
+
throw error;
|
|
13252
13382
|
}
|
|
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);
|
|
13383
|
+
if (pendingEvent) {
|
|
13384
|
+
this.eventBus.emit(ReactorEventTypes.JOB_WRITE_READY, pendingEvent).catch((error) => {
|
|
13385
|
+
this.logger.error("Failed to emit JOB_WRITE_READY event: @Event : @Error", pendingEvent, error);
|
|
13267
13386
|
});
|
|
13268
13387
|
}
|
|
13269
|
-
return
|
|
13270
|
-
job,
|
|
13271
|
-
success: true,
|
|
13272
|
-
operations: result.generatedOperations,
|
|
13273
|
-
operationsWithContext: result.operationsWithContext,
|
|
13274
|
-
duration: Date.now() - startTime
|
|
13275
|
-
};
|
|
13388
|
+
return result;
|
|
13276
13389
|
}
|
|
13277
|
-
async getCollectionMembershipsForOperations(operations) {
|
|
13390
|
+
async getCollectionMembershipsForOperations(operations, stores) {
|
|
13278
13391
|
const documentIds = [
|
|
13279
13392
|
...new Set(operations.map((op) => op.context.documentId))
|
|
13280
13393
|
];
|
|
13281
|
-
return
|
|
13394
|
+
return stores.collectionMembershipCache.getCollectionsForDocuments(documentIds);
|
|
13282
13395
|
}
|
|
13283
|
-
async processActions(job, actions2, startTime, indexTxn, skipValues, sourceOperations, sourceRemote = "") {
|
|
13396
|
+
async processActions(job, actions2, startTime, indexTxn, stores, skipValues, sourceOperations, sourceRemote = "") {
|
|
13284
13397
|
const generatedOperations = [];
|
|
13285
13398
|
const operationsWithContext = [];
|
|
13286
13399
|
try {
|
|
@@ -13298,7 +13411,7 @@ class SimpleJobExecutor {
|
|
|
13298
13411
|
const skip = skipValues?.[actionIndex] ?? 0;
|
|
13299
13412
|
const sourceOperation = sourceOperations?.[actionIndex];
|
|
13300
13413
|
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);
|
|
13414
|
+
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
13415
|
const error = this.accumulateResultOrReturnError(result, generatedOperations, operationsWithContext);
|
|
13303
13416
|
if (error !== null) {
|
|
13304
13417
|
return {
|
|
@@ -13315,10 +13428,10 @@ class SimpleJobExecutor {
|
|
|
13315
13428
|
operationsWithContext
|
|
13316
13429
|
};
|
|
13317
13430
|
}
|
|
13318
|
-
async executeRegularAction(job, action, startTime, indexTxn, skip = 0, sourceOperation, sourceRemote = "") {
|
|
13431
|
+
async executeRegularAction(job, action, startTime, indexTxn, stores, skip = 0, sourceOperation, sourceRemote = "") {
|
|
13319
13432
|
let docMeta;
|
|
13320
13433
|
try {
|
|
13321
|
-
docMeta = await
|
|
13434
|
+
docMeta = await stores.documentMetaCache.getDocumentMeta(job.documentId, job.branch);
|
|
13322
13435
|
} catch (error) {
|
|
13323
13436
|
return buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
|
|
13324
13437
|
}
|
|
@@ -13326,11 +13439,11 @@ class SimpleJobExecutor {
|
|
|
13326
13439
|
return buildErrorResult(job, new DocumentDeletedError(job.documentId, docMeta.state.deletedAtUtcIso), startTime);
|
|
13327
13440
|
}
|
|
13328
13441
|
if (isUndoRedo(action) || action.type === "PRUNE" || action.type === "NOOP" && skip > 0) {
|
|
13329
|
-
|
|
13442
|
+
stores.writeCache.invalidate(job.documentId, job.scope, job.branch);
|
|
13330
13443
|
}
|
|
13331
13444
|
let document;
|
|
13332
13445
|
try {
|
|
13333
|
-
document = await
|
|
13446
|
+
document = await stores.writeCache.getState(job.documentId, job.scope, job.branch);
|
|
13334
13447
|
} catch (error) {
|
|
13335
13448
|
return buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
|
|
13336
13449
|
}
|
|
@@ -13381,12 +13494,12 @@ ${error.stack}`;
|
|
|
13381
13494
|
header: updatedDocument.header
|
|
13382
13495
|
});
|
|
13383
13496
|
try {
|
|
13384
|
-
await
|
|
13497
|
+
await stores.operationStore.apply(job.documentId, document.header.documentType, scope, job.branch, newOperation.index, (txn) => {
|
|
13385
13498
|
txn.addOperations(newOperation);
|
|
13386
13499
|
});
|
|
13387
13500
|
} catch (error) {
|
|
13388
13501
|
this.logger.error("Error writing @Operation to IOperationStore: @Error", newOperation, error);
|
|
13389
|
-
|
|
13502
|
+
stores.writeCache.invalidate(job.documentId, scope, job.branch);
|
|
13390
13503
|
return {
|
|
13391
13504
|
job,
|
|
13392
13505
|
success: false,
|
|
@@ -13398,7 +13511,7 @@ ${error.stack}`;
|
|
|
13398
13511
|
...updatedDocument.header.revision,
|
|
13399
13512
|
[scope]: newOperation.index + 1
|
|
13400
13513
|
};
|
|
13401
|
-
|
|
13514
|
+
stores.writeCache.putState(job.documentId, scope, job.branch, newOperation.index, updatedDocument);
|
|
13402
13515
|
indexTxn.write([
|
|
13403
13516
|
{
|
|
13404
13517
|
...newOperation,
|
|
@@ -13429,13 +13542,13 @@ ${error.stack}`;
|
|
|
13429
13542
|
duration: Date.now() - startTime
|
|
13430
13543
|
};
|
|
13431
13544
|
}
|
|
13432
|
-
async executeLoadJob(job, startTime, indexTxn) {
|
|
13545
|
+
async executeLoadJob(job, startTime, indexTxn, stores) {
|
|
13433
13546
|
if (job.operations.length === 0) {
|
|
13434
13547
|
return buildErrorResult(job, new Error("Load job must include at least one operation"), startTime);
|
|
13435
13548
|
}
|
|
13436
13549
|
let docMeta;
|
|
13437
13550
|
try {
|
|
13438
|
-
docMeta = await
|
|
13551
|
+
docMeta = await stores.documentMetaCache.getDocumentMeta(job.documentId, job.branch);
|
|
13439
13552
|
} catch {}
|
|
13440
13553
|
if (docMeta?.state.isDeleted) {
|
|
13441
13554
|
return buildErrorResult(job, new DocumentDeletedError(job.documentId, docMeta.state.deletedAtUtcIso), startTime);
|
|
@@ -13443,7 +13556,7 @@ ${error.stack}`;
|
|
|
13443
13556
|
const scope = job.scope;
|
|
13444
13557
|
let latestRevision = 0;
|
|
13445
13558
|
try {
|
|
13446
|
-
const revisions = await
|
|
13559
|
+
const revisions = await stores.operationStore.getRevisions(job.documentId, job.branch);
|
|
13447
13560
|
latestRevision = revisions.revision[scope] ?? 0;
|
|
13448
13561
|
} catch {
|
|
13449
13562
|
latestRevision = 0;
|
|
@@ -13459,7 +13572,7 @@ ${error.stack}`;
|
|
|
13459
13572
|
}
|
|
13460
13573
|
let conflictingOps = [];
|
|
13461
13574
|
try {
|
|
13462
|
-
const conflictingResult = await
|
|
13575
|
+
const conflictingResult = await stores.operationStore.getConflicting(job.documentId, scope, job.branch, minIncomingTimestamp);
|
|
13463
13576
|
conflictingOps = conflictingResult.results;
|
|
13464
13577
|
} catch {
|
|
13465
13578
|
conflictingOps = [];
|
|
@@ -13468,7 +13581,7 @@ ${error.stack}`;
|
|
|
13468
13581
|
if (conflictingOps.length > 0) {
|
|
13469
13582
|
const minConflictingIndex = Math.min(...conflictingOps.map((op) => op.index));
|
|
13470
13583
|
try {
|
|
13471
|
-
const allOpsResult = await
|
|
13584
|
+
const allOpsResult = await stores.operationStore.getSince(job.documentId, scope, job.branch, minConflictingIndex - 1);
|
|
13472
13585
|
allOpsFromMinConflictingIndex = allOpsResult.results;
|
|
13473
13586
|
} catch {
|
|
13474
13587
|
allOpsFromMinConflictingIndex = conflictingOps;
|
|
@@ -13529,7 +13642,7 @@ ${error.stack}`;
|
|
|
13529
13642
|
const actions2 = reshuffledOperations.map((operation) => operation.action);
|
|
13530
13643
|
const skipValues = reshuffledOperations.map((operation) => operation.skip);
|
|
13531
13644
|
const effectiveSourceRemote = skipCount > 0 ? "" : job.meta.sourceRemote || "";
|
|
13532
|
-
const result = await this.processActions(job, actions2, startTime, indexTxn, skipValues, reshuffledOperations, effectiveSourceRemote);
|
|
13645
|
+
const result = await this.processActions(job, actions2, startTime, indexTxn, stores, skipValues, reshuffledOperations, effectiveSourceRemote);
|
|
13533
13646
|
if (!result.success) {
|
|
13534
13647
|
return {
|
|
13535
13648
|
job,
|
|
@@ -13538,9 +13651,9 @@ ${error.stack}`;
|
|
|
13538
13651
|
duration: Date.now() - startTime
|
|
13539
13652
|
};
|
|
13540
13653
|
}
|
|
13541
|
-
|
|
13654
|
+
stores.writeCache.invalidate(job.documentId, scope, job.branch);
|
|
13542
13655
|
if (scope === "document") {
|
|
13543
|
-
|
|
13656
|
+
stores.documentMetaCache.invalidate(job.documentId, job.branch);
|
|
13544
13657
|
}
|
|
13545
13658
|
return {
|
|
13546
13659
|
job,
|
|
@@ -15456,14 +15569,23 @@ class KyselyDocumentIndexer extends BaseReadModel {
|
|
|
15456
15569
|
// src/storage/kysely/keyframe-store.ts
|
|
15457
15570
|
class KyselyKeyframeStore {
|
|
15458
15571
|
db;
|
|
15572
|
+
trx;
|
|
15459
15573
|
constructor(db) {
|
|
15460
15574
|
this.db = db;
|
|
15461
15575
|
}
|
|
15576
|
+
get queryExecutor() {
|
|
15577
|
+
return this.trx ?? this.db;
|
|
15578
|
+
}
|
|
15579
|
+
withTransaction(trx) {
|
|
15580
|
+
const instance = new KyselyKeyframeStore(this.db);
|
|
15581
|
+
instance.trx = trx;
|
|
15582
|
+
return instance;
|
|
15583
|
+
}
|
|
15462
15584
|
async putKeyframe(documentId, scope, branch, revision, document, signal) {
|
|
15463
15585
|
if (signal?.aborted) {
|
|
15464
15586
|
throw new Error("Operation aborted");
|
|
15465
15587
|
}
|
|
15466
|
-
await this.
|
|
15588
|
+
await this.queryExecutor.insertInto("Keyframe").values({
|
|
15467
15589
|
documentId,
|
|
15468
15590
|
documentType: document.header.documentType,
|
|
15469
15591
|
scope,
|
|
@@ -15476,7 +15598,7 @@ class KyselyKeyframeStore {
|
|
|
15476
15598
|
if (signal?.aborted) {
|
|
15477
15599
|
throw new Error("Operation aborted");
|
|
15478
15600
|
}
|
|
15479
|
-
const row = await this.
|
|
15601
|
+
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
15602
|
if (!row) {
|
|
15481
15603
|
return;
|
|
15482
15604
|
}
|
|
@@ -15489,7 +15611,7 @@ class KyselyKeyframeStore {
|
|
|
15489
15611
|
if (signal?.aborted) {
|
|
15490
15612
|
throw new Error("Operation aborted");
|
|
15491
15613
|
}
|
|
15492
|
-
let query = this.
|
|
15614
|
+
let query = this.queryExecutor.deleteFrom("Keyframe").where("documentId", "=", documentId);
|
|
15493
15615
|
if (scope !== undefined && branch !== undefined) {
|
|
15494
15616
|
query = query.where("scope", "=", scope).where("branch", "=", branch);
|
|
15495
15617
|
} else if (scope !== undefined) {
|
|
@@ -15564,48 +15686,64 @@ class AtomicTransaction {
|
|
|
15564
15686
|
// src/storage/kysely/store.ts
|
|
15565
15687
|
class KyselyOperationStore {
|
|
15566
15688
|
db;
|
|
15689
|
+
trx;
|
|
15567
15690
|
constructor(db) {
|
|
15568
15691
|
this.db = db;
|
|
15569
15692
|
}
|
|
15693
|
+
get queryExecutor() {
|
|
15694
|
+
return this.trx ?? this.db;
|
|
15695
|
+
}
|
|
15696
|
+
withTransaction(trx) {
|
|
15697
|
+
const instance = new KyselyOperationStore(this.db);
|
|
15698
|
+
instance.trx = trx;
|
|
15699
|
+
return instance;
|
|
15700
|
+
}
|
|
15570
15701
|
async apply(documentId, documentType, scope, branch, revision, fn, signal) {
|
|
15571
|
-
|
|
15572
|
-
|
|
15573
|
-
|
|
15702
|
+
if (this.trx) {
|
|
15703
|
+
await this.executeApply(this.trx, documentId, documentType, scope, branch, revision, fn, signal);
|
|
15704
|
+
} else {
|
|
15705
|
+
await this.db.transaction().execute(async (trx) => {
|
|
15706
|
+
await this.executeApply(trx, documentId, documentType, scope, branch, revision, fn, signal);
|
|
15707
|
+
});
|
|
15708
|
+
}
|
|
15709
|
+
}
|
|
15710
|
+
async executeApply(trx, documentId, documentType, scope, branch, revision, fn, signal) {
|
|
15711
|
+
if (signal?.aborted) {
|
|
15712
|
+
throw new Error("Operation aborted");
|
|
15713
|
+
}
|
|
15714
|
+
const latestOp = await trx.selectFrom("Operation").selectAll().where("documentId", "=", documentId).where("scope", "=", scope).where("branch", "=", branch).orderBy("index", "desc").limit(1).executeTakeFirst();
|
|
15715
|
+
const currentRevision = latestOp ? latestOp.index : -1;
|
|
15716
|
+
if (currentRevision !== revision - 1) {
|
|
15717
|
+
throw new RevisionMismatchError(currentRevision + 1, revision);
|
|
15718
|
+
}
|
|
15719
|
+
const atomicTxn = new AtomicTransaction(documentId, documentType, scope, branch, revision);
|
|
15720
|
+
await fn(atomicTxn);
|
|
15721
|
+
const operations = atomicTxn.getOperations();
|
|
15722
|
+
if (operations.length > 0) {
|
|
15723
|
+
let prevOpId = latestOp?.opId || "";
|
|
15724
|
+
for (const op of operations) {
|
|
15725
|
+
op.prevOpId = prevOpId;
|
|
15726
|
+
prevOpId = op.opId;
|
|
15574
15727
|
}
|
|
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;
|
|
15728
|
+
try {
|
|
15729
|
+
await trx.insertInto("Operation").values(operations).execute();
|
|
15730
|
+
} catch (error) {
|
|
15731
|
+
if (error instanceof Error) {
|
|
15732
|
+
if (error.message.includes("unique constraint")) {
|
|
15733
|
+
const op = operations[0];
|
|
15734
|
+
throw new DuplicateOperationError(`${op.opId} at index ${op.index} with skip ${op.skip}`);
|
|
15598
15735
|
}
|
|
15599
15736
|
throw error;
|
|
15600
15737
|
}
|
|
15738
|
+
throw error;
|
|
15601
15739
|
}
|
|
15602
|
-
}
|
|
15740
|
+
}
|
|
15603
15741
|
}
|
|
15604
15742
|
async getSince(documentId, scope, branch, revision, filter, paging, signal) {
|
|
15605
15743
|
if (signal?.aborted) {
|
|
15606
15744
|
throw new Error("Operation aborted");
|
|
15607
15745
|
}
|
|
15608
|
-
let query = this.
|
|
15746
|
+
let query = this.queryExecutor.selectFrom("Operation").selectAll().where("documentId", "=", documentId).where("scope", "=", scope).where("branch", "=", branch).where("index", ">", revision).orderBy("index", "asc");
|
|
15609
15747
|
if (filter) {
|
|
15610
15748
|
if (filter.actionTypes && filter.actionTypes.length > 0) {
|
|
15611
15749
|
const actionTypesArray = filter.actionTypes.map((t) => `'${t.replace(/'/g, "''")}'`).join(",");
|
|
@@ -15652,7 +15790,7 @@ class KyselyOperationStore {
|
|
|
15652
15790
|
if (signal?.aborted) {
|
|
15653
15791
|
throw new Error("Operation aborted");
|
|
15654
15792
|
}
|
|
15655
|
-
let query = this.
|
|
15793
|
+
let query = this.queryExecutor.selectFrom("Operation").selectAll().where("id", ">", id).orderBy("id", "asc");
|
|
15656
15794
|
if (paging) {
|
|
15657
15795
|
const cursorValue = Number.parseInt(paging.cursor, 10);
|
|
15658
15796
|
if (cursorValue > 0) {
|
|
@@ -15684,7 +15822,7 @@ class KyselyOperationStore {
|
|
|
15684
15822
|
if (signal?.aborted) {
|
|
15685
15823
|
throw new Error("Operation aborted");
|
|
15686
15824
|
}
|
|
15687
|
-
let query = this.
|
|
15825
|
+
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
15826
|
if (paging) {
|
|
15689
15827
|
const cursorValue = Number.parseInt(paging.cursor, 10);
|
|
15690
15828
|
if (cursorValue > 0) {
|
|
@@ -15716,7 +15854,7 @@ class KyselyOperationStore {
|
|
|
15716
15854
|
if (signal?.aborted) {
|
|
15717
15855
|
throw new Error("Operation aborted");
|
|
15718
15856
|
}
|
|
15719
|
-
const scopeRevisions = await this.
|
|
15857
|
+
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
15858
|
const revision = {};
|
|
15721
15859
|
let latestTimestamp = new Date(0).toISOString();
|
|
15722
15860
|
for (const row of scopeRevisions) {
|
|
@@ -19755,9 +19893,10 @@ class ReactorBuilder {
|
|
|
19755
19893
|
});
|
|
19756
19894
|
await documentMetaCache.startup();
|
|
19757
19895
|
const collectionMembershipCache = new CollectionMembershipCache(operationIndex);
|
|
19896
|
+
const executionScope = new KyselyExecutionScope(database, operationStore, operationIndex, keyframeStore, writeCache, documentMetaCache, collectionMembershipCache);
|
|
19758
19897
|
let executorManager = this.executorManager;
|
|
19759
19898
|
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);
|
|
19899
|
+
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
19900
|
}
|
|
19762
19901
|
await executorManager.start(this.executorConfig.maxConcurrency ?? 1);
|
|
19763
19902
|
const readModelInstances = Array.from(new Set([...this.readModels]));
|
|
@@ -19889,6 +20028,7 @@ class ReactorClientBuilder {
|
|
|
19889
20028
|
signatureVerifier;
|
|
19890
20029
|
subscriptionManager;
|
|
19891
20030
|
jobAwaiter;
|
|
20031
|
+
documentModelLoader;
|
|
19892
20032
|
withLogger(logger) {
|
|
19893
20033
|
this.logger = logger;
|
|
19894
20034
|
return this;
|
|
@@ -19927,6 +20067,10 @@ class ReactorClientBuilder {
|
|
|
19927
20067
|
this.jobAwaiter = jobAwaiter;
|
|
19928
20068
|
return this;
|
|
19929
20069
|
}
|
|
20070
|
+
withDocumentModelLoader(loader) {
|
|
20071
|
+
this.documentModelLoader = loader;
|
|
20072
|
+
return this;
|
|
20073
|
+
}
|
|
19930
20074
|
async build() {
|
|
19931
20075
|
const module = await this.buildModule();
|
|
19932
20076
|
return module.client;
|
|
@@ -19944,6 +20088,9 @@ class ReactorClientBuilder {
|
|
|
19944
20088
|
if (this.signatureVerifier) {
|
|
19945
20089
|
this.reactorBuilder.withSignatureVerifier(this.signatureVerifier);
|
|
19946
20090
|
}
|
|
20091
|
+
if (this.documentModelLoader) {
|
|
20092
|
+
this.reactorBuilder.withDocumentModelLoader(this.documentModelLoader);
|
|
20093
|
+
}
|
|
19947
20094
|
reactorModule = await this.reactorBuilder.buildModule();
|
|
19948
20095
|
reactor = reactorModule.reactor;
|
|
19949
20096
|
eventBus = reactorModule.eventBus;
|