@powerhousedao/reactor 6.2.2-dev.32 → 6.2.2-dev.33

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.
@@ -425,6 +425,16 @@ function buildErrorResult(job, error, startTime) {
425
425
  duration: Date.now() - startTime
426
426
  };
427
427
  }
428
+ /**
429
+ * Whether this operation is part of the document's creation. The create and the
430
+ * upgrade from version zero hold the first two indexes for the life of the
431
+ * document, so a reshuffle has to leave them where they are.
432
+ */
433
+ function isGenesisOperation(operation) {
434
+ if (operation.action.type === "CREATE_DOCUMENT") return true;
435
+ if (operation.action.type !== "UPGRADE_DOCUMENT") return false;
436
+ return operation.action.input.fromVersion === 0;
437
+ }
428
438
  //#endregion
429
439
  //#region src/cache/lru/lru-tracker.ts
430
440
  var LRUNode = class {
@@ -1773,14 +1783,9 @@ function comparePositions(a, b) {
1773
1783
  return (a.operation.id ?? "").localeCompare(b.operation.id ?? "");
1774
1784
  }
1775
1785
  /**
1776
- * Merges the read-set streams into one sequence by position. Each stream keeps
1777
- * its stored order; only how far to go is decided by the timestamp, so a stream
1778
- * whose stored order disagrees with its timestamp order still applies in the
1779
- * order it is stored.
1780
- *
1781
- * An operation's position in the result is the bound a decision at that
1782
- * operation reads to: every operation before it has been applied, and it has
1783
- * not.
1786
+ * Merges the read-set streams into one sequence by position. An operation's
1787
+ * place in the result is the bound a decision at that operation reads to: every
1788
+ * operation before it has been applied, and it has not.
1784
1789
  */
1785
1790
  function mergeByPosition(streams) {
1786
1791
  const merged = [];
@@ -1791,8 +1796,9 @@ function mergeByPosition(streams) {
1791
1796
  return merged.sort(comparePositions);
1792
1797
  }
1793
1798
  /**
1794
- * For auth-related reshuffles, we may need to retract previous operations.
1795
- * This function calculates the needed skip value.
1799
+ * The skip that retracts everything from `firstRetractedIndex` up to where the
1800
+ * re-appended operation lands. It spans the indexes rather than counting the
1801
+ * operations, because a stream with a gap in it makes those differ.
1796
1802
  */
1797
1803
  function retractionSkip(nextIndex, firstRetractedIndex) {
1798
1804
  return nextIndex - firstRetractedIndex;
@@ -1836,6 +1842,7 @@ function* walkByPosition(streams) {
1836
1842
  operations
1837
1843
  };
1838
1844
  }));
1845
+ const byKey = new Map(streams.map((stream) => [stream.streamKey, stream]));
1839
1846
  const states = new Map(streams.map((stream) => [stream.streamKey, stream.document]));
1840
1847
  for (const { streamKey, operation } of merged) {
1841
1848
  yield {
@@ -1844,7 +1851,7 @@ function* walkByPosition(streams) {
1844
1851
  states: new Map(states)
1845
1852
  };
1846
1853
  if (isDenied(operation)) continue;
1847
- const stream = streams.find((candidate) => candidate.streamKey === streamKey);
1854
+ const stream = byKey.get(streamKey);
1848
1855
  const before = states.get(streamKey);
1849
1856
  if (before === void 0 || stream === void 0) throw new Error(`No state for stream ${streamKey}`);
1850
1857
  states.set(streamKey, stream.apply(before, operation));
@@ -1871,11 +1878,10 @@ function firstDeleted(candidates) {
1871
1878
  * and leaves the earlier ones alone. A deletion among the ones passed in does
1872
1879
  * the same to those after it.
1873
1880
  */
1874
- async function evaluateDeletionsByPosition(documentId, scope, branch, operations, writeCache, operationStore, signal) {
1875
- const definition = documentDecisionModel({
1876
- documentId,
1877
- branch
1878
- });
1881
+ async function evaluateDeletionsByPosition(target, subject, stores, signal) {
1882
+ const { scope, operations } = subject;
1883
+ const { writeCache, operationStore } = stores;
1884
+ const definition = documentDecisionModel(target);
1879
1885
  const readSet = staticReadSet(definition);
1880
1886
  if (!definition.evaluatesScope(scope)) return operations.map(() => void 0);
1881
1887
  const evaluating = new Set(operations.map((operation) => operation.id));
@@ -1885,6 +1891,7 @@ async function evaluateDeletionsByPosition(documentId, scope, branch, operations
1885
1891
  })));
1886
1892
  const decidingWritten = operations.filter((operation) => canRefuseOthers(operation, readSet));
1887
1893
  if (readStreams.every((read) => read.operations.length === 0) && decidingWritten.length === 0) return operations.map(() => void 0);
1894
+ if (readStreams.length === 0) throw new Error(`Decision model for ${target.documentId} reads no stream whose query is known before it is built`);
1888
1895
  const walked = [];
1889
1896
  for (const read of readStreams) {
1890
1897
  const before = await writeCache.getState(read.stream.query.documentId, read.stream.query.scope, read.stream.query.branch, 0, signal);
@@ -1965,25 +1972,33 @@ var DriveCollectionId = class DriveCollectionId {
1965
1972
  //#endregion
1966
1973
  //#region src/executor/document-action-handler.ts
1967
1974
  var DocumentActionHandler = class {
1968
- constructor(registry, logger, driveContainerTypes) {
1975
+ constructor(registry, logger, driveContainerTypes, featureFlags) {
1969
1976
  this.registry = registry;
1970
1977
  this.logger = logger;
1971
1978
  this.driveContainerTypes = driveContainerTypes;
1979
+ this.featureFlags = featureFlags;
1972
1980
  }
1973
- async execute(job, action, startTime, indexTxn, stores, skip = 0, sourceRemote = "", signal, alreadyEvaluated = false, deniedReason) {
1974
- if (deniedReason !== void 0) return this.writeDenied(job, action, startTime, indexTxn, stores, skip, sourceRemote, deniedReason, signal);
1981
+ /** Whether the write arrives with its evaluation already decided. */
1982
+ alreadyEvaluated(executing) {
1983
+ return this.featureFlags.documentDecisions && executing.replayingAcceptedHistory;
1984
+ }
1985
+ async execute(write, executing) {
1986
+ const { action } = write;
1987
+ if (write.deniedReason !== void 0) return this.writeDenied(write, executing);
1975
1988
  switch (action.type) {
1976
- case "CREATE_DOCUMENT": return this.executeCreate(job, action, startTime, indexTxn, stores, skip, sourceRemote, signal);
1977
- case "DELETE_DOCUMENT": return this.executeDelete(job, action, startTime, indexTxn, stores, skip, sourceRemote, signal, alreadyEvaluated);
1978
- case "UPGRADE_DOCUMENT": return this.executeUpgrade(job, action, startTime, indexTxn, stores, skip, sourceRemote, signal, alreadyEvaluated);
1979
- case "ADD_RELATIONSHIP": return this.executeAddRelationship(job, action, startTime, indexTxn, stores, sourceRemote, signal);
1980
- case "REMOVE_RELATIONSHIP": return this.executeRemoveRelationship(job, action, startTime, indexTxn, stores, sourceRemote, signal);
1981
- case "UPDATE_RELATIONSHIP": return this.executeUpdateRelationship(job, action, startTime, indexTxn, stores, sourceRemote, signal);
1982
- default: return buildErrorResult(job, /* @__PURE__ */ new Error(`Unknown document action type: ${action.type}`), startTime);
1989
+ case "CREATE_DOCUMENT": return this.executeCreate(write, executing);
1990
+ case "DELETE_DOCUMENT": return this.executeDelete(write, executing);
1991
+ case "UPGRADE_DOCUMENT": return this.executeUpgrade(write, executing);
1992
+ case "ADD_RELATIONSHIP": return this.executeAddRelationship(write, executing);
1993
+ case "REMOVE_RELATIONSHIP": return this.executeRemoveRelationship(write, executing);
1994
+ case "UPDATE_RELATIONSHIP": return this.executeUpdateRelationship(write, executing);
1995
+ default: return buildErrorResult(executing.job, /* @__PURE__ */ new Error(`Unknown document action type: ${action.type}`), executing.startTime);
1983
1996
  }
1984
1997
  }
1985
1998
  /** A refused operation holds a position in the stream but changes nothing. */
1986
- async writeDenied(job, action, startTime, indexTxn, stores, skip, sourceRemote, deniedReason, signal) {
1999
+ async writeDenied(write, executing) {
2000
+ const { action, skip, sourceRemote, deniedReason } = write;
2001
+ const { job, startTime, indexTxn, stores, signal } = executing;
1987
2002
  let document;
1988
2003
  try {
1989
2004
  document = await stores.writeCache.getState(job.documentId, job.scope, job.branch, void 0, signal);
@@ -1997,7 +2012,12 @@ var DocumentActionHandler = class {
1997
2012
  });
1998
2013
  operation.deniedReason = deniedReason;
1999
2014
  operation.hash = hashDocumentStateForScope(document, job.scope);
2000
- const writeResult = await this.writeOperationToStore(job.documentId, document.header.documentType, job.scope, job.branch, operation, job, startTime, stores, signal);
2015
+ const writeResult = await this.writeOperationToStore({
2016
+ documentId: job.documentId,
2017
+ documentType: document.header.documentType,
2018
+ scope: job.scope,
2019
+ branch: job.branch
2020
+ }, operation, executing);
2001
2021
  if (!Array.isArray(writeResult)) return writeResult;
2002
2022
  operation = writeResult[0];
2003
2023
  updateDocumentRevision(document, job.scope, operation.index);
@@ -2024,7 +2044,9 @@ var DocumentActionHandler = class {
2024
2044
  document: document.state.document
2025
2045
  }), startTime);
2026
2046
  }
2027
- async executeCreate(job, action, startTime, indexTxn, stores, skip = 0, sourceRemote = "", signal) {
2047
+ async executeCreate(write, executing) {
2048
+ const { action, skip, sourceRemote } = write;
2049
+ const { job, startTime, indexTxn, stores, signal } = executing;
2028
2050
  if (job.scope !== "document") return {
2029
2051
  job,
2030
2052
  success: false,
@@ -2042,7 +2064,12 @@ var DocumentActionHandler = class {
2042
2064
  ...document.state
2043
2065
  };
2044
2066
  const resultingState = JSON.stringify(resultingStateObj);
2045
- const writeResult = await this.writeOperationToStore(document.header.id, document.header.documentType, job.scope, job.branch, operation, job, startTime, stores, signal);
2067
+ const writeResult = await this.writeOperationToStore({
2068
+ documentId: document.header.id,
2069
+ documentType: document.header.documentType,
2070
+ scope: job.scope,
2071
+ branch: job.branch
2072
+ }, operation, executing);
2046
2073
  if (!Array.isArray(writeResult)) return writeResult;
2047
2074
  operation = writeResult[0];
2048
2075
  updateDocumentRevision(document, job.scope, operation.index);
@@ -2071,7 +2098,9 @@ var DocumentActionHandler = class {
2071
2098
  });
2072
2099
  return buildSuccessResult(job, operation, document.header.id, document.header.documentType, resultingState, startTime);
2073
2100
  }
2074
- async executeDelete(job, action, startTime, indexTxn, stores, skip = 0, sourceRemote = "", signal, alreadyEvaluated = false) {
2101
+ async executeDelete(write, executing) {
2102
+ const { action, skip, sourceRemote } = write;
2103
+ const { job, startTime, indexTxn, stores, signal } = executing;
2075
2104
  const input = action.input;
2076
2105
  if (!input.documentId) return buildErrorResult(job, /* @__PURE__ */ new Error("DELETE_DOCUMENT action requires a documentId in input"), startTime);
2077
2106
  const documentId = input.documentId;
@@ -2082,7 +2111,7 @@ var DocumentActionHandler = class {
2082
2111
  return buildErrorResult(job, /* @__PURE__ */ new Error(`Failed to fetch document before deletion: ${error instanceof Error ? error.message : String(error)}`), startTime);
2083
2112
  }
2084
2113
  const documentState = document.state.document;
2085
- if (documentState.isDeleted && !alreadyEvaluated) return buildErrorResult(job, new DocumentDeletedError(documentId, documentState.deletedAtUtcIso), startTime);
2114
+ if (documentState.isDeleted && !this.alreadyEvaluated(executing)) return buildErrorResult(job, new DocumentDeletedError(documentId, documentState.deletedAtUtcIso), startTime);
2086
2115
  let operation = createOperation(action, getNextIndexForScope(document, job.scope), skip, {
2087
2116
  documentId,
2088
2117
  scope: job.scope,
@@ -2094,7 +2123,12 @@ var DocumentActionHandler = class {
2094
2123
  document: document.state.document
2095
2124
  };
2096
2125
  const resultingState = JSON.stringify(resultingStateObj);
2097
- const writeResult = await this.writeOperationToStore(documentId, document.header.documentType, job.scope, job.branch, operation, job, startTime, stores, signal);
2126
+ const writeResult = await this.writeOperationToStore({
2127
+ documentId,
2128
+ documentType: document.header.documentType,
2129
+ scope: job.scope,
2130
+ branch: job.branch
2131
+ }, operation, executing);
2098
2132
  if (!Array.isArray(writeResult)) return writeResult;
2099
2133
  operation = writeResult[0];
2100
2134
  updateDocumentRevision(document, job.scope, operation.index);
@@ -2118,7 +2152,9 @@ var DocumentActionHandler = class {
2118
2152
  });
2119
2153
  return buildSuccessResult(job, operation, documentId, document.header.documentType, resultingState, startTime);
2120
2154
  }
2121
- async executeUpgrade(job, action, startTime, indexTxn, stores, skip = 0, sourceRemote = "", signal, alreadyEvaluated = false) {
2155
+ async executeUpgrade(write, executing) {
2156
+ const { action, skip, sourceRemote } = write;
2157
+ const { job, startTime, indexTxn, stores, signal } = executing;
2122
2158
  const input = action.input;
2123
2159
  if (!input.documentId) return buildErrorResult(job, /* @__PURE__ */ new Error("UPGRADE_DOCUMENT action requires a documentId in input"), startTime);
2124
2160
  const documentId = input.documentId;
@@ -2131,7 +2167,7 @@ var DocumentActionHandler = class {
2131
2167
  return buildErrorResult(job, /* @__PURE__ */ new Error(`Failed to fetch document for upgrade: ${error instanceof Error ? error.message : String(error)}`), startTime);
2132
2168
  }
2133
2169
  const documentState = document.state.document;
2134
- if (documentState.isDeleted && !alreadyEvaluated) return buildErrorResult(job, new DocumentDeletedError(documentId, documentState.deletedAtUtcIso), startTime);
2170
+ if (documentState.isDeleted && !this.alreadyEvaluated(executing)) return buildErrorResult(job, new DocumentDeletedError(documentId, documentState.deletedAtUtcIso), startTime);
2135
2171
  const nextIndex = getNextIndexForScope(document, job.scope);
2136
2172
  let upgradePath;
2137
2173
  if (fromVersion > 0 && fromVersion < toVersion) try {
@@ -2161,7 +2197,12 @@ var DocumentActionHandler = class {
2161
2197
  ...document.state
2162
2198
  };
2163
2199
  const resultingState = JSON.stringify(resultingStateObj);
2164
- const writeResult = await this.writeOperationToStore(documentId, document.header.documentType, job.scope, job.branch, operation, job, startTime, stores, signal);
2200
+ const writeResult = await this.writeOperationToStore({
2201
+ documentId,
2202
+ documentType: document.header.documentType,
2203
+ scope: job.scope,
2204
+ branch: job.branch
2205
+ }, operation, executing);
2165
2206
  if (!Array.isArray(writeResult)) return writeResult;
2166
2207
  operation = writeResult[0];
2167
2208
  updateDocumentRevision(document, job.scope, operation.index);
@@ -2185,8 +2226,8 @@ var DocumentActionHandler = class {
2185
2226
  });
2186
2227
  return buildSuccessResult(job, operation, documentId, document.header.documentType, resultingState, startTime);
2187
2228
  }
2188
- executeAddRelationship(job, action, startTime, indexTxn, stores, sourceRemote = "", signal) {
2189
- return this.withRelationshipAction("ADD_RELATIONSHIP", job, action, startTime, indexTxn, stores, sourceRemote, signal, (input) => input.sourceId === input.targetId ? /* @__PURE__ */ new Error("ADD_RELATIONSHIP: sourceId and targetId cannot be the same (self-relationships not allowed)") : null, ({ indexTxn: txn, stores: s, sourceDoc, input, job: j }) => {
2229
+ executeAddRelationship(write, executing) {
2230
+ return this.withRelationshipAction("ADD_RELATIONSHIP", write, executing, (input) => input.sourceId === input.targetId ? /* @__PURE__ */ new Error("ADD_RELATIONSHIP: sourceId and targetId cannot be the same (self-relationships not allowed)") : null, ({ indexTxn: txn, stores: s, sourceDoc, input, job: j }) => {
2190
2231
  if (this.driveContainerTypes.has(sourceDoc.header.documentType)) {
2191
2232
  const collectionId = DriveCollectionId.forDrive(input.sourceId, j.branch).key;
2192
2233
  txn.addToCollection(collectionId, input.targetId);
@@ -2194,8 +2235,8 @@ var DocumentActionHandler = class {
2194
2235
  }
2195
2236
  });
2196
2237
  }
2197
- executeRemoveRelationship(job, action, startTime, indexTxn, stores, sourceRemote = "", signal) {
2198
- return this.withRelationshipAction("REMOVE_RELATIONSHIP", job, action, startTime, indexTxn, stores, sourceRemote, signal, null, ({ indexTxn: txn, stores: s, sourceDoc, input, job: j }) => {
2238
+ executeRemoveRelationship(write, executing) {
2239
+ return this.withRelationshipAction("REMOVE_RELATIONSHIP", write, executing, null, ({ indexTxn: txn, stores: s, sourceDoc, input, job: j }) => {
2199
2240
  if (this.driveContainerTypes.has(sourceDoc.header.documentType)) {
2200
2241
  const collectionId = DriveCollectionId.forDrive(input.sourceId, j.branch).key;
2201
2242
  txn.removeFromCollection(collectionId, input.targetId);
@@ -2203,10 +2244,12 @@ var DocumentActionHandler = class {
2203
2244
  }
2204
2245
  });
2205
2246
  }
2206
- executeUpdateRelationship(job, action, startTime, indexTxn, stores, sourceRemote = "", signal) {
2207
- return this.withRelationshipAction("UPDATE_RELATIONSHIP", job, action, startTime, indexTxn, stores, sourceRemote, signal, null, null);
2247
+ executeUpdateRelationship(write, executing) {
2248
+ return this.withRelationshipAction("UPDATE_RELATIONSHIP", write, executing, null, null);
2208
2249
  }
2209
- async withRelationshipAction(actionTypeName, job, action, startTime, indexTxn, stores, sourceRemote, signal, preValidate, postWrite) {
2250
+ async withRelationshipAction(actionTypeName, write, executing, preValidate, postWrite) {
2251
+ const { action, skip, sourceRemote } = write;
2252
+ const { job, startTime, indexTxn, stores, signal } = executing;
2210
2253
  if (job.scope !== "document") return buildErrorResult(job, /* @__PURE__ */ new Error(`${actionTypeName} must be in "document" scope, got "${job.scope}"`), startTime);
2211
2254
  const input = action.input;
2212
2255
  if (!input.sourceId || !input.targetId || !input.relationshipType) return buildErrorResult(job, /* @__PURE__ */ new Error(`${actionTypeName} action requires sourceId, targetId, and relationshipType in input`), startTime);
@@ -2220,12 +2263,17 @@ var DocumentActionHandler = class {
2220
2263
  } catch (error) {
2221
2264
  return buildErrorResult(job, /* @__PURE__ */ new Error(`${actionTypeName}: source document ${input.sourceId} not found: ${error instanceof Error ? error.message : String(error)}`), startTime);
2222
2265
  }
2223
- let operation = createOperation(action, getNextIndexForScope(sourceDoc, job.scope), 0, {
2266
+ let operation = createOperation(action, getNextIndexForScope(sourceDoc, job.scope), skip, {
2224
2267
  documentId: input.sourceId,
2225
2268
  scope: job.scope,
2226
2269
  branch: job.branch
2227
2270
  });
2228
- const writeResult = await this.writeOperationToStore(input.sourceId, sourceDoc.header.documentType, job.scope, job.branch, operation, job, startTime, stores, signal);
2271
+ const writeResult = await this.writeOperationToStore({
2272
+ documentId: input.sourceId,
2273
+ documentType: sourceDoc.header.documentType,
2274
+ scope: job.scope,
2275
+ branch: job.branch
2276
+ }, operation, executing);
2229
2277
  if (!Array.isArray(writeResult)) return writeResult;
2230
2278
  operation = writeResult[0];
2231
2279
  sourceDoc.header.lastModifiedAtUtcIso = operation.timestampUtcMs || (/* @__PURE__ */ new Date()).toISOString();
@@ -2263,7 +2311,9 @@ var DocumentActionHandler = class {
2263
2311
  });
2264
2312
  return buildSuccessResult(job, operation, input.sourceId, sourceDoc.header.documentType, resultingState, startTime);
2265
2313
  }
2266
- async writeOperationToStore(documentId, documentType, scope, branch, operation, job, startTime, stores, signal) {
2314
+ async writeOperationToStore(target, operation, executing) {
2315
+ const { documentId, documentType, scope, branch } = target;
2316
+ const { job, startTime, stores, signal } = executing;
2267
2317
  let storedOperations;
2268
2318
  try {
2269
2319
  storedOperations = await stores.operationStore.apply(documentId, documentType, scope, branch, operation.index, (txn) => {
@@ -2379,7 +2429,7 @@ var SimpleJobExecutor = class {
2379
2429
  };
2380
2430
  this.featureFlags = { documentDecisions: config.featureFlags?.documentDecisions ?? false };
2381
2431
  this.signatureVerifierModule = new SignatureVerifier(signatureVerifier);
2382
- this.documentActionHandler = new DocumentActionHandler(registry, logger, driveContainerTypes);
2432
+ this.documentActionHandler = new DocumentActionHandler(registry, logger, driveContainerTypes, this.featureFlags);
2383
2433
  this.executionScope = executionScope ?? new DefaultExecutionScope(operationStore, operationIndex, writeCache, documentMetaCache, collectionMembershipCache);
2384
2434
  }
2385
2435
  /**
@@ -2395,7 +2445,14 @@ var SimpleJobExecutor = class {
2395
2445
  result = await this.executionScope.run(async (stores) => {
2396
2446
  const indexTxn = stores.operationIndex.start();
2397
2447
  if (job.kind === "load") {
2398
- const loadResult = await this.executeLoadJob(job, startTime, indexTxn, stores, signal);
2448
+ const loadResult = await this.executeLoadJob({
2449
+ job,
2450
+ startTime,
2451
+ indexTxn,
2452
+ stores,
2453
+ signal,
2454
+ replayingAcceptedHistory: true
2455
+ });
2399
2456
  if (loadResult.success && loadResult.operationsWithContext) {
2400
2457
  for (const owc of loadResult.operationsWithContext) touchedCacheEntries.push({
2401
2458
  documentId: owc.context.documentId,
@@ -2414,7 +2471,20 @@ var SimpleJobExecutor = class {
2414
2471
  }
2415
2472
  return loadResult;
2416
2473
  }
2417
- const actionResult = await this.processActions(job, job.actions, startTime, indexTxn, stores, void 0, void 0, "", signal);
2474
+ const executing = {
2475
+ job,
2476
+ startTime,
2477
+ indexTxn,
2478
+ stores,
2479
+ signal,
2480
+ replayingAcceptedHistory: false
2481
+ };
2482
+ const positioned = await this.positionByTimestamp(job, stores, signal);
2483
+ const actionResult = await this.processActions(positioned.actions.map((action, i) => ({
2484
+ action,
2485
+ skip: positioned.skipValues?.[i] ?? 0,
2486
+ sourceRemote: ""
2487
+ })), executing);
2418
2488
  if (!actionResult.success) return {
2419
2489
  job,
2420
2490
  success: false,
@@ -2429,13 +2499,7 @@ var SimpleJobExecutor = class {
2429
2499
  const reevaluationError = await this.reevaluateIfCriteriaMet({
2430
2500
  scope: job.scope,
2431
2501
  operations: actionResult.generatedOperations
2432
- }, {
2433
- job,
2434
- startTime,
2435
- indexTxn,
2436
- stores,
2437
- signal
2438
- });
2502
+ }, executing);
2439
2503
  if (reevaluationError) return {
2440
2504
  job,
2441
2505
  success: false,
@@ -2477,7 +2541,9 @@ var SimpleJobExecutor = class {
2477
2541
  const documentIds = [...new Set(operations.map((op) => op.context.documentId))];
2478
2542
  return stores.collectionMembershipCache.getCollectionsForDocuments(documentIds);
2479
2543
  }
2480
- async processActions(job, actions, startTime, indexTxn, stores, skipValues, sourceOperations, sourceRemote = "", signal, deniedReasons) {
2544
+ async processActions(writes, executing) {
2545
+ const { job, signal } = executing;
2546
+ const actions = writes.map((write) => write.action);
2481
2547
  const generatedOperations = [];
2482
2548
  const operationsWithContext = [];
2483
2549
  try {
@@ -2497,13 +2563,8 @@ var SimpleJobExecutor = class {
2497
2563
  error: /* @__PURE__ */ new Error(`Invalid timestamp "${action.timestampUtcMs}" on action ${action.type} (id: ${action.id})`)
2498
2564
  };
2499
2565
  let lastYield = performance.now();
2500
- const replayingAcceptedHistory = job.kind === "load" || deniedReasons !== void 0;
2501
- for (let actionIndex = 0; actionIndex < actions.length; actionIndex++) {
2502
- const action = actions[actionIndex];
2503
- const skip = skipValues?.[actionIndex] ?? 0;
2504
- const sourceOperation = sourceOperations?.[actionIndex];
2505
- const deniedReason = deniedReasons?.[actionIndex];
2506
- const result = documentScopeActions.includes(action.type) ? await this.documentActionHandler.execute(job, action, startTime, indexTxn, stores, skip, sourceRemote, signal, this.featureFlags.documentDecisions && replayingAcceptedHistory, deniedReason) : await this.executeRegularAction(job, action, startTime, indexTxn, stores, skip, sourceOperation, sourceRemote, signal, deniedReason, replayingAcceptedHistory);
2566
+ for (const write of writes) {
2567
+ const result = documentScopeActions.includes(write.action.type) ? await this.documentActionHandler.execute(write, executing) : await this.executeRegularAction(write, executing);
2507
2568
  const error = this.accumulateResultOrReturnError(result, generatedOperations, operationsWithContext);
2508
2569
  if (error !== null) return {
2509
2570
  success: false,
@@ -2528,10 +2589,12 @@ var SimpleJobExecutor = class {
2528
2589
  operationsWithContext
2529
2590
  };
2530
2591
  }
2531
- async executeRegularAction(job, action, startTime, indexTxn, stores, skip = 0, sourceOperation, sourceRemote = "", signal, deniedReason, replayingAcceptedHistory = false) {
2592
+ async executeRegularAction(write, executing) {
2593
+ const { action, skip, sourceOperation, sourceRemote, deniedReason } = write;
2594
+ const { job, startTime, indexTxn, stores, signal } = executing;
2532
2595
  let appendCondition;
2533
2596
  let documentVersion;
2534
- const alreadyEvaluated = this.featureFlags.documentDecisions && replayingAcceptedHistory;
2597
+ const alreadyEvaluated = this.featureFlags.documentDecisions && executing.replayingAcceptedHistory;
2535
2598
  if (this.featureFlags.documentDecisions && !alreadyEvaluated) {
2536
2599
  const target = {
2537
2600
  documentId: job.documentId,
@@ -2572,10 +2635,10 @@ var SimpleJobExecutor = class {
2572
2635
  } catch (error) {
2573
2636
  return buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
2574
2637
  }
2575
- if (!replayingAcceptedHistory) {
2638
+ if (!executing.replayingAcceptedHistory) {
2576
2639
  const subject = {
2577
- address: action.context?.signer?.user.address,
2578
- key: action.context?.signer?.app.key
2640
+ address: write.action.context?.signer?.user.address,
2641
+ key: write.action.context?.signer?.app.key
2579
2642
  };
2580
2643
  if (decide(document.state.auth, subject, {
2581
2644
  verb: "execute",
@@ -2690,6 +2753,41 @@ var SimpleJobExecutor = class {
2690
2753
  };
2691
2754
  }
2692
2755
  /**
2756
+ * Orders a write by timestamp. The caller supplies the timestamp, so a write
2757
+ * can belong before operations already stored, and appending it at the tail
2758
+ * would leave the scope out of order. The operations it belongs before are
2759
+ * re-appended alongside it, the way a load reshuffles.
2760
+ */
2761
+ async positionByTimestamp(job, stores, signal) {
2762
+ if (!this.featureFlags.documentDecisions || job.actions.length === 0) return { actions: job.actions };
2763
+ let earliest = job.actions[0].timestampUtcMs;
2764
+ for (const action of job.actions) if (action.timestampUtcMs < earliest) earliest = action.timestampUtcMs;
2765
+ const revisions = await stores.operationStore.getRevisions(job.documentId, job.branch, signal);
2766
+ if (earliest >= revisions.latestTimestamp) return { actions: job.actions };
2767
+ const conflicting = (await stores.operationStore.getConflicting(job.documentId, job.scope, job.branch, earliest, void 0, signal)).results.filter((operation) => !isGenesisOperation(operation));
2768
+ if (conflicting.length === 0) return { actions: job.actions };
2769
+ const nextIndex = revisions.revision[job.scope] ?? 0;
2770
+ let firstConflicting = conflicting[0].index;
2771
+ for (const operation of conflicting) if (operation.index < firstConflicting) firstConflicting = operation.index;
2772
+ const incoming = job.actions.map((action, i) => ({
2773
+ id: action.id,
2774
+ index: nextIndex + i,
2775
+ skip: 0,
2776
+ hash: "",
2777
+ timestampUtcMs: action.timestampUtcMs,
2778
+ action
2779
+ }));
2780
+ const merged = reshuffleByTimestamp({
2781
+ index: nextIndex,
2782
+ skip: retractionSkip(nextIndex, firstConflicting)
2783
+ }, conflicting, incoming);
2784
+ stores.writeCache.invalidate(job.documentId, job.scope, job.branch);
2785
+ return {
2786
+ actions: merged.map((operation) => operation.action),
2787
+ skipValues: merged.map((operation) => operation.skip)
2788
+ };
2789
+ }
2790
+ /**
2693
2791
  * Re-evaluates the document when a write meets both criteria: it was written
2694
2792
  * to a stream the model reads, and it is timestamped before an operation
2695
2793
  * already stored. The caller supplies the timestamp and the reactor does not replace
@@ -2714,7 +2812,7 @@ var SimpleJobExecutor = class {
2714
2812
  * re-appended, carrying a skip that spans the indices it supersedes.
2715
2813
  */
2716
2814
  async reevaluateDocument(executing) {
2717
- const { job, startTime, indexTxn, stores, signal } = executing;
2815
+ const { job, stores, signal } = executing;
2718
2816
  const definition = documentDecisionModel({
2719
2817
  documentId: job.documentId,
2720
2818
  branch: job.branch
@@ -2725,20 +2823,36 @@ var SimpleJobExecutor = class {
2725
2823
  const stored = (await stores.operationStore.getSince(job.documentId, scope, job.branch, -1, void 0, void 0, signal)).results;
2726
2824
  const effective = garbageCollect(sortOperations([...stored]));
2727
2825
  if (effective.length === 0) continue;
2728
- const reevaluated = await evaluateDeletionsByPosition(job.documentId, scope, job.branch, effective, stores.writeCache, stores.operationStore, signal);
2826
+ const reevaluated = await evaluateDeletionsByPosition({
2827
+ documentId: job.documentId,
2828
+ branch: job.branch
2829
+ }, {
2830
+ scope,
2831
+ operations: effective
2832
+ }, stores, signal);
2729
2833
  const firstChange = effective.findIndex((operation, i) => operation.deniedReason !== reevaluated[i]);
2730
2834
  if (firstChange === -1) continue;
2731
2835
  const tail = effective.slice(firstChange);
2732
2836
  const nextIndex = revisions.revision[scope];
2733
2837
  stores.writeCache.invalidate(job.documentId, scope, job.branch);
2734
- const result = await this.processActions({
2735
- ...job,
2736
- scope
2737
- }, tail.map((operation) => operation.action), startTime, indexTxn, stores, tail.map((_, i) => i === 0 ? retractionSkip(nextIndex, tail[0].index) : 0), void 0, "", signal, reevaluated.slice(firstChange));
2838
+ const result = await this.processActions(tail.map((operation, i) => ({
2839
+ action: operation.action,
2840
+ skip: i === 0 ? retractionSkip(nextIndex, tail[0].index) : 0,
2841
+ sourceRemote: "",
2842
+ deniedReason: reevaluated[firstChange + i]
2843
+ })), {
2844
+ ...executing,
2845
+ job: {
2846
+ ...job,
2847
+ scope
2848
+ },
2849
+ replayingAcceptedHistory: true
2850
+ });
2738
2851
  if (!result.success) return result.error ?? /* @__PURE__ */ new Error(`Re-evaluation of ${job.documentId} ${scope} failed`);
2739
2852
  }
2740
2853
  }
2741
- async executeLoadJob(job, startTime, indexTxn, stores, signal) {
2854
+ async executeLoadJob(executing) {
2855
+ const { job, startTime, indexTxn, stores, signal } = executing;
2742
2856
  if (job.operations.length === 0) return buildErrorResult(job, /* @__PURE__ */ new Error("Load job must include at least one operation"), startTime);
2743
2857
  let docMeta;
2744
2858
  try {
@@ -2788,7 +2902,7 @@ var SimpleJobExecutor = class {
2788
2902
  }
2789
2903
  return true;
2790
2904
  });
2791
- const existingOpsToReshuffle = nonSupersededOps;
2905
+ const existingOpsToReshuffle = nonSupersededOps.filter((operation) => !isGenesisOperation(operation));
2792
2906
  if (existingOpsToReshuffle.length > this.config.maxSkipThreshold) return {
2793
2907
  job,
2794
2908
  success: false,
@@ -2831,11 +2945,15 @@ var SimpleJobExecutor = class {
2831
2945
  id: operation.id
2832
2946
  })));
2833
2947
  for (const operation of reshuffledOperations) if (operation.action.type === "NOOP") operation.skip = 1;
2834
- const actions = reshuffledOperations.map((operation) => operation.action);
2835
- const skipValues = reshuffledOperations.map((operation) => operation.skip);
2836
2948
  let deniedReasons;
2837
2949
  if (this.featureFlags.documentDecisions) try {
2838
- deniedReasons = await evaluateDeletionsByPosition(job.documentId, scope, job.branch, reshuffledOperations, stores.writeCache, stores.operationStore, signal);
2950
+ deniedReasons = await evaluateDeletionsByPosition({
2951
+ documentId: job.documentId,
2952
+ branch: job.branch
2953
+ }, {
2954
+ scope,
2955
+ operations: reshuffledOperations
2956
+ }, stores, signal);
2839
2957
  } catch (error) {
2840
2958
  return {
2841
2959
  job,
@@ -2845,7 +2963,13 @@ var SimpleJobExecutor = class {
2845
2963
  };
2846
2964
  }
2847
2965
  const effectiveSourceRemote = skipCount > 0 ? "" : job.meta.sourceRemote || "";
2848
- const result = await this.processActions(job, actions, startTime, indexTxn, stores, skipValues, reshuffledOperations, effectiveSourceRemote, signal, deniedReasons);
2966
+ const result = await this.processActions(reshuffledOperations.map((operation, i) => ({
2967
+ action: operation.action,
2968
+ skip: operation.skip,
2969
+ sourceOperation: operation,
2970
+ sourceRemote: effectiveSourceRemote,
2971
+ deniedReason: deniedReasons?.[i]
2972
+ })), executing);
2849
2973
  if (!result.success) return {
2850
2974
  job,
2851
2975
  success: false,
@@ -2857,13 +2981,7 @@ var SimpleJobExecutor = class {
2857
2981
  const reevaluationError = await this.reevaluateIfCriteriaMet({
2858
2982
  scope,
2859
2983
  operations: result.generatedOperations
2860
- }, {
2861
- job,
2862
- startTime,
2863
- indexTxn,
2864
- stores,
2865
- signal
2866
- });
2984
+ }, executing);
2867
2985
  if (reevaluationError) return {
2868
2986
  job,
2869
2987
  success: false,
@@ -3374,16 +3492,12 @@ var KyselyOperationStore = class KyselyOperationStore {
3374
3492
  "o1.index",
3375
3493
  "o1.timestampUtcMs"
3376
3494
  ]).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();
3495
+ const latest = await this.queryExecutor.selectFrom("Operation").select((eb) => eb.fn.max("timestampUtcMs").as("latestTimestamp")).where("documentId", "=", documentId).where("branch", "=", branch).executeTakeFirst();
3377
3496
  const revision = {};
3378
- let latestTimestamp = (/* @__PURE__ */ new Date(0)).toISOString();
3379
- for (const row of scopeRevisions) {
3380
- revision[row.scope] = row.index + 1;
3381
- const timestamp = row.timestampUtcMs.toISOString();
3382
- if (timestamp > latestTimestamp) latestTimestamp = timestamp;
3383
- }
3497
+ for (const row of scopeRevisions) revision[row.scope] = row.index + 1;
3384
3498
  return {
3385
3499
  revision,
3386
- latestTimestamp
3500
+ latestTimestamp: latest?.latestTimestamp ? new Date(latest.latestTimestamp).toISOString() : (/* @__PURE__ */ new Date(0)).toISOString()
3387
3501
  };
3388
3502
  }
3389
3503
  rowToOperation(row) {
@@ -3730,4 +3844,4 @@ const DEFAULT_DRIVE_CONTAINER_TYPES = new Set(["powerhouse/document-drive", "pow
3730
3844
  //#endregion
3731
3845
  export { AuthorizationDeniedError as A, DuplicateOperationError as C, DuplicateModuleError as D, DuplicateManifestError as E, throwIfAborted as F, __exportAll as I, DocumentNotFoundError as M, matchesScope as N, InvalidModuleError as O, parsePagingOptions as P, AppendConditionFailedError as S, RevisionMismatchError as T, DocumentMetaCache as _, createForwardingPoolInstrumentation as a, CollectionMembershipCache as b, KyselyKeyframeStore as c, DriveCollectionId as d, buildDecisionModel as f, KyselyOperationIndex as g, KyselyWriteCache as h, runMigrations as i, DocumentDeletedError as j, ModuleNotFoundError as k, DocumentModelRegistry as l, EventBus as m, REACTOR_SCHEMA as n, instrumentPgPool as o, KyselyExecutionScope as p, getMigrationStatus as r, KyselyOperationStore as s, DEFAULT_DRIVE_CONTAINER_TYPES as t, SimpleJobExecutor as u, createConsistencyToken as v, OptimisticLockError as w, APPEND_CONDITION_FAILED_PREFIX as x, createEmptyConsistencyToken as y };
3732
3846
 
3733
- //# sourceMappingURL=drive-container-types-CPQDOggp.js.map
3847
+ //# sourceMappingURL=drive-container-types-ZSLCC3lC.js.map