@powerhousedao/reactor 6.2.3-dev.25 → 6.2.3-dev.26

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 { C as DocumentMetaCache, S as KyselyOperationIndex, a as DEFAULT_DRIVE_CONTAINER_TYPES, b as EventBus, d as KyselyOperationStore, f as KyselyKeyframeStore, m as SimpleJobExecutor, o as REACTOR_SCHEMA, ot as ReactorEventTypes, p as DocumentModelRegistry, v as KyselyExecutionScope, w as CollectionMembershipCache, x as KyselyWriteCache } from "./forwarding-logger-CSsCJ38f.js";
1
+ import { C as DocumentMetaCache, S as KyselyOperationIndex, a as DEFAULT_DRIVE_CONTAINER_TYPES, b as EventBus, d as KyselyOperationStore, f as KyselyKeyframeStore, m as SimpleJobExecutor, o as REACTOR_SCHEMA, ot as ReactorEventTypes, p as DocumentModelRegistry, v as KyselyExecutionScope, w as CollectionMembershipCache, x as KyselyWriteCache } from "./forwarding-logger-CnuVDVze.js";
2
2
  //#region src/executor/worker/build-worker-executor.ts
3
3
  async function defaultLoadFactory(spec) {
4
4
  const ref = spec.module;
@@ -96,4 +96,4 @@ async function buildWorkerExecutor(options) {
96
96
  //#endregion
97
97
  export { defaultLoadFactory as n, buildWorkerExecutor as t };
98
98
 
99
- //# sourceMappingURL=build-worker-executor-DPGjh9Uc.js.map
99
+ //# sourceMappingURL=build-worker-executor-Bvoba-AV.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"build-worker-executor-DPGjh9Uc.js","names":[],"sources":["../src/executor/worker/build-worker-executor.ts"],"sourcesContent":["import type {\n DocumentModelModule,\n ISigner,\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 type SignatureRefusedEvent,\n} from \"../../events/types.js\";\nimport { DocumentModelRegistry } from \"../../registry/implementation.js\";\nimport type { JobMeta } from \"../../shared/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 { SignatureTrustPolicy } from \"../../signer/types.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 /** Drains the refusals this worker's executor emitted since the last call. */\n takeSignatureRefusals(): SignatureRefusedEvent[];\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 signer: ISigner | undefined;\n if (init.signer) {\n try {\n signer = (await loadFactory(init.signer)) as ISigner;\n } catch (error) {\n logger.error(\n \"worker failed to load signer: @spec @error\",\n init.signer.module,\n error,\n );\n throw error;\n }\n }\n\n let trustPolicy: SignatureTrustPolicy | undefined;\n if (init.trustPolicy) {\n try {\n trustPolicy = (await loadFactory(\n init.trustPolicy,\n )) as SignatureTrustPolicy;\n } catch (error) {\n logger.error(\n \"worker failed to load trust policy: @spec @error\",\n init.trustPolicy.module,\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 let signatureRefusals: SignatureRefusedEvent[] = [];\n eventBus.subscribe(\n ReactorEventTypes.SIGNATURE_REFUSED,\n (_t: number, event: SignatureRefusedEvent) => {\n signatureRefusals.push(event);\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 executionScope,\n signer,\n trustPolicy,\n );\n\n return {\n executor,\n registry,\n takeLastWriteReady(): WorkerWriteReadyCapture | null {\n const captured = lastWriteReady;\n lastWriteReady = null;\n return captured;\n },\n takeSignatureRefusals(): SignatureRefusedEvent[] {\n const captured = signatureRefusals;\n signatureRefusals = [];\n return captured;\n },\n };\n}\n"],"mappings":";;AAyEA,eAAsB,mBAAmB,MAAqC;CAC5E,MAAM,MAAM,KAAK;CAMjB,MAAM,YAAW,OAJf,cAAc,MAAA,OACV,IAAI,IAAI,UAAU,IAAI,UAAU,CAAC,CAAC,QAAA,OAClC,IAAI,cAAA,CAEW,IAAI;CACzB,IAAI,OAAO,aAAa,YACtB,OAAQ,SAAwC,KAAK,QAAQ;CAE/D,OAAO;AACT;AAEA,eAAe,kBACb,SACA,aACA,UACA,QACe;CACf,KAAK,MAAM,SAAS,SAAS;EAC3B,IAAI;EACJ,IAAI;GACF,SAAU,MAAM,YAAY,MAAM,IAAI;EACxC,SAAS,OAAO;GACd,OAAO,MACL,uDACA,OACA,KACF;GACA,MAAM;EACR;EACA,MAAM,CAAC,UAAU,SAAS,gBAAgB,MAAM;EAChD,IAAI,OAAO,WAAW,SAAS;GAC7B,OAAO,MACL,2DACA,OACA,OAAO,KACT;GACA,MAAM,OAAO;EACf;CACF;AACF;;;;;;;;;;AAWA,eAAsB,oBACpB,SAC8B;CAC9B,MAAM,EAAE,MAAM,UAAU,cAAc,WAAW;CACjD,MAAM,sBACJ,QAAQ,uBAAuB;CACjC,MAAM,cAAc,QAAQ,eAAe;CAE3C,MAAM,WAAW,IAAI,sBAAsB;CAC3C,MAAM,kBAAkB,KAAK,QAAQ,aAAa,UAAU,MAAM;CAElE,IAAI;CACJ,IAAI,KAAK,QACP,IAAI;EACF,SAAU,MAAM,YAAY,KAAK,MAAM;CACzC,SAAS,OAAO;EACd,OAAO,MACL,8CACA,KAAK,OAAO,QACZ,KACF;EACA,MAAM;CACR;CAGF,IAAI;CACJ,IAAI,KAAK,aACP,IAAI;EACF,cAAe,MAAM,YACnB,KAAK,WACP;CACF,SAAS,OAAO;EACd,OAAO,MACL,oDACA,KAAK,YAAY,QACjB,KACF;EACA,MAAM;CACR;CAGF,MAAM,WAAW,aAAa,WAAW,cAAc;CACvD,MAAM,iBAAiB,IAAI,qBACzB,QACF;CACA,MAAM,gBAAgB,IAAI,oBACxB,QACF;CAOA,MAAM,aAAa,IAAI,iBACrB,eACA,gBACA,UACA;EARA,cAAc;EACd,gBAAgB;EAChB,kBAAkB;CAMR,CACZ;CACA,MAAM,WAAW,QAAQ;CAEzB,MAAM,iBAAiB,IAAI,qBACzB,QACF;CAEA,MAAM,oBAAoB,IAAI,kBAAkB,gBAAgB,EAC9D,cAAc,IAChB,CAAC;CACD,MAAM,kBAAkB,QAAQ;CAEhC,MAAM,4BAA4B,IAAI,0BACpC,cACF;CAEA,MAAM,iBAAiB,IAAI,qBACzB,UACA,gBACA,gBACA,eACA,YACA,mBACA,yBACF;CAEA,MAAM,WAAW,IAAI,SAAS;CAC9B,IAAI,iBAAiD;CACrD,SAAS,UACP,kBAAkB,kBACjB,IAAY,UAA8B;EACzC,iBAAiB;GACf,YAAY,MAAM;GAClB,SAAS,MAAM;EACjB;CACF,CACF;CAEA,IAAI,oBAA6C,CAAC;CAClD,SAAS,UACP,kBAAkB,oBACjB,IAAY,UAAiC;EAC5C,kBAAkB,KAAK,KAAK;CAC9B,CACF;CAEA,MAAM,iBAAiB,QAAQ,kBAAkB,CAAC;CAiBlD,OAAO;EACL,UAAA,IAjBmB,kBACnB,QACA,UACA,gBACA,UACA,YACA,gBACA,mBACA,2BACA,qBACA,gBACA,gBACA,QACA,WAIO;EACP;EACA,qBAAqD;GACnD,MAAM,WAAW;GACjB,iBAAiB;GACjB,OAAO;EACT;EACA,wBAAiD;GAC/C,MAAM,WAAW;GACjB,oBAAoB,CAAC;GACrB,OAAO;EACT;CACF;AACF"}
1
+ {"version":3,"file":"build-worker-executor-Bvoba-AV.js","names":[],"sources":["../src/executor/worker/build-worker-executor.ts"],"sourcesContent":["import type {\n DocumentModelModule,\n ISigner,\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 type SignatureRefusedEvent,\n} from \"../../events/types.js\";\nimport { DocumentModelRegistry } from \"../../registry/implementation.js\";\nimport type { JobMeta } from \"../../shared/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 { SignatureTrustPolicy } from \"../../signer/types.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 /** Drains the refusals this worker's executor emitted since the last call. */\n takeSignatureRefusals(): SignatureRefusedEvent[];\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 signer: ISigner | undefined;\n if (init.signer) {\n try {\n signer = (await loadFactory(init.signer)) as ISigner;\n } catch (error) {\n logger.error(\n \"worker failed to load signer: @spec @error\",\n init.signer.module,\n error,\n );\n throw error;\n }\n }\n\n let trustPolicy: SignatureTrustPolicy | undefined;\n if (init.trustPolicy) {\n try {\n trustPolicy = (await loadFactory(\n init.trustPolicy,\n )) as SignatureTrustPolicy;\n } catch (error) {\n logger.error(\n \"worker failed to load trust policy: @spec @error\",\n init.trustPolicy.module,\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 let signatureRefusals: SignatureRefusedEvent[] = [];\n eventBus.subscribe(\n ReactorEventTypes.SIGNATURE_REFUSED,\n (_t: number, event: SignatureRefusedEvent) => {\n signatureRefusals.push(event);\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 executionScope,\n signer,\n trustPolicy,\n );\n\n return {\n executor,\n registry,\n takeLastWriteReady(): WorkerWriteReadyCapture | null {\n const captured = lastWriteReady;\n lastWriteReady = null;\n return captured;\n },\n takeSignatureRefusals(): SignatureRefusedEvent[] {\n const captured = signatureRefusals;\n signatureRefusals = [];\n return captured;\n },\n };\n}\n"],"mappings":";;AAyEA,eAAsB,mBAAmB,MAAqC;CAC5E,MAAM,MAAM,KAAK;CAMjB,MAAM,YAAW,OAJf,cAAc,MAAA,OACV,IAAI,IAAI,UAAU,IAAI,UAAU,CAAC,CAAC,QAAA,OAClC,IAAI,cAAA,CAEW,IAAI;CACzB,IAAI,OAAO,aAAa,YACtB,OAAQ,SAAwC,KAAK,QAAQ;CAE/D,OAAO;AACT;AAEA,eAAe,kBACb,SACA,aACA,UACA,QACe;CACf,KAAK,MAAM,SAAS,SAAS;EAC3B,IAAI;EACJ,IAAI;GACF,SAAU,MAAM,YAAY,MAAM,IAAI;EACxC,SAAS,OAAO;GACd,OAAO,MACL,uDACA,OACA,KACF;GACA,MAAM;EACR;EACA,MAAM,CAAC,UAAU,SAAS,gBAAgB,MAAM;EAChD,IAAI,OAAO,WAAW,SAAS;GAC7B,OAAO,MACL,2DACA,OACA,OAAO,KACT;GACA,MAAM,OAAO;EACf;CACF;AACF;;;;;;;;;;AAWA,eAAsB,oBACpB,SAC8B;CAC9B,MAAM,EAAE,MAAM,UAAU,cAAc,WAAW;CACjD,MAAM,sBACJ,QAAQ,uBAAuB;CACjC,MAAM,cAAc,QAAQ,eAAe;CAE3C,MAAM,WAAW,IAAI,sBAAsB;CAC3C,MAAM,kBAAkB,KAAK,QAAQ,aAAa,UAAU,MAAM;CAElE,IAAI;CACJ,IAAI,KAAK,QACP,IAAI;EACF,SAAU,MAAM,YAAY,KAAK,MAAM;CACzC,SAAS,OAAO;EACd,OAAO,MACL,8CACA,KAAK,OAAO,QACZ,KACF;EACA,MAAM;CACR;CAGF,IAAI;CACJ,IAAI,KAAK,aACP,IAAI;EACF,cAAe,MAAM,YACnB,KAAK,WACP;CACF,SAAS,OAAO;EACd,OAAO,MACL,oDACA,KAAK,YAAY,QACjB,KACF;EACA,MAAM;CACR;CAGF,MAAM,WAAW,aAAa,WAAW,cAAc;CACvD,MAAM,iBAAiB,IAAI,qBACzB,QACF;CACA,MAAM,gBAAgB,IAAI,oBACxB,QACF;CAOA,MAAM,aAAa,IAAI,iBACrB,eACA,gBACA,UACA;EARA,cAAc;EACd,gBAAgB;EAChB,kBAAkB;CAMR,CACZ;CACA,MAAM,WAAW,QAAQ;CAEzB,MAAM,iBAAiB,IAAI,qBACzB,QACF;CAEA,MAAM,oBAAoB,IAAI,kBAAkB,gBAAgB,EAC9D,cAAc,IAChB,CAAC;CACD,MAAM,kBAAkB,QAAQ;CAEhC,MAAM,4BAA4B,IAAI,0BACpC,cACF;CAEA,MAAM,iBAAiB,IAAI,qBACzB,UACA,gBACA,gBACA,eACA,YACA,mBACA,yBACF;CAEA,MAAM,WAAW,IAAI,SAAS;CAC9B,IAAI,iBAAiD;CACrD,SAAS,UACP,kBAAkB,kBACjB,IAAY,UAA8B;EACzC,iBAAiB;GACf,YAAY,MAAM;GAClB,SAAS,MAAM;EACjB;CACF,CACF;CAEA,IAAI,oBAA6C,CAAC;CAClD,SAAS,UACP,kBAAkB,oBACjB,IAAY,UAAiC;EAC5C,kBAAkB,KAAK,KAAK;CAC9B,CACF;CAEA,MAAM,iBAAiB,QAAQ,kBAAkB,CAAC;CAiBlD,OAAO;EACL,UAAA,IAjBmB,kBACnB,QACA,UACA,gBACA,UACA,YACA,gBACA,mBACA,2BACA,qBACA,gBACA,gBACA,QACA,WAIO;EACP;EACA,qBAAqD;GACnD,MAAM,WAAW;GACjB,iBAAiB;GACjB,OAAO;EACT;EACA,wBAAiD;GAC/C,MAAM,WAAW;GACjB,oBAAoB,CAAC;GACrB,OAAO;EACT;CACF;AACF"}
@@ -1,4 +1,4 @@
1
- import { ot as ReactorEventTypes, ut as yieldToMain } from "./forwarding-logger-CSsCJ38f.js";
1
+ import { ot as ReactorEventTypes, ut as yieldToMain } from "./forwarding-logger-CnuVDVze.js";
2
2
  import { createAuthState, isDenied } from "@powerhousedao/shared/document-model";
3
3
  import { v4 } from "uuid";
4
4
  import { childLogger } from "document-model";
@@ -659,17 +659,11 @@ var KyselyDocumentView = class extends BaseReadModel {
659
659
  const branch = view?.branch || "main";
660
660
  const startIndex = paging?.cursor ? parseInt(paging.cursor) : 0;
661
661
  const limit = paging?.limit || 100;
662
- const documents = [];
663
- const processedDocumentIds = /* @__PURE__ */ new Set();
664
- const allDocumentIds = [];
665
- const snapshots = await this._db.selectFrom("DocumentSnapshot").selectAll().where("documentType", "=", type).where("branch", "=", branch).where("isDeleted", "=", false).orderBy("lastUpdatedAt", "desc").execute();
662
+ const rows = await this._db.selectFrom("DocumentSnapshot").select("documentId").select((eb) => eb.fn.max("lastUpdatedAt").as("lastUpdatedAt")).where("documentType", "=", type).where("branch", "=", branch).where("isDeleted", "=", false).groupBy("documentId").orderBy("lastUpdatedAt", "desc").orderBy("documentId", "asc").offset(startIndex).limit(limit + 1).execute();
666
663
  if (signal?.aborted) throw new Error("Operation aborted");
667
- for (const snapshot of snapshots) {
668
- if (processedDocumentIds.has(snapshot.documentId)) continue;
669
- processedDocumentIds.add(snapshot.documentId);
670
- allDocumentIds.push(snapshot.documentId);
671
- }
672
- const docsToFetch = allDocumentIds.slice(startIndex, startIndex + limit);
664
+ const hasMore = rows.length > limit;
665
+ const docsToFetch = rows.slice(0, limit).map((row) => row.documentId);
666
+ const documents = [];
673
667
  for (const documentId of docsToFetch) {
674
668
  if (signal?.aborted) throw new Error("Operation aborted");
675
669
  try {
@@ -679,7 +673,6 @@ var KyselyDocumentView = class extends BaseReadModel {
679
673
  continue;
680
674
  }
681
675
  }
682
- const hasMore = allDocumentIds.length > startIndex + limit;
683
676
  const nextCursor = hasMore ? String(startIndex + limit) : void 0;
684
677
  return {
685
678
  results: documents,
@@ -688,7 +681,6 @@ var KyselyDocumentView = class extends BaseReadModel {
688
681
  limit: 100
689
682
  },
690
683
  nextCursor,
691
- totalCount: allDocumentIds.length,
692
684
  next: hasMore ? () => this.findByType(type, view, {
693
685
  cursor: nextCursor,
694
686
  limit
@@ -1113,4 +1105,4 @@ var KyselyDocumentIndexer = class extends BaseReadModel {
1113
1105
  //#endregion
1114
1106
  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 };
1115
1107
 
1116
- //# sourceMappingURL=document-indexer-zbdzqZ3K.js.map
1108
+ //# sourceMappingURL=document-indexer-BVTGbEB0.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document-indexer-BVTGbEB0.js","names":["uuidv4","uuidv4"],"sources":["../src/read-models/base-read-model.ts","../src/read-models/coordinator.ts","../src/read-models/names.ts","../src/read-models/document-view.ts","../src/shared/consistency-tracker.ts","../src/shared/collect-all-pages.ts","../src/storage/kysely/document-indexer.ts"],"sourcesContent":["import type { OperationWithContext } from \"@powerhousedao/shared/document-model\";\nimport type { Kysely, Transaction } from \"kysely\";\nimport type { IOperationIndex } from \"../cache/operation-index-types.js\";\nimport type { IWriteCache } from \"../cache/write/interfaces.js\";\nimport type { IConsistencyTracker } from \"../shared/consistency-tracker.js\";\nimport type {\n ConsistencyCoordinate,\n ConsistencyToken,\n} from \"../shared/types.js\";\nimport { yieldToMain } from \"../shared/utils.js\";\nimport type { IReadModel } from \"./interfaces.js\";\nimport type { DocumentViewDatabase } from \"./types.js\";\n\n/** Bounds on an indexing pass: one transaction, and the stall between yields. */\nexport type ReadModelIndexingConfig = {\n /** Maximum operations committed in a single transaction. */\n commitChunkSize: number;\n /** Maximum elapsed milliseconds before yielding between chunks. */\n yieldDeadlineMs: number;\n};\n\n/** Small enough that a chunk's transaction rarely outlasts the yield deadline. */\nexport const DEFAULT_COMMIT_CHUNK_SIZE = 50;\n\n/** Matches the executor's own default, so both paths yield on the same cadence. */\nexport const DEFAULT_READ_MODEL_YIELD_DEADLINE_MS = 50;\n\nexport const defaultReadModelIndexingConfig: ReadModelIndexingConfig = {\n commitChunkSize: DEFAULT_COMMIT_CHUNK_SIZE,\n yieldDeadlineMs: DEFAULT_READ_MODEL_YIELD_DEADLINE_MS,\n};\n\n/** For read models whose callers can observe where a batch was split. */\nexport const unchunkedReadModelIndexingConfig: ReadModelIndexingConfig = {\n commitChunkSize: Number.MAX_SAFE_INTEGER,\n yieldDeadlineMs: DEFAULT_READ_MODEL_YIELD_DEADLINE_MS,\n};\n\n/**\n * Keeps the chunk size at one operation or more: a chunk of zero or less never\n * advances the indexing loop, so the pass would spin without ever resolving.\n */\nfunction normalizeIndexingConfig(\n config: ReadModelIndexingConfig,\n): ReadModelIndexingConfig {\n if (Number.isNaN(config.commitChunkSize)) {\n return { ...config, commitChunkSize: DEFAULT_COMMIT_CHUNK_SIZE };\n }\n\n return {\n ...config,\n commitChunkSize: Math.max(\n 1,\n Math.min(Math.floor(config.commitChunkSize), Number.MAX_SAFE_INTEGER),\n ),\n };\n}\n\nexport type BaseReadModelConfig = {\n readModelId: string;\n rebuildStateOnInit: boolean;\n /** Defaults to {@link defaultReadModelIndexingConfig}. */\n indexing?: ReadModelIndexingConfig;\n};\n\n/**\n * Base class for read models that provides catch-up/rewind functionality.\n * Handles initialization, state tracking via ViewState table, and consistency tracking.\n * Subclasses override commitOperations() with their specific domain logic.\n */\nexport class BaseReadModel implements IReadModel {\n protected lastOrdinal: number = 0;\n\n readonly name: string;\n\n private readonly indexing: ReadModelIndexingConfig;\n\n /**\n * Lowest ordinal this model failed to commit and has not committed since, or\n * zero when there is none. The stored cursor is held below it so replay from\n * the cursor still reaches every operation the failed pass left out.\n */\n private uncommittedOrdinal: number = 0;\n\n constructor(\n protected db: Kysely<DocumentViewDatabase>,\n protected operationIndex: IOperationIndex,\n protected writeCache: IWriteCache,\n protected consistencyTracker: IConsistencyTracker,\n protected config: BaseReadModelConfig,\n ) {\n this.name = config.readModelId;\n this.indexing = normalizeIndexingConfig(\n config.indexing ?? defaultReadModelIndexingConfig,\n );\n }\n\n /**\n * Initializes the read model by loading state and catching up on missed operations.\n */\n async init(): Promise<void> {\n const viewState = await this.loadState();\n\n if (viewState !== undefined) {\n this.lastOrdinal = viewState;\n } else {\n await this.initializeState();\n }\n\n let page = await this.operationIndex.getSinceOrdinal(this.lastOrdinal);\n while (page.results.length > 0) {\n const ops = this.config.rebuildStateOnInit\n ? await this.rebuildStateForOperations(page.results)\n : page.results;\n await this.indexOperations(ops);\n\n if (!page.next) break;\n page = await page.next();\n }\n }\n\n /**\n * Commits the batch in chunks, yielding between them with no transaction\n * open. A chunk that throws leaves the earlier chunks committed, so the pass\n * saves the cursor for that prefix and parks it below the operation it could\n * not commit before rethrowing.\n */\n async indexOperations(items: OperationWithContext[]): Promise<void> {\n if (items.length === 0) return;\n\n const { commitChunkSize, yieldDeadlineMs } = this.indexing;\n let lastYield = performance.now();\n let committed = 0;\n\n for (let start = 0; start < items.length; start += commitChunkSize) {\n if (start > 0 && performance.now() - lastYield > yieldDeadlineMs) {\n await yieldToMain();\n lastYield = performance.now();\n }\n\n const chunk = items.slice(start, start + commitChunkSize);\n\n try {\n await this.commitOperations(chunk);\n } catch (error) {\n this.park(items, committed);\n await this.recordCommittedPrefix(items.slice(0, committed));\n throw error;\n }\n\n committed += chunk.length;\n }\n\n this.liftParkIfCommitted(items);\n await this.persistCursor(items);\n this.updateConsistencyTracker(items);\n }\n\n /**\n * Waits for the read model to reach the specified consistency level.\n */\n async waitForConsistency(\n token: ConsistencyToken,\n timeoutMs?: number,\n signal?: AbortSignal,\n ): Promise<void> {\n if (token.coordinates.length === 0) {\n return;\n }\n await this.consistencyTracker.waitFor(token.coordinates, timeoutMs, signal);\n }\n\n // Subclass does domain-specific work here (snapshots, relationships, processor routing, etc.).\n protected async commitOperations(\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n items: OperationWithContext[],\n ): Promise<void> {}\n\n /**\n * Rebuilds document state for each operation using the write cache.\n */\n protected async rebuildStateForOperations(\n operations: OperationWithContext[],\n ): Promise<OperationWithContext[]> {\n const result: OperationWithContext[] = [];\n\n for (const op of operations) {\n const { documentId, scope, branch } = op.context;\n const targetRevision = op.operation.index;\n\n const document = await this.writeCache.getState(\n documentId,\n scope,\n branch,\n targetRevision,\n );\n\n result.push({\n operation: op.operation,\n context: {\n ...op.context,\n resultingState: JSON.stringify(document),\n },\n });\n }\n\n return result;\n }\n\n /**\n * Loads the last processed ordinal from the ViewState table.\n * Returns undefined if no state exists for this read model.\n */\n protected async loadState(): Promise<number | undefined> {\n const viewStateDb = this.db as unknown as Kysely<DocumentViewDatabase>;\n const row = await viewStateDb\n .selectFrom(\"ViewState\")\n .select(\"lastOrdinal\")\n .where(\"readModelId\", \"=\", this.config.readModelId)\n .executeTakeFirst();\n\n return row?.lastOrdinal;\n }\n\n /**\n * Initializes the ViewState row for this read model.\n */\n protected async initializeState(): Promise<void> {\n const viewStateDb = this.db as unknown as Kysely<DocumentViewDatabase>;\n await viewStateDb\n .insertInto(\"ViewState\")\n .values({\n readModelId: this.config.readModelId,\n lastOrdinal: 0,\n })\n .execute();\n }\n\n /**\n * Saves the last processed ordinal to the ViewState table.\n */\n protected async saveState(\n trx: Transaction<DocumentViewDatabase>,\n items: OperationWithContext[],\n ): Promise<void> {\n let maxOrdinal = 0;\n for (const item of items) {\n maxOrdinal = Math.max(maxOrdinal, item.context.ordinal);\n }\n const next = Math.max(this.lastOrdinal, maxOrdinal);\n this.lastOrdinal = next;\n\n await trx\n .updateTable(\"ViewState\")\n .set({\n lastOrdinal: next,\n lastOperationTimestamp: new Date(),\n })\n .where(\"readModelId\", \"=\", this.config.readModelId)\n .execute();\n }\n\n /**\n * Updates the consistency tracker with the processed operations.\n */\n protected updateConsistencyTracker(items: OperationWithContext[]): void {\n const coordinates: ConsistencyCoordinate[] = [];\n\n for (let i = 0; i < items.length; i++) {\n const item = items[i]!;\n coordinates.push({\n documentId: item.context.documentId,\n scope: item.context.scope,\n branch: item.context.branch,\n operationIndex: item.operation.index,\n });\n }\n\n this.consistencyTracker.update(coordinates);\n }\n\n /**\n * Saves the cursor for the chunks that did commit before a later chunk threw.\n * A failure here is swallowed: the cursor simply stays where the pass found\n * it, which is equally safe, and the commit error is the one worth raising.\n */\n private async recordCommittedPrefix(\n prefix: OperationWithContext[],\n ): Promise<void> {\n if (prefix.length === 0) return;\n\n try {\n await this.persistCursor(prefix);\n } catch {\n return;\n }\n\n this.updateConsistencyTracker(prefix);\n }\n\n /** Writes the cursor for the given items, never past a parked ordinal. */\n private async persistCursor(items: OperationWithContext[]): Promise<void> {\n await this.db.transaction().execute(async (trx) => {\n await this.saveState(trx, items);\n await this.clampCursorToPark(trx);\n });\n }\n\n /**\n * Holds the cursor written by {@link saveState}, which subclasses may\n * override, below the lowest operation this model failed to commit.\n */\n private async clampCursorToPark(\n trx: Transaction<DocumentViewDatabase>,\n ): Promise<void> {\n if (this.uncommittedOrdinal === 0) return;\n\n const ceiling = this.uncommittedOrdinal - 1;\n if (this.lastOrdinal <= ceiling) return;\n\n this.lastOrdinal = ceiling;\n await trx\n .updateTable(\"ViewState\")\n .set({\n lastOrdinal: ceiling,\n lastOperationTimestamp: new Date(),\n })\n .where(\"readModelId\", \"=\", this.config.readModelId)\n .execute();\n }\n\n /** Remembers the lowest ordinal the failed pass left uncommitted. */\n private park(items: OperationWithContext[], committed: number): void {\n let lowest = 0;\n for (let i = committed; i < items.length; i++) {\n const ordinal = items[i]!.context.ordinal;\n if (lowest === 0 || ordinal < lowest) lowest = ordinal;\n }\n\n if (lowest === 0) return;\n if (this.uncommittedOrdinal === 0 || lowest < this.uncommittedOrdinal) {\n this.uncommittedOrdinal = lowest;\n }\n }\n\n /** The park lifts once a later pass commits the operation that failed. */\n private liftParkIfCommitted(items: OperationWithContext[]): void {\n if (this.uncommittedOrdinal === 0) return;\n\n for (const item of items) {\n if (item.context.ordinal === this.uncommittedOrdinal) {\n this.uncommittedOrdinal = 0;\n return;\n }\n }\n }\n}\n","import { childLogger, type ILogger } from \"document-model\";\nimport type { IEventBus } from \"../events/interfaces.js\";\nimport {\n ReactorEventTypes,\n type JobReadReadyEvent,\n type JobWriteReadyEvent,\n type ReadModelBatchCompletedEvent,\n type ReadModelIndexedEvent,\n type ReadModelIndexingStage,\n type Unsubscribe,\n} from \"../events/types.js\";\nimport type {\n ILiveReadModelCoordinator,\n IReadModel,\n ReadModelRegistrationStage,\n} from \"./interfaces.js\";\n\n/**\n * Coordinates read model synchronization by listening to operation write events\n * and updating all registered read models on per-`documentId:scope:branch`\n * serial chains. Cross-key projection runs in parallel; same-key projection is\n * serialized so the executor can return to dispatch without holding ordering\n * implicitly.\n */\nexport class ReadModelCoordinator implements ILiveReadModelCoordinator {\n private unsubscribe?: Unsubscribe;\n private isRunning = false;\n private readonly chains = new Map<string, Promise<void>>();\n private readonly logger: ILogger;\n\n readonly readModels: IReadModel[];\n\n constructor(\n private eventBus: IEventBus,\n public readonly preReady: IReadModel[],\n public readonly postReady: IReadModel[],\n ) {\n this.readModels = [...preReady, ...postReady];\n this.logger = childLogger([\"reactor\", \"read-model-coordinator\"]);\n }\n\n start(): void {\n if (this.isRunning) {\n return;\n }\n\n this.unsubscribe = this.eventBus.subscribe(\n ReactorEventTypes.JOB_WRITE_READY,\n (type, event: JobWriteReadyEvent) => {\n return this.handleWriteReady(event);\n },\n );\n\n this.isRunning = true;\n }\n\n stop(): void {\n if (!this.isRunning) {\n return;\n }\n\n if (this.unsubscribe) {\n this.unsubscribe();\n this.unsubscribe = undefined;\n }\n\n this.isRunning = false;\n }\n\n /**\n * Resolves when every per-queueKey projection chain has flushed. Intended\n * for test fixtures and explicit shutdown; production callers use\n * consistency tokens instead.\n */\n async drain(): Promise<void> {\n while (this.chains.size > 0) {\n const pending = Array.from(this.chains.values());\n await Promise.allSettled(pending);\n }\n }\n\n getChainDepth(): number {\n return this.chains.size;\n }\n\n addReadModel(readModel: IReadModel, stage: ReadModelRegistrationStage): void {\n if (this.readModels.some(({ name }) => name === readModel.name)) {\n throw new Error(`Read model \"${readModel.name}\" is already registered`);\n }\n\n if (stage === \"pre_ready\") {\n this.preReady.push(readModel);\n } else {\n this.postReady.push(readModel);\n }\n this.readModels.push(readModel);\n }\n\n private handleWriteReady(event: JobWriteReadyEvent): void {\n if (event.operations.length === 0) {\n void this.emitEmptyReadReady(event);\n return;\n }\n\n const enqueuedAt = performance.now();\n const key = this.queueKeyFor(event);\n const previous = this.chains.get(key) ?? Promise.resolve();\n const current = previous.then(() => this.runChain(event, enqueuedAt));\n\n this.chains.set(key, current);\n void current.finally(() => {\n if (this.chains.get(key) === current) {\n this.chains.delete(key);\n }\n });\n }\n\n private async emitEmptyReadReady(event: JobWriteReadyEvent): Promise<void> {\n const readyEvent: JobReadReadyEvent = {\n jobId: event.jobId,\n operations: event.operations,\n };\n try {\n await this.eventBus.emit(ReactorEventTypes.JOB_READ_READY, readyEvent);\n } catch (error) {\n this.logger.error(\n \"JOB_READ_READY emit failed for job @jobId: @Error\",\n { jobId: event.jobId },\n error,\n );\n }\n this.emitBatchCompleted({\n jobId: event.jobId,\n batchSize: 0,\n chainWaitDurationMs: 0,\n preReadyDurationMs: 0,\n emitDurationMs: 0,\n postReadyDurationMs: 0,\n });\n }\n\n private async runChain(\n event: JobWriteReadyEvent,\n enqueuedAt: number,\n ): Promise<void> {\n const chainStartedAt = performance.now();\n const chainWaitDurationMs = chainStartedAt - enqueuedAt;\n\n const preReadyStart = performance.now();\n try {\n await Promise.all(\n this.preReady.map((readModel) =>\n this.indexWithTiming(readModel, \"pre_ready\", event),\n ),\n );\n } catch (error) {\n this.logger.error(\n \"Pre-ready read model indexing failed for job @jobId: @Error\",\n { jobId: event.jobId },\n error,\n );\n }\n const preReadyDurationMs = performance.now() - preReadyStart;\n\n const readyEvent: JobReadReadyEvent = {\n jobId: event.jobId,\n operations: event.operations,\n };\n const emitStart = performance.now();\n try {\n await this.eventBus.emit(ReactorEventTypes.JOB_READ_READY, readyEvent);\n } catch (error) {\n this.logger.error(\n \"JOB_READ_READY emit failed for job @jobId: @Error\",\n { jobId: event.jobId },\n error,\n );\n }\n const emitDurationMs = performance.now() - emitStart;\n\n const postReadyStart = performance.now();\n try {\n await Promise.all(\n this.postReady.map((readModel) =>\n this.indexWithTiming(readModel, \"post_ready\", event),\n ),\n );\n } catch (error) {\n this.logger.error(\n \"Post-ready read model indexing failed for job @jobId: @Error\",\n { jobId: event.jobId },\n error,\n );\n }\n const postReadyDurationMs = performance.now() - postReadyStart;\n\n this.emitBatchCompleted({\n jobId: event.jobId,\n batchSize: event.operations.length,\n chainWaitDurationMs,\n preReadyDurationMs,\n emitDurationMs,\n postReadyDurationMs,\n });\n }\n\n private async indexWithTiming(\n readModel: IReadModel,\n stage: ReadModelIndexingStage,\n event: JobWriteReadyEvent,\n ): Promise<void> {\n const start = performance.now();\n let success = false;\n try {\n await readModel.indexOperations(event.operations);\n success = true;\n } finally {\n this.emitReadModelIndexed({\n jobId: event.jobId,\n readModelName: readModel.name,\n stage,\n durationMs: performance.now() - start,\n operationCount: event.operations.length,\n success,\n });\n }\n }\n\n private emitReadModelIndexed(payload: ReadModelIndexedEvent): void {\n void this.eventBus\n .emit(ReactorEventTypes.READMODEL_INDEXED, payload)\n .catch((err: unknown) =>\n this.logger.error(\n \"READMODEL_INDEXED emit failed for job @jobId: @Error\",\n { jobId: payload.jobId },\n err,\n ),\n );\n }\n\n private emitBatchCompleted(payload: ReadModelBatchCompletedEvent): void {\n void this.eventBus\n .emit(ReactorEventTypes.READMODEL_BATCH_COMPLETED, payload)\n .catch((err: unknown) =>\n this.logger.error(\n \"READMODEL_BATCH_COMPLETED emit failed for job @jobId: @Error\",\n { jobId: payload.jobId },\n err,\n ),\n );\n }\n\n private queueKeyFor(event: JobWriteReadyEvent): string {\n const ctx = event.operations[0]!.context;\n return `${ctx.documentId}:${ctx.scope}:${ctx.branch}`;\n }\n}\n","import type { IDocumentIndexer, IDocumentView } from \"../storage/interfaces.js\";\n\n/** `getReadModel` name of the document view; see {@link ReactorReadModels}. */\nexport const DOCUMENT_VIEW_READ_MODEL = \"document-view\";\n/** `getReadModel` name of the document indexer; see {@link ReactorReadModels}. */\nexport const DOCUMENT_INDEXER_READ_MODEL = \"document-indexer\";\n\n/**\n * Read models the reactor registers on every host, keyed by the name passed to\n * `IProcessorHostModule.getReadModel`. Each entry says what the model holds\n * and when a processor should reach for it instead of the reactor client.\n */\nexport interface ReactorReadModels {\n /**\n * Materialized document snapshots — header plus per-scope state — kept\n * current as operations are indexed, so reads never replay operations.\n *\n * Reach for it to read a document's current state from inside a\n * processor: `get` / `getMany` by id, `getByIdOrSlug`, `findByType`,\n * `exists`, and `resolveSlug` / `resolveIdOrSlug`. Every query takes an\n * optional `ConsistencyToken` (from a job result) for read-after-write.\n * Unlike `client.get`, results are the raw snapshot — scopes are not\n * filtered to what the caller may read.\n */\n [DOCUMENT_VIEW_READ_MODEL]: IDocumentView;\n\n /**\n * Directed graph of document relationships, built from ADD_RELATIONSHIP /\n * REMOVE_RELATIONSHIP operations. Edges carry a `relationshipType`;\n * parent/child links (drive → document, folder → file) use `\"child\"`.\n *\n * Reach for it to navigate structure rather than content: `getOutgoing` /\n * `getIncoming` neighbours, `hasRelationship`, `findPath`, and\n * `findAncestors` (e.g. which drive(s) contain a document). Snapshots of\n * the documents themselves come from the document view.\n */\n [DOCUMENT_INDEXER_READ_MODEL]: IDocumentIndexer;\n}\n","import type {\n OperationWithContext,\n PHAuthState,\n PHDocument,\n PHDocumentHeader,\n} from \"@powerhousedao/shared/document-model\";\nimport {\n createAuthState,\n isDenied,\n} from \"@powerhousedao/shared/document-model\";\nimport type { Kysely } from \"kysely\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport type { IOperationIndex } from \"../cache/operation-index-types.js\";\nimport type { IWriteCache } from \"../cache/write/interfaces.js\";\nimport type { IConsistencyTracker } from \"../shared/consistency-tracker.js\";\nimport { DOCUMENT_VIEW_READ_MODEL } from \"./names.js\";\nimport type {\n ConsistencyToken,\n PagedResults,\n PagingOptions,\n} from \"../shared/types.js\";\nimport {\n DocumentExistence,\n type DocumentRevisions,\n type IDocumentView,\n type IOperationStore,\n type ViewFilter,\n} from \"../storage/interfaces.js\";\nimport type { Database as StorageDatabase } from \"../storage/kysely/types.js\";\nimport {\n BaseReadModel,\n defaultReadModelIndexingConfig,\n type ReadModelIndexingConfig,\n} from \"./base-read-model.js\";\nimport type {\n DocumentViewDatabase,\n InsertableDocumentSnapshot,\n} from \"./types.js\";\n\ntype Database = StorageDatabase & DocumentViewDatabase;\n\n/**\n * What a single-document read of a deleted document returns. A listing omits a\n * deleted document under either value.\n */\nexport enum DeletedDocumentRead {\n /**\n * The document reads as missing: `get` throws and `resolveIdOrSlug` does not\n * match its id.\n */\n NotFound = \"NotFound\",\n /**\n * The document's state as of the deletion, with `state.document.isDeleted`\n * telling the caller what it holds. Only meaningful with `documentDecisions`,\n * which is what makes deletion positional.\n */\n StateAtDeletion = \"StateAtDeletion\",\n}\n\nexport class KyselyDocumentView extends BaseReadModel implements IDocumentView {\n private _db: Kysely<Database>;\n\n constructor(\n db: Kysely<Database>,\n private operationStore: IOperationStore,\n operationIndex: IOperationIndex,\n writeCache: IWriteCache,\n consistencyTracker: IConsistencyTracker,\n private readonly deletedDocumentRead: DeletedDocumentRead,\n indexing: ReadModelIndexingConfig = defaultReadModelIndexingConfig,\n ) {\n super(\n db as unknown as Kysely<DocumentViewDatabase>,\n operationIndex,\n writeCache,\n consistencyTracker,\n {\n readModelId: DOCUMENT_VIEW_READ_MODEL,\n rebuildStateOnInit: true,\n indexing,\n },\n );\n this._db = db;\n }\n\n /**\n * Indexes committed operations into DocumentSnapshot rows. CREATE_DOCUMENT\n * only seeds header/document/auth. UPGRADE_DOCUMENT reindexes every scope\n * present in resultingState when the operation vouches for them — a seed\n * carrying initialState or a migration stamped with the __migrated marker\n * — since the upgrade reducer may have reshaped any of them; upgrades\n * without either fall back to header/document/auth, because their sibling\n * echoes may be stale. All other action types index only header and their\n * own scope.\n *\n * The header row is the one row every scope's chain writes, so it accepts a\n * write only from an operation whose global ordinal is at least the one the\n * row already carries. Without that, a chunked pass that started earlier\n * reverts a concurrent rename with the stale echo its later chunks carry.\n */\n protected override async commitOperations(\n items: OperationWithContext[],\n ): Promise<void> {\n await this._db.transaction().execute(async (trx) => {\n for (const item of items) {\n const { operation, context } = item;\n const {\n documentId,\n scope,\n branch,\n documentType,\n resultingState,\n ordinal,\n } = context;\n const { index, hash } = operation;\n\n if (!resultingState) {\n throw new Error(\n `Missing resultingState in context for operation ${operation.id || \"unknown\"}. ` +\n `IDocumentView requires resultingState from upstream - it does not rebuild documents.`,\n );\n }\n\n let fullState: Record<string, unknown>;\n try {\n fullState = JSON.parse(resultingState) as Record<string, unknown>;\n } catch (error) {\n throw new Error(\n `Failed to parse resultingState for operation ${operation.id || \"unknown\"}: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n }\n\n const operationType = operation.action.type;\n\n // A refused operation holds its index and changes nothing, so it must\n // not carry its action type's side effects. A refused DELETE_DOCUMENT\n // is the case that matters: taking the branch below would hide the\n // document from every listing and destroy its slug mapping, with\n // nothing in any path to put either back.\n const denied = isDenied(operation);\n\n // Non-header ops carry a denormalized header echo with possibly stale\n // `meta`; only header-scope ops and CREATE/UPGRADE/DELETE may write it.\n const preserveHeaderMeta =\n denied ||\n (operationType !== \"CREATE_DOCUMENT\" &&\n operationType !== \"UPGRADE_DOCUMENT\" &&\n operationType !== \"DELETE_DOCUMENT\" &&\n scope !== \"header\");\n\n if (operationType === \"DELETE_DOCUMENT\" && !denied) {\n const now = new Date();\n await trx\n .updateTable(\"DocumentSnapshot\")\n .set({\n isDeleted: true,\n deletedAt: now,\n lastOperationIndex: index,\n lastOperationHash: hash,\n lastOperationOrdinal: ordinal,\n lastUpdatedAt: now,\n })\n .where(\"documentId\", \"=\", documentId)\n .where(\"branch\", \"=\", branch)\n .execute();\n\n // The content has to say so too, or a caller served the boundary state\n // would get state claiming the document is live.\n const deletedDocumentScope = (\n fullState as Record<string, unknown> | undefined\n )?.document;\n if (deletedDocumentScope !== undefined) {\n await trx\n .updateTable(\"DocumentSnapshot\")\n .set({ content: deletedDocumentScope })\n .where(\"documentId\", \"=\", documentId)\n .where(\"branch\", \"=\", branch)\n .where(\"scope\", \"=\", \"document\")\n .execute();\n }\n\n await trx\n .deleteFrom(\"SlugMapping\")\n .where(\"documentId\", \"=\", documentId)\n .where(\"branch\", \"=\", branch)\n .execute();\n\n continue;\n }\n\n let scopesToIndex: Array<[string, unknown]>;\n\n if (operationType === \"CREATE_DOCUMENT\") {\n scopesToIndex = Object.entries(fullState).filter(\n ([key]) => key === \"header\" || key === \"document\" || key === \"auth\",\n );\n } else if (operationType === \"UPGRADE_DOCUMENT\") {\n // Sibling-scope echoes are only trustworthy when the operation\n // itself defines them: a seed carrying initialState, or a migration\n // whose executor vouched (via the __migrated marker) that it\n // fetched every scope fresh. Upgrades persisted by executors that\n // never fetched sibling scopes echo whatever stale state the write\n // cache held, and indexing those would clobber good snapshots.\n const upgradeInput = operation.action.input as {\n initialState?: unknown;\n };\n const scopesVouchedFor =\n fullState.__migrated === true ||\n upgradeInput.initialState !== undefined;\n delete fullState.__migrated;\n\n scopesToIndex = scopesVouchedFor\n ? Object.entries(fullState)\n : Object.entries(fullState).filter(\n ([key]) =>\n key === \"header\" || key === \"document\" || key === \"auth\",\n );\n } else {\n scopesToIndex = [];\n\n if (fullState.header !== undefined) {\n scopesToIndex.push([\"header\", fullState.header]);\n }\n\n if (fullState[scope] !== undefined) {\n scopesToIndex.push([scope, fullState[scope]]);\n } else {\n scopesToIndex.push([scope, {}]);\n }\n }\n\n for (const [scopeName, scopeState] of scopesToIndex) {\n // The previous `content` is the whole prior scope state - hundreds of\n // kilobytes of jsonb on a large drive - and only the header-meta\n // carry-over below reads it. Selecting it unconditionally made every\n // write pay to decompress and ship a copy of the state it was about\n // to overwrite.\n const needsExistingContent =\n scopeName === \"header\" && preserveHeaderMeta;\n\n const existingSnapshot = await trx\n .selectFrom(\"DocumentSnapshot\")\n .select([\n \"slug\",\n \"name\",\n \"isDeleted\",\n \"snapshotVersion\",\n \"lastOperationOrdinal\",\n ])\n .$if(needsExistingContent, (qb) => qb.select(\"content\"))\n .where(\"documentId\", \"=\", documentId)\n .where(\"scope\", \"=\", scopeName)\n .where(\"branch\", \"=\", branch)\n .executeTakeFirst();\n\n // Every scope's chain writes the header row, and since the chains\n // interleave at chunk boundaries an older one can arrive last. Its\n // header echo is stale, so it must not claim the row.\n if (\n scopeName === \"header\" &&\n existingSnapshot !== undefined &&\n existingSnapshot.lastOperationOrdinal > ordinal\n ) {\n continue;\n }\n\n const newState =\n typeof scopeState === \"object\" && scopeState !== null\n ? (scopeState as Record<string, unknown>)\n : {};\n\n let slug: string | null = existingSnapshot?.slug ?? null;\n let name: string | null = existingSnapshot?.name ?? null;\n\n if (scopeName === \"header\") {\n const headerSlug = newState.slug;\n const headerName = newState.name;\n\n if (typeof headerSlug === \"string\") {\n slug = headerSlug;\n }\n if (typeof headerName === \"string\") {\n name = headerName;\n }\n\n if (needsExistingContent && existingSnapshot) {\n const existingHeader = existingSnapshot.content as Record<\n string,\n unknown\n >;\n if (existingHeader && \"meta\" in existingHeader) {\n newState.meta = existingHeader.meta;\n }\n }\n\n // Never for a deleted document. Deleting removes its SlugMapping\n // rows, and resolveIdOrSlug relies on that to keep the slug branch\n // from reaching one: re-inserting here would make the document\n // resolvable by slug again, and the upsert would take the slug from\n // a live document that has since claimed it.\n if (slug && slug !== documentId && !existingSnapshot?.isDeleted) {\n await trx\n .insertInto(\"SlugMapping\")\n .values({\n slug,\n documentId,\n scope: scopeName,\n branch,\n })\n .onConflict((oc) =>\n oc.column(\"slug\").doUpdateSet({\n documentId,\n scope: scopeName,\n branch,\n }),\n )\n .execute();\n }\n }\n\n if (existingSnapshot) {\n await trx\n .updateTable(\"DocumentSnapshot\")\n .set({\n lastOperationIndex: index,\n lastOperationHash: hash,\n lastOperationOrdinal: ordinal,\n lastUpdatedAt: new Date(),\n snapshotVersion: existingSnapshot.snapshotVersion + 1,\n content: newState,\n slug,\n name,\n })\n .where(\"documentId\", \"=\", documentId)\n .where(\"scope\", \"=\", scopeName)\n .where(\"branch\", \"=\", branch)\n // Repeats the guard where the database can enforce it.\n .$if(scopeName === \"header\", (qb) =>\n qb.where(\"lastOperationOrdinal\", \"<=\", ordinal),\n )\n .execute();\n } else {\n const snapshot: InsertableDocumentSnapshot = {\n id: uuidv4(),\n documentId,\n slug,\n name,\n scope: scopeName,\n branch,\n content: newState,\n documentType,\n lastOperationIndex: index,\n lastOperationHash: hash,\n lastOperationOrdinal: ordinal,\n identifiers: null,\n metadata: null,\n deletedAt: null,\n };\n\n await trx.insertInto(\"DocumentSnapshot\").values(snapshot).execute();\n }\n }\n }\n });\n }\n\n async exists(\n documentIds: string[],\n existence: DocumentExistence,\n consistencyToken?: ConsistencyToken,\n signal?: AbortSignal,\n ): Promise<boolean[]> {\n if (consistencyToken) {\n await this.waitForConsistency(consistencyToken, undefined, signal);\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n if (documentIds.length === 0) {\n return [];\n }\n\n const existingIds =\n existence === DocumentExistence.IncludingDeleted\n ? await this.idsWithStreams(documentIds, signal)\n : await this.liveIds(documentIds);\n\n return documentIds.map((id) => existingIds.has(id));\n }\n\n async get<TDocument extends PHDocument>(\n documentId: string,\n view?: ViewFilter,\n consistencyToken?: ConsistencyToken,\n signal?: AbortSignal,\n ): Promise<TDocument> {\n if (consistencyToken) {\n await this.waitForConsistency(consistencyToken, undefined, signal);\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const branch = view?.branch || \"main\";\n\n let scopesToQuery: string[];\n if (view?.scopes && view.scopes.length > 0) {\n scopesToQuery = [...new Set([\"header\", \"document\", ...view.scopes])];\n } else {\n scopesToQuery = [];\n }\n\n let query = this._db\n .selectFrom(\"DocumentSnapshot\")\n .selectAll()\n .where(\"documentId\", \"=\", documentId)\n .where(\"branch\", \"=\", branch);\n\n if (this.deletedDocumentRead === DeletedDocumentRead.NotFound) {\n query = query.where(\"isDeleted\", \"=\", false);\n }\n\n if (scopesToQuery.length > 0) {\n query = query.where(\"scope\", \"in\", scopesToQuery);\n }\n\n const snapshots = await query.execute();\n\n if (snapshots.length === 0) {\n throw new Error(`Document not found: ${documentId}`);\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const headerSnapshot = snapshots.find((s) => s.scope === \"header\");\n if (!headerSnapshot) {\n throw new Error(`Document header not found: ${documentId}`);\n }\n\n const header = headerSnapshot.content as PHDocumentHeader;\n\n const revisions = await this.operationStore.getRevisions(\n documentId,\n branch,\n signal,\n );\n header.revision = revisions.revision;\n header.lastModifiedAtUtcIso = revisions.latestTimestamp;\n\n const state: Record<string, unknown> = {};\n for (const snapshot of snapshots) {\n if (snapshot.scope === \"header\") {\n continue;\n }\n\n // snapshot rows written before PHAuthState had a version carry auth: {}\n state[snapshot.scope] =\n snapshot.scope === \"auth\"\n ? createAuthState(snapshot.content as Partial<PHAuthState>)\n : snapshot.content;\n }\n\n const document: PHDocument = {\n header,\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n state: state as any,\n operations: {},\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n initialState: state as any,\n clipboard: [],\n };\n\n return document as TDocument;\n }\n\n async getMany<TDocument extends PHDocument>(\n documentIds: string[],\n view?: ViewFilter,\n consistencyToken?: ConsistencyToken,\n signal?: AbortSignal,\n ): Promise<TDocument[]> {\n if (documentIds.length === 0) {\n return [];\n }\n\n if (consistencyToken) {\n await this.waitForConsistency(consistencyToken, undefined, signal);\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const branch = view?.branch || \"main\";\n\n let scopesToQuery: string[];\n if (view?.scopes && view.scopes.length > 0) {\n scopesToQuery = [...new Set([\"header\", \"document\", ...view.scopes])];\n } else {\n scopesToQuery = [];\n }\n\n let query = this._db\n .selectFrom(\"DocumentSnapshot\")\n .selectAll()\n .where(\"documentId\", \"in\", documentIds)\n .where(\"branch\", \"=\", branch)\n .where(\"isDeleted\", \"=\", false);\n\n if (scopesToQuery.length > 0) {\n query = query.where(\"scope\", \"in\", scopesToQuery);\n }\n\n const allSnapshots = await query.execute();\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const snapshotsByDocId = new Map<string, typeof allSnapshots>();\n for (const snapshot of allSnapshots) {\n const existing = snapshotsByDocId.get(snapshot.documentId) || [];\n existing.push(snapshot);\n snapshotsByDocId.set(snapshot.documentId, existing);\n }\n\n const foundDocumentIds = [...snapshotsByDocId.keys()];\n const revisionResults = await Promise.all(\n foundDocumentIds.map((docId) =>\n this.operationStore.getRevisions(docId, branch, signal),\n ),\n );\n\n const revisionsByDocId = new Map<string, DocumentRevisions>();\n for (let i = 0; i < foundDocumentIds.length; i++) {\n revisionsByDocId.set(foundDocumentIds[i], revisionResults[i]);\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const documents: TDocument[] = [];\n for (const documentId of documentIds) {\n const snapshots = snapshotsByDocId.get(documentId);\n if (!snapshots || snapshots.length === 0) {\n continue;\n }\n\n const headerSnapshot = snapshots.find((s) => s.scope === \"header\");\n if (!headerSnapshot) {\n continue;\n }\n\n const header = headerSnapshot.content as PHDocumentHeader;\n const revisions = revisionsByDocId.get(documentId);\n if (revisions) {\n header.revision = revisions.revision;\n header.lastModifiedAtUtcIso = revisions.latestTimestamp;\n }\n\n const state: Record<string, unknown> = {};\n for (const snapshot of snapshots) {\n if (snapshot.scope === \"header\") {\n continue;\n }\n // snapshot rows written before PHAuthState had a version carry auth: {}\n state[snapshot.scope] =\n snapshot.scope === \"auth\"\n ? createAuthState(snapshot.content as Partial<PHAuthState>)\n : snapshot.content;\n }\n\n const document: PHDocument = {\n header,\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n state: state as any,\n operations: {},\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n initialState: state as any,\n clipboard: [],\n };\n\n documents.push(document as TDocument);\n }\n\n return documents;\n }\n\n async getByIdOrSlug<TDocument extends PHDocument>(\n identifier: string,\n view?: ViewFilter,\n consistencyToken?: ConsistencyToken,\n signal?: AbortSignal,\n ): Promise<TDocument> {\n const documentId = await this.resolveIdOrSlug(\n identifier,\n view,\n consistencyToken,\n signal,\n );\n return this.get<TDocument>(documentId, view, undefined, signal);\n }\n\n async findByType(\n type: string,\n view?: ViewFilter,\n paging?: PagingOptions,\n consistencyToken?: ConsistencyToken,\n signal?: AbortSignal,\n ): Promise<PagedResults<PHDocument>> {\n if (consistencyToken) {\n await this.waitForConsistency(consistencyToken, undefined, signal);\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const branch = view?.branch || \"main\";\n\n const startIndex = paging?.cursor ? parseInt(paging.cursor) : 0;\n const limit = paging?.limit || 100;\n\n const rows = await this._db\n .selectFrom(\"DocumentSnapshot\")\n .select(\"documentId\")\n .select((eb) => eb.fn.max(\"lastUpdatedAt\").as(\"lastUpdatedAt\"))\n .where(\"documentType\", \"=\", type)\n .where(\"branch\", \"=\", branch)\n .where(\"isDeleted\", \"=\", false)\n .groupBy(\"documentId\")\n .orderBy(\"lastUpdatedAt\", \"desc\")\n .orderBy(\"documentId\", \"asc\")\n .offset(startIndex)\n .limit(limit + 1)\n .execute();\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const hasMore = rows.length > limit;\n const docsToFetch = rows.slice(0, limit).map((row) => row.documentId);\n\n const documents: PHDocument[] = [];\n for (const documentId of docsToFetch) {\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n try {\n const document = await this.get<PHDocument>(\n documentId,\n view,\n undefined,\n signal,\n );\n documents.push(document);\n } catch {\n continue;\n }\n }\n\n const nextCursor = hasMore ? String(startIndex + limit) : undefined;\n\n return {\n results: documents,\n options: paging || { cursor: \"0\", limit: 100 },\n nextCursor,\n next: hasMore\n ? () =>\n this.findByType(\n type,\n view,\n { cursor: nextCursor!, limit },\n consistencyToken,\n signal,\n )\n : undefined,\n };\n }\n\n async resolveSlug(\n slug: string,\n // TODO: this should only be branch\n view?: ViewFilter,\n consistencyToken?: ConsistencyToken,\n signal?: AbortSignal,\n ): Promise<string | undefined> {\n if (consistencyToken) {\n await this.waitForConsistency(consistencyToken, undefined, signal);\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const branch = view?.branch || \"main\";\n\n const mapping = await this._db\n .selectFrom(\"SlugMapping\")\n .select(\"documentId\")\n .where(\"slug\", \"=\", slug)\n .where(\"branch\", \"=\", branch)\n .executeTakeFirst();\n\n if (!mapping) {\n return undefined;\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n if (view?.scopes && view.scopes.length > 0) {\n const scopeCheck = await this._db\n .selectFrom(\"DocumentSnapshot\")\n .select(\"scope\")\n .where(\"documentId\", \"=\", mapping.documentId)\n .where(\"branch\", \"=\", branch)\n .where(\"scope\", \"in\", view.scopes)\n .where(\"isDeleted\", \"=\", false)\n .executeTakeFirst();\n\n if (!scopeCheck) {\n return undefined;\n }\n }\n\n return mapping.documentId;\n }\n\n // TODO: fix\n async resolveSlugs(\n slugs: string[],\n // TODO: this should only be branch\n view?: ViewFilter,\n consistencyToken?: ConsistencyToken,\n signal?: AbortSignal,\n ): Promise<string[]> {\n const ids = await Promise.all(\n slugs.map((slug) =>\n this.resolveSlug(slug, view, consistencyToken, signal),\n ),\n );\n return ids.filter((id) => id !== undefined) as string[];\n }\n\n async resolveIdOrSlug(\n identifier: string,\n // TODO: this should only be branch\n view?: ViewFilter,\n consistencyToken?: ConsistencyToken,\n signal?: AbortSignal,\n ): Promise<string> {\n if (consistencyToken) {\n await this.waitForConsistency(consistencyToken, undefined, signal);\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const branch = view?.branch || \"main\";\n\n // The id branch relaxes with `get`, or resolution throws before it. The slug\n // branch never does: deleting a document removes its SlugMapping rows.\n let idCheckQuery = this._db\n .selectFrom(\"DocumentSnapshot\")\n .select(\"documentId\")\n .where(\"documentId\", \"=\", identifier)\n .where(\"branch\", \"=\", branch);\n\n if (this.deletedDocumentRead === DeletedDocumentRead.NotFound) {\n idCheckQuery = idCheckQuery.where(\"isDeleted\", \"=\", false);\n }\n\n const idCheckPromise = idCheckQuery.executeTakeFirst();\n\n const slugCheckPromise = this._db\n .selectFrom(\"SlugMapping\")\n .select(\"documentId\")\n .where(\"slug\", \"=\", identifier)\n .where(\"branch\", \"=\", branch)\n .executeTakeFirst();\n\n const [idMatch, slugMatch] = await Promise.all([\n idCheckPromise,\n slugCheckPromise,\n ]);\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const idMatchDocId = idMatch?.documentId;\n const slugMatchDocId = slugMatch?.documentId;\n\n if (idMatchDocId && slugMatchDocId && idMatchDocId !== slugMatchDocId) {\n throw new Error(\n `Ambiguous identifier \"${identifier}\": matches both document ID \"${idMatchDocId}\" and slug for document ID \"${slugMatchDocId}\". ` +\n `Please use get() for ID or resolveSlug() + get() for slug to be explicit.`,\n );\n }\n\n const resolvedDocumentId = idMatchDocId || slugMatchDocId;\n\n if (!resolvedDocumentId) {\n throw new Error(`Document not found: ${identifier}`);\n }\n\n return resolvedDocumentId;\n }\n\n /**\n * Ids that have at least one stream, asked of the operation store rather than\n * of a table, so the answer holds for any store implementation. A deleted\n * document keeps its stream, so this is the id-is-taken answer, and it does\n * not drift when a snapshot row is missing.\n *\n * Asked on main because that is the branch a create writes: the store this\n * question exists to agree with validates the create against main's head.\n */\n private async idsWithStreams(\n documentIds: string[],\n signal?: AbortSignal,\n ): Promise<Set<string>> {\n const unique = [...new Set(documentIds)];\n const revisions = await Promise.all(\n unique.map((documentId) =>\n this.operationStore.getRevisions(documentId, \"main\", signal),\n ),\n );\n\n const taken = new Set<string>();\n for (let i = 0; i < unique.length; i++) {\n if (Object.keys(revisions[i].revision).length > 0) {\n taken.add(unique[i]);\n }\n }\n return taken;\n }\n\n private async liveIds(documentIds: string[]): Promise<Set<string>> {\n const rows = await this._db\n .selectFrom(\"DocumentSnapshot\")\n .select([\"documentId\"])\n .where(\"documentId\", \"in\", documentIds)\n .where(\"isDeleted\", \"=\", false)\n .distinct()\n .execute();\n\n return new Set(rows.map((row) => row.documentId));\n }\n}\n","import type { ConsistencyCoordinate, ConsistencyKey } from \"./types.js\";\n\nexport interface IConsistencyTracker {\n /**\n * Updates the tracker with new operation indexes.\n * When multiple coordinates have the same key, keeps the highest operationIndex.\n * Resolves any pending waiters whose coordinates are now satisfied.\n */\n update(coordinates: ConsistencyCoordinate[]): void;\n\n /**\n * Returns the latest operation index for a given key, or undefined if not tracked.\n */\n getLatest(key: ConsistencyKey): number | undefined;\n\n /**\n * Returns a promise that resolves when all coordinates are satisfied.\n * Rejects if the timeout is reached or the signal is aborted.\n */\n waitFor(\n coordinates: ConsistencyCoordinate[],\n timeoutMs?: number,\n signal?: AbortSignal,\n ): Promise<void>;\n\n /**\n * Returns a serializable snapshot of the current state.\n */\n serialize(): Array<[ConsistencyKey, number]>;\n\n /**\n * Restores state from a serialized snapshot.\n */\n hydrate(entries: Array<[ConsistencyKey, number]>): void;\n}\n\ntype Waiter = {\n coordinates: ConsistencyCoordinate[];\n resolve: () => void;\n reject: (reason: Error) => void;\n signal?: AbortSignal;\n timeoutId?: NodeJS.Timeout;\n};\n\n/**\n * Creates a consistency key from documentId, scope, and branch.\n */\nexport function makeConsistencyKey(\n documentId: string,\n scope: string,\n branch: string,\n): ConsistencyKey {\n return `${documentId}:${scope}:${branch}`;\n}\n\n/**\n * Tracks operation indexes for documents and provides read-after-write consistency guarantees.\n * Maintains an in-memory map of the latest operation index for each (documentId, scope, branch) tuple.\n */\nexport class ConsistencyTracker implements IConsistencyTracker {\n private state = new Map<ConsistencyKey, number>();\n private waiters: Waiter[] = [];\n\n update(coordinates: ConsistencyCoordinate[]): void {\n const deduplicated = this.deduplicateCoordinates(coordinates);\n\n for (let i = 0; i < deduplicated.length; i++) {\n const coord = deduplicated[i]!;\n const key = makeConsistencyKey(\n coord.documentId,\n coord.scope,\n coord.branch,\n );\n const current = this.state.get(key);\n if (current === undefined || coord.operationIndex > current) {\n this.state.set(key, coord.operationIndex);\n }\n }\n\n this.checkWaiters();\n }\n\n getLatest(key: ConsistencyKey): number | undefined {\n return this.state.get(key);\n }\n\n waitFor(\n coordinates: ConsistencyCoordinate[],\n timeoutMs?: number,\n signal?: AbortSignal,\n ): Promise<void> {\n if (signal?.aborted) {\n return Promise.reject(new Error(\"Operation aborted\"));\n }\n\n if (this.areCoordinatesSatisfied(coordinates)) {\n return Promise.resolve();\n }\n\n return new Promise<void>((resolve, reject) => {\n const waiter: Waiter = {\n coordinates,\n resolve,\n reject,\n signal,\n };\n\n if (timeoutMs !== undefined) {\n waiter.timeoutId = setTimeout(() => {\n this.removeWaiter(waiter);\n reject(new Error(`Consistency wait timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n }\n\n if (signal) {\n const abortHandler = () => {\n this.removeWaiter(waiter);\n reject(new Error(\"Operation aborted\"));\n };\n signal.addEventListener(\"abort\", abortHandler, { once: true });\n }\n\n this.waiters.push(waiter);\n });\n }\n\n serialize(): Array<[ConsistencyKey, number]> {\n return Array.from(this.state.entries());\n }\n\n hydrate(entries: Array<[ConsistencyKey, number]>): void {\n this.state.clear();\n for (const [key, index] of entries) {\n this.state.set(key, index);\n }\n }\n\n private deduplicateCoordinates(\n coordinates: ConsistencyCoordinate[],\n ): ConsistencyCoordinate[] {\n const map = new Map<ConsistencyKey, ConsistencyCoordinate>();\n\n for (let i = 0; i < coordinates.length; i++) {\n const coord = coordinates[i]!;\n const key = makeConsistencyKey(\n coord.documentId,\n coord.scope,\n coord.branch,\n );\n const existing = map.get(key);\n\n if (!existing || coord.operationIndex > existing.operationIndex) {\n map.set(key, coord);\n }\n }\n\n return Array.from(map.values());\n }\n\n private areCoordinatesSatisfied(\n coordinates: ConsistencyCoordinate[],\n ): boolean {\n for (let i = 0; i < coordinates.length; i++) {\n const coord = coordinates[i]!;\n const key = makeConsistencyKey(\n coord.documentId,\n coord.scope,\n coord.branch,\n );\n const latest = this.state.get(key);\n if (latest === undefined || latest < coord.operationIndex) {\n return false;\n }\n }\n return true;\n }\n\n private checkWaiters(): void {\n const satisfiedWaiters: Waiter[] = [];\n const unsatisfiedWaiters: Waiter[] = [];\n\n for (const waiter of this.waiters) {\n if (waiter.signal?.aborted) {\n continue;\n }\n\n if (this.areCoordinatesSatisfied(waiter.coordinates)) {\n satisfiedWaiters.push(waiter);\n } else {\n unsatisfiedWaiters.push(waiter);\n }\n }\n\n this.waiters = unsatisfiedWaiters;\n\n for (const waiter of satisfiedWaiters) {\n if (waiter.timeoutId !== undefined) {\n clearTimeout(waiter.timeoutId);\n }\n waiter.resolve();\n }\n }\n\n private removeWaiter(waiter: Waiter): void {\n const index = this.waiters.indexOf(waiter);\n if (index !== -1) {\n this.waiters.splice(index, 1);\n }\n if (waiter.timeoutId !== undefined) {\n clearTimeout(waiter.timeoutId);\n }\n }\n}\n","import type { PagedResults } from \"./types.js\";\n\n/**\n * Collects all results from a paged result set by following the next() function\n * until all pages have been fetched.\n */\nexport async function collectAllPages<T>(\n firstPage: PagedResults<T>,\n signal?: AbortSignal,\n): Promise<T[]> {\n const allResults: T[] = [...firstPage.results];\n let currentPage = firstPage;\n\n while (currentPage.next) {\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n currentPage = await currentPage.next();\n allResults.push(...currentPage.results);\n }\n\n return allResults;\n}\n","import type {\n Operation,\n OperationWithContext,\n} from \"@powerhousedao/shared/document-model\";\nimport type { Kysely } from \"kysely\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport type { IOperationIndex } from \"../../cache/operation-index-types.js\";\nimport type { IWriteCache } from \"../../cache/write/interfaces.js\";\nimport {\n BaseReadModel,\n defaultReadModelIndexingConfig,\n type ReadModelIndexingConfig,\n} from \"../../read-models/base-read-model.js\";\nimport { DOCUMENT_INDEXER_READ_MODEL } from \"../../read-models/names.js\";\nimport type { DocumentViewDatabase } from \"../../read-models/types.js\";\nimport { collectAllPages } from \"../../shared/collect-all-pages.js\";\nimport type { IConsistencyTracker } from \"../../shared/consistency-tracker.js\";\nimport type {\n ConsistencyToken,\n PagedResults,\n PagingOptions,\n} from \"../../shared/types.js\";\nimport type {\n DocumentGraphEdge,\n DocumentRelationship,\n IDocumentGraph,\n IDocumentIndexer,\n} from \"../interfaces.js\";\nimport type {\n DocumentIndexerDatabase,\n InsertableDocumentRelationship,\n Database as StorageDatabase,\n} from \"./types.js\";\n\nfunction isRelationshipAction(actionType: string): boolean {\n return (\n actionType === \"ADD_RELATIONSHIP\" ||\n actionType === \"REMOVE_RELATIONSHIP\" ||\n actionType === \"UPDATE_RELATIONSHIP\"\n );\n}\n\nexport type IndexerDatabase = StorageDatabase &\n DocumentIndexerDatabase &\n DocumentViewDatabase;\n\nexport class KyselyDocumentIndexer\n extends BaseReadModel\n implements IDocumentIndexer\n{\n private _db: Kysely<IndexerDatabase>;\n\n constructor(\n db: Kysely<IndexerDatabase>,\n operationIndex: IOperationIndex,\n writeCache: IWriteCache,\n consistencyTracker: IConsistencyTracker,\n indexing: ReadModelIndexingConfig = defaultReadModelIndexingConfig,\n ) {\n super(\n db as unknown as Kysely<DocumentViewDatabase>,\n operationIndex,\n writeCache,\n consistencyTracker,\n {\n readModelId: DOCUMENT_INDEXER_READ_MODEL,\n rebuildStateOnInit: false,\n indexing,\n },\n );\n this._db = db;\n }\n\n /** Opens no transaction for a batch carrying no relationship operation. */\n protected override async commitOperations(\n items: OperationWithContext[],\n ): Promise<void> {\n const relationshipOps = items.filter((item) =>\n isRelationshipAction(item.operation.action.type),\n );\n\n if (relationshipOps.length === 0) {\n return;\n }\n\n await this._db.transaction().execute(async (trx) => {\n for (const item of relationshipOps) {\n const { operation } = item;\n const actionType = operation.action.type;\n\n if (actionType === \"ADD_RELATIONSHIP\") {\n await this.handleAddRelationship(trx, operation);\n } else if (actionType === \"REMOVE_RELATIONSHIP\") {\n await this.handleRemoveRelationship(trx, operation);\n } else if (actionType === \"UPDATE_RELATIONSHIP\") {\n await this.handleUpdateRelationship(trx, operation);\n }\n }\n });\n }\n\n async getOutgoing(\n documentId: string,\n types?: string[],\n paging?: PagingOptions,\n consistencyToken?: ConsistencyToken,\n signal?: AbortSignal,\n ): Promise<PagedResults<DocumentRelationship>> {\n if (consistencyToken) {\n await this.waitForConsistency(consistencyToken, undefined, signal);\n }\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const startIndex = paging?.cursor ? parseInt(paging.cursor) : 0;\n const limit = paging?.limit || 100;\n\n let query = this._db\n .selectFrom(\"DocumentRelationship\")\n .selectAll()\n .where(\"sourceId\", \"=\", documentId);\n\n if (types && types.length > 0) {\n query = query.where(\"relationshipType\", \"in\", types);\n }\n\n const rows = await query\n .orderBy(\"createdAt\", \"asc\")\n .orderBy(\"id\", \"asc\")\n .offset(startIndex)\n .limit(limit + 1)\n .execute();\n\n const hasMore = rows.length > limit;\n const results = hasMore ? rows.slice(0, limit) : rows;\n const nextCursor = hasMore ? String(startIndex + limit) : undefined;\n\n return {\n results: results.map((row) => ({\n sourceId: row.sourceId,\n targetId: row.targetId,\n relationshipType: row.relationshipType,\n metadata: row.metadata\n ? (row.metadata as Record<string, unknown>)\n : undefined,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n })),\n options: paging || { cursor: \"0\", limit: 100 },\n nextCursor,\n next: hasMore\n ? () =>\n this.getOutgoing(\n documentId,\n types,\n { cursor: nextCursor!, limit },\n consistencyToken,\n signal,\n )\n : undefined,\n };\n }\n\n async getIncoming(\n documentId: string,\n types?: string[],\n paging?: PagingOptions,\n consistencyToken?: ConsistencyToken,\n signal?: AbortSignal,\n ): Promise<PagedResults<DocumentRelationship>> {\n if (consistencyToken) {\n await this.waitForConsistency(consistencyToken, undefined, signal);\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const startIndex = paging?.cursor ? parseInt(paging.cursor) : 0;\n const limit = paging?.limit || 100;\n\n let query = this._db\n .selectFrom(\"DocumentRelationship\")\n .selectAll()\n .where(\"targetId\", \"=\", documentId);\n\n if (types && types.length > 0) {\n query = query.where(\"relationshipType\", \"in\", types);\n }\n\n const rows = await query\n .orderBy(\"createdAt\", \"asc\")\n .orderBy(\"id\", \"asc\")\n .offset(startIndex)\n .limit(limit + 1)\n .execute();\n\n const hasMore = rows.length > limit;\n const results = hasMore ? rows.slice(0, limit) : rows;\n const nextCursor = hasMore ? String(startIndex + limit) : undefined;\n\n return {\n results: results.map((row) => ({\n sourceId: row.sourceId,\n targetId: row.targetId,\n relationshipType: row.relationshipType,\n metadata: row.metadata\n ? (row.metadata as Record<string, unknown>)\n : undefined,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n })),\n options: paging || { cursor: \"0\", limit: 100 },\n nextCursor,\n next: hasMore\n ? () =>\n this.getIncoming(\n documentId,\n types,\n { cursor: nextCursor!, limit },\n consistencyToken,\n signal,\n )\n : undefined,\n };\n }\n\n async getOrphanedChildren(\n parentIds: string[],\n types?: string[],\n signal?: AbortSignal,\n ): Promise<string[]> {\n if (parentIds.length === 0) {\n return [];\n }\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n let query = this._db\n .selectFrom(\"DocumentRelationship as r\")\n .select(\"r.targetId\")\n .distinct()\n .where(\"r.sourceId\", \"in\", parentIds)\n .where((eb) =>\n eb.not(\n eb.exists(\n eb\n .selectFrom(\"DocumentRelationship as other\")\n .select(\"other.id\")\n .whereRef(\"other.targetId\", \"=\", \"r.targetId\")\n .where(\"other.sourceId\", \"not in\", parentIds),\n ),\n ),\n );\n\n if (types && types.length > 0) {\n query = query.where(\"r.relationshipType\", \"in\", types);\n }\n\n const rows = await query.execute();\n return rows.map((row) => row.targetId);\n }\n\n async hasRelationship(\n sourceId: string,\n targetId: string,\n types?: string[],\n consistencyToken?: ConsistencyToken,\n signal?: AbortSignal,\n ): Promise<boolean> {\n if (consistencyToken) {\n await this.waitForConsistency(consistencyToken, undefined, signal);\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n let query = this._db\n .selectFrom(\"DocumentRelationship\")\n .select(\"id\")\n .where(\"sourceId\", \"=\", sourceId)\n .where(\"targetId\", \"=\", targetId);\n\n if (types && types.length > 0) {\n query = query.where(\"relationshipType\", \"in\", types);\n }\n\n const result = await query.executeTakeFirst();\n\n return result !== undefined;\n }\n\n async getUndirectedRelationships(\n a: string,\n b: string,\n types?: string[],\n paging?: PagingOptions,\n consistencyToken?: ConsistencyToken,\n signal?: AbortSignal,\n ): Promise<PagedResults<DocumentRelationship>> {\n if (consistencyToken) {\n await this.waitForConsistency(consistencyToken, undefined, signal);\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const startIndex = paging?.cursor ? parseInt(paging.cursor) : 0;\n const limit = paging?.limit || 100;\n\n let query = this._db\n .selectFrom(\"DocumentRelationship\")\n .selectAll()\n .where((eb) =>\n eb.or([\n eb.and([eb(\"sourceId\", \"=\", a), eb(\"targetId\", \"=\", b)]),\n eb.and([eb(\"sourceId\", \"=\", b), eb(\"targetId\", \"=\", a)]),\n ]),\n );\n\n if (types && types.length > 0) {\n query = query.where(\"relationshipType\", \"in\", types);\n }\n\n const rows = await query\n .orderBy(\"createdAt\", \"asc\")\n .orderBy(\"id\", \"asc\")\n .offset(startIndex)\n .limit(limit + 1)\n .execute();\n\n const hasMore = rows.length > limit;\n const results = hasMore ? rows.slice(0, limit) : rows;\n const nextCursor = hasMore ? String(startIndex + limit) : undefined;\n\n return {\n results: results.map((row) => ({\n sourceId: row.sourceId,\n targetId: row.targetId,\n relationshipType: row.relationshipType,\n metadata: row.metadata\n ? (row.metadata as Record<string, unknown>)\n : undefined,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n })),\n options: paging || { cursor: \"0\", limit: 100 },\n nextCursor,\n next: hasMore\n ? () =>\n this.getUndirectedRelationships(\n a,\n b,\n types,\n { cursor: nextCursor!, limit },\n consistencyToken,\n signal,\n )\n : undefined,\n };\n }\n\n async getDirectedRelationships(\n sourceId: string,\n targetId: string,\n types?: string[],\n paging?: PagingOptions,\n consistencyToken?: ConsistencyToken,\n signal?: AbortSignal,\n ): Promise<PagedResults<DocumentRelationship>> {\n if (consistencyToken) {\n await this.waitForConsistency(consistencyToken, undefined, signal);\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const startIndex = paging?.cursor ? parseInt(paging.cursor) : 0;\n const limit = paging?.limit || 100;\n\n let query = this._db\n .selectFrom(\"DocumentRelationship\")\n .selectAll()\n .where(\"sourceId\", \"=\", sourceId)\n .where(\"targetId\", \"=\", targetId);\n\n if (types && types.length > 0) {\n query = query.where(\"relationshipType\", \"in\", types);\n }\n\n const rows = await query\n .orderBy(\"createdAt\", \"asc\")\n .orderBy(\"id\", \"asc\")\n .offset(startIndex)\n .limit(limit + 1)\n .execute();\n\n const hasMore = rows.length > limit;\n const results = hasMore ? rows.slice(0, limit) : rows;\n const nextCursor = hasMore ? String(startIndex + limit) : undefined;\n\n return {\n results: results.map((row) => ({\n sourceId: row.sourceId,\n targetId: row.targetId,\n relationshipType: row.relationshipType,\n metadata: row.metadata\n ? (row.metadata as Record<string, unknown>)\n : undefined,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n })),\n options: paging || { cursor: \"0\", limit: 100 },\n nextCursor,\n next: hasMore\n ? () =>\n this.getDirectedRelationships(\n sourceId,\n targetId,\n types,\n { cursor: nextCursor!, limit },\n consistencyToken,\n signal,\n )\n : undefined,\n };\n }\n\n async findPath(\n sourceId: string,\n targetId: string,\n types?: string[],\n consistencyToken?: ConsistencyToken,\n signal?: AbortSignal,\n ): Promise<string[] | null> {\n if (consistencyToken) {\n await this.waitForConsistency(consistencyToken, undefined, signal);\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n if (sourceId === targetId) {\n return [sourceId];\n }\n\n const visited = new Set<string>();\n const queue: Array<{ id: string; path: string[] }> = [\n { id: sourceId, path: [sourceId] },\n ];\n\n while (queue.length > 0) {\n const current = queue.shift()!;\n\n if (current.id === targetId) {\n return current.path;\n }\n\n if (visited.has(current.id)) {\n continue;\n }\n\n visited.add(current.id);\n\n const outgoingPage = await this.getOutgoing(\n current.id,\n types,\n undefined,\n consistencyToken,\n signal,\n );\n const outgoingRelationships = await collectAllPages(outgoingPage, signal);\n\n for (const rel of outgoingRelationships) {\n if (!visited.has(rel.targetId)) {\n queue.push({\n id: rel.targetId,\n path: [...current.path, rel.targetId],\n });\n }\n }\n }\n\n return null;\n }\n\n async findAncestors(\n documentId: string,\n types?: string[],\n consistencyToken?: ConsistencyToken,\n signal?: AbortSignal,\n ): Promise<IDocumentGraph> {\n if (consistencyToken) {\n await this.waitForConsistency(consistencyToken, undefined, signal);\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const nodes = new Set<string>([documentId]);\n const edges: DocumentGraphEdge[] = [];\n const queue: string[] = [documentId];\n const visited = new Set<string>();\n\n while (queue.length > 0) {\n const currentId = queue.shift()!;\n\n if (visited.has(currentId)) {\n continue;\n }\n\n visited.add(currentId);\n\n const incomingPage = await this.getIncoming(\n currentId,\n types,\n undefined,\n consistencyToken,\n signal,\n );\n const incomingRelationships = await collectAllPages(incomingPage, signal);\n\n for (const rel of incomingRelationships) {\n nodes.add(rel.sourceId);\n edges.push({\n from: rel.sourceId,\n to: rel.targetId,\n type: rel.relationshipType,\n });\n\n if (!visited.has(rel.sourceId)) {\n queue.push(rel.sourceId);\n }\n }\n }\n\n return {\n nodes: Array.from(nodes),\n edges,\n };\n }\n\n async getRelationshipTypes(\n consistencyToken?: ConsistencyToken,\n signal?: AbortSignal,\n ): Promise<string[]> {\n if (consistencyToken) {\n await this.waitForConsistency(consistencyToken, undefined, signal);\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const rows = await this._db\n .selectFrom(\"DocumentRelationship\")\n .select(\"relationshipType\")\n .distinct()\n .execute();\n\n return rows.map((row) => row.relationshipType);\n }\n\n private async handleAddRelationship(\n trx: Kysely<IndexerDatabase>,\n operation: Operation,\n ): Promise<void> {\n const input = operation.action.input as {\n sourceId: string;\n targetId: string;\n relationshipType: string;\n metadata?: Record<string, unknown>;\n };\n\n const existingDoc = await trx\n .selectFrom(\"Document\")\n .select(\"id\")\n .where(\"id\", \"=\", input.sourceId)\n .executeTakeFirst();\n\n if (!existingDoc) {\n await trx\n .insertInto(\"Document\")\n .values({\n id: input.sourceId,\n })\n .execute();\n }\n\n const existingTargetDoc = await trx\n .selectFrom(\"Document\")\n .select(\"id\")\n .where(\"id\", \"=\", input.targetId)\n .executeTakeFirst();\n\n if (!existingTargetDoc) {\n await trx\n .insertInto(\"Document\")\n .values({\n id: input.targetId,\n })\n .execute();\n }\n\n const existingRel = await trx\n .selectFrom(\"DocumentRelationship\")\n .select(\"id\")\n .where(\"sourceId\", \"=\", input.sourceId)\n .where(\"targetId\", \"=\", input.targetId)\n .where(\"relationshipType\", \"=\", input.relationshipType)\n .executeTakeFirst();\n\n if (!existingRel) {\n const relationship: InsertableDocumentRelationship = {\n id: uuidv4(),\n sourceId: input.sourceId,\n targetId: input.targetId,\n relationshipType: input.relationshipType,\n metadata: input.metadata || null,\n };\n\n await trx\n .insertInto(\"DocumentRelationship\")\n .values(relationship)\n .execute();\n }\n }\n\n private async handleRemoveRelationship(\n trx: Kysely<IndexerDatabase>,\n operation: Operation,\n ): Promise<void> {\n const input = operation.action.input as {\n sourceId: string;\n targetId: string;\n relationshipType: string;\n };\n\n await trx\n .deleteFrom(\"DocumentRelationship\")\n .where(\"sourceId\", \"=\", input.sourceId)\n .where(\"targetId\", \"=\", input.targetId)\n .where(\"relationshipType\", \"=\", input.relationshipType)\n .execute();\n }\n\n private async handleUpdateRelationship(\n trx: Kysely<IndexerDatabase>,\n operation: Operation,\n ): Promise<void> {\n const input = operation.action.input as {\n sourceId: string;\n targetId: string;\n relationshipType: string;\n metadata: Record<string, unknown> | null;\n };\n\n await trx\n .updateTable(\"DocumentRelationship\")\n .set({\n metadata: input.metadata,\n updatedAt: new Date(),\n })\n .where(\"sourceId\", \"=\", input.sourceId)\n .where(\"targetId\", \"=\", input.targetId)\n .where(\"relationshipType\", \"=\", input.relationshipType)\n .execute();\n }\n}\n"],"mappings":"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"}
package/dist/entry.js CHANGED
@@ -1,5 +1,5 @@
1
- import { n as errorToInfo, t as createForwardingLogger, u as instrumentPgPool } from "./forwarding-logger-CSsCJ38f.js";
2
- import { n as defaultLoadFactory, t as buildWorkerExecutor } from "./build-worker-executor-DPGjh9Uc.js";
1
+ import { n as errorToInfo, t as createForwardingLogger, u as instrumentPgPool } from "./forwarding-logger-CnuVDVze.js";
2
+ import { n as defaultLoadFactory, t as buildWorkerExecutor } from "./build-worker-executor-Bvoba-AV.js";
3
3
  import { ConsoleLogger } from "document-model";
4
4
  import { isMainThread, parentPort } from "node:worker_threads";
5
5
  //#region src/executor/worker/run-worker.ts
@@ -3729,23 +3729,24 @@ var SignatureAdmission = class {
3729
3729
  operation
3730
3730
  }));
3731
3731
  const live = await this.liveOperationIds(candidates, operationStore, signal);
3732
+ const redelivered = await this.redelivered(candidates, live, operationStore, signal);
3732
3733
  await resolvePolicies(candidates, job.operations.map((operation) => operation.action), (documentId, branch) => storedPolicy(stores, this.policySource, documentId, branch, signal));
3733
3734
  const dropped = /* @__PURE__ */ new Set();
3734
3735
  for (let i = 0; i < candidates.length; i++) {
3735
- const verdict = await this.verdict(candidates[i], live, void 0, "load", signal);
3736
+ const verdict = await this.verdict(candidates[i], live, void 0, "load", signal, redelivered.has(candidates[i]));
3736
3737
  if (verdict.ok) continue;
3737
3738
  if (this.record(job, candidates[i], verdict, "load")) dropped.add(operations[i]);
3738
3739
  }
3739
3740
  return dropped;
3740
3741
  }
3741
- async verdict(entry, live, submitted, path, signal) {
3742
+ async verdict(entry, live, submitted, path, signal, known = false) {
3742
3743
  const verdict = await verifyActionSignature(entry.action, {
3743
3744
  documentId: entry.stream.documentId,
3744
3745
  branch: entry.stream.branch,
3745
3746
  policy: entry.policy
3746
3747
  }, path, entry.operation);
3747
3748
  if (!verdict.ok) return verdict;
3748
- if (isLive(entry, live) || submitted?.has(entry.opId)) return {
3749
+ if (!known && (isLive(entry, live) || submitted?.has(entry.opId))) return {
3749
3750
  ok: false,
3750
3751
  scheme: verdict.scheme,
3751
3752
  code: "DUPLICATE_ACTION",
@@ -3760,6 +3761,7 @@ var SignatureAdmission = class {
3760
3761
  reason: `action ${entry.action.id} is unsigned but claims to act as ${address}`
3761
3762
  } : verdict;
3762
3763
  }
3764
+ if (known) return verdict;
3763
3765
  const signer = entry.action.context.signer;
3764
3766
  if (!await this.authorize(signer, entry.stream.documentId, signal)) return {
3765
3767
  ok: false,
@@ -3791,7 +3793,9 @@ var SignatureAdmission = class {
3791
3793
  }
3792
3794
  }
3793
3795
  /**
3794
- * The live candidates stored exactly as submitted. A synthesized operation
3796
+ * The live candidates stored exactly as submitted and still standing: one
3797
+ * an undo or a reshuffle has since superseded is refused, not re-emitted with
3798
+ * the state it once produced. A synthesized operation
3795
3799
  * holds another action, so it cannot be compared; only a retry, whose first
3796
3800
  * attempt this job was, takes it as committed.
3797
3801
  */
@@ -3804,13 +3808,29 @@ var SignatureAdmission = class {
3804
3808
  for (const [stream, entries] of byStream(toCompare)) {
3805
3809
  const stored = await operationStore.getOperationsByIds(stream.documentId, stream.scope, stream.branch, entries.map((entry) => entry.opId), signal);
3806
3810
  const byId = new Map(stored.map((operation) => [operation.id, operation]));
3807
- for (const entry of entries) {
3811
+ const matching = entries.filter((entry) => {
3808
3812
  const operation = byId.get(entry.opId);
3809
- if (operation && sameContent(operation.action, entry.action)) committed.set(entry, committedWrite(entry, entry.opId));
3810
- }
3813
+ return operation && sameContent(operation.action, entry.action);
3814
+ });
3815
+ if (matching.length === 0) continue;
3816
+ const later = await operationStore.getSince(stream.documentId, stream.scope, stream.branch, Math.min(...matching.map((entry) => byId.get(entry.opId).index)), void 0, void 0, signal);
3817
+ for (const entry of matching) if (!isSuperseded(byId.get(entry.opId), later.results)) committed.set(entry, committedWrite(entry, entry.opId));
3811
3818
  }
3812
3819
  return committed;
3813
3820
  }
3821
+ /**
3822
+ * The load candidates stored exactly as they arrive, timestamp included: a
3823
+ * peer re-sending what this stream holds, not a replay of its action.
3824
+ */
3825
+ async redelivered(candidates, live, operationStore, signal) {
3826
+ const found = /* @__PURE__ */ new Set();
3827
+ const toCompare = candidates.filter((entry) => live.has(entry.opId) && !(entry.synthesizedOpId && live.has(entry.synthesizedOpId)));
3828
+ for (const [stream, entries] of byStream(toCompare)) {
3829
+ const stored = await operationStore.getOperationsByIds(stream.documentId, stream.scope, stream.branch, entries.map((entry) => entry.opId), signal);
3830
+ for (const entry of entries) if (stored.some((operation) => operation.id === entry.opId && Date.parse(operation.timestampUtcMs) === Date.parse(entry.operation?.timestampUtcMs ?? "") && sameContent(operation.action, entry.action))) found.add(entry);
3831
+ }
3832
+ return found;
3833
+ }
3814
3834
  async liveOperationIds(candidates, operationStore, signal) {
3815
3835
  const live = /* @__PURE__ */ new Set();
3816
3836
  for (const [stream, entries] of byStream(candidates)) {
@@ -3920,6 +3940,9 @@ function committedWrite(entry, opId) {
3920
3940
  opId
3921
3941
  };
3922
3942
  }
3943
+ function isSuperseded(operation, later) {
3944
+ return later.some((next) => next.index > operation.index && next.skip > 0 && next.index - next.skip <= operation.index);
3945
+ }
3923
3946
  function sameContent(stored, submitted) {
3924
3947
  try {
3925
3948
  return canonicalJson(stored) === canonicalJson(submitted);
@@ -4886,7 +4909,7 @@ var SimpleJobExecutor = class {
4886
4909
  stores.writeCache.invalidate(job.documentId, scope, job.branch);
4887
4910
  const result = await this.processActions(tail.map((operation, i) => ({
4888
4911
  action: operation.action,
4889
- skip: i === 0 ? retractionSkip(nextIndex, tail[0].index) : 0,
4912
+ skip: i === 0 ? retractionSkip(nextIndex, tail[0].index - tail[0].skip) : 0,
4890
4913
  sourceRemote: "",
4891
4914
  deniedReason: reevaluated[firstChange + i]
4892
4915
  })), {
@@ -5048,7 +5071,8 @@ var SimpleJobExecutor = class {
5048
5071
  ...operation,
5049
5072
  id: operation.id
5050
5073
  })));
5051
- for (const operation of reshuffledOperations) if (operation.action.type === "NOOP" && operation.skip === 0) operation.skip = 1;
5074
+ const incomingIds = new Set(incomingOpsToApply.map((operation) => operation.action.id));
5075
+ for (const operation of reshuffledOperations) if (operation.action.type === "NOOP" && operation.skip === 0 && incomingIds.has(operation.action.id)) operation.skip = 1;
5052
5076
  let deniedReasons;
5053
5077
  if (this.featureFlags.documentDecisions) try {
5054
5078
  deniedReasons = await evaluateByPosition(this.decisionModel, {
@@ -5126,17 +5150,35 @@ var SimpleJobExecutor = class {
5126
5150
  }
5127
5151
  }
5128
5152
  const incomingActionIds = new Set(operations.map((op) => op.action.id));
5153
+ const supersededBy = (op) => {
5154
+ let nearest;
5155
+ for (const laterOp of allOpsFromMinConflictingIndex) if (laterOp.index > op.index && laterOp.skip > 0 && laterOp.index - laterOp.skip <= op.index && (nearest === void 0 || laterOp.index < nearest.index)) nearest = laterOp;
5156
+ return nearest;
5157
+ };
5158
+ const undoneActionIds = /* @__PURE__ */ new Set();
5159
+ let predecessorBound = minIncomingIndex;
5160
+ const rowsByActionId = /* @__PURE__ */ new Map();
5161
+ for (const op of conflictingOps) if (incomingActionIds.has(op.action.id)) rowsByActionId.set(op.action.id, [...rowsByActionId.get(op.action.id) ?? [], op]);
5162
+ const liveOps = /* @__PURE__ */ new Set();
5163
+ for (const op of conflictingOps) if (supersededBy(op) === void 0) liveOps.add(op);
5164
+ for (const [actionId, rows] of rowsByActionId) {
5165
+ if (rows.some((row) => liveOps.has(row))) continue;
5166
+ if (rows.some((row) => supersededBy(row)?.action.type === "NOOP")) undoneActionIds.add(actionId);
5167
+ else predecessorBound = Math.min(predecessorBound, Math.max(...rows.map((row) => row.index)));
5168
+ }
5129
5169
  const nonSupersededOps = conflictingOps.filter((op) => {
5130
- if (op.index < minIncomingIndex && !incomingActionIds.has(op.action.id)) return false;
5131
- for (const laterOp of allOpsFromMinConflictingIndex) if (laterOp.index > op.index && laterOp.skip > 0) {
5132
- if (laterOp.index - laterOp.skip <= op.index) return false;
5133
- }
5134
- return true;
5170
+ if (op.index < predecessorBound && !incomingActionIds.has(op.action.id)) return false;
5171
+ return liveOps.has(op);
5135
5172
  });
5173
+ let rewoundFrom = Number.POSITIVE_INFINITY;
5174
+ for (const op of nonSupersededOps) if (!isGenesisOperation(op)) rewoundFrom = Math.min(rewoundFrom, op.index - op.skip);
5175
+ const reappended = new Set(nonSupersededOps.map((op) => op.index));
5176
+ for (const op of allOpsFromMinConflictingIndex) if (op.index >= rewoundFrom && !reappended.has(op.index) && supersededBy(op) === void 0) nonSupersededOps.push(op);
5136
5177
  const existingActionIds = new Set(nonSupersededOps.map((op) => op.action.id));
5137
5178
  const seenIncomingActionIds = /* @__PURE__ */ new Set();
5138
5179
  const incomingOpsToApply = operations.filter((op) => {
5139
5180
  if (existingActionIds.has(op.action.id)) return false;
5181
+ if (undoneActionIds.has(op.action.id)) return false;
5140
5182
  if (seenIncomingActionIds.has(op.action.id)) return false;
5141
5183
  seenIncomingActionIds.add(op.action.id);
5142
5184
  return true;
@@ -6321,4 +6363,4 @@ function createForwardingLogger(post, level = "info", tags = []) {
6321
6363
  //#endregion
6322
6364
  export { DocumentNotFoundError as $, ModuleNotFoundError as A, authDecisionModel as B, DocumentMetaCache as C, DuplicateManifestError as D, JobExecutorEventTypes as E, summarizeSubmittedActions as F, DocumentExistence as G, APPEND_CONDITION_FAILED_PREFIX as H, targetDocumentId as I, RevisionMismatchError as J, DuplicateOperationError as K, decideAtHead as L, createConsistencyToken as M, createEmptyConsistencyToken as N, DuplicateModuleError as O, submittedActionIds as P, DocumentDeletedError as Q, selectDecisionModel as R, KyselyOperationIndex as S, DEFAULT_DEFERRED_JOB_TTL_MS as T, AppendConditionFailedError as U, buildDecisionModel as V, DocumentAlreadyExistsError as W, AuthTimestampNotMonotonicError as X, AuthEnforcementDisabledError as Y, AuthorizationDeniedError as Z, DriveCollectionId as _, DEFAULT_DRIVE_CONTAINER_TYPES as a, EventBusAggregateError as at, EventBus as b, runMigrations as c, parsePagingOptions as ct, KyselyOperationStore as d, __exportAll as dt, ExcessiveReshuffleError as et, KyselyKeyframeStore as f, PassthroughSigner as g, verifyActionSignature as h, fromErrorInfo as i, UpgradePreconditionFailedError as it, GATED_DOCUMENT_ACTIONS as j, InvalidModuleError as k, createForwardingPoolInstrumentation as l, throwIfAborted as lt, SimpleJobExecutor as m, errorToInfo as n, InvalidSignatureError as nt, REACTOR_SCHEMA as o, ReactorEventTypes as ot, DocumentModelRegistry as p, OptimisticLockError as q, sanitizeArg as r, RelationshipNotFoundError as rt, getMigrationStatus as s, matchesScope as st, createForwardingLogger as t, InvalidOperationTimestampError as tt, instrumentPgPool as u, yieldToMain as ut, KyselyExecutionScope as v, CollectionMembershipCache as w, KyselyWriteCache as x, resolveFeatureFlags as y, documentDecisionModel as z };
6323
6365
 
6324
- //# sourceMappingURL=forwarding-logger-CSsCJ38f.js.map
6366
+ //# sourceMappingURL=forwarding-logger-CnuVDVze.js.map