@powerhousedao/reactor 6.2.2-dev.63 → 6.2.2-dev.65

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.
@@ -1,4 +1,4 @@
1
- import { _ as DocumentMetaCache, c as KyselyKeyframeStore, f as KyselyExecutionScope, g as KyselyOperationIndex, h as KyselyWriteCache, l as DocumentModelRegistry, m as EventBus, n as REACTOR_SCHEMA, s as KyselyOperationStore, t as DEFAULT_DRIVE_CONTAINER_TYPES, u as SimpleJobExecutor, v as CollectionMembershipCache } from "./drive-container-types-CruMdXue.js";
1
+ import { _ as DocumentMetaCache, c as KyselyKeyframeStore, f as KyselyExecutionScope, g as KyselyOperationIndex, h as KyselyWriteCache, l as DocumentModelRegistry, m as EventBus, n as REACTOR_SCHEMA, s as KyselyOperationStore, t as DEFAULT_DRIVE_CONTAINER_TYPES, u as SimpleJobExecutor, v as CollectionMembershipCache } from "./drive-container-types-yzXFiYNp.js";
2
2
  import { n as ReactorEventTypes } from "./types-DMKLa0Ok.js";
3
3
  //#region src/executor/worker/build-worker-executor.ts
4
4
  async function defaultLoadFactory(spec) {
@@ -80,4 +80,4 @@ async function buildWorkerExecutor(options) {
80
80
  //#endregion
81
81
  export { defaultLoadFactory as n, buildWorkerExecutor as t };
82
82
 
83
- //# sourceMappingURL=build-worker-executor-DymEQNP9.js.map
83
+ //# sourceMappingURL=build-worker-executor-F755haxr.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"build-worker-executor-DymEQNP9.js","names":[],"sources":["../src/executor/worker/build-worker-executor.ts"],"sourcesContent":["import type {\n DocumentModelModule,\n OperationWithContext,\n} from \"@powerhousedao/shared/document-model\";\nimport type { ILogger } from \"document-model\";\nimport type { Kysely } from \"kysely\";\nimport { CollectionMembershipCache } from \"../../cache/collection-membership-cache.js\";\nimport { DocumentMetaCache } from \"../../cache/document-meta-cache.js\";\nimport { KyselyOperationIndex } from \"../../cache/kysely-operation-index.js\";\nimport { KyselyWriteCache } from \"../../cache/kysely-write-cache.js\";\nimport type { WriteCacheConfig } from \"../../cache/write-cache-types.js\";\nimport { DEFAULT_DRIVE_CONTAINER_TYPES } from \"../../core/drive-container-types.js\";\nimport type { Database } from \"../../core/types.js\";\nimport { EventBus } from \"../../events/event-bus.js\";\nimport {\n ReactorEventTypes,\n type JobWriteReadyEvent,\n} from \"../../events/types.js\";\nimport { DocumentModelRegistry } from \"../../registry/implementation.js\";\nimport type { JobMeta } from \"../../shared/types.js\";\nimport type { SignatureVerificationHandler } from \"../../signer/types.js\";\nimport { KyselyKeyframeStore } from \"../../storage/kysely/keyframe-store.js\";\nimport { KyselyOperationStore } from \"../../storage/kysely/store.js\";\nimport type { Database as StorageDatabase } from \"../../storage/kysely/types.js\";\nimport { REACTOR_SCHEMA } from \"../../storage/migrations/migrator.js\";\nimport { KyselyExecutionScope } from \"../execution-scope.js\";\nimport { SimpleJobExecutor } from \"../simple-job-executor.js\";\nimport type { JobExecutorConfig } from \"../types.js\";\nimport type {\n FactorySpec,\n InitMessage,\n ModelManifestEntry,\n} from \"./protocol.js\";\n\n/**\n * In-worker capture of the JOB_WRITE_READY event emitted by the executor.\n * The worker forwards `operations` and `jobMeta` back to the parent; the\n * parent re-enriches `collectionMemberships` at emit time.\n */\nexport type WorkerWriteReadyCapture = {\n operations: OperationWithContext[];\n jobMeta: JobMeta;\n};\n\nexport type WorkerExecutorStack = {\n executor: SimpleJobExecutor;\n registry: DocumentModelRegistry;\n /**\n * Synchronously pops the most-recent JOB_WRITE_READY captured on this\n * worker's local event bus and clears it. Returns null if the executor\n * did not produce one for this job.\n */\n takeLastWriteReady(): WorkerWriteReadyCapture | null;\n};\n\nexport type BuildWorkerExecutorOptions = {\n init: InitMessage;\n database: Kysely<Database>;\n logger: ILogger;\n executorConfig?: JobExecutorConfig;\n driveContainerTypes?: ReadonlySet<string>;\n /**\n * Override the module loader used to materialize factory specs. Tests\n * can inject a deterministic resolver instead of touching the real\n * Node module loader.\n */\n loadFactory?: (spec: FactorySpec) => Promise<unknown>;\n};\n\nexport async function defaultLoadFactory(spec: FactorySpec): Promise<unknown> {\n const ref = spec.module;\n const specifier =\n \"filePath\" in ref\n ? new URL(`file://${ref.filePath}`).href\n : ref.packageName;\n const mod = (await import(specifier)) as Record<string, unknown>;\n const exported = mod[ref.exportName];\n if (typeof exported === \"function\") {\n return (exported as (args: unknown) => unknown)(spec.initArgs);\n }\n return exported;\n}\n\nasync function loadModelManifest(\n entries: ModelManifestEntry[],\n loadFactory: (spec: FactorySpec) => Promise<unknown>,\n registry: DocumentModelRegistry,\n logger: ILogger,\n): Promise<void> {\n for (const entry of entries) {\n let module: DocumentModelModule;\n try {\n module = (await loadFactory(entry.spec)) as DocumentModelModule;\n } catch (error) {\n logger.error(\n \"worker failed to load document model: @entry @error\",\n entry,\n error,\n );\n throw error;\n }\n const [result] = registry.registerModules(module);\n if (result.status === \"error\") {\n logger.error(\n \"worker failed to register document model: @entry @error\",\n entry,\n result.error,\n );\n throw result.error;\n }\n }\n}\n\n/**\n * Assembles the in-worker storage stack plus a {@link SimpleJobExecutor}\n * bound to a pre-built Kysely instance. The parent owns the wire protocol\n * and routing; the worker owns everything below `SimpleJobExecutor`.\n *\n * The local event bus exists only to satisfy the executor's contract: its\n * JOB_WRITE_READY emissions are captured here and shipped to the parent\n * via {@link WorkerExecutorStack.takeLastWriteReady}.\n */\nexport async function buildWorkerExecutor(\n options: BuildWorkerExecutorOptions,\n): Promise<WorkerExecutorStack> {\n const { init, database: baseDatabase, logger } = options;\n const driveContainerTypes =\n options.driveContainerTypes ?? DEFAULT_DRIVE_CONTAINER_TYPES;\n const loadFactory = options.loadFactory ?? defaultLoadFactory;\n\n const registry = new DocumentModelRegistry();\n await loadModelManifest(init.models, loadFactory, registry, logger);\n\n let signatureVerifier: SignatureVerificationHandler | undefined;\n if (init.signatureVerifier) {\n try {\n signatureVerifier = (await loadFactory(\n init.signatureVerifier,\n )) as SignatureVerificationHandler;\n } catch (error) {\n logger.error(\n \"worker failed to load signature verifier: @spec @error\",\n init.signatureVerifier,\n error,\n );\n throw error;\n }\n }\n\n const database = baseDatabase.withSchema(REACTOR_SCHEMA);\n const operationStore = new KyselyOperationStore(\n database as unknown as Kysely<StorageDatabase>,\n );\n const keyframeStore = new KyselyKeyframeStore(\n database as unknown as Kysely<StorageDatabase>,\n );\n\n const cacheConfig: WriteCacheConfig = {\n maxDocuments: 100,\n ringBufferSize: 10,\n keyframeInterval: 10,\n };\n const writeCache = new KyselyWriteCache(\n keyframeStore,\n operationStore,\n registry,\n cacheConfig,\n );\n await writeCache.startup();\n\n const operationIndex = new KyselyOperationIndex(\n database as unknown as Kysely<StorageDatabase>,\n );\n\n const documentMetaCache = new DocumentMetaCache(operationStore, {\n maxDocuments: 1000,\n });\n await documentMetaCache.startup();\n\n const collectionMembershipCache = new CollectionMembershipCache(\n operationIndex,\n );\n\n const executionScope = new KyselyExecutionScope(\n database as unknown as Kysely<StorageDatabase>,\n operationStore,\n operationIndex,\n keyframeStore,\n writeCache,\n documentMetaCache,\n collectionMembershipCache,\n );\n\n const eventBus = new EventBus();\n let lastWriteReady: WorkerWriteReadyCapture | null = null;\n eventBus.subscribe(\n ReactorEventTypes.JOB_WRITE_READY,\n (_t: number, event: JobWriteReadyEvent) => {\n lastWriteReady = {\n operations: event.operations,\n jobMeta: event.jobMeta,\n };\n },\n );\n\n const executorConfig = options.executorConfig ?? {};\n const executor = new SimpleJobExecutor(\n logger,\n registry,\n operationStore,\n eventBus,\n writeCache,\n operationIndex,\n documentMetaCache,\n collectionMembershipCache,\n driveContainerTypes,\n executorConfig,\n signatureVerifier,\n executionScope,\n );\n\n return {\n executor,\n registry,\n takeLastWriteReady(): WorkerWriteReadyCapture | null {\n const captured = lastWriteReady;\n lastWriteReady = null;\n return captured;\n },\n };\n}\n"],"mappings":";;;AAqEA,eAAsB,mBAAmB,MAAqC;CAC5E,MAAM,MAAM,KAAK;CAMjB,MAAM,YADO,OAHX,cAAc,MAAA,OACV,IAAI,IAAI,UAAU,IAAI,WAAW,CAAC,QAAA,OAClC,IAAI,eAEW,IAAI;AACzB,KAAI,OAAO,aAAa,WACtB,QAAQ,SAAwC,KAAK,SAAS;AAEhE,QAAO;;AAGT,eAAe,kBACb,SACA,aACA,UACA,QACe;AACf,MAAK,MAAM,SAAS,SAAS;EAC3B,IAAI;AACJ,MAAI;AACF,YAAU,MAAM,YAAY,MAAM,KAAK;WAChC,OAAO;AACd,UAAO,MACL,uDACA,OACA,MACD;AACD,SAAM;;EAER,MAAM,CAAC,UAAU,SAAS,gBAAgB,OAAO;AACjD,MAAI,OAAO,WAAW,SAAS;AAC7B,UAAO,MACL,2DACA,OACA,OAAO,MACR;AACD,SAAM,OAAO;;;;;;;;;;;;;AAcnB,eAAsB,oBACpB,SAC8B;CAC9B,MAAM,EAAE,MAAM,UAAU,cAAc,WAAW;CACjD,MAAM,sBACJ,QAAQ,uBAAuB;CACjC,MAAM,cAAc,QAAQ,eAAe;CAE3C,MAAM,WAAW,IAAI,uBAAuB;AAC5C,OAAM,kBAAkB,KAAK,QAAQ,aAAa,UAAU,OAAO;CAEnE,IAAI;AACJ,KAAI,KAAK,kBACP,KAAI;AACF,sBAAqB,MAAM,YACzB,KAAK,kBACN;UACM,OAAO;AACd,SAAO,MACL,0DACA,KAAK,mBACL,MACD;AACD,QAAM;;CAIV,MAAM,WAAW,aAAa,WAAW,eAAe;CACxD,MAAM,iBAAiB,IAAI,qBACzB,SACD;CACD,MAAM,gBAAgB,IAAI,oBACxB,SACD;CAOD,MAAM,aAAa,IAAI,iBACrB,eACA,gBACA,UARoC;EACpC,cAAc;EACd,gBAAgB;EAChB,kBAAkB;EACnB,CAMA;AACD,OAAM,WAAW,SAAS;CAE1B,MAAM,iBAAiB,IAAI,qBACzB,SACD;CAED,MAAM,oBAAoB,IAAI,kBAAkB,gBAAgB,EAC9D,cAAc,KACf,CAAC;AACF,OAAM,kBAAkB,SAAS;CAEjC,MAAM,4BAA4B,IAAI,0BACpC,eACD;CAED,MAAM,iBAAiB,IAAI,qBACzB,UACA,gBACA,gBACA,eACA,YACA,mBACA,0BACD;CAED,MAAM,WAAW,IAAI,UAAU;CAC/B,IAAI,iBAAiD;AACrD,UAAS,UACP,kBAAkB,kBACjB,IAAY,UAA8B;AACzC,mBAAiB;GACf,YAAY,MAAM;GAClB,SAAS,MAAM;GAChB;GAEJ;AAkBD,QAAO;EACL,UAhBe,IAAI,kBACnB,QACA,UACA,gBACA,UACA,YACA,gBACA,mBACA,2BACA,qBAVqB,QAAQ,kBAAkB,EAAE,EAYjD,mBACA,eACD;EAIC;EACA,qBAAqD;GACnD,MAAM,WAAW;AACjB,oBAAiB;AACjB,UAAO;;EAEV"}
1
+ {"version":3,"file":"build-worker-executor-F755haxr.js","names":[],"sources":["../src/executor/worker/build-worker-executor.ts"],"sourcesContent":["import type {\n DocumentModelModule,\n OperationWithContext,\n} from \"@powerhousedao/shared/document-model\";\nimport type { ILogger } from \"document-model\";\nimport type { Kysely } from \"kysely\";\nimport { CollectionMembershipCache } from \"../../cache/collection-membership-cache.js\";\nimport { DocumentMetaCache } from \"../../cache/document-meta-cache.js\";\nimport { KyselyOperationIndex } from \"../../cache/kysely-operation-index.js\";\nimport { KyselyWriteCache } from \"../../cache/kysely-write-cache.js\";\nimport type { WriteCacheConfig } from \"../../cache/write-cache-types.js\";\nimport { DEFAULT_DRIVE_CONTAINER_TYPES } from \"../../core/drive-container-types.js\";\nimport type { Database } from \"../../core/types.js\";\nimport { EventBus } from \"../../events/event-bus.js\";\nimport {\n ReactorEventTypes,\n type JobWriteReadyEvent,\n} from \"../../events/types.js\";\nimport { DocumentModelRegistry } from \"../../registry/implementation.js\";\nimport type { JobMeta } from \"../../shared/types.js\";\nimport type { SignatureVerificationHandler } from \"../../signer/types.js\";\nimport { KyselyKeyframeStore } from \"../../storage/kysely/keyframe-store.js\";\nimport { KyselyOperationStore } from \"../../storage/kysely/store.js\";\nimport type { Database as StorageDatabase } from \"../../storage/kysely/types.js\";\nimport { REACTOR_SCHEMA } from \"../../storage/migrations/migrator.js\";\nimport { KyselyExecutionScope } from \"../execution-scope.js\";\nimport { SimpleJobExecutor } from \"../simple-job-executor.js\";\nimport type { JobExecutorConfig } from \"../types.js\";\nimport type {\n FactorySpec,\n InitMessage,\n ModelManifestEntry,\n} from \"./protocol.js\";\n\n/**\n * In-worker capture of the JOB_WRITE_READY event emitted by the executor.\n * The worker forwards `operations` and `jobMeta` back to the parent; the\n * parent re-enriches `collectionMemberships` at emit time.\n */\nexport type WorkerWriteReadyCapture = {\n operations: OperationWithContext[];\n jobMeta: JobMeta;\n};\n\nexport type WorkerExecutorStack = {\n executor: SimpleJobExecutor;\n registry: DocumentModelRegistry;\n /**\n * Synchronously pops the most-recent JOB_WRITE_READY captured on this\n * worker's local event bus and clears it. Returns null if the executor\n * did not produce one for this job.\n */\n takeLastWriteReady(): WorkerWriteReadyCapture | null;\n};\n\nexport type BuildWorkerExecutorOptions = {\n init: InitMessage;\n database: Kysely<Database>;\n logger: ILogger;\n executorConfig?: JobExecutorConfig;\n driveContainerTypes?: ReadonlySet<string>;\n /**\n * Override the module loader used to materialize factory specs. Tests\n * can inject a deterministic resolver instead of touching the real\n * Node module loader.\n */\n loadFactory?: (spec: FactorySpec) => Promise<unknown>;\n};\n\nexport async function defaultLoadFactory(spec: FactorySpec): Promise<unknown> {\n const ref = spec.module;\n const specifier =\n \"filePath\" in ref\n ? new URL(`file://${ref.filePath}`).href\n : ref.packageName;\n const mod = (await import(specifier)) as Record<string, unknown>;\n const exported = mod[ref.exportName];\n if (typeof exported === \"function\") {\n return (exported as (args: unknown) => unknown)(spec.initArgs);\n }\n return exported;\n}\n\nasync function loadModelManifest(\n entries: ModelManifestEntry[],\n loadFactory: (spec: FactorySpec) => Promise<unknown>,\n registry: DocumentModelRegistry,\n logger: ILogger,\n): Promise<void> {\n for (const entry of entries) {\n let module: DocumentModelModule;\n try {\n module = (await loadFactory(entry.spec)) as DocumentModelModule;\n } catch (error) {\n logger.error(\n \"worker failed to load document model: @entry @error\",\n entry,\n error,\n );\n throw error;\n }\n const [result] = registry.registerModules(module);\n if (result.status === \"error\") {\n logger.error(\n \"worker failed to register document model: @entry @error\",\n entry,\n result.error,\n );\n throw result.error;\n }\n }\n}\n\n/**\n * Assembles the in-worker storage stack plus a {@link SimpleJobExecutor}\n * bound to a pre-built Kysely instance. The parent owns the wire protocol\n * and routing; the worker owns everything below `SimpleJobExecutor`.\n *\n * The local event bus exists only to satisfy the executor's contract: its\n * JOB_WRITE_READY emissions are captured here and shipped to the parent\n * via {@link WorkerExecutorStack.takeLastWriteReady}.\n */\nexport async function buildWorkerExecutor(\n options: BuildWorkerExecutorOptions,\n): Promise<WorkerExecutorStack> {\n const { init, database: baseDatabase, logger } = options;\n const driveContainerTypes =\n options.driveContainerTypes ?? DEFAULT_DRIVE_CONTAINER_TYPES;\n const loadFactory = options.loadFactory ?? defaultLoadFactory;\n\n const registry = new DocumentModelRegistry();\n await loadModelManifest(init.models, loadFactory, registry, logger);\n\n let signatureVerifier: SignatureVerificationHandler | undefined;\n if (init.signatureVerifier) {\n try {\n signatureVerifier = (await loadFactory(\n init.signatureVerifier,\n )) as SignatureVerificationHandler;\n } catch (error) {\n logger.error(\n \"worker failed to load signature verifier: @spec @error\",\n init.signatureVerifier,\n error,\n );\n throw error;\n }\n }\n\n const database = baseDatabase.withSchema(REACTOR_SCHEMA);\n const operationStore = new KyselyOperationStore(\n database as unknown as Kysely<StorageDatabase>,\n );\n const keyframeStore = new KyselyKeyframeStore(\n database as unknown as Kysely<StorageDatabase>,\n );\n\n const cacheConfig: WriteCacheConfig = {\n maxDocuments: 100,\n ringBufferSize: 10,\n keyframeInterval: 10,\n };\n const writeCache = new KyselyWriteCache(\n keyframeStore,\n operationStore,\n registry,\n cacheConfig,\n );\n await writeCache.startup();\n\n const operationIndex = new KyselyOperationIndex(\n database as unknown as Kysely<StorageDatabase>,\n );\n\n const documentMetaCache = new DocumentMetaCache(operationStore, {\n maxDocuments: 1000,\n });\n await documentMetaCache.startup();\n\n const collectionMembershipCache = new CollectionMembershipCache(\n operationIndex,\n );\n\n const executionScope = new KyselyExecutionScope(\n database as unknown as Kysely<StorageDatabase>,\n operationStore,\n operationIndex,\n keyframeStore,\n writeCache,\n documentMetaCache,\n collectionMembershipCache,\n );\n\n const eventBus = new EventBus();\n let lastWriteReady: WorkerWriteReadyCapture | null = null;\n eventBus.subscribe(\n ReactorEventTypes.JOB_WRITE_READY,\n (_t: number, event: JobWriteReadyEvent) => {\n lastWriteReady = {\n operations: event.operations,\n jobMeta: event.jobMeta,\n };\n },\n );\n\n const executorConfig = options.executorConfig ?? {};\n const executor = new SimpleJobExecutor(\n logger,\n registry,\n operationStore,\n eventBus,\n writeCache,\n operationIndex,\n documentMetaCache,\n collectionMembershipCache,\n driveContainerTypes,\n executorConfig,\n signatureVerifier,\n executionScope,\n );\n\n return {\n executor,\n registry,\n takeLastWriteReady(): WorkerWriteReadyCapture | null {\n const captured = lastWriteReady;\n lastWriteReady = null;\n return captured;\n },\n };\n}\n"],"mappings":";;;AAqEA,eAAsB,mBAAmB,MAAqC;CAC5E,MAAM,MAAM,KAAK;CAMjB,MAAM,YADO,OAHX,cAAc,MAAA,OACV,IAAI,IAAI,UAAU,IAAI,WAAW,CAAC,QAAA,OAClC,IAAI,eAEW,IAAI;AACzB,KAAI,OAAO,aAAa,WACtB,QAAQ,SAAwC,KAAK,SAAS;AAEhE,QAAO;;AAGT,eAAe,kBACb,SACA,aACA,UACA,QACe;AACf,MAAK,MAAM,SAAS,SAAS;EAC3B,IAAI;AACJ,MAAI;AACF,YAAU,MAAM,YAAY,MAAM,KAAK;WAChC,OAAO;AACd,UAAO,MACL,uDACA,OACA,MACD;AACD,SAAM;;EAER,MAAM,CAAC,UAAU,SAAS,gBAAgB,OAAO;AACjD,MAAI,OAAO,WAAW,SAAS;AAC7B,UAAO,MACL,2DACA,OACA,OAAO,MACR;AACD,SAAM,OAAO;;;;;;;;;;;;;AAcnB,eAAsB,oBACpB,SAC8B;CAC9B,MAAM,EAAE,MAAM,UAAU,cAAc,WAAW;CACjD,MAAM,sBACJ,QAAQ,uBAAuB;CACjC,MAAM,cAAc,QAAQ,eAAe;CAE3C,MAAM,WAAW,IAAI,uBAAuB;AAC5C,OAAM,kBAAkB,KAAK,QAAQ,aAAa,UAAU,OAAO;CAEnE,IAAI;AACJ,KAAI,KAAK,kBACP,KAAI;AACF,sBAAqB,MAAM,YACzB,KAAK,kBACN;UACM,OAAO;AACd,SAAO,MACL,0DACA,KAAK,mBACL,MACD;AACD,QAAM;;CAIV,MAAM,WAAW,aAAa,WAAW,eAAe;CACxD,MAAM,iBAAiB,IAAI,qBACzB,SACD;CACD,MAAM,gBAAgB,IAAI,oBACxB,SACD;CAOD,MAAM,aAAa,IAAI,iBACrB,eACA,gBACA,UARoC;EACpC,cAAc;EACd,gBAAgB;EAChB,kBAAkB;EACnB,CAMA;AACD,OAAM,WAAW,SAAS;CAE1B,MAAM,iBAAiB,IAAI,qBACzB,SACD;CAED,MAAM,oBAAoB,IAAI,kBAAkB,gBAAgB,EAC9D,cAAc,KACf,CAAC;AACF,OAAM,kBAAkB,SAAS;CAEjC,MAAM,4BAA4B,IAAI,0BACpC,eACD;CAED,MAAM,iBAAiB,IAAI,qBACzB,UACA,gBACA,gBACA,eACA,YACA,mBACA,0BACD;CAED,MAAM,WAAW,IAAI,UAAU;CAC/B,IAAI,iBAAiD;AACrD,UAAS,UACP,kBAAkB,kBACjB,IAAY,UAA8B;AACzC,mBAAiB;GACf,YAAY,MAAM;GAClB,SAAS,MAAM;GAChB;GAEJ;AAkBD,QAAO;EACL,UAhBe,IAAI,kBACnB,QACA,UACA,gBACA,UACA,YACA,gBACA,mBACA,2BACA,qBAVqB,QAAQ,kBAAkB,EAAE,EAYjD,mBACA,eACD;EAIC;EACA,qBAAqD;GACnD,MAAM,WAAW;AACjB,oBAAiB;AACjB,UAAO;;EAEV"}
@@ -788,6 +788,30 @@ function isGenesisOperation(operation) {
788
788
  if (operation.action.type !== "UPGRADE_DOCUMENT") return false;
789
789
  return operation.action.input.fromVersion === 0;
790
790
  }
791
+ /**
792
+ * The distinct streams a job wrote, collected so a rollback knows what to evict.
793
+ *
794
+ * A write records its stream on every apply, and a long run applies the same
795
+ * stream hundreds of times, so this keeps one entry per stream rather than one
796
+ * per write: the eviction only cares which streams were touched, and the
797
+ * successful jobs that never read this back pay for a lookup instead of an
798
+ * allocation.
799
+ */
800
+ var TouchedStreams = class {
801
+ streams = /* @__PURE__ */ new Map();
802
+ add(documentId, scope, branch) {
803
+ const key = `${documentId}\u0000${scope}\u0000${branch}`;
804
+ if (this.streams.has(key)) return;
805
+ this.streams.set(key, {
806
+ documentId,
807
+ scope,
808
+ branch
809
+ });
810
+ }
811
+ [Symbol.iterator]() {
812
+ return this.streams.values();
813
+ }
814
+ };
791
815
  //#endregion
792
816
  //#region src/registry/errors.ts
793
817
  /**
@@ -1159,6 +1183,14 @@ var KyselyOperationIndexTxn = class {
1159
1183
  collectionRemovals = [];
1160
1184
  groupReferences = [];
1161
1185
  operations = [];
1186
+ membershipInvalidations = /* @__PURE__ */ new Set();
1187
+ /** Called by the commit as it writes each document_collections row. */
1188
+ recordMembershipInvalidation(documentId) {
1189
+ this.membershipInvalidations.add(documentId);
1190
+ }
1191
+ getMembershipInvalidations() {
1192
+ return [...this.membershipInvalidations];
1193
+ }
1162
1194
  createCollection(collectionId) {
1163
1195
  this.collections.push(collectionId);
1164
1196
  }
@@ -1240,7 +1272,8 @@ var KyselyOperationIndex = class KyselyOperationIndex {
1240
1272
  * never shrinks a backfill window remotes already rely on, and reopens a
1241
1273
  * closed membership because a policy reference is not a removable one.
1242
1274
  */
1243
- async joinKeepingEarliest(trx, documentId, collectionId, ordinal) {
1275
+ async joinKeepingEarliest(trx, kyselyTxn, documentId, collectionId, ordinal) {
1276
+ kyselyTxn.recordMembershipInvalidation(documentId);
1244
1277
  await trx.insertInto("document_collections").values({
1245
1278
  documentId,
1246
1279
  collectionId,
@@ -1264,6 +1297,7 @@ var KyselyOperationIndex = class KyselyOperationIndex {
1264
1297
  joinedOrdinal: BigInt(0),
1265
1298
  leftOrdinal: null
1266
1299
  }));
1300
+ for (const collectionId of collections) kyselyTxn.recordMembershipInvalidation(collectionId);
1267
1301
  await trx.insertInto("document_collections").values(collectionRows).onConflict((oc) => oc.doNothing()).execute();
1268
1302
  }
1269
1303
  let operationOrdinals = [];
@@ -1286,6 +1320,7 @@ var KyselyOperationIndex = class KyselyOperationIndex {
1286
1320
  }
1287
1321
  if (memberships.length > 0) for (const m of memberships) {
1288
1322
  const ordinal = operationOrdinals[m.operationIndex];
1323
+ kyselyTxn.recordMembershipInvalidation(m.documentId);
1289
1324
  await trx.insertInto("document_collections").values({
1290
1325
  documentId: m.documentId,
1291
1326
  collectionId: m.collectionId,
@@ -1296,10 +1331,11 @@ var KyselyOperationIndex = class KyselyOperationIndex {
1296
1331
  leftOrdinal: null
1297
1332
  })).execute();
1298
1333
  const references = await trx.selectFrom("group_references").select("groupId").where("documentId", "=", m.documentId).execute();
1299
- for (const { groupId } of references) await this.joinKeepingEarliest(trx, groupId, m.collectionId, BigInt(ordinal));
1334
+ for (const { groupId } of references) await this.joinKeepingEarliest(trx, kyselyTxn, groupId, m.collectionId, BigInt(ordinal));
1300
1335
  }
1301
1336
  if (removals.length > 0) for (const r of removals) {
1302
1337
  const ordinal = operationOrdinals[r.operationIndex];
1338
+ kyselyTxn.recordMembershipInvalidation(r.documentId);
1303
1339
  await trx.updateTable("document_collections").set({ leftOrdinal: BigInt(ordinal) }).where("collectionId", "=", r.collectionId).where("documentId", "=", r.documentId).where("leftOrdinal", "is", null).execute();
1304
1340
  }
1305
1341
  if (groupReferences.length > 0) for (const record of groupReferences) {
@@ -1309,7 +1345,7 @@ var KyselyOperationIndex = class KyselyOperationIndex {
1309
1345
  groupId
1310
1346
  }))).onConflict((oc) => oc.doNothing()).execute();
1311
1347
  const rows = await trx.selectFrom("document_collections").select("collectionId").where("documentId", "=", record.documentId).execute();
1312
- for (const groupId of record.groupIds) for (const { collectionId } of rows) await this.joinKeepingEarliest(trx, groupId, collectionId, BigInt(ordinal));
1348
+ for (const groupId of record.groupIds) for (const { collectionId } of rows) await this.joinKeepingEarliest(trx, kyselyTxn, groupId, collectionId, BigInt(ordinal));
1313
1349
  }
1314
1350
  return operationOrdinals;
1315
1351
  }
@@ -2766,6 +2802,7 @@ var DocumentActionHandler = class {
2766
2802
  ...standing.operations,
2767
2803
  [job.scope]: [...standing.operations[job.scope] ?? [], operation]
2768
2804
  };
2805
+ executing.touchedStreams.add(job.documentId, job.scope, job.branch);
2769
2806
  stores.writeCache.putState(job.documentId, job.scope, job.branch, operation.index, standing, SnapshotPosition.Head);
2770
2807
  indexTxn.write([{
2771
2808
  ...operation,
@@ -2818,6 +2855,7 @@ var DocumentActionHandler = class {
2818
2855
  ...document.operations,
2819
2856
  [job.scope]: [...document.operations[job.scope] ?? [], operation]
2820
2857
  };
2858
+ executing.touchedStreams.add(document.header.id, job.scope, job.branch);
2821
2859
  stores.writeCache.putState(document.header.id, job.scope, job.branch, operation.index, document, SnapshotPosition.Head);
2822
2860
  indexTxn.write([{
2823
2861
  ...operation,
@@ -2877,6 +2915,7 @@ var DocumentActionHandler = class {
2877
2915
  ...document.operations,
2878
2916
  [job.scope]: [...document.operations[job.scope] ?? [], operation]
2879
2917
  };
2918
+ executing.touchedStreams.add(documentId, job.scope, job.branch);
2880
2919
  stores.writeCache.putState(documentId, job.scope, job.branch, operation.index, document, SnapshotPosition.Head);
2881
2920
  indexTxn.write([{
2882
2921
  ...operation,
@@ -2987,6 +3026,7 @@ var DocumentActionHandler = class {
2987
3026
  ...document.operations,
2988
3027
  [job.scope]: [...document.operations[job.scope] ?? [], operation]
2989
3028
  };
3029
+ executing.touchedStreams.add(documentId, job.scope, job.branch);
2990
3030
  stores.writeCache.putState(documentId, job.scope, job.branch, operation.index, document, SnapshotPosition.Head);
2991
3031
  for (const scope of otherScopes) executing.postCommitInvalidations.push({
2992
3032
  documentId,
@@ -3071,6 +3111,8 @@ var DocumentActionHandler = class {
3071
3111
  [job.scope]: scopeState === void 0 ? {} : structuredClone(scopeState)
3072
3112
  };
3073
3113
  const resultingState = JSON.stringify(resultingStateObj);
3114
+ executing.touchedStreams.add(input.sourceId, job.scope, job.branch);
3115
+ executing.touchedStreams.add(input.targetId, job.scope, job.branch);
3074
3116
  stores.writeCache.putState(input.sourceId, job.scope, job.branch, operation.index, sourceDoc, SnapshotPosition.Head);
3075
3117
  indexTxn.write([{
3076
3118
  ...operation,
@@ -3097,6 +3139,7 @@ var DocumentActionHandler = class {
3097
3139
  async writeOperationToStore(target, operation, executing) {
3098
3140
  const { documentId, documentType, scope, branch } = target;
3099
3141
  const { job, startTime, stores, signal } = executing;
3142
+ executing.touchedStreams.add(documentId, scope, branch);
3100
3143
  let storedOperations;
3101
3144
  try {
3102
3145
  storedOperations = await stores.operationStore.apply(documentId, documentType, scope, branch, operation.index, (txn) => {
@@ -3174,6 +3217,21 @@ function isValidISOTimestamp(value) {
3174
3217
  if (!ISO_TIMESTAMP_REGEX.test(value)) return false;
3175
3218
  return !isNaN(new Date(value).getTime());
3176
3219
  }
3220
+ /**
3221
+ * Carries a failed job out of the execution scope so its transaction rolls
3222
+ * back. A scope callback that returns commits, whatever result it returns, so
3223
+ * a returned failure would leave the writes the job made before it failed
3224
+ * standing. Never escapes executeJob: the failure goes back to being a
3225
+ * returned JobResult there, which is what the queue, the worker protocol and
3226
+ * every test expect a failed job to look like.
3227
+ */
3228
+ var JobRollbackSignal = class extends Error {
3229
+ constructor(result) {
3230
+ super("job rolled back");
3231
+ this.result = result;
3232
+ this.name = "JobRollbackSignal";
3233
+ }
3234
+ };
3177
3235
  var SimpleJobExecutor = class {
3178
3236
  config;
3179
3237
  featureFlags;
@@ -3211,141 +3269,202 @@ var SimpleJobExecutor = class {
3211
3269
  /**
3212
3270
  * Execute a single job by applying all its actions through the appropriate reducers.
3213
3271
  * Actions are processed sequentially in order.
3272
+ *
3273
+ * The whole job runs inside one execution scope, and a scope callback that
3274
+ * returns commits. A failed job must therefore leave the scope by throwing,
3275
+ * or the writes it made before it failed would be durable: JobRollbackSignal
3276
+ * carries the failure out through the transaction and this method turns it
3277
+ * back into the returned JobResult every caller expects. A job either fully
3278
+ * applies or leaves nothing durable behind.
3279
+ *
3280
+ * Durable is the whole of the guarantee. The caches are shared with the
3281
+ * copies the scope hands the job, so a failed job's writes sit in them from
3282
+ * the moment it makes them until the eviction below, and a concurrent read
3283
+ * in that window sees a write that is never going to commit. The window is
3284
+ * not new -- it has always been there for a job that failed by throwing --
3285
+ * but nothing here closes it, and a caller that needs to know a write is
3286
+ * real has the job status to ask.
3214
3287
  */
3215
3288
  async executeJob(job, signal) {
3216
3289
  const startTime = Date.now();
3217
- const touchedCacheEntries = [];
3290
+ const touchedStreams = new TouchedStreams();
3218
3291
  const postCommitInvalidations = [];
3219
- let pendingEvent;
3220
- let result;
3292
+ const postCommitMembershipInvalidations = [];
3293
+ let outcome;
3221
3294
  try {
3222
- result = await this.executionScope.run(async (stores) => {
3223
- const indexTxn = stores.operationIndex.start();
3224
- if (job.kind === "load") {
3225
- const loadResult = await this.executeLoadJob({
3226
- job,
3227
- startTime,
3228
- indexTxn,
3229
- stores,
3230
- signal,
3231
- replayingAcceptedHistory: true,
3232
- evaluatedByPosition: false,
3233
- postCommitInvalidations
3234
- });
3235
- if (loadResult.success && loadResult.operationsWithContext) {
3236
- for (const owc of loadResult.operationsWithContext) touchedCacheEntries.push({
3237
- documentId: owc.context.documentId,
3238
- scope: owc.context.scope,
3239
- branch: owc.context.branch
3240
- });
3241
- const ordinals = await stores.operationIndex.commit(indexTxn, signal);
3242
- for (let i = 0; i < loadResult.operationsWithContext.length; i++) loadResult.operationsWithContext[i].context.ordinal = ordinals[i];
3243
- const collectionMemberships = loadResult.operationsWithContext.length > 0 ? await this.getCollectionMembershipsForOperations(loadResult.operationsWithContext, stores) : {};
3244
- pendingEvent = {
3245
- jobId: job.id,
3246
- operations: loadResult.operationsWithContext,
3247
- jobMeta: job.meta,
3248
- collectionMemberships
3249
- };
3250
- }
3251
- return loadResult;
3252
- }
3253
- if (job.kind === "reevaluation") {
3254
- const reevalResult = await this.executeReevaluationJob({
3255
- job,
3256
- startTime,
3257
- indexTxn,
3258
- stores,
3259
- signal,
3260
- replayingAcceptedHistory: false,
3261
- evaluatedByPosition: false,
3262
- postCommitInvalidations
3263
- });
3264
- if (reevalResult.success && reevalResult.operationsWithContext) {
3265
- for (const owc of reevalResult.operationsWithContext) touchedCacheEntries.push({
3266
- documentId: owc.context.documentId,
3267
- scope: owc.context.scope,
3268
- branch: owc.context.branch
3269
- });
3270
- const ordinals = await stores.operationIndex.commit(indexTxn, signal);
3271
- for (let i = 0; i < reevalResult.operationsWithContext.length; i++) reevalResult.operationsWithContext[i].context.ordinal = ordinals[i];
3272
- if (reevalResult.operationsWithContext.length > 0) {
3273
- const collectionMemberships = await this.getCollectionMembershipsForOperations(reevalResult.operationsWithContext, stores);
3274
- pendingEvent = {
3275
- jobId: job.id,
3276
- operations: reevalResult.operationsWithContext,
3277
- jobMeta: job.meta,
3278
- collectionMemberships
3279
- };
3280
- }
3281
- }
3282
- return reevalResult;
3283
- }
3284
- const positioned = await this.positionByTimestamp(job, stores, signal);
3285
- if (positioned.error) return buildErrorResult(job, positioned.error, startTime);
3286
- const executing = {
3295
+ outcome = await this.executionScope.run(async (stores) => {
3296
+ const scoped = await this.executeInScope({
3287
3297
  job,
3288
3298
  startTime,
3289
- indexTxn,
3290
3299
  stores,
3291
3300
  signal,
3292
- replayingAcceptedHistory: false,
3293
- evaluatedByPosition: positioned.evaluatedByPosition,
3294
- postCommitInvalidations
3295
- };
3296
- const actionResult = await this.processActions(positioned.writes, executing);
3297
- if (!actionResult.success) return {
3298
- job,
3299
- success: false,
3300
- error: actionResult.error,
3301
- duration: Date.now() - startTime
3302
- };
3303
- if (actionResult.operationsWithContext.length > 0) for (const owc of actionResult.operationsWithContext) touchedCacheEntries.push({
3304
- documentId: owc.context.documentId,
3305
- scope: owc.context.scope,
3306
- branch: owc.context.branch
3301
+ touchedStreams,
3302
+ postCommitInvalidations,
3303
+ postCommitMembershipInvalidations
3307
3304
  });
3308
- const reevaluationError = await this.reevaluateIfCriteriaMet({
3309
- scope: job.scope,
3310
- operations: actionResult.generatedOperations
3311
- }, executing);
3312
- if (reevaluationError) return {
3313
- job,
3314
- success: false,
3315
- error: reevaluationError,
3316
- duration: Date.now() - startTime
3305
+ if (!scoped.result.success) throw new JobRollbackSignal(scoped.result);
3306
+ return scoped;
3307
+ }, signal);
3308
+ } catch (error) {
3309
+ this.evictTouchedStreams(touchedStreams);
3310
+ if (error instanceof JobRollbackSignal) return error.result;
3311
+ throw error;
3312
+ }
3313
+ for (const entry of postCommitInvalidations) this.writeCache.invalidate(entry.documentId, entry.scope, entry.branch);
3314
+ for (const documentId of postCommitMembershipInvalidations) this.collectionMembershipCache.invalidate(documentId);
3315
+ const { pendingEvent } = outcome;
3316
+ if (pendingEvent) this.eventBus.emit(ReactorEventTypes.JOB_WRITE_READY, pendingEvent).catch((error) => {
3317
+ this.logger.error("Failed to emit JOB_WRITE_READY event: @Event : @Error", pendingEvent, error);
3318
+ });
3319
+ return outcome.result;
3320
+ }
3321
+ /**
3322
+ * The body of a job, run inside the execution scope's transaction.
3323
+ *
3324
+ * The stores and caches it works through are the copies scoped to that
3325
+ * transaction, so nothing it does is durable until the scope commits. The
3326
+ * write-ready event is handed back rather than emitted, because a job that
3327
+ * has not committed yet has nothing to announce.
3328
+ */
3329
+ async executeInScope(params) {
3330
+ const { job, startTime, stores, signal, touchedStreams, postCommitInvalidations, postCommitMembershipInvalidations } = params;
3331
+ let pendingEvent;
3332
+ const indexTxn = stores.operationIndex.start();
3333
+ if (job.kind === "load") {
3334
+ const loadResult = await this.executeLoadJob({
3335
+ job,
3336
+ startTime,
3337
+ indexTxn,
3338
+ stores,
3339
+ signal,
3340
+ replayingAcceptedHistory: true,
3341
+ evaluatedByPosition: false,
3342
+ postCommitInvalidations,
3343
+ postCommitMembershipInvalidations,
3344
+ touchedStreams
3345
+ });
3346
+ if (loadResult.success && loadResult.operationsWithContext) {
3347
+ const ordinals = await stores.operationIndex.commit(indexTxn, signal);
3348
+ postCommitMembershipInvalidations.push(...indexTxn.getMembershipInvalidations());
3349
+ for (let i = 0; i < loadResult.operationsWithContext.length; i++) loadResult.operationsWithContext[i].context.ordinal = ordinals[i];
3350
+ const collectionMemberships = loadResult.operationsWithContext.length > 0 ? await this.getCollectionMembershipsForOperations(loadResult.operationsWithContext, stores) : {};
3351
+ pendingEvent = {
3352
+ jobId: job.id,
3353
+ operations: loadResult.operationsWithContext,
3354
+ jobMeta: job.meta,
3355
+ collectionMemberships
3317
3356
  };
3357
+ }
3358
+ return {
3359
+ result: loadResult,
3360
+ pendingEvent
3361
+ };
3362
+ }
3363
+ if (job.kind === "reevaluation") {
3364
+ const reevalResult = await this.executeReevaluationJob({
3365
+ job,
3366
+ startTime,
3367
+ indexTxn,
3368
+ stores,
3369
+ signal,
3370
+ replayingAcceptedHistory: false,
3371
+ evaluatedByPosition: false,
3372
+ postCommitInvalidations,
3373
+ postCommitMembershipInvalidations,
3374
+ touchedStreams
3375
+ });
3376
+ if (reevalResult.success && reevalResult.operationsWithContext) {
3318
3377
  const ordinals = await stores.operationIndex.commit(indexTxn, signal);
3319
- if (actionResult.operationsWithContext.length > 0) {
3320
- for (let i = 0; i < actionResult.operationsWithContext.length; i++) actionResult.operationsWithContext[i].context.ordinal = ordinals[i];
3321
- const collectionMemberships = await this.getCollectionMembershipsForOperations(actionResult.operationsWithContext, stores);
3378
+ postCommitMembershipInvalidations.push(...indexTxn.getMembershipInvalidations());
3379
+ for (let i = 0; i < reevalResult.operationsWithContext.length; i++) reevalResult.operationsWithContext[i].context.ordinal = ordinals[i];
3380
+ if (reevalResult.operationsWithContext.length > 0) {
3381
+ const collectionMemberships = await this.getCollectionMembershipsForOperations(reevalResult.operationsWithContext, stores);
3322
3382
  pendingEvent = {
3323
3383
  jobId: job.id,
3324
- operations: actionResult.operationsWithContext,
3384
+ operations: reevalResult.operationsWithContext,
3325
3385
  jobMeta: job.meta,
3326
3386
  collectionMemberships
3327
3387
  };
3328
3388
  }
3329
- return {
3330
- job,
3331
- success: true,
3332
- operations: actionResult.generatedOperations,
3333
- operationsWithContext: actionResult.operationsWithContext,
3334
- duration: Date.now() - startTime
3335
- };
3336
- }, signal);
3337
- } catch (error) {
3338
- for (const entry of touchedCacheEntries) {
3339
- this.writeCache.invalidate(entry.documentId, entry.scope, entry.branch);
3340
- this.documentMetaCache.invalidate(entry.documentId, entry.branch);
3341
3389
  }
3342
- throw error;
3390
+ return {
3391
+ result: reevalResult,
3392
+ pendingEvent
3393
+ };
3394
+ }
3395
+ const positioned = await this.positionByTimestamp(job, stores, signal);
3396
+ if (positioned.error) return { result: buildErrorResult(job, positioned.error, startTime) };
3397
+ const executing = {
3398
+ job,
3399
+ startTime,
3400
+ indexTxn,
3401
+ stores,
3402
+ signal,
3403
+ replayingAcceptedHistory: false,
3404
+ evaluatedByPosition: positioned.evaluatedByPosition,
3405
+ postCommitInvalidations,
3406
+ postCommitMembershipInvalidations,
3407
+ touchedStreams
3408
+ };
3409
+ const actionResult = await this.processActions(positioned.writes, executing);
3410
+ if (!actionResult.success) return { result: {
3411
+ job,
3412
+ success: false,
3413
+ error: actionResult.error,
3414
+ duration: Date.now() - startTime
3415
+ } };
3416
+ const reevaluationError = await this.reevaluateIfCriteriaMet({
3417
+ scope: job.scope,
3418
+ operations: actionResult.generatedOperations
3419
+ }, executing);
3420
+ if (reevaluationError) return { result: {
3421
+ job,
3422
+ success: false,
3423
+ error: reevaluationError,
3424
+ duration: Date.now() - startTime
3425
+ } };
3426
+ const ordinals = await stores.operationIndex.commit(indexTxn, signal);
3427
+ postCommitMembershipInvalidations.push(...indexTxn.getMembershipInvalidations());
3428
+ if (actionResult.operationsWithContext.length > 0) {
3429
+ for (let i = 0; i < actionResult.operationsWithContext.length; i++) actionResult.operationsWithContext[i].context.ordinal = ordinals[i];
3430
+ const collectionMemberships = await this.getCollectionMembershipsForOperations(actionResult.operationsWithContext, stores);
3431
+ pendingEvent = {
3432
+ jobId: job.id,
3433
+ operations: actionResult.operationsWithContext,
3434
+ jobMeta: job.meta,
3435
+ collectionMemberships
3436
+ };
3437
+ }
3438
+ return {
3439
+ result: {
3440
+ job,
3441
+ success: true,
3442
+ operations: actionResult.generatedOperations,
3443
+ operationsWithContext: actionResult.operationsWithContext,
3444
+ duration: Date.now() - startTime
3445
+ },
3446
+ pendingEvent
3447
+ };
3448
+ }
3449
+ /**
3450
+ * Drops the cached state of every stream a job wrote, after its transaction
3451
+ * did not commit.
3452
+ *
3453
+ * The caches are shared by reference with the copies the scope hands the job,
3454
+ * so a rollback undoes nothing in them: what the job put there survives, as
3455
+ * does anything a read filled from the store while the job's own writes were
3456
+ * still uncommitted. An eviction that throws is swallowed rather than allowed
3457
+ * to replace the failure the caller is owed, and the remaining streams are
3458
+ * still evicted.
3459
+ */
3460
+ evictTouchedStreams(touchedStreams) {
3461
+ for (const entry of touchedStreams) try {
3462
+ this.writeCache.invalidate(entry.documentId, entry.scope, entry.branch);
3463
+ this.documentMetaCache.invalidate(entry.documentId, entry.branch);
3464
+ this.collectionMembershipCache.invalidate(entry.documentId);
3465
+ } catch (error) {
3466
+ this.logger.error("Failed to evict cached state for rolled back @Stream : @Error", entry, error);
3343
3467
  }
3344
- if (result.success) for (const entry of postCommitInvalidations) this.writeCache.invalidate(entry.documentId, entry.scope, entry.branch);
3345
- if (pendingEvent) this.eventBus.emit(ReactorEventTypes.JOB_WRITE_READY, pendingEvent).catch((error) => {
3346
- this.logger.error("Failed to emit JOB_WRITE_READY event: @Event : @Error", pendingEvent, error);
3347
- });
3348
- return result;
3349
3468
  }
3350
3469
  async getCollectionMembershipsForOperations(operations, stores) {
3351
3470
  const documentIds = [...new Set(operations.map((op) => op.context.documentId))];
@@ -3571,6 +3690,7 @@ var SimpleJobExecutor = class {
3571
3690
  const scope = first.scope;
3572
3691
  const documentType = first.document.header.documentType;
3573
3692
  const operations = prepared.map((write) => write.operation);
3693
+ executing.touchedStreams.add(job.documentId, scope, job.branch);
3574
3694
  let storedOperations;
3575
3695
  try {
3576
3696
  storedOperations = await stores.operationStore.apply(job.documentId, documentType, scope, job.branch, first.operation.index, (txn) => {
@@ -3662,10 +3782,12 @@ var SimpleJobExecutor = class {
3662
3782
  * produced - but the result is threaded in memory rather than read back from
3663
3783
  * the cache, and the whole run reaches the store in a single apply.
3664
3784
  *
3665
- * A write that cannot be prepared, or that turns out to be denied, abandons
3666
- * the batch and the caller replays the whole job one write at a time. That is
3667
- * simpler than committing a partial run, and these are the paths where the
3668
- * per-write behaviour is load-bearing.
3785
+ * A write that turns out to be denied abandons the batch and replays the run
3786
+ * one write at a time, because a denied write holds a position of its own and
3787
+ * that is the path where the per-write behaviour is load-bearing. A write
3788
+ * that cannot be prepared fails the job outright: preparing is a read, so the
3789
+ * replay would only reach the same failure, and the job leaves nothing behind
3790
+ * either way.
3669
3791
  */
3670
3792
  async executeRegularActionsBatched(writes, executing) {
3671
3793
  const prepared = [];
@@ -3673,7 +3795,7 @@ var SimpleJobExecutor = class {
3673
3795
  let lastYield = performance.now();
3674
3796
  for (const write of writes) {
3675
3797
  const outcome = await this.prepareRegularWrite(write, executing, carried);
3676
- if ("success" in outcome) return prepared.length === 0 ? outcome : this.executeRegularActionsSequentially(writes, executing);
3798
+ if ("success" in outcome) return outcome;
3677
3799
  if (outcome.denied) return this.executeRegularActionsSequentially(writes, executing);
3678
3800
  prepared.push(outcome);
3679
3801
  carried = outcome.updatedDocument;
@@ -5134,4 +5256,4 @@ const DEFAULT_DRIVE_CONTAINER_TYPES = new Set(["powerhouse/document-drive", "pow
5134
5256
  //#endregion
5135
5257
  export { GATED_DOCUMENT_ACTIONS as A, AuthTimestampNotMonotonicError as B, DuplicateOperationError as C, DuplicateModuleError as D, DuplicateManifestError as E, selectDecisionModel as F, InvalidOperationTimestampError as G, DocumentDeletedError as H, documentDecisionModel as I, parsePagingOptions as J, UpgradePreconditionFailedError as K, authDecisionModel as L, createEmptyConsistencyToken as M, targetDocumentId as N, InvalidModuleError as O, decideAtHead as P, buildDecisionModel as R, AppendConditionFailedError as S, RevisionMismatchError as T, DocumentNotFoundError as U, AuthorizationDeniedError as V, ExcessiveReshuffleError as W, __exportAll as X, throwIfAborted as Y, DocumentMetaCache as _, createForwardingPoolInstrumentation as a, JobExecutorEventTypes as b, KyselyKeyframeStore as c, DriveCollectionId as d, KyselyExecutionScope as f, KyselyOperationIndex as g, KyselyWriteCache as h, runMigrations as i, createConsistencyToken as j, ModuleNotFoundError as k, DocumentModelRegistry as l, EventBus as m, REACTOR_SCHEMA as n, instrumentPgPool as o, resolveFeatureFlags as p, matchesScope as q, getMigrationStatus as r, KyselyOperationStore as s, DEFAULT_DRIVE_CONTAINER_TYPES as t, SimpleJobExecutor as u, CollectionMembershipCache as v, OptimisticLockError as w, APPEND_CONDITION_FAILED_PREFIX as x, DEFAULT_DEFERRED_JOB_TTL_MS as y, AuthEnforcementDisabledError as z };
5136
5258
 
5137
- //# sourceMappingURL=drive-container-types-CruMdXue.js.map
5259
+ //# sourceMappingURL=drive-container-types-yzXFiYNp.js.map