@powerhousedao/reactor 6.0.0-dev.84 → 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.
Files changed (29) hide show
  1. package/dist/src/cache/collection-membership-cache.d.ts +1 -0
  2. package/dist/src/cache/collection-membership-cache.d.ts.map +1 -1
  3. package/dist/src/cache/document-meta-cache.d.ts +1 -0
  4. package/dist/src/cache/document-meta-cache.d.ts.map +1 -1
  5. package/dist/src/cache/kysely-operation-index.d.ts +5 -1
  6. package/dist/src/cache/kysely-operation-index.d.ts.map +1 -1
  7. package/dist/src/cache/kysely-write-cache.d.ts +1 -0
  8. package/dist/src/cache/kysely-write-cache.d.ts.map +1 -1
  9. package/dist/src/core/reactor-builder.d.ts.map +1 -1
  10. package/dist/src/core/reactor-client-builder.d.ts +3 -0
  11. package/dist/src/core/reactor-client-builder.d.ts.map +1 -1
  12. package/dist/src/executor/document-action-handler.d.ts +3 -10
  13. package/dist/src/executor/document-action-handler.d.ts.map +1 -1
  14. package/dist/src/executor/execution-scope.d.ts +44 -0
  15. package/dist/src/executor/execution-scope.d.ts.map +1 -0
  16. package/dist/src/executor/simple-job-executor.d.ts +3 -1
  17. package/dist/src/executor/simple-job-executor.d.ts.map +1 -1
  18. package/dist/src/index.js +350 -203
  19. package/dist/src/processors/index.d.ts +0 -1
  20. package/dist/src/processors/index.d.ts.map +1 -1
  21. package/dist/src/storage/kysely/keyframe-store.d.ts +4 -1
  22. package/dist/src/storage/kysely/keyframe-store.d.ts.map +1 -1
  23. package/dist/src/storage/kysely/store.d.ts +5 -1
  24. package/dist/src/storage/kysely/store.d.ts.map +1 -1
  25. package/package.json +6 -6
  26. package/dist/src/processors/relational/types.d.ts +0 -2
  27. package/dist/src/processors/relational/types.d.ts.map +0 -1
  28. package/dist/src/processors/relational/utils.d.ts +0 -2
  29. 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
- let resultOrdinals = [];
11604
- await this.db.transaction().execute(async (trx) => {
11605
- if (collections.length > 0) {
11606
- const collectionRows = collections.map((collectionId) => ({
11607
- documentId: collectionId,
11608
- collectionId,
11609
- joinedOrdinal: BigInt(0),
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
- await trx.insertInto("document_collections").values(collectionRows).onConflict((oc) => oc.doNothing()).execute();
11613
- }
11614
- let operationOrdinals = [];
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
- if (removals.length > 0) {
11648
- for (const r of removals) {
11649
- const ordinal = operationOrdinals[r.operationIndex];
11650
- await trx.updateTable("document_collections").set({
11651
- leftOrdinal: BigInt(ordinal)
11652
- }).where("collectionId", "=", r.collectionId).where("documentId", "=", r.documentId).where("leftOrdinal", "is", null).execute();
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 resultOrdinals;
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.db.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");
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.db.selectFrom("operation_index_operations").selectAll().where("documentId", "=", documentId).orderBy("ordinal", "asc");
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.db.selectFrom("operation_index_operations").selectAll().where("ordinal", ">", ordinal).orderBy("ordinal", "asc");
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.db.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();
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.db.selectFrom("document_collections").select(["documentId", "collectionId"]).where("documentId", "in", documentIds).where("leftOrdinal", "is", null).execute();
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(writeCache, operationStore, documentMetaCache, collectionMembershipCache, registry, logger) {
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
- this.writeCache.putState(document.header.id, job.scope, job.branch, operation.index, document);
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
- this.documentMetaCache.putDocumentMeta(document.header.id, job.branch, {
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 this.writeCache.getState(documentId, job.scope, job.branch);
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
- this.writeCache.putState(documentId, job.scope, job.branch, operation.index, document);
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
- this.documentMetaCache.putDocumentMeta(documentId, job.branch, {
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 this.writeCache.getState(documentId, job.scope, job.branch);
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
- this.writeCache.putState(documentId, job.scope, job.branch, operation.index, document);
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
- this.documentMetaCache.putDocumentMeta(documentId, job.branch, {
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 this.writeCache.getState(input.sourceId, "document", job.branch);
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
- this.writeCache.putState(input.sourceId, job.scope, job.branch, operation.index, sourceDoc);
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
- this.collectionMembershipCache.invalidate(input.targetId);
13107
+ stores.collectionMembershipCache.invalidate(input.targetId);
13026
13108
  }
13027
- this.documentMetaCache.putDocumentMeta(input.sourceId, job.branch, {
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 this.writeCache.getState(input.sourceId, "document", job.branch);
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
- this.writeCache.putState(input.sourceId, job.scope, job.branch, operation.index, sourceDoc);
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
- this.collectionMembershipCache.invalidate(input.targetId);
13166
+ stores.collectionMembershipCache.invalidate(input.targetId);
13085
13167
  }
13086
- this.documentMetaCache.putDocumentMeta(input.sourceId, job.branch, {
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 this.operationStore.apply(documentId, documentType, scope, branch, operation.index, (txn) => {
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
- this.writeCache.invalidate(documentId, scope, branch);
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
- constructor(logger, registry, operationStore, eventBus, writeCache, operationIndex, documentMetaCache, collectionMembershipCache, config, signatureVerifier) {
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(writeCache, operationStore, documentMetaCache, collectionMembershipCache, registry, logger);
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 indexTxn = this.operationIndex.start();
13224
- if (job.kind === "load") {
13225
- const result2 = await this.executeLoadJob(job, startTime, indexTxn);
13226
- if (result2.success && result2.operationsWithContext) {
13227
- const ordinals2 = await this.operationIndex.commit(indexTxn);
13228
- for (let i = 0;i < result2.operationsWithContext.length; i++) {
13229
- result2.operationsWithContext[i].context.ordinal = ordinals2[i];
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 collectionMemberships = result2.operationsWithContext.length > 0 ? await this.getCollectionMembershipsForOperations(result2.operationsWithContext) : {};
13232
- const event = {
13233
- jobId: job.id,
13234
- operations: result2.operationsWithContext,
13235
- jobMeta: job.meta,
13236
- collectionMemberships
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
- this.eventBus.emit(ReactorEventTypes.JOB_WRITE_READY, event).catch((error) => {
13239
- this.logger.error("Failed to emit JOB_WRITE_READY event: @Event : @Error", event, error);
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
- return result2;
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
- const ordinals = await this.operationIndex.commit(indexTxn);
13254
- if (result.operationsWithContext.length > 0) {
13255
- for (let i = 0;i < result.operationsWithContext.length; i++) {
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 this.collectionMembershipCache.getCollectionsForDocuments(documentIds);
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 this.documentMetaCache.getDocumentMeta(job.documentId, job.branch);
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
- this.writeCache.invalidate(job.documentId, job.scope, job.branch);
13442
+ stores.writeCache.invalidate(job.documentId, job.scope, job.branch);
13330
13443
  }
13331
13444
  let document;
13332
13445
  try {
13333
- document = await this.writeCache.getState(job.documentId, job.scope, job.branch);
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 this.operationStore.apply(job.documentId, document.header.documentType, scope, job.branch, newOperation.index, (txn) => {
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
- this.writeCache.invalidate(job.documentId, scope, job.branch);
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
- this.writeCache.putState(job.documentId, scope, job.branch, newOperation.index, updatedDocument);
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 this.documentMetaCache.getDocumentMeta(job.documentId, job.branch);
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 this.operationStore.getRevisions(job.documentId, job.branch);
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 this.operationStore.getConflicting(job.documentId, scope, job.branch, minIncomingTimestamp);
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 this.operationStore.getSince(job.documentId, scope, job.branch, minConflictingIndex - 1);
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
- this.writeCache.invalidate(job.documentId, scope, job.branch);
13654
+ stores.writeCache.invalidate(job.documentId, scope, job.branch);
13542
13655
  if (scope === "document") {
13543
- this.documentMetaCache.invalidate(job.documentId, job.branch);
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.db.insertInto("Keyframe").values({
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.db.selectFrom("Keyframe").selectAll().where("documentId", "=", documentId).where("scope", "=", scope).where("branch", "=", branch).where("revision", "<=", targetRevision).orderBy("revision", "desc").limit(1).executeTakeFirst();
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.db.deleteFrom("Keyframe").where("documentId", "=", documentId);
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
- await this.db.transaction().execute(async (trx) => {
15572
- if (signal?.aborted) {
15573
- throw new Error("Operation aborted");
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
- const latestOp = await trx.selectFrom("Operation").selectAll().where("documentId", "=", documentId).where("scope", "=", scope).where("branch", "=", branch).orderBy("index", "desc").limit(1).executeTakeFirst();
15576
- const currentRevision = latestOp ? latestOp.index : -1;
15577
- if (currentRevision !== revision - 1) {
15578
- throw new RevisionMismatchError(currentRevision + 1, revision);
15579
- }
15580
- const atomicTxn = new AtomicTransaction(documentId, documentType, scope, branch, revision);
15581
- await fn(atomicTxn);
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.db.selectFrom("Operation").selectAll().where("documentId", "=", documentId).where("scope", "=", scope).where("branch", "=", branch).where("index", ">", revision).orderBy("index", "asc");
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.db.selectFrom("Operation").selectAll().where("id", ">", id).orderBy("id", "asc");
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.db.selectFrom("Operation").selectAll().where("documentId", "=", documentId).where("scope", "=", scope).where("branch", "=", branch).where("timestampUtcMs", ">=", new Date(minTimestamp)).orderBy("index", "asc");
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.db.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();
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;