@powerhousedao/reactor 6.2.1 → 6.2.2-dev.0

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,8 +1,8 @@
1
- import { S as CollectionMembershipCache, _ as KyselyWriteCache, d as KyselyKeyframeStore, f as DocumentModelRegistry, g as EventBus, n as REACTOR_SCHEMA, o as instrumentPgPool, s as KyselyOperationStore, v as KyselyOperationIndex, y as DocumentMetaCache } from "./drive-container-types-BxnXaOAp.js";
2
- import { n as ReactorEventTypes } from "./types-CxSpmNGK.js";
3
- import { a as ReadModelCoordinator, i as KyselyDocumentView, n as ConsistencyTracker, t as KyselyDocumentIndexer } from "./document-indexer-DLEyUQxU.js";
1
+ import { S as CollectionMembershipCache, _ as KyselyWriteCache, d as KyselyKeyframeStore, f as DocumentModelRegistry, g as EventBus, n as REACTOR_SCHEMA, o as instrumentPgPool, s as KyselyOperationStore, v as KyselyOperationIndex, y as DocumentMetaCache } from "./drive-container-types-DpJp2AmE.js";
2
+ import { n as ReactorEventTypes } from "./types-DMKLa0Ok.js";
3
+ import { a as ReadModelCoordinator, i as KyselyDocumentView, n as ConsistencyTracker, t as KyselyDocumentIndexer } from "./document-indexer-FGJmRAdX.js";
4
4
  import { n as errorToInfo, t as createForwardingLogger } from "./forwarding-logger-BBkMSxuJ.js";
5
- import { n as defaultLoadFactory } from "./build-worker-executor-DeJ2DW16.js";
5
+ import { n as defaultLoadFactory } from "./build-worker-executor-BDrPlCJl.js";
6
6
  import { ConsoleLogger } from "document-model";
7
7
  import { isMainThread, parentPort } from "node:worker_threads";
8
8
  //#region src/projection/projection-worker/build-projection-stack.ts
@@ -1,4 +1,4 @@
1
- import { n as ReactorEventTypes } from "./types-CxSpmNGK.js";
1
+ import { n as ReactorEventTypes } from "./types-DMKLa0Ok.js";
2
2
  import { childLogger } from "document-model";
3
3
  import { randomUUID } from "node:crypto";
4
4
  //#region src/projection/projection-shard-manager.ts
@@ -310,4 +310,4 @@ var ProjectionShardManager = class {
310
310
  //#endregion
311
311
  export { ProjectionShardManager };
312
312
 
313
- //# sourceMappingURL=projection-shard-manager-_c7orNo5.js.map
313
+ //# sourceMappingURL=projection-shard-manager-BnQKFqQd.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"projection-shard-manager-_c7orNo5.js","names":[],"sources":["../src/projection/projection-shard-manager.ts"],"sourcesContent":["import { childLogger, type ILogger } from \"document-model\";\nimport { randomUUID } from \"node:crypto\";\nimport type { IEventBus } from \"../events/interfaces.js\";\nimport {\n ReactorEventTypes,\n type JobReadReadyEvent,\n type JobWriteReadyEvent,\n type ReadModelBatchCompletedEvent,\n type ReadModelIndexedEvent,\n type Unsubscribe,\n} from \"../events/types.js\";\nimport type {\n IReadModel,\n IReadModelCoordinator,\n} from \"../read-models/interfaces.js\";\nimport type { ForwardingPoolInstrumentation } from \"../storage/pool-instrumentation.js\";\nimport type {\n BuiltInReadModelKind,\n ChainDepthReport,\n DbConfig,\n ModelManifestEntry,\n ProjectionDrainedMessage,\n ProjectionInitMessage,\n ProjectionParentMessage,\n ProjectionPoolAcquireSamplesMessage,\n ProjectionWorkerMessage,\n} from \"./protocol.js\";\nimport type { IProjectionTransport } from \"./transport.js\";\n\nconst DEFAULT_INIT_TIMEOUT_MS = 30_000;\nconst DEFAULT_SHUTDOWN_GRACE_MS = 5_000;\nconst DEFAULT_DRAIN_TIMEOUT_MS = 30_000;\nconst DEFAULT_CHAIN_DEPTH_REPORT_INTERVAL_MS = 250;\n\nconst FNV_OFFSET_BASIS = 0x811c9dc5;\nconst FNV_PRIME = 0x01000193;\n\nfunction bucketFor(documentId: string, numWorkers: number): number {\n if (numWorkers < 1) {\n throw new Error(`bucketFor: numWorkers must be >= 1 (got ${numWorkers})`);\n }\n let hash = FNV_OFFSET_BASIS;\n for (let i = 0; i < documentId.length; i++) {\n hash ^= documentId.charCodeAt(i);\n hash = Math.imul(hash, FNV_PRIME);\n }\n return (hash >>> 0) % numWorkers;\n}\n\n/**\n * Factory that builds one projection-worker transport. Mirrors\n * `WorkerFactory` from the executor pool: lets tests inject fake\n * transports without spawning real worker threads.\n */\nexport type ProjectionWorkerFactory = (\n shardIndex: number,\n shardId: string,\n) => IProjectionTransport;\n\nexport type ProjectionShardManagerConfig = {\n shardCount: number;\n db: DbConfig;\n models: ModelManifestEntry[];\n preReadyKinds: BuiltInReadModelKind[];\n postReadyKinds: BuiltInReadModelKind[];\n factory: ProjectionWorkerFactory;\n logger: ILogger;\n hostBus: IEventBus;\n initTimeoutMs?: number;\n shutdownGraceMs?: number;\n drainTimeoutMs?: number;\n chainDepthReportIntervalMs?: number;\n /**\n * Host-side forwarding instrumentations indexed by shard index. The\n * manager routes each shard's `pool-acquire-samples` message to the\n * matching forwarder so the host's OpenTelemetry instrumentation records\n * acquire-wait latencies as if each shard's pg.Pool were local.\n */\n poolInstrumentations?: ForwardingPoolInstrumentation[];\n};\n\ntype ShardState = {\n shardIndex: number;\n shardId: string;\n transport: IProjectionTransport;\n ready: boolean;\n lastDepth: number;\n lastDepthAt: number;\n poolInstrumentation?: ForwardingPoolInstrumentation;\n onMessage: (msg: ProjectionWorkerMessage) => void;\n onError: (err: Error) => void;\n onExit: (code: number) => void;\n};\n\ntype PendingDrain = {\n resolve: () => void;\n reject: (err: Error) => void;\n remaining: Set<string>;\n timer: NodeJS.Timeout;\n};\n\n/**\n * Host-side coordinator for N sharded projection workers.\n *\n * Implements {@link IReadModelCoordinator} so it slots into the same\n * `readModelCoordinator` field on the reactor module as the in-process\n * {@link ReadModelCoordinator}. The host subscribes to JOB_WRITE_READY\n * exactly once; events are routed to a single shard by\n * `bucketFor(documentId, shardCount)`. Each worker maintains the\n * per-queueKey serial chain locally and forwards JOB_READ_READY and\n * READMODEL_* events back to the host for the rest of the reactor (sync\n * manager, awaiters, observers) to consume on the host bus.\n *\n * @see Sharded projection workers sub-feature brief\n * (Powerhouse board wiki id: eb26f01f-8f68-4918-a6f6-ac7a4679b533)\n */\nexport class ProjectionShardManager implements IReadModelCoordinator {\n readonly readModels: IReadModel[] = [];\n\n private readonly config: ProjectionShardManagerConfig;\n private readonly logger: ILogger;\n private readonly hostBus: IEventBus;\n private readonly shards: ShardState[] = [];\n private readonly initPromises = new Map<\n string,\n { resolve: () => void; reject: (err: Error) => void; timer: NodeJS.Timeout }\n >();\n private readonly pendingDrains = new Map<string, PendingDrain>();\n private hostSubscription?: Unsubscribe;\n private isRunning = false;\n private started = false;\n\n constructor(config: ProjectionShardManagerConfig) {\n if (config.shardCount < 1) {\n throw new Error(\n `ProjectionShardManager: shardCount must be >= 1 (got ${config.shardCount})`,\n );\n }\n this.config = config;\n this.logger = childLogger([\"reactor\", \"projection-shard-manager\"]);\n this.hostBus = config.hostBus;\n }\n\n async startup(): Promise<void> {\n if (this.started) {\n return;\n }\n this.started = true;\n const initTimeoutMs = this.config.initTimeoutMs ?? DEFAULT_INIT_TIMEOUT_MS;\n const reportIntervalMs =\n this.config.chainDepthReportIntervalMs ??\n DEFAULT_CHAIN_DEPTH_REPORT_INTERVAL_MS;\n\n const initPromises: Promise<void>[] = [];\n for (let i = 0; i < this.config.shardCount; i++) {\n const shardId = `projection-shard-${i}`;\n const transport = this.config.factory(i, shardId);\n const state: ShardState = {\n shardIndex: i,\n shardId,\n transport,\n ready: false,\n lastDepth: 0,\n lastDepthAt: 0,\n poolInstrumentation: this.config.poolInstrumentations?.[i],\n onMessage: (msg) => this.handleWorkerMessage(state, msg),\n onError: (err) => this.handleTransportError(state, err),\n onExit: (code) => this.handleTransportExit(state, code),\n };\n transport.on(\"message\", state.onMessage);\n transport.on(\"error\", state.onError);\n transport.on(\"exit\", state.onExit);\n this.shards.push(state);\n\n const correlationId = randomUUID();\n const initPromise = new Promise<void>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.initPromises.delete(correlationId);\n reject(\n new Error(\n `projection shard ${shardId} did not become ready within ${initTimeoutMs}ms`,\n ),\n );\n }, initTimeoutMs);\n this.initPromises.set(correlationId, { resolve, reject, timer });\n });\n\n const init: ProjectionInitMessage = {\n type: \"init\",\n correlationId,\n shardId,\n shardIndex: i,\n shardCount: this.config.shardCount,\n db: this.config.db,\n models: this.config.models,\n preReadyKinds: this.config.preReadyKinds,\n postReadyKinds: this.config.postReadyKinds,\n chainDepthReportIntervalMs: reportIntervalMs,\n };\n transport.postMessage(init);\n initPromises.push(initPromise);\n }\n\n await Promise.all(initPromises);\n this.logger.info(\n \"projection shard manager ready: @count shards\",\n this.shards.length,\n );\n }\n\n start(): void {\n if (this.isRunning) {\n return;\n }\n this.hostSubscription = this.hostBus.subscribe(\n ReactorEventTypes.JOB_WRITE_READY,\n (_t: number, event: JobWriteReadyEvent) => {\n this.routeWriteReady(event);\n },\n );\n this.isRunning = true;\n }\n\n stop(): void {\n if (!this.isRunning) {\n return;\n }\n if (this.hostSubscription) {\n this.hostSubscription();\n this.hostSubscription = undefined;\n }\n this.isRunning = false;\n }\n\n async drain(): Promise<void> {\n if (this.shards.length === 0) {\n return;\n }\n const drainTimeoutMs =\n this.config.drainTimeoutMs ?? DEFAULT_DRAIN_TIMEOUT_MS;\n const correlationId = randomUUID();\n const remaining = new Set(this.shards.map((s) => s.shardId));\n const promise = new Promise<void>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.pendingDrains.delete(correlationId);\n reject(\n new Error(\n `projection shards did not drain within ${drainTimeoutMs}ms (remaining: ${[\n ...remaining,\n ].join(\", \")})`,\n ),\n );\n }, drainTimeoutMs);\n this.pendingDrains.set(correlationId, {\n resolve,\n reject,\n remaining,\n timer,\n });\n });\n for (const shard of this.shards) {\n shard.transport.postMessage({ type: \"drain\", correlationId });\n }\n await promise;\n }\n\n getChainDepth(): number {\n let total = 0;\n for (const shard of this.shards) {\n total += shard.lastDepth;\n }\n return total;\n }\n\n getShardDepths(): ChainDepthReport[] {\n return this.shards.map((shard) => ({\n shardId: shard.shardId,\n depth: shard.lastDepth,\n timestamp: shard.lastDepthAt,\n }));\n }\n\n async shutdown(): Promise<void> {\n this.stop();\n const graceMs = this.config.shutdownGraceMs ?? DEFAULT_SHUTDOWN_GRACE_MS;\n const correlationId = randomUUID();\n for (const shard of this.shards) {\n try {\n const msg: ProjectionParentMessage = {\n type: \"shutdown\",\n correlationId,\n graceMs,\n };\n shard.transport.postMessage(msg);\n } catch (err) {\n this.logger.warn(\n \"projection shard postMessage(shutdown) failed for @shardId: @error\",\n shard.shardId,\n err,\n );\n }\n }\n const terminationDeadline = Date.now() + graceMs;\n while (\n this.shards.some((s) => s.ready) &&\n Date.now() < terminationDeadline\n ) {\n await new Promise<void>((resolve) => setTimeout(resolve, 25));\n }\n for (const shard of this.shards) {\n try {\n await shard.transport.terminate();\n } catch (err) {\n this.logger.warn(\n \"projection shard terminate failed for @shardId: @error\",\n shard.shardId,\n err,\n );\n }\n shard.transport.off(\"message\", shard.onMessage);\n shard.transport.off(\"error\", shard.onError);\n shard.transport.off(\"exit\", shard.onExit);\n }\n this.shards.length = 0;\n }\n\n private routeWriteReady(event: JobWriteReadyEvent): void {\n if (event.operations.length === 0) {\n return;\n }\n const documentId = event.operations[0]!.context.documentId;\n const index = bucketFor(documentId, this.shards.length);\n const shard = this.shards[index]!;\n if (!shard.ready) {\n this.logger.warn(\n \"dropping JOB_WRITE_READY: shard @index not ready\",\n index,\n );\n return;\n }\n shard.transport.postMessage({\n type: \"write-ready\",\n jobId: event.jobId,\n operations: event.operations,\n jobMeta: event.jobMeta,\n collectionMemberships: event.collectionMemberships,\n });\n }\n\n private handleWorkerMessage(\n shard: ShardState,\n msg: ProjectionWorkerMessage,\n ): void {\n switch (msg.type) {\n case \"ready\":\n this.handleReady(shard, msg.correlationId);\n return;\n case \"read-ready\":\n this.relayReadReady({\n jobId: msg.jobId,\n operations: msg.operations,\n });\n return;\n case \"readmodel-indexed\":\n this.relayReadModelIndexed({\n jobId: msg.jobId,\n readModelName: msg.readModelName,\n stage: msg.stage,\n durationMs: msg.durationMs,\n operationCount: msg.operationCount,\n success: msg.success,\n });\n return;\n case \"readmodel-batch-completed\":\n this.relayBatchCompleted({\n jobId: msg.jobId,\n batchSize: msg.batchSize,\n chainWaitDurationMs: msg.chainWaitDurationMs,\n preReadyDurationMs: msg.preReadyDurationMs,\n emitDurationMs: msg.emitDurationMs,\n postReadyDurationMs: msg.postReadyDurationMs,\n });\n return;\n case \"chain-depth\":\n shard.lastDepth = msg.depth;\n shard.lastDepthAt = msg.timestamp;\n return;\n case \"pool-acquire-samples\":\n this.handlePoolAcquireSamples(shard, msg);\n return;\n case \"drained\":\n this.handleDrained(msg);\n return;\n case \"log\":\n this.handleLog(shard, msg);\n return;\n default: {\n const exhaustive: never = msg;\n void exhaustive;\n return;\n }\n }\n }\n\n private handlePoolAcquireSamples(\n shard: ShardState,\n msg: ProjectionPoolAcquireSamplesMessage,\n ): void {\n if (!shard.poolInstrumentation) {\n return;\n }\n shard.poolInstrumentation.updateStats({\n size: msg.size,\n idle: msg.idle,\n waiting: msg.waiting,\n });\n shard.poolInstrumentation.pushSamples(msg.durations);\n }\n\n private handleReady(shard: ShardState, correlationId: string): void {\n shard.ready = true;\n const pending = this.initPromises.get(correlationId);\n if (!pending) {\n return;\n }\n this.initPromises.delete(correlationId);\n clearTimeout(pending.timer);\n pending.resolve();\n }\n\n private handleDrained(msg: ProjectionDrainedMessage): void {\n const pending = this.pendingDrains.get(msg.correlationId);\n if (!pending) {\n return;\n }\n pending.remaining.delete(msg.shardId);\n if (pending.remaining.size === 0) {\n this.pendingDrains.delete(msg.correlationId);\n clearTimeout(pending.timer);\n pending.resolve();\n }\n }\n\n private handleLog(\n shard: ShardState,\n msg: Extract<ProjectionWorkerMessage, { type: \"log\" }>,\n ): void {\n switch (msg.level) {\n case \"debug\":\n this.logger.debug(msg.message, ...msg.args);\n return;\n case \"info\":\n this.logger.info(msg.message, ...msg.args);\n return;\n case \"warn\":\n this.logger.warn(msg.message, ...msg.args);\n return;\n case \"error\":\n this.logger.error(msg.message, ...msg.args);\n return;\n default: {\n const exhaustive: never = msg.level;\n void exhaustive;\n }\n }\n void shard;\n }\n\n private handleTransportError(shard: ShardState, err: Error): void {\n this.logger.error(\n \"projection shard transport error @shardId: @error\",\n shard.shardId,\n err,\n );\n }\n\n private handleTransportExit(shard: ShardState, code: number): void {\n if (shard.ready) {\n this.logger.warn(\n \"projection shard exited unexpectedly @shardId code=@code\",\n shard.shardId,\n code,\n );\n }\n shard.ready = false;\n }\n\n private relayReadReady(event: JobReadReadyEvent): void {\n void this.hostBus\n .emit(ReactorEventTypes.JOB_READ_READY, event)\n .catch((err: unknown) =>\n this.logger.error(\n \"host JOB_READ_READY emit failed for job @jobId: @error\",\n event.jobId,\n err,\n ),\n );\n }\n\n private relayReadModelIndexed(event: ReadModelIndexedEvent): void {\n void this.hostBus\n .emit(ReactorEventTypes.READMODEL_INDEXED, event)\n .catch((err: unknown) =>\n this.logger.error(\n \"host READMODEL_INDEXED emit failed for job @jobId: @error\",\n event.jobId,\n err,\n ),\n );\n }\n\n private relayBatchCompleted(event: ReadModelBatchCompletedEvent): void {\n void this.hostBus\n .emit(ReactorEventTypes.READMODEL_BATCH_COMPLETED, event)\n .catch((err: unknown) =>\n this.logger.error(\n \"host READMODEL_BATCH_COMPLETED emit failed for job @jobId: @error\",\n event.jobId,\n err,\n ),\n );\n }\n}\n"],"mappings":";;;;AA6BA,MAAM,0BAA0B;AAChC,MAAM,4BAA4B;AAClC,MAAM,2BAA2B;AACjC,MAAM,yCAAyC;AAE/C,MAAM,mBAAmB;AACzB,MAAM,YAAY;AAElB,SAAS,UAAU,YAAoB,YAA4B;AACjE,KAAI,aAAa,EACf,OAAM,IAAI,MAAM,2CAA2C,WAAW,GAAG;CAE3E,IAAI,OAAO;AACX,MAAK,IAAI,IAAI,GAAG,IAAI,WAAW,QAAQ,KAAK;AAC1C,UAAQ,WAAW,WAAW,EAAE;AAChC,SAAO,KAAK,KAAK,MAAM,UAAU;;AAEnC,SAAQ,SAAS,KAAK;;;;;;;;;;;;;;;;;AAsExB,IAAa,yBAAb,MAAqE;CACnE,aAAoC,EAAE;CAEtC;CACA;CACA;CACA,SAAwC,EAAE;CAC1C,+BAAgC,IAAI,KAGjC;CACH,gCAAiC,IAAI,KAA2B;CAChE;CACA,YAAoB;CACpB,UAAkB;CAElB,YAAY,QAAsC;AAChD,MAAI,OAAO,aAAa,EACtB,OAAM,IAAI,MACR,wDAAwD,OAAO,WAAW,GAC3E;AAEH,OAAK,SAAS;AACd,OAAK,SAAS,YAAY,CAAC,WAAW,2BAA2B,CAAC;AAClE,OAAK,UAAU,OAAO;;CAGxB,MAAM,UAAyB;AAC7B,MAAI,KAAK,QACP;AAEF,OAAK,UAAU;EACf,MAAM,gBAAgB,KAAK,OAAO,iBAAiB;EACnD,MAAM,mBACJ,KAAK,OAAO,8BACZ;EAEF,MAAM,eAAgC,EAAE;AACxC,OAAK,IAAI,IAAI,GAAG,IAAI,KAAK,OAAO,YAAY,KAAK;GAC/C,MAAM,UAAU,oBAAoB;GACpC,MAAM,YAAY,KAAK,OAAO,QAAQ,GAAG,QAAQ;GACjD,MAAM,QAAoB;IACxB,YAAY;IACZ;IACA;IACA,OAAO;IACP,WAAW;IACX,aAAa;IACb,qBAAqB,KAAK,OAAO,uBAAuB;IACxD,YAAY,QAAQ,KAAK,oBAAoB,OAAO,IAAI;IACxD,UAAU,QAAQ,KAAK,qBAAqB,OAAO,IAAI;IACvD,SAAS,SAAS,KAAK,oBAAoB,OAAO,KAAK;IACxD;AACD,aAAU,GAAG,WAAW,MAAM,UAAU;AACxC,aAAU,GAAG,SAAS,MAAM,QAAQ;AACpC,aAAU,GAAG,QAAQ,MAAM,OAAO;AAClC,QAAK,OAAO,KAAK,MAAM;GAEvB,MAAM,gBAAgB,YAAY;GAClC,MAAM,cAAc,IAAI,SAAe,SAAS,WAAW;IACzD,MAAM,QAAQ,iBAAiB;AAC7B,UAAK,aAAa,OAAO,cAAc;AACvC,4BACE,IAAI,MACF,oBAAoB,QAAQ,+BAA+B,cAAc,IAC1E,CACF;OACA,cAAc;AACjB,SAAK,aAAa,IAAI,eAAe;KAAE;KAAS;KAAQ;KAAO,CAAC;KAChE;GAEF,MAAM,OAA8B;IAClC,MAAM;IACN;IACA;IACA,YAAY;IACZ,YAAY,KAAK,OAAO;IACxB,IAAI,KAAK,OAAO;IAChB,QAAQ,KAAK,OAAO;IACpB,eAAe,KAAK,OAAO;IAC3B,gBAAgB,KAAK,OAAO;IAC5B,4BAA4B;IAC7B;AACD,aAAU,YAAY,KAAK;AAC3B,gBAAa,KAAK,YAAY;;AAGhC,QAAM,QAAQ,IAAI,aAAa;AAC/B,OAAK,OAAO,KACV,iDACA,KAAK,OAAO,OACb;;CAGH,QAAc;AACZ,MAAI,KAAK,UACP;AAEF,OAAK,mBAAmB,KAAK,QAAQ,UACnC,kBAAkB,kBACjB,IAAY,UAA8B;AACzC,QAAK,gBAAgB,MAAM;IAE9B;AACD,OAAK,YAAY;;CAGnB,OAAa;AACX,MAAI,CAAC,KAAK,UACR;AAEF,MAAI,KAAK,kBAAkB;AACzB,QAAK,kBAAkB;AACvB,QAAK,mBAAmB,KAAA;;AAE1B,OAAK,YAAY;;CAGnB,MAAM,QAAuB;AAC3B,MAAI,KAAK,OAAO,WAAW,EACzB;EAEF,MAAM,iBACJ,KAAK,OAAO,kBAAkB;EAChC,MAAM,gBAAgB,YAAY;EAClC,MAAM,YAAY,IAAI,IAAI,KAAK,OAAO,KAAK,MAAM,EAAE,QAAQ,CAAC;EAC5D,MAAM,UAAU,IAAI,SAAe,SAAS,WAAW;GACrD,MAAM,QAAQ,iBAAiB;AAC7B,SAAK,cAAc,OAAO,cAAc;AACxC,2BACE,IAAI,MACF,0CAA0C,eAAe,iBAAiB,CACxE,GAAG,UACJ,CAAC,KAAK,KAAK,CAAC,GACd,CACF;MACA,eAAe;AAClB,QAAK,cAAc,IAAI,eAAe;IACpC;IACA;IACA;IACA;IACD,CAAC;IACF;AACF,OAAK,MAAM,SAAS,KAAK,OACvB,OAAM,UAAU,YAAY;GAAE,MAAM;GAAS;GAAe,CAAC;AAE/D,QAAM;;CAGR,gBAAwB;EACtB,IAAI,QAAQ;AACZ,OAAK,MAAM,SAAS,KAAK,OACvB,UAAS,MAAM;AAEjB,SAAO;;CAGT,iBAAqC;AACnC,SAAO,KAAK,OAAO,KAAK,WAAW;GACjC,SAAS,MAAM;GACf,OAAO,MAAM;GACb,WAAW,MAAM;GAClB,EAAE;;CAGL,MAAM,WAA0B;AAC9B,OAAK,MAAM;EACX,MAAM,UAAU,KAAK,OAAO,mBAAmB;EAC/C,MAAM,gBAAgB,YAAY;AAClC,OAAK,MAAM,SAAS,KAAK,OACvB,KAAI;GACF,MAAM,MAA+B;IACnC,MAAM;IACN;IACA;IACD;AACD,SAAM,UAAU,YAAY,IAAI;WACzB,KAAK;AACZ,QAAK,OAAO,KACV,sEACA,MAAM,SACN,IACD;;EAGL,MAAM,sBAAsB,KAAK,KAAK,GAAG;AACzC,SACE,KAAK,OAAO,MAAM,MAAM,EAAE,MAAM,IAChC,KAAK,KAAK,GAAG,oBAEb,OAAM,IAAI,SAAe,YAAY,WAAW,SAAS,GAAG,CAAC;AAE/D,OAAK,MAAM,SAAS,KAAK,QAAQ;AAC/B,OAAI;AACF,UAAM,MAAM,UAAU,WAAW;YAC1B,KAAK;AACZ,SAAK,OAAO,KACV,0DACA,MAAM,SACN,IACD;;AAEH,SAAM,UAAU,IAAI,WAAW,MAAM,UAAU;AAC/C,SAAM,UAAU,IAAI,SAAS,MAAM,QAAQ;AAC3C,SAAM,UAAU,IAAI,QAAQ,MAAM,OAAO;;AAE3C,OAAK,OAAO,SAAS;;CAGvB,gBAAwB,OAAiC;AACvD,MAAI,MAAM,WAAW,WAAW,EAC9B;EAEF,MAAM,aAAa,MAAM,WAAW,GAAI,QAAQ;EAChD,MAAM,QAAQ,UAAU,YAAY,KAAK,OAAO,OAAO;EACvD,MAAM,QAAQ,KAAK,OAAO;AAC1B,MAAI,CAAC,MAAM,OAAO;AAChB,QAAK,OAAO,KACV,oDACA,MACD;AACD;;AAEF,QAAM,UAAU,YAAY;GAC1B,MAAM;GACN,OAAO,MAAM;GACb,YAAY,MAAM;GAClB,SAAS,MAAM;GACf,uBAAuB,MAAM;GAC9B,CAAC;;CAGJ,oBACE,OACA,KACM;AACN,UAAQ,IAAI,MAAZ;GACE,KAAK;AACH,SAAK,YAAY,OAAO,IAAI,cAAc;AAC1C;GACF,KAAK;AACH,SAAK,eAAe;KAClB,OAAO,IAAI;KACX,YAAY,IAAI;KACjB,CAAC;AACF;GACF,KAAK;AACH,SAAK,sBAAsB;KACzB,OAAO,IAAI;KACX,eAAe,IAAI;KACnB,OAAO,IAAI;KACX,YAAY,IAAI;KAChB,gBAAgB,IAAI;KACpB,SAAS,IAAI;KACd,CAAC;AACF;GACF,KAAK;AACH,SAAK,oBAAoB;KACvB,OAAO,IAAI;KACX,WAAW,IAAI;KACf,qBAAqB,IAAI;KACzB,oBAAoB,IAAI;KACxB,gBAAgB,IAAI;KACpB,qBAAqB,IAAI;KAC1B,CAAC;AACF;GACF,KAAK;AACH,UAAM,YAAY,IAAI;AACtB,UAAM,cAAc,IAAI;AACxB;GACF,KAAK;AACH,SAAK,yBAAyB,OAAO,IAAI;AACzC;GACF,KAAK;AACH,SAAK,cAAc,IAAI;AACvB;GACF,KAAK;AACH,SAAK,UAAU,OAAO,IAAI;AAC1B;GACF,QAGE;;;CAKN,yBACE,OACA,KACM;AACN,MAAI,CAAC,MAAM,oBACT;AAEF,QAAM,oBAAoB,YAAY;GACpC,MAAM,IAAI;GACV,MAAM,IAAI;GACV,SAAS,IAAI;GACd,CAAC;AACF,QAAM,oBAAoB,YAAY,IAAI,UAAU;;CAGtD,YAAoB,OAAmB,eAA6B;AAClE,QAAM,QAAQ;EACd,MAAM,UAAU,KAAK,aAAa,IAAI,cAAc;AACpD,MAAI,CAAC,QACH;AAEF,OAAK,aAAa,OAAO,cAAc;AACvC,eAAa,QAAQ,MAAM;AAC3B,UAAQ,SAAS;;CAGnB,cAAsB,KAAqC;EACzD,MAAM,UAAU,KAAK,cAAc,IAAI,IAAI,cAAc;AACzD,MAAI,CAAC,QACH;AAEF,UAAQ,UAAU,OAAO,IAAI,QAAQ;AACrC,MAAI,QAAQ,UAAU,SAAS,GAAG;AAChC,QAAK,cAAc,OAAO,IAAI,cAAc;AAC5C,gBAAa,QAAQ,MAAM;AAC3B,WAAQ,SAAS;;;CAIrB,UACE,OACA,KACM;AACN,UAAQ,IAAI,OAAZ;GACE,KAAK;AACH,SAAK,OAAO,MAAM,IAAI,SAAS,GAAG,IAAI,KAAK;AAC3C;GACF,KAAK;AACH,SAAK,OAAO,KAAK,IAAI,SAAS,GAAG,IAAI,KAAK;AAC1C;GACF,KAAK;AACH,SAAK,OAAO,KAAK,IAAI,SAAS,GAAG,IAAI,KAAK;AAC1C;GACF,KAAK;AACH,SAAK,OAAO,MAAM,IAAI,SAAS,GAAG,IAAI,KAAK;AAC3C;GACF,QAC4B,KAAI;;;CAOpC,qBAA6B,OAAmB,KAAkB;AAChE,OAAK,OAAO,MACV,qDACA,MAAM,SACN,IACD;;CAGH,oBAA4B,OAAmB,MAAoB;AACjE,MAAI,MAAM,MACR,MAAK,OAAO,KACV,4DACA,MAAM,SACN,KACD;AAEH,QAAM,QAAQ;;CAGhB,eAAuB,OAAgC;AAChD,OAAK,QACP,KAAK,kBAAkB,gBAAgB,MAAM,CAC7C,OAAO,QACN,KAAK,OAAO,MACV,0DACA,MAAM,OACN,IACD,CACF;;CAGL,sBAA8B,OAAoC;AAC3D,OAAK,QACP,KAAK,kBAAkB,mBAAmB,MAAM,CAChD,OAAO,QACN,KAAK,OAAO,MACV,6DACA,MAAM,OACN,IACD,CACF;;CAGL,oBAA4B,OAA2C;AAChE,OAAK,QACP,KAAK,kBAAkB,2BAA2B,MAAM,CACxD,OAAO,QACN,KAAK,OAAO,MACV,qEACA,MAAM,OACN,IACD,CACF"}
1
+ {"version":3,"file":"projection-shard-manager-BnQKFqQd.js","names":[],"sources":["../src/projection/projection-shard-manager.ts"],"sourcesContent":["import { childLogger, type ILogger } from \"document-model\";\nimport { randomUUID } from \"node:crypto\";\nimport type { IEventBus } from \"../events/interfaces.js\";\nimport {\n ReactorEventTypes,\n type JobReadReadyEvent,\n type JobWriteReadyEvent,\n type ReadModelBatchCompletedEvent,\n type ReadModelIndexedEvent,\n type Unsubscribe,\n} from \"../events/types.js\";\nimport type {\n IReadModel,\n IReadModelCoordinator,\n} from \"../read-models/interfaces.js\";\nimport type { ForwardingPoolInstrumentation } from \"../storage/pool-instrumentation.js\";\nimport type {\n BuiltInReadModelKind,\n ChainDepthReport,\n DbConfig,\n ModelManifestEntry,\n ProjectionDrainedMessage,\n ProjectionInitMessage,\n ProjectionParentMessage,\n ProjectionPoolAcquireSamplesMessage,\n ProjectionWorkerMessage,\n} from \"./protocol.js\";\nimport type { IProjectionTransport } from \"./transport.js\";\n\nconst DEFAULT_INIT_TIMEOUT_MS = 30_000;\nconst DEFAULT_SHUTDOWN_GRACE_MS = 5_000;\nconst DEFAULT_DRAIN_TIMEOUT_MS = 30_000;\nconst DEFAULT_CHAIN_DEPTH_REPORT_INTERVAL_MS = 250;\n\nconst FNV_OFFSET_BASIS = 0x811c9dc5;\nconst FNV_PRIME = 0x01000193;\n\nfunction bucketFor(documentId: string, numWorkers: number): number {\n if (numWorkers < 1) {\n throw new Error(`bucketFor: numWorkers must be >= 1 (got ${numWorkers})`);\n }\n let hash = FNV_OFFSET_BASIS;\n for (let i = 0; i < documentId.length; i++) {\n hash ^= documentId.charCodeAt(i);\n hash = Math.imul(hash, FNV_PRIME);\n }\n return (hash >>> 0) % numWorkers;\n}\n\n/**\n * Factory that builds one projection-worker transport. Mirrors\n * `WorkerFactory` from the executor pool: lets tests inject fake\n * transports without spawning real worker threads.\n */\nexport type ProjectionWorkerFactory = (\n shardIndex: number,\n shardId: string,\n) => IProjectionTransport;\n\nexport type ProjectionShardManagerConfig = {\n shardCount: number;\n db: DbConfig;\n models: ModelManifestEntry[];\n preReadyKinds: BuiltInReadModelKind[];\n postReadyKinds: BuiltInReadModelKind[];\n factory: ProjectionWorkerFactory;\n logger: ILogger;\n hostBus: IEventBus;\n initTimeoutMs?: number;\n shutdownGraceMs?: number;\n drainTimeoutMs?: number;\n chainDepthReportIntervalMs?: number;\n /**\n * Host-side forwarding instrumentations indexed by shard index. The\n * manager routes each shard's `pool-acquire-samples` message to the\n * matching forwarder so the host's OpenTelemetry instrumentation records\n * acquire-wait latencies as if each shard's pg.Pool were local.\n */\n poolInstrumentations?: ForwardingPoolInstrumentation[];\n};\n\ntype ShardState = {\n shardIndex: number;\n shardId: string;\n transport: IProjectionTransport;\n ready: boolean;\n lastDepth: number;\n lastDepthAt: number;\n poolInstrumentation?: ForwardingPoolInstrumentation;\n onMessage: (msg: ProjectionWorkerMessage) => void;\n onError: (err: Error) => void;\n onExit: (code: number) => void;\n};\n\ntype PendingDrain = {\n resolve: () => void;\n reject: (err: Error) => void;\n remaining: Set<string>;\n timer: NodeJS.Timeout;\n};\n\n/**\n * Host-side coordinator for N sharded projection workers.\n *\n * Implements {@link IReadModelCoordinator} so it slots into the same\n * `readModelCoordinator` field on the reactor module as the in-process\n * {@link ReadModelCoordinator}. The host subscribes to JOB_WRITE_READY\n * exactly once; events are routed to a single shard by\n * `bucketFor(documentId, shardCount)`. Each worker maintains the\n * per-queueKey serial chain locally and forwards JOB_READ_READY and\n * READMODEL_* events back to the host for the rest of the reactor (sync\n * manager, awaiters, observers) to consume on the host bus.\n *\n * @see Sharded projection workers sub-feature brief\n * (Powerhouse board wiki id: eb26f01f-8f68-4918-a6f6-ac7a4679b533)\n */\nexport class ProjectionShardManager implements IReadModelCoordinator {\n readonly readModels: IReadModel[] = [];\n\n private readonly config: ProjectionShardManagerConfig;\n private readonly logger: ILogger;\n private readonly hostBus: IEventBus;\n private readonly shards: ShardState[] = [];\n private readonly initPromises = new Map<\n string,\n { resolve: () => void; reject: (err: Error) => void; timer: NodeJS.Timeout }\n >();\n private readonly pendingDrains = new Map<string, PendingDrain>();\n private hostSubscription?: Unsubscribe;\n private isRunning = false;\n private started = false;\n\n constructor(config: ProjectionShardManagerConfig) {\n if (config.shardCount < 1) {\n throw new Error(\n `ProjectionShardManager: shardCount must be >= 1 (got ${config.shardCount})`,\n );\n }\n this.config = config;\n this.logger = childLogger([\"reactor\", \"projection-shard-manager\"]);\n this.hostBus = config.hostBus;\n }\n\n async startup(): Promise<void> {\n if (this.started) {\n return;\n }\n this.started = true;\n const initTimeoutMs = this.config.initTimeoutMs ?? DEFAULT_INIT_TIMEOUT_MS;\n const reportIntervalMs =\n this.config.chainDepthReportIntervalMs ??\n DEFAULT_CHAIN_DEPTH_REPORT_INTERVAL_MS;\n\n const initPromises: Promise<void>[] = [];\n for (let i = 0; i < this.config.shardCount; i++) {\n const shardId = `projection-shard-${i}`;\n const transport = this.config.factory(i, shardId);\n const state: ShardState = {\n shardIndex: i,\n shardId,\n transport,\n ready: false,\n lastDepth: 0,\n lastDepthAt: 0,\n poolInstrumentation: this.config.poolInstrumentations?.[i],\n onMessage: (msg) => this.handleWorkerMessage(state, msg),\n onError: (err) => this.handleTransportError(state, err),\n onExit: (code) => this.handleTransportExit(state, code),\n };\n transport.on(\"message\", state.onMessage);\n transport.on(\"error\", state.onError);\n transport.on(\"exit\", state.onExit);\n this.shards.push(state);\n\n const correlationId = randomUUID();\n const initPromise = new Promise<void>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.initPromises.delete(correlationId);\n reject(\n new Error(\n `projection shard ${shardId} did not become ready within ${initTimeoutMs}ms`,\n ),\n );\n }, initTimeoutMs);\n this.initPromises.set(correlationId, { resolve, reject, timer });\n });\n\n const init: ProjectionInitMessage = {\n type: \"init\",\n correlationId,\n shardId,\n shardIndex: i,\n shardCount: this.config.shardCount,\n db: this.config.db,\n models: this.config.models,\n preReadyKinds: this.config.preReadyKinds,\n postReadyKinds: this.config.postReadyKinds,\n chainDepthReportIntervalMs: reportIntervalMs,\n };\n transport.postMessage(init);\n initPromises.push(initPromise);\n }\n\n await Promise.all(initPromises);\n this.logger.info(\n \"projection shard manager ready: @count shards\",\n this.shards.length,\n );\n }\n\n start(): void {\n if (this.isRunning) {\n return;\n }\n this.hostSubscription = this.hostBus.subscribe(\n ReactorEventTypes.JOB_WRITE_READY,\n (_t: number, event: JobWriteReadyEvent) => {\n this.routeWriteReady(event);\n },\n );\n this.isRunning = true;\n }\n\n stop(): void {\n if (!this.isRunning) {\n return;\n }\n if (this.hostSubscription) {\n this.hostSubscription();\n this.hostSubscription = undefined;\n }\n this.isRunning = false;\n }\n\n async drain(): Promise<void> {\n if (this.shards.length === 0) {\n return;\n }\n const drainTimeoutMs =\n this.config.drainTimeoutMs ?? DEFAULT_DRAIN_TIMEOUT_MS;\n const correlationId = randomUUID();\n const remaining = new Set(this.shards.map((s) => s.shardId));\n const promise = new Promise<void>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.pendingDrains.delete(correlationId);\n reject(\n new Error(\n `projection shards did not drain within ${drainTimeoutMs}ms (remaining: ${[\n ...remaining,\n ].join(\", \")})`,\n ),\n );\n }, drainTimeoutMs);\n this.pendingDrains.set(correlationId, {\n resolve,\n reject,\n remaining,\n timer,\n });\n });\n for (const shard of this.shards) {\n shard.transport.postMessage({ type: \"drain\", correlationId });\n }\n await promise;\n }\n\n getChainDepth(): number {\n let total = 0;\n for (const shard of this.shards) {\n total += shard.lastDepth;\n }\n return total;\n }\n\n getShardDepths(): ChainDepthReport[] {\n return this.shards.map((shard) => ({\n shardId: shard.shardId,\n depth: shard.lastDepth,\n timestamp: shard.lastDepthAt,\n }));\n }\n\n async shutdown(): Promise<void> {\n this.stop();\n const graceMs = this.config.shutdownGraceMs ?? DEFAULT_SHUTDOWN_GRACE_MS;\n const correlationId = randomUUID();\n for (const shard of this.shards) {\n try {\n const msg: ProjectionParentMessage = {\n type: \"shutdown\",\n correlationId,\n graceMs,\n };\n shard.transport.postMessage(msg);\n } catch (err) {\n this.logger.warn(\n \"projection shard postMessage(shutdown) failed for @shardId: @error\",\n shard.shardId,\n err,\n );\n }\n }\n const terminationDeadline = Date.now() + graceMs;\n while (\n this.shards.some((s) => s.ready) &&\n Date.now() < terminationDeadline\n ) {\n await new Promise<void>((resolve) => setTimeout(resolve, 25));\n }\n for (const shard of this.shards) {\n try {\n await shard.transport.terminate();\n } catch (err) {\n this.logger.warn(\n \"projection shard terminate failed for @shardId: @error\",\n shard.shardId,\n err,\n );\n }\n shard.transport.off(\"message\", shard.onMessage);\n shard.transport.off(\"error\", shard.onError);\n shard.transport.off(\"exit\", shard.onExit);\n }\n this.shards.length = 0;\n }\n\n private routeWriteReady(event: JobWriteReadyEvent): void {\n if (event.operations.length === 0) {\n return;\n }\n const documentId = event.operations[0]!.context.documentId;\n const index = bucketFor(documentId, this.shards.length);\n const shard = this.shards[index]!;\n if (!shard.ready) {\n this.logger.warn(\n \"dropping JOB_WRITE_READY: shard @index not ready\",\n index,\n );\n return;\n }\n shard.transport.postMessage({\n type: \"write-ready\",\n jobId: event.jobId,\n operations: event.operations,\n jobMeta: event.jobMeta,\n collectionMemberships: event.collectionMemberships,\n });\n }\n\n private handleWorkerMessage(\n shard: ShardState,\n msg: ProjectionWorkerMessage,\n ): void {\n switch (msg.type) {\n case \"ready\":\n this.handleReady(shard, msg.correlationId);\n return;\n case \"read-ready\":\n this.relayReadReady({\n jobId: msg.jobId,\n operations: msg.operations,\n });\n return;\n case \"readmodel-indexed\":\n this.relayReadModelIndexed({\n jobId: msg.jobId,\n readModelName: msg.readModelName,\n stage: msg.stage,\n durationMs: msg.durationMs,\n operationCount: msg.operationCount,\n success: msg.success,\n });\n return;\n case \"readmodel-batch-completed\":\n this.relayBatchCompleted({\n jobId: msg.jobId,\n batchSize: msg.batchSize,\n chainWaitDurationMs: msg.chainWaitDurationMs,\n preReadyDurationMs: msg.preReadyDurationMs,\n emitDurationMs: msg.emitDurationMs,\n postReadyDurationMs: msg.postReadyDurationMs,\n });\n return;\n case \"chain-depth\":\n shard.lastDepth = msg.depth;\n shard.lastDepthAt = msg.timestamp;\n return;\n case \"pool-acquire-samples\":\n this.handlePoolAcquireSamples(shard, msg);\n return;\n case \"drained\":\n this.handleDrained(msg);\n return;\n case \"log\":\n this.handleLog(shard, msg);\n return;\n default: {\n const exhaustive: never = msg;\n void exhaustive;\n return;\n }\n }\n }\n\n private handlePoolAcquireSamples(\n shard: ShardState,\n msg: ProjectionPoolAcquireSamplesMessage,\n ): void {\n if (!shard.poolInstrumentation) {\n return;\n }\n shard.poolInstrumentation.updateStats({\n size: msg.size,\n idle: msg.idle,\n waiting: msg.waiting,\n });\n shard.poolInstrumentation.pushSamples(msg.durations);\n }\n\n private handleReady(shard: ShardState, correlationId: string): void {\n shard.ready = true;\n const pending = this.initPromises.get(correlationId);\n if (!pending) {\n return;\n }\n this.initPromises.delete(correlationId);\n clearTimeout(pending.timer);\n pending.resolve();\n }\n\n private handleDrained(msg: ProjectionDrainedMessage): void {\n const pending = this.pendingDrains.get(msg.correlationId);\n if (!pending) {\n return;\n }\n pending.remaining.delete(msg.shardId);\n if (pending.remaining.size === 0) {\n this.pendingDrains.delete(msg.correlationId);\n clearTimeout(pending.timer);\n pending.resolve();\n }\n }\n\n private handleLog(\n shard: ShardState,\n msg: Extract<ProjectionWorkerMessage, { type: \"log\" }>,\n ): void {\n switch (msg.level) {\n case \"debug\":\n this.logger.debug(msg.message, ...msg.args);\n return;\n case \"info\":\n this.logger.info(msg.message, ...msg.args);\n return;\n case \"warn\":\n this.logger.warn(msg.message, ...msg.args);\n return;\n case \"error\":\n this.logger.error(msg.message, ...msg.args);\n return;\n default: {\n const exhaustive: never = msg.level;\n void exhaustive;\n }\n }\n void shard;\n }\n\n private handleTransportError(shard: ShardState, err: Error): void {\n this.logger.error(\n \"projection shard transport error @shardId: @error\",\n shard.shardId,\n err,\n );\n }\n\n private handleTransportExit(shard: ShardState, code: number): void {\n if (shard.ready) {\n this.logger.warn(\n \"projection shard exited unexpectedly @shardId code=@code\",\n shard.shardId,\n code,\n );\n }\n shard.ready = false;\n }\n\n private relayReadReady(event: JobReadReadyEvent): void {\n void this.hostBus\n .emit(ReactorEventTypes.JOB_READ_READY, event)\n .catch((err: unknown) =>\n this.logger.error(\n \"host JOB_READ_READY emit failed for job @jobId: @error\",\n event.jobId,\n err,\n ),\n );\n }\n\n private relayReadModelIndexed(event: ReadModelIndexedEvent): void {\n void this.hostBus\n .emit(ReactorEventTypes.READMODEL_INDEXED, event)\n .catch((err: unknown) =>\n this.logger.error(\n \"host READMODEL_INDEXED emit failed for job @jobId: @error\",\n event.jobId,\n err,\n ),\n );\n }\n\n private relayBatchCompleted(event: ReadModelBatchCompletedEvent): void {\n void this.hostBus\n .emit(ReactorEventTypes.READMODEL_BATCH_COMPLETED, event)\n .catch((err: unknown) =>\n this.logger.error(\n \"host READMODEL_BATCH_COMPLETED emit failed for job @jobId: @error\",\n event.jobId,\n err,\n ),\n );\n }\n}\n"],"mappings":"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"}
@@ -24,9 +24,10 @@ const ReactorEventTypes = {
24
24
  JOB_READ_READY: 10004,
25
25
  JOB_FAILED: 10005,
26
26
  READMODEL_BATCH_COMPLETED: 10006,
27
- READMODEL_INDEXED: 10007
27
+ READMODEL_INDEXED: 10007,
28
+ MODEL_LOADED: 10008
28
29
  };
29
30
  //#endregion
30
31
  export { ReactorEventTypes as n, EventBusAggregateError as t };
31
32
 
32
- //# sourceMappingURL=types-CxSpmNGK.js.map
33
+ //# sourceMappingURL=types-DMKLa0Ok.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-DMKLa0Ok.js","names":[],"sources":["../src/events/types.ts"],"sourcesContent":["import type { OperationWithContext } from \"@powerhousedao/shared/document-model\";\nimport type { Job } from \"../queue/types.js\";\nimport type { JobMeta } from \"../shared/types.js\";\n\n/**\n * Describes a function to unsubscribe from an event.\n */\nexport type Unsubscribe = () => void;\n\n/**\n * A subscriber is a function that is called when an event is emitted.\n *\n * It is passed the event type and the data.\n * It can return a promise or a value.\n * If it returns a promise, the event bus will wait for the promise to resolve before calling the next subscriber.\n * If it throws an error, the event bus will reject with an aggregate error of all errors.\n *\n * @param type - The type of event to emit.\n * @param data - The data to pass to the subscriber.\n */\nexport type Subscriber = (type: number, data: any) => void | Promise<void>;\n\n/**\n * Custom error class that aggregates multiple errors from event subscribers.\n */\nexport class EventBusAggregateError extends Error {\n public readonly errors: any[];\n\n constructor(errors: unknown[]) {\n const message = `EventBus emit failed with ${errors.length} error(s): ${errors\n .map((e) => {\n if (e && typeof e === \"object\" && \"message\" in e) {\n return (e as Error).message;\n }\n return String(e);\n })\n .join(\"; \")}`;\n super(message);\n\n this.name = \"EventBusAggregateError\";\n this.errors = errors;\n }\n}\n\n/**\n * Event types for reactor lifecycle events.\n */\nexport const ReactorEventTypes = {\n JOB_PENDING: 10001,\n JOB_RUNNING: 10002,\n JOB_WRITE_READY: 10003,\n JOB_READ_READY: 10004,\n JOB_FAILED: 10005,\n READMODEL_BATCH_COMPLETED: 10006,\n READMODEL_INDEXED: 10007,\n MODEL_LOADED: 10008,\n} as const;\n\n/**\n * Stage within ReadModelCoordinator.runChain. Used as a dimension on\n * stage-attribution events and histograms.\n */\nexport type ReadModelStage = \"pre_ready\" | \"emit\" | \"post_ready\";\n\n/**\n * Stage in which an individual read model ran. The emit stage does not\n * involve a read model so it is excluded from this discriminant.\n */\nexport type ReadModelIndexingStage = \"pre_ready\" | \"post_ready\";\n\n/**\n * Emitted after the resolver loads a model on demand, so peers load it too.\n */\nexport type ModelLoadedEvent = {\n documentType: string;\n};\n\n/**\n * Event emitted when a job is registered and waiting to be executed.\n */\nexport type JobPendingEvent = {\n jobId: string;\n jobMeta: JobMeta;\n};\n\n/**\n * Event emitted when a job starts executing.\n */\nexport type JobRunningEvent = {\n jobId: string;\n jobMeta: JobMeta;\n};\n\n/**\n * Event emitted when operations are written to IOperationStore.\n * Contains the operations directly to avoid round-trip queries.\n */\nexport type JobWriteReadyEvent = {\n jobId: string;\n operations: OperationWithContext[];\n jobMeta: JobMeta;\n /**\n * Maps documentId to the collection IDs it belongs to.\n * Used by SyncManager to route operations only to remotes\n * whose collection contains the document.\n */\n collectionMemberships?: Record<string, string[]>;\n};\n\n/**\n * Event emitted after all read models have finished processing operations.\n * This event fires after JOB_WRITE_READY and guarantees that:\n * - All read models (DocumentView, DocumentIndexer, etc.) have indexed the operations\n * - All consistency trackers have been updated with the new operation indices\n * - Queries without consistency tokens will now see the updated data\n *\n * This event is useful for:\n * - Test synchronization (knowing when read models are ready)\n * - Observability (measuring read model latency)\n * - Event-driven workflows (triggering downstream processes)\n */\nexport type JobReadReadyEvent = {\n jobId: string;\n operations: OperationWithContext[];\n};\n\n/**\n * Event emitted when a job fails with an unrecoverable error.\n * This event allows the JobAwaiter and other subscribers to react to job failures\n * without polling.\n */\nexport type JobFailedEvent = {\n jobId: string;\n error: Error;\n job?: Job;\n};\n\n/**\n * Event emitted once per batch processed by ReadModelCoordinator.runChain,\n * after all three projection stages complete. Carries per-stage wall times,\n * the chain wait time, and the batch size so projection cost can be\n * attributed by an observer.\n */\nexport type ReadModelBatchCompletedEvent = {\n jobId: string;\n batchSize: number;\n chainWaitDurationMs: number;\n preReadyDurationMs: number;\n emitDurationMs: number;\n postReadyDurationMs: number;\n};\n\n/**\n * Event emitted once per individual read model per batch and stage, after\n * that read model's indexOperations call resolves (or rejects). Lets\n * observers attribute projection cost to a specific read model.\n */\nexport type ReadModelIndexedEvent = {\n jobId: string;\n readModelName: string;\n stage: ReadModelIndexingStage;\n durationMs: number;\n operationCount: number;\n success: boolean;\n};\n"],"mappings":";;;;AAyBA,IAAa,yBAAb,cAA4C,MAAM;CAChD;CAEA,YAAY,QAAmB;EAC7B,MAAM,UAAU,6BAA6B,OAAO,OAAO,aAAa,OACrE,KAAK,MAAM;AACV,OAAI,KAAK,OAAO,MAAM,YAAY,aAAa,EAC7C,QAAQ,EAAY;AAEtB,UAAO,OAAO,EAAE;IAChB,CACD,KAAK,KAAK;AACb,QAAM,QAAQ;AAEd,OAAK,OAAO;AACZ,OAAK,SAAS;;;;;;AAOlB,MAAa,oBAAoB;CAC/B,aAAa;CACb,aAAa;CACb,iBAAiB;CACjB,gBAAgB;CAChB,YAAY;CACZ,2BAA2B;CAC3B,mBAAmB;CACnB,cAAc;CACf"}
@@ -1,4 +1,4 @@
1
- import { M as __exportAll } from "./drive-container-types-BxnXaOAp.js";
1
+ import { M as __exportAll } from "./drive-container-types-DpJp2AmE.js";
2
2
  import { n as errorToInfo, r as sanitizeArg, t as createForwardingLogger } from "./forwarding-logger-BBkMSxuJ.js";
3
3
  //#region src/executor/worker/index.ts
4
4
  var worker_exports = /* @__PURE__ */ __exportAll({
@@ -19,4 +19,4 @@ const workerEntryPath = resolveWorkerEntryPath();
19
19
  //#endregion
20
20
  export { worker_exports as n, workerEntryPath as t };
21
21
 
22
- //# sourceMappingURL=worker-XYrQaEmt.js.map
22
+ //# sourceMappingURL=worker-DBJOv8Gp.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"worker-XYrQaEmt.js","names":[],"sources":["../src/executor/worker/index.ts"],"sourcesContent":["export { createForwardingLogger } from \"./forwarding-logger.js\";\nexport { errorToInfo, sanitizeArg } from \"./sanitize.js\";\n\nfunction resolveWorkerEntryPath(): string {\n const url = new URL(\"./entry.js\", import.meta.url);\n if (url.protocol !== \"file:\") {\n return url.href;\n }\n let pathname = decodeURIComponent(url.pathname);\n if (/^\\/[A-Za-z]:[/\\\\]/.test(pathname)) {\n pathname = pathname.slice(1);\n }\n return pathname;\n}\n\n/** Absolute path to the worker entry script for use with `new Worker(workerEntryPath)`. */\nexport const workerEntryPath = resolveWorkerEntryPath();\n"],"mappings":";;;;;;;;;AAGA,SAAS,yBAAiC;CACxC,MAAM,MAAM,IAAI,IAAI,cAAc,OAAO,KAAK,IAAI;AAClD,KAAI,IAAI,aAAa,QACnB,QAAO,IAAI;CAEb,IAAI,WAAW,mBAAmB,IAAI,SAAS;AAC/C,KAAI,oBAAoB,KAAK,SAAS,CACpC,YAAW,SAAS,MAAM,EAAE;AAE9B,QAAO;;;AAIT,MAAa,kBAAkB,wBAAwB"}
1
+ {"version":3,"file":"worker-DBJOv8Gp.js","names":[],"sources":["../src/executor/worker/index.ts"],"sourcesContent":["export { createForwardingLogger } from \"./forwarding-logger.js\";\nexport { errorToInfo, sanitizeArg } from \"./sanitize.js\";\n\nfunction resolveWorkerEntryPath(): string {\n const url = new URL(\"./entry.js\", import.meta.url);\n if (url.protocol !== \"file:\") {\n return url.href;\n }\n let pathname = decodeURIComponent(url.pathname);\n if (/^\\/[A-Za-z]:[/\\\\]/.test(pathname)) {\n pathname = pathname.slice(1);\n }\n return pathname;\n}\n\n/** Absolute path to the worker entry script for use with `new Worker(workerEntryPath)`. */\nexport const workerEntryPath = resolveWorkerEntryPath();\n"],"mappings":";;;;;;;;;AAGA,SAAS,yBAAiC;CACxC,MAAM,MAAM,IAAI,IAAI,cAAc,OAAO,KAAK,IAAI;AAClD,KAAI,IAAI,aAAa,QACnB,QAAO,IAAI;CAEb,IAAI,WAAW,mBAAmB,IAAI,SAAS;AAC/C,KAAI,oBAAoB,KAAK,SAAS,CACpC,YAAW,SAAS,MAAM,EAAE;AAE9B,QAAO;;;AAIT,MAAa,kBAAkB,wBAAwB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@powerhousedao/reactor",
3
- "version": "6.2.1",
3
+ "version": "6.2.2-dev.0",
4
4
  "description": "Core Powerhouse reactor — stores documents, resolves conflicts, and reprocesses operation histories. Supports local, cloud, and decentralized storage backends.",
5
5
  "repository": {
6
6
  "url": "https://github.com/powerhouse-inc/powerhouse",
@@ -42,8 +42,8 @@
42
42
  "@electric-sql/pglite": "0.3.15",
43
43
  "pg": "8.18.0",
44
44
  "@sindresorhus/fnv1a": "3.1.0",
45
- "@powerhousedao/shared": "6.2.1",
46
- "document-model": "6.2.1"
45
+ "@powerhousedao/shared": "6.2.2-dev.0",
46
+ "document-model": "6.2.2-dev.0"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/pg": "8.16.0",
@@ -54,7 +54,7 @@
54
54
  "vitest": "4.1.1",
55
55
  "@types/bun": "1.3.8",
56
56
  "tsdown": "0.21.1",
57
- "@powerhousedao/pglite-fs": "6.2.1"
57
+ "@powerhousedao/pglite-fs": "6.2.2-dev.0"
58
58
  },
59
59
  "homepage": "https://academy.vetra.io/academy/Reference/Reactor/WorkingWithTheReactor",
60
60
  "scripts": {
@@ -1 +0,0 @@
1
- {"version":3,"file":"document-indexer-DLEyUQxU.js","names":["uuidv4","uuidv4"],"sources":["../src/read-models/base-read-model.ts","../src/read-models/coordinator.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 type { IReadModel } from \"./interfaces.js\";\nimport type { DocumentViewDatabase } from \"./types.js\";\n\nexport type BaseReadModelConfig = {\n readModelId: string;\n rebuildStateOnInit: boolean;\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 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 }\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 const missedOperations = await this.operationIndex.getSinceOrdinal(\n this.lastOrdinal,\n );\n\n if (missedOperations.results.length > 0) {\n const ops = this.config.rebuildStateOnInit\n ? await this.rebuildStateForOperations(missedOperations.results)\n : missedOperations.results;\n await this.indexOperations(ops);\n }\n } else {\n await this.initializeState();\n const allOperations = await this.operationIndex.getSinceOrdinal(0);\n\n if (allOperations.results.length > 0) {\n const ops = this.config.rebuildStateOnInit\n ? await this.rebuildStateForOperations(allOperations.results)\n : allOperations.results;\n await this.indexOperations(ops);\n }\n }\n }\n\n /**\n * Template method: runs domain-specific commitOperations, then persists\n * state and updates consistency tracking.\n */\n async indexOperations(items: OperationWithContext[]): Promise<void> {\n if (items.length === 0) return;\n\n await this.commitOperations(items);\n\n await this.db.transaction().execute(async (trx) => {\n await this.saveState(trx, items);\n });\n\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 const maxOrdinal = Math.max(...items.map((item) => item.context.ordinal));\n this.lastOrdinal = maxOrdinal;\n\n await trx\n .updateTable(\"ViewState\")\n .set({\n lastOrdinal: maxOrdinal,\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","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 { IReadModel, IReadModelCoordinator } 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 IReadModelCoordinator {\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 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 {\n OperationWithContext,\n PHDocument,\n PHDocumentHeader,\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 type {\n ConsistencyToken,\n PagedResults,\n PagingOptions,\n} from \"../shared/types.js\";\nimport type {\n DocumentRevisions,\n IDocumentView,\n IOperationStore,\n ViewFilter,\n} from \"../storage/interfaces.js\";\nimport type { Database as StorageDatabase } from \"../storage/kysely/types.js\";\nimport { BaseReadModel } from \"./base-read-model.js\";\nimport type {\n DocumentViewDatabase,\n InsertableDocumentSnapshot,\n} from \"./types.js\";\n\ntype Database = StorageDatabase & DocumentViewDatabase;\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 ) {\n super(\n db as unknown as Kysely<DocumentViewDatabase>,\n operationIndex,\n writeCache,\n consistencyTracker,\n { readModelId: \"document-view\", rebuildStateOnInit: true },\n );\n this._db = db;\n }\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 { documentId, scope, branch, documentType, resultingState } =\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 // 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 operationType !== \"CREATE_DOCUMENT\" &&\n operationType !== \"UPGRADE_DOCUMENT\" &&\n operationType !== \"DELETE_DOCUMENT\" &&\n scope !== \"header\";\n\n if (operationType === \"DELETE_DOCUMENT\") {\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 lastUpdatedAt: now,\n })\n .where(\"documentId\", \"=\", documentId)\n .where(\"branch\", \"=\", branch)\n .execute();\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 const scopeStatesToIndex: Array<[string, unknown]> = [];\n\n for (const [scopeName, scopeState] of Object.entries(fullState)) {\n if (scopeName === \"header\") {\n scopeStatesToIndex.push([scopeName, scopeState]);\n continue;\n }\n\n if (scopeName === scope) {\n scopeStatesToIndex.push([scopeName, scopeState]);\n continue;\n }\n\n const existingSnapshot = await trx\n .selectFrom(\"DocumentSnapshot\")\n .select(\"scope\")\n .where(\"documentId\", \"=\", documentId)\n .where(\"scope\", \"=\", scopeName)\n .where(\"branch\", \"=\", branch)\n .executeTakeFirst();\n\n if (!existingSnapshot) {\n scopeStatesToIndex.push([scopeName, scopeState]);\n }\n }\n\n scopesToIndex = scopeStatesToIndex;\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 const existingSnapshot = await trx\n .selectFrom(\"DocumentSnapshot\")\n .selectAll()\n .where(\"documentId\", \"=\", documentId)\n .where(\"scope\", \"=\", scopeName)\n .where(\"branch\", \"=\", branch)\n .executeTakeFirst();\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 (preserveHeaderMeta && 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 if (slug && slug !== documentId) {\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 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 .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 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 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 snapshots = await this._db\n .selectFrom(\"DocumentSnapshot\")\n .select([\"documentId\"])\n .where(\"documentId\", \"in\", documentIds)\n .where(\"isDeleted\", \"=\", false)\n .distinct()\n .execute();\n\n const existingIds = new Set(snapshots.map((s) => s.documentId));\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 .where(\"isDeleted\", \"=\", false);\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 state[snapshot.scope] = 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 state[snapshot.scope] = 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 documents: PHDocument[] = [];\n const processedDocumentIds = new Set<string>();\n const allDocumentIds: string[] = [];\n\n const snapshots = await this._db\n .selectFrom(\"DocumentSnapshot\")\n .selectAll()\n .where(\"documentType\", \"=\", type)\n .where(\"branch\", \"=\", branch)\n .where(\"isDeleted\", \"=\", false)\n .orderBy(\"lastUpdatedAt\", \"desc\")\n .execute();\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n for (const snapshot of snapshots) {\n if (processedDocumentIds.has(snapshot.documentId)) {\n continue;\n }\n\n processedDocumentIds.add(snapshot.documentId);\n allDocumentIds.push(snapshot.documentId);\n }\n\n const docsToFetch = allDocumentIds.slice(startIndex, startIndex + limit);\n\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 hasMore = allDocumentIds.length > startIndex + limit;\n const nextCursor = hasMore ? String(startIndex + limit) : undefined;\n\n return {\n results: documents,\n options: paging || { cursor: \"0\", limit: 100 },\n nextCursor,\n totalCount: allDocumentIds.length,\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 const idCheckPromise = this._db\n .selectFrom(\"DocumentSnapshot\")\n .select(\"documentId\")\n .where(\"documentId\", \"=\", identifier)\n .where(\"branch\", \"=\", branch)\n .where(\"isDeleted\", \"=\", false)\n .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","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 { BaseReadModel } from \"../../read-models/base-read-model.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\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 ) {\n super(\n db as unknown as Kysely<DocumentViewDatabase>,\n operationIndex,\n writeCache,\n consistencyTracker,\n { readModelId: \"document-indexer\", rebuildStateOnInit: false },\n );\n this._db = db;\n }\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 } = 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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"}