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

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-bVQ_8YwX.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-CruMdXue.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-DBHkoWBR.js.map
83
+ //# sourceMappingURL=build-worker-executor-DymEQNP9.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"build-worker-executor-DBHkoWBR.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-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"}
@@ -572,14 +572,16 @@ function documentDecisionModel(target) {
572
572
  * append condition it returns is the read-set the store enforces at write time.
573
573
  *
574
574
  * With `conditions` supplied, the executing scope's state is read at the head
575
- * for `doc.<scope>.*` paths. That read carries no append-condition entry of
576
- * its own: the written stream's expected-revision check already refuses a
577
- * write whose scope grew between the read and the append.
575
+ * for `doc.<scope>.*` paths, or taken from the run's carried document when the
576
+ * caller has already reduced earlier writes into it. That read carries no
577
+ * append-condition entry of its own: the written stream's expected-revision
578
+ * check already refuses a write whose scope grew between the read and the
579
+ * append.
578
580
  */
579
581
  async function decideAtHead(model, cache, target, subject, request, signal, conditions) {
580
582
  const built = await buildDecisionModel(cache, model, target, signal);
581
583
  let scopeState;
582
- if (conditions !== void 0) scopeState = (await cache.getState(target.documentId, request.scope, target.branch, void 0, signal)).state[request.scope];
584
+ if (conditions !== void 0) scopeState = (conditions.carriedDocument ?? await cache.getState(target.documentId, request.scope, target.branch, void 0, signal)).state[request.scope];
583
585
  return {
584
586
  evaluation: model(target).decide(built.model, subject, request, {
585
587
  scopeState,
@@ -1740,6 +1742,17 @@ var KyselyWriteCache = class KyselyWriteCache {
1740
1742
  putState(documentId, scope, branch, revision, document, position) {
1741
1743
  this.store(documentId, scope, branch, revision, document, position);
1742
1744
  }
1745
+ /**
1746
+ * Stores the run's head and mints a keyframe for every interval the run
1747
+ * crossed on its way there. Only the head enters the ring buffer; the
1748
+ * earlier revisions are keyframe candidates and nothing more.
1749
+ */
1750
+ putRun(documentId, scope, branch, run) {
1751
+ if (run.length === 0) return;
1752
+ for (const entry of run.slice(0, -1)) this.persistKeyframe(documentId, scope, branch, entry.revision, entry.document);
1753
+ const head = run[run.length - 1];
1754
+ this.store(documentId, scope, branch, head.revision, head.document, SnapshotPosition.Head);
1755
+ }
1743
1756
  store(documentId, scope, branch, revision, document, position) {
1744
1757
  const streamKey = this.makeStreamKey(documentId, scope, branch);
1745
1758
  const stream = this.getOrCreateStream(streamKey);
@@ -1753,7 +1766,12 @@ var KyselyWriteCache = class KyselyWriteCache {
1753
1766
  position
1754
1767
  };
1755
1768
  stream.ringBuffer.push(snapshot);
1756
- if (this.isKeyframeRevision(revision)) this.keyframeStore.putKeyframe(documentId, scope, branch, revision, {
1769
+ this.persistKeyframe(documentId, scope, branch, revision, document);
1770
+ }
1771
+ /** Persists the snapshot if this revision is one the interval falls on. */
1772
+ persistKeyframe(documentId, scope, branch, revision, document) {
1773
+ if (!this.isKeyframeRevision(revision)) return;
1774
+ this.keyframeStore.putKeyframe(documentId, scope, branch, revision, {
1757
1775
  ...document,
1758
1776
  operations: {},
1759
1777
  clipboard: []
@@ -2327,14 +2345,15 @@ function reshuffleByTimestamp(startIndex, opsA, opsB) {
2327
2345
  return [...opsA, ...opsB].sort((a, b) => {
2328
2346
  const timestampDiff = new Date(a.timestampUtcMs).getTime() - new Date(b.timestampUtcMs).getTime();
2329
2347
  if (timestampDiff !== 0) return timestampDiff;
2330
- const shouldPrioritizeLogicalIndex = STRICT_ORDER_ACTION_TYPES.has(a.action?.type ?? "") || STRICT_ORDER_ACTION_TYPES.has(b.action?.type ?? "");
2331
- const logicalIndexDiff = a.index - a.skip - (b.index - b.skip);
2332
- if (shouldPrioritizeLogicalIndex) {
2348
+ const rank = (op) => STRICT_ORDER_ACTION_TYPES.has(op.action?.type ?? "") ? 0 : 1;
2349
+ const rankDiff = rank(a) - rank(b);
2350
+ if (rankDiff !== 0) return rankDiff;
2351
+ if (rank(a) === 0) {
2352
+ const logicalIndexDiff = a.index - a.skip - (b.index - b.skip);
2333
2353
  if (logicalIndexDiff !== 0) return logicalIndexDiff;
2334
2354
  }
2335
2355
  const actionIdDiff = (a.action?.id ?? "").localeCompare(b.action?.id ?? "");
2336
2356
  if (actionIdDiff !== 0) return actionIdDiff;
2337
- if (!shouldPrioritizeLogicalIndex && logicalIndexDiff !== 0) return logicalIndexDiff;
2338
2357
  return a.id.localeCompare(b.id);
2339
2358
  }).map((op, i) => ({
2340
2359
  ...op,
@@ -3155,9 +3174,6 @@ function isValidISOTimestamp(value) {
3155
3174
  if (!ISO_TIMESTAMP_REGEX.test(value)) return false;
3156
3175
  return !isNaN(new Date(value).getTime());
3157
3176
  }
3158
- /**
3159
- * Simple job executor that processes a job by applying actions through document model reducers.
3160
- */
3161
3177
  var SimpleJobExecutor = class {
3162
3178
  config;
3163
3179
  featureFlags;
@@ -3183,7 +3199,8 @@ var SimpleJobExecutor = class {
3183
3199
  deferredJobTtlMs: config.deferredJobTtlMs ?? 3e4,
3184
3200
  retryBaseDelayMs: config.retryBaseDelayMs ?? 100,
3185
3201
  retryMaxDelayMs: config.retryMaxDelayMs ?? 5e3,
3186
- yieldDeadlineMs: config.yieldDeadlineMs ?? 50
3202
+ yieldDeadlineMs: config.yieldDeadlineMs ?? 50,
3203
+ batchApplies: config.batchApplies ?? true
3187
3204
  };
3188
3205
  this.featureFlags = resolveFeatureFlags(config.featureFlags);
3189
3206
  this.decisionModel = selectDecisionModel(this.featureFlags, registry);
@@ -3356,6 +3373,21 @@ var SimpleJobExecutor = class {
3356
3373
  error: new InvalidOperationTimestampError(job.documentId, action.scope, action.timestampUtcMs, `action ${action.type} (id: ${action.id})`)
3357
3374
  };
3358
3375
  let lastYield = performance.now();
3376
+ if (this.config.batchApplies && this.canBatch(writes, executing)) {
3377
+ const batched = await this.executeRegularActionsBatched(writes, executing);
3378
+ const error = this.accumulateResultOrReturnError(batched, generatedOperations, operationsWithContext);
3379
+ if (error !== null) return {
3380
+ success: false,
3381
+ generatedOperations,
3382
+ operationsWithContext,
3383
+ error: error.error
3384
+ };
3385
+ return {
3386
+ success: true,
3387
+ generatedOperations,
3388
+ operationsWithContext
3389
+ };
3390
+ }
3359
3391
  for (const write of writes) {
3360
3392
  const result = DOCUMENT_SCOPE_ACTIONS.has(write.action.type) ? await this.documentActionHandler.execute(write, executing) : await this.executeRegularAction(write, executing);
3361
3393
  const error = this.accumulateResultOrReturnError(result, generatedOperations, operationsWithContext);
@@ -3382,7 +3414,15 @@ var SimpleJobExecutor = class {
3382
3414
  operationsWithContext
3383
3415
  };
3384
3416
  }
3385
- async executeRegularAction(write, executing) {
3417
+ /**
3418
+ * Decides a write and reduces it, without persisting anything.
3419
+ *
3420
+ * Split from the commit so one write and a batch of them share this logic
3421
+ * rather than keeping two copies of it. `baseDocument` lets a batch thread
3422
+ * the previous action's result forward instead of reading its own write back
3423
+ * out of the cache, which is the only reason the reduce has to be sequential.
3424
+ */
3425
+ async prepareRegularWrite(write, executing, baseDocument) {
3386
3426
  const { action, skip, sourceOperation, sourceRemote, deniedReason } = write;
3387
3427
  const { job, startTime, indexTxn, stores, signal } = executing;
3388
3428
  let appendCondition;
@@ -3402,7 +3442,10 @@ var SimpleJobExecutor = class {
3402
3442
  verb: "execute",
3403
3443
  scope: action.scope,
3404
3444
  operation: action.type
3405
- }, signal, this.featureFlags.authConditions ? { actionInput: action.input } : void 0);
3445
+ }, signal, this.featureFlags.authConditions ? {
3446
+ actionInput: action.input,
3447
+ carriedDocument: baseDocument
3448
+ } : void 0);
3406
3449
  } catch (error) {
3407
3450
  return buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
3408
3451
  }
@@ -3420,9 +3463,10 @@ var SimpleJobExecutor = class {
3420
3463
  if (docMeta.state.isDeleted) return buildErrorResult(job, new DocumentDeletedError(job.documentId, docMeta.state.deletedAtUtcIso), startTime);
3421
3464
  documentVersion = docMeta.state.version;
3422
3465
  }
3423
- if (isUndoRedo(action) || action.type === "PRUNE" || action.type === "NOOP" && skip > 0) stores.writeCache.invalidate(job.documentId, job.scope, job.branch);
3466
+ if (isUndoRedo(action) || action.type === "PRUNE" || skip > 0) stores.writeCache.invalidate(job.documentId, job.scope, job.branch);
3424
3467
  let document;
3425
- try {
3468
+ if (baseDocument !== void 0) document = baseDocument;
3469
+ else try {
3426
3470
  document = await stores.writeCache.getState(job.documentId, job.scope, job.branch, void 0, signal);
3427
3471
  } catch (error) {
3428
3472
  return buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
@@ -3495,13 +3539,45 @@ var SimpleJobExecutor = class {
3495
3539
  ...updatedDocument.state,
3496
3540
  header: updatedDocument.header
3497
3541
  });
3542
+ return {
3543
+ action,
3544
+ sourceRemote,
3545
+ scope,
3546
+ document,
3547
+ updatedDocument,
3548
+ operation: newOperation,
3549
+ resultingState,
3550
+ appendCondition,
3551
+ denied: deniedReason !== void 0
3552
+ };
3553
+ }
3554
+ /**
3555
+ * Persists a run of prepared writes in one store transaction.
3556
+ *
3557
+ * The store has always accepted many operations per apply; the executor only
3558
+ * ever handed it one. Passing the whole run means one advisory lock over the
3559
+ * read set and one guarded insert for the batch, instead of one of each per
3560
+ * operation.
3561
+ *
3562
+ * The append condition is taken from the first write. Every write in a run
3563
+ * reads the same streams at the same revisions, because nothing outside the
3564
+ * run can change them mid-batch, and the caller has already refused to batch
3565
+ * the scopes where that does not hold.
3566
+ */
3567
+ async commitPreparedWrites(prepared, executing) {
3568
+ const { job, startTime, indexTxn, stores, signal } = executing;
3569
+ const first = prepared[0];
3570
+ const last = prepared[prepared.length - 1];
3571
+ const scope = first.scope;
3572
+ const documentType = first.document.header.documentType;
3573
+ const operations = prepared.map((write) => write.operation);
3498
3574
  let storedOperations;
3499
3575
  try {
3500
- storedOperations = await stores.operationStore.apply(job.documentId, document.header.documentType, scope, job.branch, newOperation.index, (txn) => {
3501
- txn.addOperations(newOperation);
3502
- }, signal, appendCondition);
3576
+ storedOperations = await stores.operationStore.apply(job.documentId, documentType, scope, job.branch, first.operation.index, (txn) => {
3577
+ txn.addOperations(...operations);
3578
+ }, signal, first.appendCondition);
3503
3579
  } catch (error) {
3504
- this.logger.error("Error writing @Operation to IOperationStore: @Error", newOperation, error);
3580
+ this.logger.error("Error writing @Operation to IOperationStore: @Error", operations, error);
3505
3581
  stores.writeCache.invalidate(job.documentId, scope, job.branch);
3506
3582
  if (AppendConditionFailedError.isError(error)) for (const stream of error.condition.streams) stores.writeCache.invalidate(stream.documentId, stream.scope, stream.branch);
3507
3583
  return {
@@ -3511,40 +3587,151 @@ var SimpleJobExecutor = class {
3511
3587
  duration: Date.now() - startTime
3512
3588
  };
3513
3589
  }
3514
- const storedOperation = storedOperations[0];
3515
- updatedDocument.header.revision = {
3516
- ...updatedDocument.header.revision,
3517
- [scope]: storedOperation.index + 1
3590
+ const head = storedOperations[storedOperations.length - 1];
3591
+ last.updatedDocument.header.revision = {
3592
+ ...last.updatedDocument.header.revision,
3593
+ [scope]: head.index + 1
3518
3594
  };
3519
- stores.writeCache.putState(job.documentId, scope, job.branch, storedOperation.index, updatedDocument, SnapshotPosition.Head);
3520
- indexTxn.write([{
3521
- ...storedOperation,
3595
+ stores.writeCache.putRun(job.documentId, scope, job.branch, storedOperations.map((operation, position) => ({
3596
+ revision: operation.index,
3597
+ document: prepared[position].updatedDocument
3598
+ })));
3599
+ indexTxn.write(storedOperations.map((operation, position) => ({
3600
+ ...operation,
3522
3601
  documentId: job.documentId,
3523
- documentType: document.header.documentType,
3602
+ documentType,
3524
3603
  branch: job.branch,
3525
3604
  scope,
3526
- sourceRemote
3527
- }]);
3528
- if (scope === "auth") indexTxn.recordGroupReferences(job.documentId, mentionedGroupIds(action));
3605
+ sourceRemote: prepared[position].sourceRemote
3606
+ })));
3607
+ if (scope === "auth") for (const write of prepared) indexTxn.recordGroupReferences(job.documentId, mentionedGroupIds(write.action));
3529
3608
  return {
3530
3609
  job,
3531
3610
  success: true,
3532
- operations: [storedOperation],
3533
- operationsWithContext: [{
3534
- operation: storedOperation,
3611
+ operations: storedOperations,
3612
+ operationsWithContext: storedOperations.map((operation, position) => ({
3613
+ operation,
3535
3614
  context: {
3536
3615
  documentId: job.documentId,
3537
3616
  scope,
3538
3617
  branch: job.branch,
3539
- documentType: document.header.documentType,
3540
- resultingState,
3618
+ documentType,
3619
+ resultingState: prepared[position].resultingState,
3541
3620
  ordinal: 0
3542
3621
  }
3543
- }],
3622
+ })),
3544
3623
  duration: Date.now() - startTime
3545
3624
  };
3546
3625
  }
3547
3626
  /**
3627
+ * Whether a job's writes may share one store transaction.
3628
+ *
3629
+ * Deliberately narrow. Batching changes only how many transactions the
3630
+ * operations arrive in, and every condition below is a case where that would
3631
+ * change something else as well:
3632
+ *
3633
+ * - A document-scope action goes through its own handler, which has its own
3634
+ * apply and its own reasons for it.
3635
+ * - A positional or replayed run carries skips and re-appended operations,
3636
+ * whose indices are not a simple ascending run from the head.
3637
+ * - UNDO, REDO, PRUNE and NOOP-with-skip each invalidate the write cache to
3638
+ * force a full-history rebuild, so they cannot be reduced against state
3639
+ * threaded from the write before them.
3640
+ * - The auth scope decides later writes against the policy earlier ones
3641
+ * install, so a batch would decide them all against the policy as it stood
3642
+ * before the batch.
3643
+ * - The document scope is read by every decision model, so writing it is
3644
+ * writing part of the read set; the per-write conditions would not agree.
3645
+ *
3646
+ * A run that fails any of these is executed one write at a time, unchanged.
3647
+ */
3648
+ canBatch(writes, executing) {
3649
+ if (writes.length < 2) return false;
3650
+ if (executing.evaluatedByPosition || executing.replayingAcceptedHistory) return false;
3651
+ const scope = executing.job.scope;
3652
+ if (scope === "auth" || scope === "document") return false;
3653
+ return writes.every((write) => {
3654
+ const type = write.action.type;
3655
+ return write.skip === 0 && write.deniedReason === void 0 && write.sourceOperation === void 0 && !DOCUMENT_SCOPE_ACTIONS.has(type) && !isUndoRedo(write.action) && type !== "PRUNE" && type !== "NOOP";
3656
+ });
3657
+ }
3658
+ /**
3659
+ * Decides and reduces a run of writes, then persists them together.
3660
+ *
3661
+ * The reduce stays sequential - each action needs the state the one before it
3662
+ * produced - but the result is threaded in memory rather than read back from
3663
+ * the cache, and the whole run reaches the store in a single apply.
3664
+ *
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.
3669
+ */
3670
+ async executeRegularActionsBatched(writes, executing) {
3671
+ const prepared = [];
3672
+ let carried;
3673
+ let lastYield = performance.now();
3674
+ for (const write of writes) {
3675
+ const outcome = await this.prepareRegularWrite(write, executing, carried);
3676
+ if ("success" in outcome) return prepared.length === 0 ? outcome : this.executeRegularActionsSequentially(writes, executing);
3677
+ if (outcome.denied) return this.executeRegularActionsSequentially(writes, executing);
3678
+ prepared.push(outcome);
3679
+ carried = outcome.updatedDocument;
3680
+ if (performance.now() - lastYield > this.config.yieldDeadlineMs) {
3681
+ await yieldToMain();
3682
+ lastYield = performance.now();
3683
+ if (executing.signal?.aborted) return buildErrorResult(executing.job, /* @__PURE__ */ new Error("Aborted"), executing.startTime);
3684
+ }
3685
+ }
3686
+ if (!this.conditionsAgree(prepared)) return this.executeRegularActionsSequentially(writes, executing);
3687
+ return this.commitPreparedWrites(prepared, executing);
3688
+ }
3689
+ /** Whether every prepared write carries the same read-set condition. */
3690
+ conditionsAgree(prepared) {
3691
+ const shape = (write) => write.appendCondition === void 0 ? "none" : JSON.stringify([...write.appendCondition.streams].map((stream) => [
3692
+ stream.documentId,
3693
+ stream.scope,
3694
+ stream.branch,
3695
+ stream.revision
3696
+ ]).sort());
3697
+ const first = shape(prepared[0]);
3698
+ return prepared.every((write) => shape(write) === first);
3699
+ }
3700
+ /**
3701
+ * The unbatched path, for a run that turned out not to qualify after its
3702
+ * writes were prepared. Nothing has been persisted at that point, so
3703
+ * replaying the whole run per write is safe.
3704
+ */
3705
+ async executeRegularActionsSequentially(writes, executing) {
3706
+ const operations = [];
3707
+ const contexts = [];
3708
+ let lastYield = performance.now();
3709
+ for (const write of writes) {
3710
+ const result = await this.executeRegularAction(write, executing);
3711
+ if (!result.success) return result;
3712
+ operations.push(...result.operations ?? []);
3713
+ contexts.push(...result.operationsWithContext ?? []);
3714
+ if (performance.now() - lastYield > this.config.yieldDeadlineMs) {
3715
+ await yieldToMain();
3716
+ lastYield = performance.now();
3717
+ if (executing.signal?.aborted) return buildErrorResult(executing.job, /* @__PURE__ */ new Error("Aborted"), executing.startTime);
3718
+ }
3719
+ }
3720
+ return {
3721
+ job: executing.job,
3722
+ success: true,
3723
+ operations,
3724
+ operationsWithContext: contexts,
3725
+ duration: Date.now() - executing.startTime
3726
+ };
3727
+ }
3728
+ /** Decides, reduces and persists one write. */
3729
+ async executeRegularAction(write, executing) {
3730
+ const prepared = await this.prepareRegularWrite(write, executing);
3731
+ if ("success" in prepared) return prepared;
3732
+ return this.commitPreparedWrites([prepared], executing);
3733
+ }
3734
+ /**
3548
3735
  * Orders a write by timestamp and decides it where it lands. The caller
3549
3736
  * supplies the timestamp, so a write can belong before operations already
3550
3737
  * stored; those are re-appended alongside it, the way a load reshuffles.
@@ -3926,7 +4113,7 @@ var SimpleJobExecutor = class {
3926
4113
  ...operation,
3927
4114
  id: operation.id
3928
4115
  })));
3929
- for (const operation of reshuffledOperations) if (operation.action.type === "NOOP") operation.skip = 1;
4116
+ for (const operation of reshuffledOperations) if (operation.action.type === "NOOP" && operation.skip === 0) operation.skip = 1;
3930
4117
  let deniedReasons;
3931
4118
  if (this.featureFlags.documentDecisions) try {
3932
4119
  deniedReasons = await evaluateByPosition(this.decisionModel, {
@@ -4947,4 +5134,4 @@ const DEFAULT_DRIVE_CONTAINER_TYPES = new Set(["powerhouse/document-drive", "pow
4947
5134
  //#endregion
4948
5135
  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 };
4949
5136
 
4950
- //# sourceMappingURL=drive-container-types-bVQ_8YwX.js.map
5137
+ //# sourceMappingURL=drive-container-types-CruMdXue.js.map