@powerhousedao/reactor 6.2.3-dev.2 → 6.2.3-dev.21

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-yZrksiJR.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-CS5IxDiA.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-DnU3ZU4H.js.map
83
+ //# sourceMappingURL=build-worker-executor-DUjyF4NM.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"build-worker-executor-DnU3ZU4H.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-DUjyF4NM.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,9 +1,36 @@
1
- import { L as DocumentExistence } from "./drive-container-types-yZrksiJR.js";
1
+ import { tt as yieldToMain, z as DocumentExistence } from "./drive-container-types-CS5IxDiA.js";
2
2
  import { n as ReactorEventTypes } from "./types-DMKLa0Ok.js";
3
3
  import { createAuthState, isDenied } from "@powerhousedao/shared/document-model";
4
4
  import { v4 } from "uuid";
5
5
  import { childLogger } from "document-model";
6
6
  //#region src/read-models/base-read-model.ts
7
+ /** Small enough that a chunk's transaction rarely outlasts the yield deadline. */
8
+ const DEFAULT_COMMIT_CHUNK_SIZE = 50;
9
+ /** Matches the executor's own default, so both paths yield on the same cadence. */
10
+ const DEFAULT_READ_MODEL_YIELD_DEADLINE_MS = 50;
11
+ const defaultReadModelIndexingConfig = {
12
+ commitChunkSize: 50,
13
+ yieldDeadlineMs: 50
14
+ };
15
+ /** For read models whose callers can observe where a batch was split. */
16
+ const unchunkedReadModelIndexingConfig = {
17
+ commitChunkSize: Number.MAX_SAFE_INTEGER,
18
+ yieldDeadlineMs: 50
19
+ };
20
+ /**
21
+ * Keeps the chunk size at one operation or more: a chunk of zero or less never
22
+ * advances the indexing loop, so the pass would spin without ever resolving.
23
+ */
24
+ function normalizeIndexingConfig(config) {
25
+ if (Number.isNaN(config.commitChunkSize)) return {
26
+ ...config,
27
+ commitChunkSize: 50
28
+ };
29
+ return {
30
+ ...config,
31
+ commitChunkSize: Math.max(1, Math.min(Math.floor(config.commitChunkSize), Number.MAX_SAFE_INTEGER))
32
+ };
33
+ }
7
34
  /**
8
35
  * Base class for read models that provides catch-up/rewind functionality.
9
36
  * Handles initialization, state tracking via ViewState table, and consistency tracking.
@@ -12,6 +39,13 @@ import { childLogger } from "document-model";
12
39
  var BaseReadModel = class {
13
40
  lastOrdinal = 0;
14
41
  name;
42
+ indexing;
43
+ /**
44
+ * Lowest ordinal this model failed to commit and has not committed since, or
45
+ * zero when there is none. The stored cursor is held below it so replay from
46
+ * the cursor still reaches every operation the failed pass left out.
47
+ */
48
+ uncommittedOrdinal = 0;
15
49
  constructor(db, operationIndex, writeCache, consistencyTracker, config) {
16
50
  this.db = db;
17
51
  this.operationIndex = operationIndex;
@@ -19,6 +53,7 @@ var BaseReadModel = class {
19
53
  this.consistencyTracker = consistencyTracker;
20
54
  this.config = config;
21
55
  this.name = config.readModelId;
56
+ this.indexing = normalizeIndexingConfig(config.indexing ?? defaultReadModelIndexingConfig);
22
57
  }
23
58
  /**
24
59
  * Initializes the read model by loading state and catching up on missed operations.
@@ -36,15 +71,33 @@ var BaseReadModel = class {
36
71
  }
37
72
  }
38
73
  /**
39
- * Template method: runs domain-specific commitOperations, then persists
40
- * state and updates consistency tracking.
74
+ * Commits the batch in chunks, yielding between them with no transaction
75
+ * open. A chunk that throws leaves the earlier chunks committed, so the pass
76
+ * saves the cursor for that prefix and parks it below the operation it could
77
+ * not commit before rethrowing.
41
78
  */
42
79
  async indexOperations(items) {
43
80
  if (items.length === 0) return;
44
- await this.commitOperations(items);
45
- await this.db.transaction().execute(async (trx) => {
46
- await this.saveState(trx, items);
47
- });
81
+ const { commitChunkSize, yieldDeadlineMs } = this.indexing;
82
+ let lastYield = performance.now();
83
+ let committed = 0;
84
+ for (let start = 0; start < items.length; start += commitChunkSize) {
85
+ if (start > 0 && performance.now() - lastYield > yieldDeadlineMs) {
86
+ await yieldToMain();
87
+ lastYield = performance.now();
88
+ }
89
+ const chunk = items.slice(start, start + commitChunkSize);
90
+ try {
91
+ await this.commitOperations(chunk);
92
+ } catch (error) {
93
+ this.park(items, committed);
94
+ await this.recordCommittedPrefix(items.slice(0, committed));
95
+ throw error;
96
+ }
97
+ committed += chunk.length;
98
+ }
99
+ this.liftParkIfCommitted(items);
100
+ await this.persistCursor(items);
48
101
  this.updateConsistencyTracker(items);
49
102
  }
50
103
  /**
@@ -118,6 +171,59 @@ var BaseReadModel = class {
118
171
  }
119
172
  this.consistencyTracker.update(coordinates);
120
173
  }
174
+ /**
175
+ * Saves the cursor for the chunks that did commit before a later chunk threw.
176
+ * A failure here is swallowed: the cursor simply stays where the pass found
177
+ * it, which is equally safe, and the commit error is the one worth raising.
178
+ */
179
+ async recordCommittedPrefix(prefix) {
180
+ if (prefix.length === 0) return;
181
+ try {
182
+ await this.persistCursor(prefix);
183
+ } catch {
184
+ return;
185
+ }
186
+ this.updateConsistencyTracker(prefix);
187
+ }
188
+ /** Writes the cursor for the given items, never past a parked ordinal. */
189
+ async persistCursor(items) {
190
+ await this.db.transaction().execute(async (trx) => {
191
+ await this.saveState(trx, items);
192
+ await this.clampCursorToPark(trx);
193
+ });
194
+ }
195
+ /**
196
+ * Holds the cursor written by {@link saveState}, which subclasses may
197
+ * override, below the lowest operation this model failed to commit.
198
+ */
199
+ async clampCursorToPark(trx) {
200
+ if (this.uncommittedOrdinal === 0) return;
201
+ const ceiling = this.uncommittedOrdinal - 1;
202
+ if (this.lastOrdinal <= ceiling) return;
203
+ this.lastOrdinal = ceiling;
204
+ await trx.updateTable("ViewState").set({
205
+ lastOrdinal: ceiling,
206
+ lastOperationTimestamp: /* @__PURE__ */ new Date()
207
+ }).where("readModelId", "=", this.config.readModelId).execute();
208
+ }
209
+ /** Remembers the lowest ordinal the failed pass left uncommitted. */
210
+ park(items, committed) {
211
+ let lowest = 0;
212
+ for (let i = committed; i < items.length; i++) {
213
+ const ordinal = items[i].context.ordinal;
214
+ if (lowest === 0 || ordinal < lowest) lowest = ordinal;
215
+ }
216
+ if (lowest === 0) return;
217
+ if (this.uncommittedOrdinal === 0 || lowest < this.uncommittedOrdinal) this.uncommittedOrdinal = lowest;
218
+ }
219
+ /** The park lifts once a later pass commits the operation that failed. */
220
+ liftParkIfCommitted(items) {
221
+ if (this.uncommittedOrdinal === 0) return;
222
+ for (const item of items) if (item.context.ordinal === this.uncommittedOrdinal) {
223
+ this.uncommittedOrdinal = 0;
224
+ return;
225
+ }
226
+ }
121
227
  };
122
228
  //#endregion
123
229
  //#region src/read-models/coordinator.ts
@@ -280,15 +386,34 @@ const DOCUMENT_VIEW_READ_MODEL = "document-view";
280
386
  const DOCUMENT_INDEXER_READ_MODEL = "document-indexer";
281
387
  //#endregion
282
388
  //#region src/read-models/document-view.ts
389
+ /**
390
+ * What a single-document read of a deleted document returns. A listing omits a
391
+ * deleted document under either value.
392
+ */
393
+ let DeletedDocumentRead = /* @__PURE__ */ function(DeletedDocumentRead) {
394
+ /**
395
+ * The document reads as missing: `get` throws and `resolveIdOrSlug` does not
396
+ * match its id.
397
+ */
398
+ DeletedDocumentRead["NotFound"] = "NotFound";
399
+ /**
400
+ * The document's state as of the deletion, with `state.document.isDeleted`
401
+ * telling the caller what it holds. Only meaningful with `documentDecisions`,
402
+ * which is what makes deletion positional.
403
+ */
404
+ DeletedDocumentRead["StateAtDeletion"] = "StateAtDeletion";
405
+ return DeletedDocumentRead;
406
+ }({});
283
407
  var KyselyDocumentView = class extends BaseReadModel {
284
408
  _db;
285
- constructor(db, operationStore, operationIndex, writeCache, consistencyTracker, servesDeletionBoundary) {
409
+ constructor(db, operationStore, operationIndex, writeCache, consistencyTracker, deletedDocumentRead, indexing = defaultReadModelIndexingConfig) {
286
410
  super(db, operationIndex, writeCache, consistencyTracker, {
287
411
  readModelId: DOCUMENT_VIEW_READ_MODEL,
288
- rebuildStateOnInit: true
412
+ rebuildStateOnInit: true,
413
+ indexing
289
414
  });
290
415
  this.operationStore = operationStore;
291
- this.servesDeletionBoundary = servesDeletionBoundary;
416
+ this.deletedDocumentRead = deletedDocumentRead;
292
417
  this._db = db;
293
418
  }
294
419
  /**
@@ -300,12 +425,17 @@ var KyselyDocumentView = class extends BaseReadModel {
300
425
  * without either fall back to header/document/auth, because their sibling
301
426
  * echoes may be stale. All other action types index only header and their
302
427
  * own scope.
428
+ *
429
+ * The header row is the one row every scope's chain writes, so it accepts a
430
+ * write only from an operation whose global ordinal is at least the one the
431
+ * row already carries. Without that, a chunked pass that started earlier
432
+ * reverts a concurrent rename with the stale echo its later chunks carry.
303
433
  */
304
434
  async commitOperations(items) {
305
435
  await this._db.transaction().execute(async (trx) => {
306
436
  for (const item of items) {
307
437
  const { operation, context } = item;
308
- const { documentId, scope, branch, documentType, resultingState } = context;
438
+ const { documentId, scope, branch, documentType, resultingState, ordinal } = context;
309
439
  const { index, hash } = operation;
310
440
  if (!resultingState) throw new Error(`Missing resultingState in context for operation ${operation.id || "unknown"}. IDocumentView requires resultingState from upstream - it does not rebuild documents.`);
311
441
  let fullState;
@@ -324,6 +454,7 @@ var KyselyDocumentView = class extends BaseReadModel {
324
454
  deletedAt: now,
325
455
  lastOperationIndex: index,
326
456
  lastOperationHash: hash,
457
+ lastOperationOrdinal: ordinal,
327
458
  lastUpdatedAt: now
328
459
  }).where("documentId", "=", documentId).where("branch", "=", branch).execute();
329
460
  const deletedDocumentScope = fullState?.document;
@@ -345,7 +476,15 @@ var KyselyDocumentView = class extends BaseReadModel {
345
476
  else scopesToIndex.push([scope, {}]);
346
477
  }
347
478
  for (const [scopeName, scopeState] of scopesToIndex) {
348
- const existingSnapshot = await trx.selectFrom("DocumentSnapshot").selectAll().where("documentId", "=", documentId).where("scope", "=", scopeName).where("branch", "=", branch).executeTakeFirst();
479
+ const needsExistingContent = scopeName === "header" && preserveHeaderMeta;
480
+ const existingSnapshot = await trx.selectFrom("DocumentSnapshot").select([
481
+ "slug",
482
+ "name",
483
+ "isDeleted",
484
+ "snapshotVersion",
485
+ "lastOperationOrdinal"
486
+ ]).$if(needsExistingContent, (qb) => qb.select("content")).where("documentId", "=", documentId).where("scope", "=", scopeName).where("branch", "=", branch).executeTakeFirst();
487
+ if (scopeName === "header" && existingSnapshot !== void 0 && existingSnapshot.lastOperationOrdinal > ordinal) continue;
349
488
  const newState = typeof scopeState === "object" && scopeState !== null ? scopeState : {};
350
489
  let slug = existingSnapshot?.slug ?? null;
351
490
  let name = existingSnapshot?.name ?? null;
@@ -354,7 +493,7 @@ var KyselyDocumentView = class extends BaseReadModel {
354
493
  const headerName = newState.name;
355
494
  if (typeof headerSlug === "string") slug = headerSlug;
356
495
  if (typeof headerName === "string") name = headerName;
357
- if (preserveHeaderMeta && existingSnapshot) {
496
+ if (needsExistingContent && existingSnapshot) {
358
497
  const existingHeader = existingSnapshot.content;
359
498
  if (existingHeader && "meta" in existingHeader) newState.meta = existingHeader.meta;
360
499
  }
@@ -372,12 +511,13 @@ var KyselyDocumentView = class extends BaseReadModel {
372
511
  if (existingSnapshot) await trx.updateTable("DocumentSnapshot").set({
373
512
  lastOperationIndex: index,
374
513
  lastOperationHash: hash,
514
+ lastOperationOrdinal: ordinal,
375
515
  lastUpdatedAt: /* @__PURE__ */ new Date(),
376
516
  snapshotVersion: existingSnapshot.snapshotVersion + 1,
377
517
  content: newState,
378
518
  slug,
379
519
  name
380
- }).where("documentId", "=", documentId).where("scope", "=", scopeName).where("branch", "=", branch).execute();
520
+ }).where("documentId", "=", documentId).where("scope", "=", scopeName).where("branch", "=", branch).$if(scopeName === "header", (qb) => qb.where("lastOperationOrdinal", "<=", ordinal)).execute();
381
521
  else {
382
522
  const snapshot = {
383
523
  id: v4(),
@@ -390,6 +530,7 @@ var KyselyDocumentView = class extends BaseReadModel {
390
530
  documentType,
391
531
  lastOperationIndex: index,
392
532
  lastOperationHash: hash,
533
+ lastOperationOrdinal: ordinal,
393
534
  identifiers: null,
394
535
  metadata: null,
395
536
  deletedAt: null
@@ -419,7 +560,7 @@ var KyselyDocumentView = class extends BaseReadModel {
419
560
  ])];
420
561
  else scopesToQuery = [];
421
562
  let query = this._db.selectFrom("DocumentSnapshot").selectAll().where("documentId", "=", documentId).where("branch", "=", branch);
422
- if (!this.servesDeletionBoundary) query = query.where("isDeleted", "=", false);
563
+ if (this.deletedDocumentRead === DeletedDocumentRead.NotFound) query = query.where("isDeleted", "=", false);
423
564
  if (scopesToQuery.length > 0) query = query.where("scope", "in", scopesToQuery);
424
565
  const snapshots = await query.execute();
425
566
  if (snapshots.length === 0) throw new Error(`Document not found: ${documentId}`);
@@ -564,7 +705,7 @@ var KyselyDocumentView = class extends BaseReadModel {
564
705
  if (signal?.aborted) throw new Error("Operation aborted");
565
706
  const branch = view?.branch || "main";
566
707
  let idCheckQuery = this._db.selectFrom("DocumentSnapshot").select("documentId").where("documentId", "=", identifier).where("branch", "=", branch);
567
- if (!this.servesDeletionBoundary) idCheckQuery = idCheckQuery.where("isDeleted", "=", false);
708
+ if (this.deletedDocumentRead === DeletedDocumentRead.NotFound) idCheckQuery = idCheckQuery.where("isDeleted", "=", false);
568
709
  const idCheckPromise = idCheckQuery.executeTakeFirst();
569
710
  const slugCheckPromise = this._db.selectFrom("SlugMapping").select("documentId").where("slug", "=", identifier).where("branch", "=", branch).executeTakeFirst();
570
711
  const [idMatch, slugMatch] = await Promise.all([idCheckPromise, slugCheckPromise]);
@@ -713,18 +854,25 @@ async function collectAllPages(firstPage, signal) {
713
854
  }
714
855
  //#endregion
715
856
  //#region src/storage/kysely/document-indexer.ts
857
+ function isRelationshipAction(actionType) {
858
+ return actionType === "ADD_RELATIONSHIP" || actionType === "REMOVE_RELATIONSHIP" || actionType === "UPDATE_RELATIONSHIP";
859
+ }
716
860
  var KyselyDocumentIndexer = class extends BaseReadModel {
717
861
  _db;
718
- constructor(db, operationIndex, writeCache, consistencyTracker) {
862
+ constructor(db, operationIndex, writeCache, consistencyTracker, indexing = defaultReadModelIndexingConfig) {
719
863
  super(db, operationIndex, writeCache, consistencyTracker, {
720
864
  readModelId: DOCUMENT_INDEXER_READ_MODEL,
721
- rebuildStateOnInit: false
865
+ rebuildStateOnInit: false,
866
+ indexing
722
867
  });
723
868
  this._db = db;
724
869
  }
870
+ /** Opens no transaction for a batch carrying no relationship operation. */
725
871
  async commitOperations(items) {
872
+ const relationshipOps = items.filter((item) => isRelationshipAction(item.operation.action.type));
873
+ if (relationshipOps.length === 0) return;
726
874
  await this._db.transaction().execute(async (trx) => {
727
- for (const item of items) {
875
+ for (const item of relationshipOps) {
728
876
  const { operation } = item;
729
877
  const actionType = operation.action.type;
730
878
  if (actionType === "ADD_RELATIONSHIP") await this.handleAddRelationship(trx, operation);
@@ -953,6 +1101,6 @@ var KyselyDocumentIndexer = class extends BaseReadModel {
953
1101
  }
954
1102
  };
955
1103
  //#endregion
956
- export { DOCUMENT_INDEXER_READ_MODEL as a, BaseReadModel as c, KyselyDocumentView as i, ConsistencyTracker as n, DOCUMENT_VIEW_READ_MODEL as o, makeConsistencyKey as r, ReadModelCoordinator as s, KyselyDocumentIndexer as t };
1104
+ export { KyselyDocumentView as a, ReadModelCoordinator as c, DEFAULT_READ_MODEL_YIELD_DEADLINE_MS as d, defaultReadModelIndexingConfig as f, DeletedDocumentRead as i, BaseReadModel as l, ConsistencyTracker as n, DOCUMENT_INDEXER_READ_MODEL as o, unchunkedReadModelIndexingConfig as p, makeConsistencyKey as r, DOCUMENT_VIEW_READ_MODEL as s, KyselyDocumentIndexer as t, DEFAULT_COMMIT_CHUNK_SIZE as u };
957
1105
 
958
- //# sourceMappingURL=document-indexer-C5Gsa1B3.js.map
1106
+ //# sourceMappingURL=document-indexer-C1oY6CaR.js.map