@powerhousedao/reactor 6.2.3-dev.2 → 6.2.3-dev.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{build-worker-executor-DnU3ZU4H.js → build-worker-executor-DUjyF4NM.js} +2 -2
- package/dist/{build-worker-executor-DnU3ZU4H.js.map → build-worker-executor-DUjyF4NM.js.map} +1 -1
- package/dist/{document-indexer-C5Gsa1B3.js → document-indexer-C1oY6CaR.js} +169 -21
- package/dist/document-indexer-C1oY6CaR.js.map +1 -0
- package/dist/{drive-container-types-yZrksiJR.js → drive-container-types-CS5IxDiA.js} +174 -67
- package/dist/drive-container-types-CS5IxDiA.js.map +1 -0
- package/dist/entry.js +2 -2
- package/dist/index.d.ts +419 -78
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +355 -43
- package/dist/index.js.map +1 -1
- package/dist/projection-entry.js +8 -8
- package/dist/projection-entry.js.map +1 -1
- package/dist/{projection-shard-manager-CPQc5XHL.js → projection-shard-manager-CUo4bF4O.js} +3 -2
- package/dist/projection-shard-manager-CUo4bF4O.js.map +1 -0
- package/dist/types-DMKLa0Ok.js.map +1 -1
- package/dist/{worker-HPysBfhx.js → worker-g03lNM42.js} +2 -2
- package/dist/{worker-HPysBfhx.js.map → worker-g03lNM42.js.map} +1 -1
- package/package.json +4 -4
- package/dist/document-indexer-C5Gsa1B3.js.map +0 -1
- package/dist/drive-container-types-yZrksiJR.js.map +0 -1
- package/dist/projection-shard-manager-CPQc5XHL.js.map +0 -1
package/dist/projection-entry.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { _ as DocumentMetaCache, c as KyselyKeyframeStore, g as KyselyOperationIndex, h as KyselyWriteCache, l as DocumentModelRegistry, m as EventBus, n as REACTOR_SCHEMA, o as instrumentPgPool, s as KyselyOperationStore, v as CollectionMembershipCache } from "./drive-container-types-
|
|
1
|
+
import { _ as DocumentMetaCache, c as KyselyKeyframeStore, g as KyselyOperationIndex, h as KyselyWriteCache, l as DocumentModelRegistry, m as EventBus, n as REACTOR_SCHEMA, o as instrumentPgPool, s as KyselyOperationStore, v as CollectionMembershipCache } from "./drive-container-types-CS5IxDiA.js";
|
|
2
2
|
import { n as ReactorEventTypes } from "./types-DMKLa0Ok.js";
|
|
3
|
-
import {
|
|
3
|
+
import { a as KyselyDocumentView, c as ReadModelCoordinator, i as DeletedDocumentRead, n as ConsistencyTracker, t as KyselyDocumentIndexer } from "./document-indexer-C1oY6CaR.js";
|
|
4
4
|
import { n as errorToInfo, t as createForwardingLogger } from "./forwarding-logger-BBkMSxuJ.js";
|
|
5
|
-
import { n as defaultLoadFactory } from "./build-worker-executor-
|
|
5
|
+
import { n as defaultLoadFactory } from "./build-worker-executor-DUjyF4NM.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
|
|
@@ -22,10 +22,10 @@ async function loadModelManifest(entries, loadFactory, registry, logger) {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
|
-
function instantiateReadModel(kind, database, operationStore, operationIndex, writeCache) {
|
|
25
|
+
function instantiateReadModel(kind, database, operationStore, operationIndex, writeCache, indexing) {
|
|
26
26
|
switch (kind) {
|
|
27
|
-
case "document-view": return new KyselyDocumentView(database, operationStore, operationIndex, writeCache, new ConsistencyTracker(),
|
|
28
|
-
case "document-indexer": return new KyselyDocumentIndexer(database, operationIndex, writeCache, new ConsistencyTracker());
|
|
27
|
+
case "document-view": return new KyselyDocumentView(database, operationStore, operationIndex, writeCache, new ConsistencyTracker(), DeletedDocumentRead.NotFound, indexing);
|
|
28
|
+
case "document-indexer": return new KyselyDocumentIndexer(database, operationIndex, writeCache, new ConsistencyTracker(), indexing);
|
|
29
29
|
default: {
|
|
30
30
|
const exhaustive = kind;
|
|
31
31
|
throw new Error(`unknown built-in read model kind: ${String(exhaustive)}`);
|
|
@@ -60,8 +60,8 @@ async function buildProjectionStack(options) {
|
|
|
60
60
|
const operationIndex = new KyselyOperationIndex(database);
|
|
61
61
|
await new DocumentMetaCache(operationStore, { maxDocuments: 1e3 }).startup();
|
|
62
62
|
new CollectionMembershipCache(operationIndex);
|
|
63
|
-
const preReady = init.preReadyKinds.map((kind) => instantiateReadModel(kind, database, operationStore, operationIndex, writeCache));
|
|
64
|
-
const postReady = init.postReadyKinds.map((kind) => instantiateReadModel(kind, database, operationStore, operationIndex, writeCache));
|
|
63
|
+
const preReady = init.preReadyKinds.map((kind) => instantiateReadModel(kind, database, operationStore, operationIndex, writeCache, init.indexing));
|
|
64
|
+
const postReady = init.postReadyKinds.map((kind) => instantiateReadModel(kind, database, operationStore, operationIndex, writeCache, init.indexing));
|
|
65
65
|
await initReadModels([...preReady, ...postReady], logger);
|
|
66
66
|
const eventBus = new EventBus();
|
|
67
67
|
const subscriptions = [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"projection-entry.js","names":[],"sources":["../src/projection/projection-worker/build-projection-stack.ts","../src/projection/projection-worker/run-projection-worker.ts","../src/projection/projection-worker/projection-entry.ts"],"sourcesContent":["/**\n * In-worker projection stack builder.\n *\n * Mirrors {@link buildWorkerExecutor} but for the projection-side\n * read models. The worker owns one full copy of the storage stack — read\n * cache, operation index, document-meta cache — bound to its own\n * pg.Pool/Kysely, plus an in-process `ReadModelCoordinator` that\n * subscribes to a local `EventBus`.\n *\n * The host relays JOB_WRITE_READY into the local bus by calling\n * `relayWriteReady`, and forwards the local bus's JOB_READ_READY plus\n * READMODEL_* events back to the host bus via the IPC bridge.\n */\n\nimport type { DocumentModelModule } from \"@powerhousedao/shared/document-model\";\nimport type { ILogger } from \"document-model\";\nimport type { Kysely } from \"kysely\";\nimport { CollectionMembershipCache } from \"../../cache/collection-membership-cache.js\";\nimport { DocumentMetaCache } from \"../../cache/document-meta-cache.js\";\nimport { KyselyOperationIndex } from \"../../cache/kysely-operation-index.js\";\nimport { KyselyWriteCache } from \"../../cache/kysely-write-cache.js\";\nimport type { WriteCacheConfig } from \"../../cache/write-cache-types.js\";\nimport type { Database } from \"../../core/types.js\";\nimport { EventBus } from \"../../events/event-bus.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 {\n defaultLoadFactory,\n type BuildWorkerExecutorOptions,\n} from \"../../executor/worker/build-worker-executor.js\";\nimport type {\n FactorySpec,\n ModelManifestEntry,\n} from \"../../executor/worker/protocol.js\";\nimport { ReadModelCoordinator } from \"../../read-models/coordinator.js\";\nimport { KyselyDocumentView } from \"../../read-models/document-view.js\";\nimport type { IReadModel } from \"../../read-models/interfaces.js\";\nimport { DocumentModelRegistry } from \"../../registry/implementation.js\";\nimport { ConsistencyTracker } from \"../../shared/consistency-tracker.js\";\nimport {\n KyselyDocumentIndexer,\n type IndexerDatabase,\n} from \"../../storage/kysely/document-indexer.js\";\nimport { KyselyKeyframeStore } from \"../../storage/kysely/keyframe-store.js\";\nimport { KyselyOperationStore } from \"../../storage/kysely/store.js\";\nimport type { Database as StorageDatabase } from \"../../storage/kysely/types.js\";\nimport { REACTOR_SCHEMA } from \"../../storage/migrations/migrator.js\";\nimport type {\n BuiltInReadModelKind,\n ProjectionInitMessage,\n} from \"../protocol.js\";\n\nexport type ProjectionStackEvents = {\n onReadReady: (event: JobReadReadyEvent) => void;\n onReadModelIndexed: (event: ReadModelIndexedEvent) => void;\n onBatchCompleted: (event: ReadModelBatchCompletedEvent) => void;\n};\n\nexport type ProjectionStack = {\n registry: DocumentModelRegistry;\n coordinator: ReadModelCoordinator;\n eventBus: EventBus;\n relayWriteReady(event: JobWriteReadyEvent): Promise<void>;\n getChainDepth(): number;\n drain(): Promise<void>;\n shutdown(): Promise<void>;\n};\n\nexport type BuildProjectionStackOptions = {\n init: ProjectionInitMessage;\n database: Kysely<Database>;\n logger: ILogger;\n events: ProjectionStackEvents;\n loadFactory?: BuildWorkerExecutorOptions[\"loadFactory\"];\n driveContainerTypes?: ReadonlySet<string>;\n};\n\nasync function loadModelManifest(\n entries: ModelManifestEntry[],\n loadFactory: (spec: FactorySpec) => Promise<unknown>,\n registry: DocumentModelRegistry,\n logger: ILogger,\n): Promise<void> {\n for (const entry of entries) {\n let module: DocumentModelModule;\n try {\n module = (await loadFactory(entry.spec)) as DocumentModelModule;\n } catch (error) {\n logger.error(\n \"projection worker failed to load document model: @entry @error\",\n entry,\n error,\n );\n throw error;\n }\n const [result] = registry.registerModules(module);\n if (result.status === \"error\") {\n logger.error(\n \"projection worker failed to register document model: @entry @error\",\n entry,\n result.error,\n );\n throw result.error;\n }\n }\n}\n\nfunction instantiateReadModel(\n kind: BuiltInReadModelKind,\n database: Kysely<Database>,\n operationStore: KyselyOperationStore,\n operationIndex: KyselyOperationIndex,\n writeCache: KyselyWriteCache,\n): IReadModel {\n switch (kind) {\n case \"document-view\": {\n return new KyselyDocumentView(\n // @ts-expect-error - Database superset\n database,\n operationStore,\n operationIndex,\n writeCache,\n new ConsistencyTracker(),\n // The init payload carries no feature flags, so this view keeps hiding a\n // deleted document. Read-side only, so it cannot diverge state.\n false,\n );\n }\n case \"document-indexer\": {\n return new KyselyDocumentIndexer(\n database as unknown as Kysely<IndexerDatabase>,\n operationIndex,\n writeCache,\n new ConsistencyTracker(),\n );\n }\n default: {\n const exhaustive: never = kind;\n throw new Error(\n `unknown built-in read model kind: ${String(exhaustive)}`,\n );\n }\n }\n}\n\nasync function initReadModels(\n models: IReadModel[],\n logger: ILogger,\n): Promise<void> {\n for (const model of models) {\n const maybeInit = model as IReadModel & { init?: () => Promise<void> };\n if (typeof maybeInit.init !== \"function\") {\n continue;\n }\n try {\n await maybeInit.init();\n } catch (error) {\n logger.error(\n \"projection worker read model init failed: @name @error\",\n model.name,\n error,\n );\n throw error;\n }\n }\n}\n\nexport async function buildProjectionStack(\n options: BuildProjectionStackOptions,\n): Promise<ProjectionStack> {\n const { init, database: baseDatabase, logger, events } = options;\n const loadFactory = options.loadFactory ?? defaultLoadFactory;\n\n const registry = new DocumentModelRegistry();\n await loadModelManifest(init.models, loadFactory, registry, logger);\n\n const database = baseDatabase.withSchema(REACTOR_SCHEMA);\n const operationStore = new KyselyOperationStore(\n database as unknown as Kysely<StorageDatabase>,\n );\n const keyframeStore = new KyselyKeyframeStore(\n database as unknown as Kysely<StorageDatabase>,\n );\n\n const cacheConfig: WriteCacheConfig = {\n maxDocuments: 100,\n ringBufferSize: 10,\n keyframeInterval: 10,\n };\n const writeCache = new KyselyWriteCache(\n keyframeStore,\n operationStore,\n registry,\n cacheConfig,\n );\n await writeCache.startup();\n\n const operationIndex = new KyselyOperationIndex(\n database as unknown as Kysely<StorageDatabase>,\n );\n\n const documentMetaCache = new DocumentMetaCache(operationStore, {\n maxDocuments: 1000,\n });\n await documentMetaCache.startup();\n\n const collectionMembershipCache = new CollectionMembershipCache(\n operationIndex,\n );\n void collectionMembershipCache;\n void documentMetaCache;\n\n const preReady = init.preReadyKinds.map((kind) =>\n instantiateReadModel(\n kind,\n database,\n operationStore,\n operationIndex,\n writeCache,\n ),\n );\n const postReady = init.postReadyKinds.map((kind) =>\n instantiateReadModel(\n kind,\n database,\n operationStore,\n operationIndex,\n writeCache,\n ),\n );\n\n await initReadModels([...preReady, ...postReady], logger);\n\n const eventBus = new EventBus();\n const subscriptions: Unsubscribe[] = [];\n\n subscriptions.push(\n eventBus.subscribe(\n ReactorEventTypes.JOB_READ_READY,\n (_t: number, event: JobReadReadyEvent) => {\n events.onReadReady(event);\n },\n ),\n );\n subscriptions.push(\n eventBus.subscribe(\n ReactorEventTypes.READMODEL_INDEXED,\n (_t: number, event: ReadModelIndexedEvent) => {\n events.onReadModelIndexed(event);\n },\n ),\n );\n subscriptions.push(\n eventBus.subscribe(\n ReactorEventTypes.READMODEL_BATCH_COMPLETED,\n (_t: number, event: ReadModelBatchCompletedEvent) => {\n events.onBatchCompleted(event);\n },\n ),\n );\n\n const coordinator = new ReadModelCoordinator(eventBus, preReady, postReady);\n coordinator.start();\n\n return {\n registry,\n coordinator,\n eventBus,\n async relayWriteReady(event: JobWriteReadyEvent): Promise<void> {\n await eventBus.emit(ReactorEventTypes.JOB_WRITE_READY, event);\n },\n getChainDepth(): number {\n return coordinator.getChainDepth();\n },\n async drain(): Promise<void> {\n await coordinator.drain();\n },\n shutdown(): Promise<void> {\n coordinator.stop();\n for (const unsub of subscriptions) {\n unsub();\n }\n return Promise.resolve();\n },\n };\n}\n","import { ConsoleLogger } from \"document-model\";\nimport type { Kysely } from \"kysely\";\nimport type { MessagePort } from \"node:worker_threads\";\nimport type { Database } from \"../../core/types.js\";\nimport { createForwardingLogger } from \"../../executor/worker/forwarding-logger.js\";\nimport type { DbConfig } from \"../../executor/worker/protocol.js\";\nimport { errorToInfo } from \"../../executor/worker/sanitize.js\";\nimport type {\n JobReadReadyEvent,\n JobWriteReadyEvent,\n ReadModelBatchCompletedEvent,\n ReadModelIndexedEvent,\n} from \"../../events/types.js\";\nimport {\n instrumentPgPool,\n type PoolInstrumentation,\n} from \"../../storage/pool-instrumentation.js\";\nimport type {\n ProjectionInitMessage,\n ProjectionParentMessage,\n ProjectionWorkerMessage,\n} from \"../protocol.js\";\nimport {\n buildProjectionStack,\n type ProjectionStack,\n} from \"./build-projection-stack.js\";\n\nconst POOL_SAMPLE_INTERVAL_MS = 1_000;\n\n/**\n * Closeable handle around the worker's Postgres pool. Decoupled from the\n * default factory so tests can swap in PGlite via `RunProjectionWorkerOverrides`.\n *\n * When the worker owns a real pg.Pool, the handle exposes a\n * {@link PoolInstrumentation} so the run loop can forward acquire-wait\n * samples to the host. Tests that swap in PGlite leave this undefined.\n */\nexport type ProjectionWorkerDatabaseHandle = {\n kysely: Kysely<Database>;\n poolInstrumentation?: PoolInstrumentation;\n shutdown(): Promise<void>;\n};\n\nexport type RunProjectionWorkerOverrides = {\n createDatabase?: (\n config: DbConfig,\n shardId: string,\n ) => Promise<ProjectionWorkerDatabaseHandle>;\n loadFactory?: Parameters<typeof buildProjectionStack>[0][\"loadFactory\"];\n beforeBuildStack?: (db: Kysely<Database>) => Promise<void>;\n};\n\nasync function defaultCreateDatabase(\n config: DbConfig,\n shardId: string,\n): Promise<ProjectionWorkerDatabaseHandle> {\n const { Kysely, PostgresDialect } = await import(\"kysely\");\n const pgModule = await import(\"pg\");\n const Pool = pgModule.default.Pool;\n const pool = new Pool({\n host: config.host,\n port: config.port,\n database: config.database,\n user: config.user,\n password: config.password,\n ssl: config.ssl ? { rejectUnauthorized: false } : undefined,\n application_name: config.applicationName ?? shardId,\n max: config.poolSize,\n connectionTimeoutMillis: config.connectionTimeoutMillis,\n idleTimeoutMillis: config.idleTimeoutMillis,\n });\n const poolInstrumentation = instrumentPgPool(pool, shardId);\n const kysely = new Kysely<Database>({\n dialect: new PostgresDialect({ pool }),\n });\n return {\n kysely,\n poolInstrumentation,\n async shutdown(): Promise<void> {\n try {\n await kysely.destroy();\n } catch {\n // best-effort\n }\n try {\n await pool.end();\n } catch {\n // best-effort\n }\n },\n };\n}\n\n/**\n * Drives the projection worker's message loop. Owns lifecycle of the\n * database handle and the projection stack. The default factory builds a\n * real Postgres pool; tests inject overrides for an in-process PGlite path.\n */\nexport function runProjectionWorker(\n parentPort: MessagePort,\n overrides: RunProjectionWorkerOverrides = {},\n): void {\n let shardId = \"\";\n let initCompleted = false;\n let stack: ProjectionStack | null = null;\n let database: ProjectionWorkerDatabaseHandle | null = null;\n let depthTimer: NodeJS.Timeout | null = null;\n let lastReportedDepth = -1;\n let poolSampleTimer: NodeJS.Timeout | null = null;\n let pendingPoolSamples: number[] = [];\n let detachPoolListener: (() => void) | null = null;\n\n function post(msg: ProjectionWorkerMessage): void {\n parentPort.postMessage(msg);\n }\n\n function startPoolReporter(instrumentation: PoolInstrumentation): void {\n detachPoolListener = instrumentation.onAcquire((durationMs) => {\n pendingPoolSamples.push(durationMs);\n });\n poolSampleTimer = setInterval(() => {\n if (pendingPoolSamples.length === 0) {\n return;\n }\n const durations = pendingPoolSamples;\n pendingPoolSamples = [];\n const stats = instrumentation.getStats();\n post({\n type: \"pool-acquire-samples\",\n shardId,\n poolName: instrumentation.name,\n timestamp: Date.now(),\n durations,\n size: stats.size,\n idle: stats.idle,\n waiting: stats.waiting,\n });\n }, POOL_SAMPLE_INTERVAL_MS);\n poolSampleTimer.unref();\n }\n\n function stopPoolReporter(): void {\n if (detachPoolListener) {\n detachPoolListener();\n detachPoolListener = null;\n }\n if (poolSampleTimer) {\n clearInterval(poolSampleTimer);\n poolSampleTimer = null;\n }\n pendingPoolSamples = [];\n }\n\n const logger = createForwardingLogger((msg) => post({ ...msg, shardId }));\n\n process.on(\"uncaughtException\", (err: unknown) => {\n try {\n post({\n type: \"log\",\n shardId,\n level: \"error\",\n message: \"projection worker uncaughtException\",\n args: [errorToInfo(err)],\n timestamp: Date.now(),\n });\n } catch {\n // nothing left to do\n }\n throw err;\n });\n\n process.on(\"unhandledRejection\", (reason: unknown) => {\n try {\n post({\n type: \"log\",\n shardId,\n level: \"error\",\n message: \"projection worker unhandledRejection\",\n args: [errorToInfo(reason)],\n timestamp: Date.now(),\n });\n } catch {\n // nothing left to do\n }\n });\n\n function startDepthReporter(intervalMs: number): void {\n if (intervalMs <= 0) {\n return;\n }\n depthTimer = setInterval(() => {\n if (!stack) {\n return;\n }\n const depth = stack.getChainDepth();\n if (depth === lastReportedDepth) {\n return;\n }\n lastReportedDepth = depth;\n post({\n type: \"chain-depth\",\n shardId,\n depth,\n timestamp: Date.now(),\n });\n }, intervalMs);\n depthTimer.unref();\n }\n\n function stopDepthReporter(): void {\n if (depthTimer) {\n clearInterval(depthTimer);\n depthTimer = null;\n }\n }\n\n async function handleInit(msg: ProjectionInitMessage): Promise<void> {\n shardId = msg.shardId;\n const createDb = overrides.createDatabase ?? defaultCreateDatabase;\n database = await createDb(msg.db, msg.shardId);\n if (overrides.beforeBuildStack) {\n await overrides.beforeBuildStack(database.kysely);\n }\n stack = await buildProjectionStack({\n init: msg,\n database: database.kysely,\n logger: new ConsoleLogger([`projection-shard:${msg.shardId}`]),\n loadFactory: overrides.loadFactory,\n events: {\n onReadReady: (event: JobReadReadyEvent) => {\n post({\n type: \"read-ready\",\n shardId,\n jobId: event.jobId,\n operations: event.operations,\n });\n },\n onReadModelIndexed: (event: ReadModelIndexedEvent) => {\n post({\n type: \"readmodel-indexed\",\n shardId,\n jobId: event.jobId,\n readModelName: event.readModelName,\n stage: event.stage,\n durationMs: event.durationMs,\n operationCount: event.operationCount,\n success: event.success,\n });\n },\n onBatchCompleted: (event: ReadModelBatchCompletedEvent) => {\n post({\n type: \"readmodel-batch-completed\",\n shardId,\n jobId: event.jobId,\n batchSize: event.batchSize,\n chainWaitDurationMs: event.chainWaitDurationMs,\n preReadyDurationMs: event.preReadyDurationMs,\n emitDurationMs: event.emitDurationMs,\n postReadyDurationMs: event.postReadyDurationMs,\n });\n },\n },\n });\n initCompleted = true;\n startDepthReporter(msg.chainDepthReportIntervalMs);\n if (database.poolInstrumentation) {\n startPoolReporter(database.poolInstrumentation);\n }\n logger.info(\"projection worker initialized: @shardId\", msg.shardId);\n post({ type: \"ready\", correlationId: msg.correlationId, shardId });\n }\n\n async function handleWriteReady(\n msg: Extract<ProjectionParentMessage, { type: \"write-ready\" }>,\n ): Promise<void> {\n if (!stack) {\n logger.warn(\n \"write-ready received before init on shard @shardId\",\n shardId,\n );\n return;\n }\n const event: JobWriteReadyEvent = {\n jobId: msg.jobId,\n operations: msg.operations,\n jobMeta: msg.jobMeta,\n collectionMemberships: msg.collectionMemberships,\n };\n await stack.relayWriteReady(event);\n }\n\n async function handleDrain(correlationId: string): Promise<void> {\n if (stack) {\n await stack.drain();\n }\n post({ type: \"drained\", correlationId, shardId });\n }\n\n async function shutdownStack(): Promise<void> {\n stopDepthReporter();\n stopPoolReporter();\n if (stack) {\n try {\n await stack.drain();\n } catch (error) {\n logger.warn(\n \"projection worker drain failed during shutdown: @error\",\n error,\n );\n }\n try {\n await stack.shutdown();\n } catch (error) {\n logger.warn(\"projection worker stack shutdown failed: @error\", error);\n }\n stack = null;\n }\n if (database) {\n await database.shutdown();\n database = null;\n }\n }\n\n function handleParentMessage(msg: ProjectionParentMessage): void {\n switch (msg.type) {\n case \"init\": {\n // Terminal: the worker has no stack, so it can project nothing. The\n // parent rejects startup on this message and terminates the thread;\n // exiting here would race the message and lose the cause.\n handleInit(msg).catch((err: unknown) => {\n post({\n type: \"init-failed\",\n correlationId: msg.correlationId,\n shardId,\n error: errorToInfo(err),\n });\n });\n break;\n }\n case \"write-ready\": {\n if (!initCompleted) {\n logger.warn(\n \"write-ready received before init on shard @shardId\",\n shardId,\n );\n break;\n }\n handleWriteReady(msg).catch((err: unknown) => {\n post({\n type: \"log\",\n shardId,\n level: \"error\",\n message: \"projection worker write-ready failed\",\n args: [errorToInfo(err)],\n timestamp: Date.now(),\n });\n });\n break;\n }\n case \"drain\": {\n handleDrain(msg.correlationId).catch((err: unknown) => {\n post({\n type: \"log\",\n shardId,\n level: \"error\",\n message: \"projection worker drain failed\",\n args: [errorToInfo(err)],\n timestamp: Date.now(),\n });\n });\n break;\n }\n case \"shutdown\": {\n logger.info(\"projection worker shutting down: @shardId\", shardId);\n void shutdownStack().finally(() => {\n post({\n type: \"log\",\n shardId,\n level: \"info\",\n message: \"projection worker shutdown\",\n args: [],\n timestamp: Date.now(),\n });\n process.exit(0);\n });\n break;\n }\n default: {\n const exhaustive: never = msg;\n void exhaustive;\n break;\n }\n }\n }\n\n parentPort.on(\"message\", handleParentMessage);\n\n const harness = {\n handleParentMessage,\n get initCompleted(): boolean {\n return initCompleted;\n },\n get shardId(): string {\n return shardId;\n },\n };\n (\n parentPort as unknown as { __reactorProjectionWorkerHarness?: unknown }\n ).__reactorProjectionWorkerHarness = harness;\n}\n","import { isMainThread, parentPort } from \"node:worker_threads\";\nimport { runProjectionWorker } from \"./run-projection-worker.js\";\n\nif (isMainThread || parentPort === null) {\n throw new Error(\"projection-worker entry.ts must be run as a worker thread\");\n}\n\nrunProjectionWorker(parentPort);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"projection-entry.js","names":[],"sources":["../src/projection/projection-worker/build-projection-stack.ts","../src/projection/projection-worker/run-projection-worker.ts","../src/projection/projection-worker/projection-entry.ts"],"sourcesContent":["/**\n * In-worker projection stack builder.\n *\n * Mirrors {@link buildWorkerExecutor} but for the projection-side\n * read models. The worker owns one full copy of the storage stack — read\n * cache, operation index, document-meta cache — bound to its own\n * pg.Pool/Kysely, plus an in-process `ReadModelCoordinator` that\n * subscribes to a local `EventBus`.\n *\n * The host relays JOB_WRITE_READY into the local bus by calling\n * `relayWriteReady`, and forwards the local bus's JOB_READ_READY plus\n * READMODEL_* events back to the host bus via the IPC bridge.\n */\n\nimport type { DocumentModelModule } from \"@powerhousedao/shared/document-model\";\nimport type { ILogger } from \"document-model\";\nimport type { Kysely } from \"kysely\";\nimport { CollectionMembershipCache } from \"../../cache/collection-membership-cache.js\";\nimport { DocumentMetaCache } from \"../../cache/document-meta-cache.js\";\nimport { KyselyOperationIndex } from \"../../cache/kysely-operation-index.js\";\nimport { KyselyWriteCache } from \"../../cache/kysely-write-cache.js\";\nimport type { WriteCacheConfig } from \"../../cache/write-cache-types.js\";\nimport type { Database } from \"../../core/types.js\";\nimport { EventBus } from \"../../events/event-bus.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 {\n defaultLoadFactory,\n type BuildWorkerExecutorOptions,\n} from \"../../executor/worker/build-worker-executor.js\";\nimport type {\n FactorySpec,\n ModelManifestEntry,\n} from \"../../executor/worker/protocol.js\";\nimport type { ReadModelIndexingConfig } from \"../../read-models/base-read-model.js\";\nimport { ReadModelCoordinator } from \"../../read-models/coordinator.js\";\nimport {\n DeletedDocumentRead,\n KyselyDocumentView,\n} from \"../../read-models/document-view.js\";\nimport type { IReadModel } from \"../../read-models/interfaces.js\";\nimport { DocumentModelRegistry } from \"../../registry/implementation.js\";\nimport { ConsistencyTracker } from \"../../shared/consistency-tracker.js\";\nimport {\n KyselyDocumentIndexer,\n type IndexerDatabase,\n} from \"../../storage/kysely/document-indexer.js\";\nimport { KyselyKeyframeStore } from \"../../storage/kysely/keyframe-store.js\";\nimport { KyselyOperationStore } from \"../../storage/kysely/store.js\";\nimport type { Database as StorageDatabase } from \"../../storage/kysely/types.js\";\nimport { REACTOR_SCHEMA } from \"../../storage/migrations/migrator.js\";\nimport type {\n BuiltInReadModelKind,\n ProjectionInitMessage,\n} from \"../protocol.js\";\n\nexport type ProjectionStackEvents = {\n onReadReady: (event: JobReadReadyEvent) => void;\n onReadModelIndexed: (event: ReadModelIndexedEvent) => void;\n onBatchCompleted: (event: ReadModelBatchCompletedEvent) => void;\n};\n\nexport type ProjectionStack = {\n registry: DocumentModelRegistry;\n coordinator: ReadModelCoordinator;\n eventBus: EventBus;\n relayWriteReady(event: JobWriteReadyEvent): Promise<void>;\n getChainDepth(): number;\n drain(): Promise<void>;\n shutdown(): Promise<void>;\n};\n\nexport type BuildProjectionStackOptions = {\n init: ProjectionInitMessage;\n database: Kysely<Database>;\n logger: ILogger;\n events: ProjectionStackEvents;\n loadFactory?: BuildWorkerExecutorOptions[\"loadFactory\"];\n driveContainerTypes?: ReadonlySet<string>;\n};\n\nasync function loadModelManifest(\n entries: ModelManifestEntry[],\n loadFactory: (spec: FactorySpec) => Promise<unknown>,\n registry: DocumentModelRegistry,\n logger: ILogger,\n): Promise<void> {\n for (const entry of entries) {\n let module: DocumentModelModule;\n try {\n module = (await loadFactory(entry.spec)) as DocumentModelModule;\n } catch (error) {\n logger.error(\n \"projection worker failed to load document model: @entry @error\",\n entry,\n error,\n );\n throw error;\n }\n const [result] = registry.registerModules(module);\n if (result.status === \"error\") {\n logger.error(\n \"projection worker failed to register document model: @entry @error\",\n entry,\n result.error,\n );\n throw result.error;\n }\n }\n}\n\nfunction instantiateReadModel(\n kind: BuiltInReadModelKind,\n database: Kysely<Database>,\n operationStore: KyselyOperationStore,\n operationIndex: KyselyOperationIndex,\n writeCache: KyselyWriteCache,\n indexing: ReadModelIndexingConfig,\n): IReadModel {\n switch (kind) {\n case \"document-view\": {\n return new KyselyDocumentView(\n // @ts-expect-error - Database superset\n database,\n operationStore,\n operationIndex,\n writeCache,\n new ConsistencyTracker(),\n // Read-side only, so it cannot diverge state.\n DeletedDocumentRead.NotFound,\n indexing,\n );\n }\n case \"document-indexer\": {\n return new KyselyDocumentIndexer(\n database as unknown as Kysely<IndexerDatabase>,\n operationIndex,\n writeCache,\n new ConsistencyTracker(),\n indexing,\n );\n }\n default: {\n const exhaustive: never = kind;\n throw new Error(\n `unknown built-in read model kind: ${String(exhaustive)}`,\n );\n }\n }\n}\n\nasync function initReadModels(\n models: IReadModel[],\n logger: ILogger,\n): Promise<void> {\n for (const model of models) {\n const maybeInit = model as IReadModel & { init?: () => Promise<void> };\n if (typeof maybeInit.init !== \"function\") {\n continue;\n }\n try {\n await maybeInit.init();\n } catch (error) {\n logger.error(\n \"projection worker read model init failed: @name @error\",\n model.name,\n error,\n );\n throw error;\n }\n }\n}\n\nexport async function buildProjectionStack(\n options: BuildProjectionStackOptions,\n): Promise<ProjectionStack> {\n const { init, database: baseDatabase, logger, events } = options;\n const loadFactory = options.loadFactory ?? defaultLoadFactory;\n\n const registry = new DocumentModelRegistry();\n await loadModelManifest(init.models, loadFactory, registry, logger);\n\n const database = baseDatabase.withSchema(REACTOR_SCHEMA);\n const operationStore = new KyselyOperationStore(\n database as unknown as Kysely<StorageDatabase>,\n );\n const keyframeStore = new KyselyKeyframeStore(\n database as unknown as Kysely<StorageDatabase>,\n );\n\n const cacheConfig: WriteCacheConfig = {\n maxDocuments: 100,\n ringBufferSize: 10,\n keyframeInterval: 10,\n };\n const writeCache = new KyselyWriteCache(\n keyframeStore,\n operationStore,\n registry,\n cacheConfig,\n );\n await writeCache.startup();\n\n const operationIndex = new KyselyOperationIndex(\n database as unknown as Kysely<StorageDatabase>,\n );\n\n const documentMetaCache = new DocumentMetaCache(operationStore, {\n maxDocuments: 1000,\n });\n await documentMetaCache.startup();\n\n const collectionMembershipCache = new CollectionMembershipCache(\n operationIndex,\n );\n void collectionMembershipCache;\n void documentMetaCache;\n\n const preReady = init.preReadyKinds.map((kind) =>\n instantiateReadModel(\n kind,\n database,\n operationStore,\n operationIndex,\n writeCache,\n init.indexing,\n ),\n );\n const postReady = init.postReadyKinds.map((kind) =>\n instantiateReadModel(\n kind,\n database,\n operationStore,\n operationIndex,\n writeCache,\n init.indexing,\n ),\n );\n\n await initReadModels([...preReady, ...postReady], logger);\n\n const eventBus = new EventBus();\n const subscriptions: Unsubscribe[] = [];\n\n subscriptions.push(\n eventBus.subscribe(\n ReactorEventTypes.JOB_READ_READY,\n (_t: number, event: JobReadReadyEvent) => {\n events.onReadReady(event);\n },\n ),\n );\n subscriptions.push(\n eventBus.subscribe(\n ReactorEventTypes.READMODEL_INDEXED,\n (_t: number, event: ReadModelIndexedEvent) => {\n events.onReadModelIndexed(event);\n },\n ),\n );\n subscriptions.push(\n eventBus.subscribe(\n ReactorEventTypes.READMODEL_BATCH_COMPLETED,\n (_t: number, event: ReadModelBatchCompletedEvent) => {\n events.onBatchCompleted(event);\n },\n ),\n );\n\n const coordinator = new ReadModelCoordinator(eventBus, preReady, postReady);\n coordinator.start();\n\n return {\n registry,\n coordinator,\n eventBus,\n async relayWriteReady(event: JobWriteReadyEvent): Promise<void> {\n await eventBus.emit(ReactorEventTypes.JOB_WRITE_READY, event);\n },\n getChainDepth(): number {\n return coordinator.getChainDepth();\n },\n async drain(): Promise<void> {\n await coordinator.drain();\n },\n shutdown(): Promise<void> {\n coordinator.stop();\n for (const unsub of subscriptions) {\n unsub();\n }\n return Promise.resolve();\n },\n };\n}\n","import { ConsoleLogger } from \"document-model\";\nimport type { Kysely } from \"kysely\";\nimport type { MessagePort } from \"node:worker_threads\";\nimport type { Database } from \"../../core/types.js\";\nimport { createForwardingLogger } from \"../../executor/worker/forwarding-logger.js\";\nimport type { DbConfig } from \"../../executor/worker/protocol.js\";\nimport { errorToInfo } from \"../../executor/worker/sanitize.js\";\nimport type {\n JobReadReadyEvent,\n JobWriteReadyEvent,\n ReadModelBatchCompletedEvent,\n ReadModelIndexedEvent,\n} from \"../../events/types.js\";\nimport {\n instrumentPgPool,\n type PoolInstrumentation,\n} from \"../../storage/pool-instrumentation.js\";\nimport type {\n ProjectionInitMessage,\n ProjectionParentMessage,\n ProjectionWorkerMessage,\n} from \"../protocol.js\";\nimport {\n buildProjectionStack,\n type ProjectionStack,\n} from \"./build-projection-stack.js\";\n\nconst POOL_SAMPLE_INTERVAL_MS = 1_000;\n\n/**\n * Closeable handle around the worker's Postgres pool. Decoupled from the\n * default factory so tests can swap in PGlite via `RunProjectionWorkerOverrides`.\n *\n * When the worker owns a real pg.Pool, the handle exposes a\n * {@link PoolInstrumentation} so the run loop can forward acquire-wait\n * samples to the host. Tests that swap in PGlite leave this undefined.\n */\nexport type ProjectionWorkerDatabaseHandle = {\n kysely: Kysely<Database>;\n poolInstrumentation?: PoolInstrumentation;\n shutdown(): Promise<void>;\n};\n\nexport type RunProjectionWorkerOverrides = {\n createDatabase?: (\n config: DbConfig,\n shardId: string,\n ) => Promise<ProjectionWorkerDatabaseHandle>;\n loadFactory?: Parameters<typeof buildProjectionStack>[0][\"loadFactory\"];\n beforeBuildStack?: (db: Kysely<Database>) => Promise<void>;\n};\n\nasync function defaultCreateDatabase(\n config: DbConfig,\n shardId: string,\n): Promise<ProjectionWorkerDatabaseHandle> {\n const { Kysely, PostgresDialect } = await import(\"kysely\");\n const pgModule = await import(\"pg\");\n const Pool = pgModule.default.Pool;\n const pool = new Pool({\n host: config.host,\n port: config.port,\n database: config.database,\n user: config.user,\n password: config.password,\n ssl: config.ssl ? { rejectUnauthorized: false } : undefined,\n application_name: config.applicationName ?? shardId,\n max: config.poolSize,\n connectionTimeoutMillis: config.connectionTimeoutMillis,\n idleTimeoutMillis: config.idleTimeoutMillis,\n });\n const poolInstrumentation = instrumentPgPool(pool, shardId);\n const kysely = new Kysely<Database>({\n dialect: new PostgresDialect({ pool }),\n });\n return {\n kysely,\n poolInstrumentation,\n async shutdown(): Promise<void> {\n try {\n await kysely.destroy();\n } catch {\n // best-effort\n }\n try {\n await pool.end();\n } catch {\n // best-effort\n }\n },\n };\n}\n\n/**\n * Drives the projection worker's message loop. Owns lifecycle of the\n * database handle and the projection stack. The default factory builds a\n * real Postgres pool; tests inject overrides for an in-process PGlite path.\n */\nexport function runProjectionWorker(\n parentPort: MessagePort,\n overrides: RunProjectionWorkerOverrides = {},\n): void {\n let shardId = \"\";\n let initCompleted = false;\n let stack: ProjectionStack | null = null;\n let database: ProjectionWorkerDatabaseHandle | null = null;\n let depthTimer: NodeJS.Timeout | null = null;\n let lastReportedDepth = -1;\n let poolSampleTimer: NodeJS.Timeout | null = null;\n let pendingPoolSamples: number[] = [];\n let detachPoolListener: (() => void) | null = null;\n\n function post(msg: ProjectionWorkerMessage): void {\n parentPort.postMessage(msg);\n }\n\n function startPoolReporter(instrumentation: PoolInstrumentation): void {\n detachPoolListener = instrumentation.onAcquire((durationMs) => {\n pendingPoolSamples.push(durationMs);\n });\n poolSampleTimer = setInterval(() => {\n if (pendingPoolSamples.length === 0) {\n return;\n }\n const durations = pendingPoolSamples;\n pendingPoolSamples = [];\n const stats = instrumentation.getStats();\n post({\n type: \"pool-acquire-samples\",\n shardId,\n poolName: instrumentation.name,\n timestamp: Date.now(),\n durations,\n size: stats.size,\n idle: stats.idle,\n waiting: stats.waiting,\n });\n }, POOL_SAMPLE_INTERVAL_MS);\n poolSampleTimer.unref();\n }\n\n function stopPoolReporter(): void {\n if (detachPoolListener) {\n detachPoolListener();\n detachPoolListener = null;\n }\n if (poolSampleTimer) {\n clearInterval(poolSampleTimer);\n poolSampleTimer = null;\n }\n pendingPoolSamples = [];\n }\n\n const logger = createForwardingLogger((msg) => post({ ...msg, shardId }));\n\n process.on(\"uncaughtException\", (err: unknown) => {\n try {\n post({\n type: \"log\",\n shardId,\n level: \"error\",\n message: \"projection worker uncaughtException\",\n args: [errorToInfo(err)],\n timestamp: Date.now(),\n });\n } catch {\n // nothing left to do\n }\n throw err;\n });\n\n process.on(\"unhandledRejection\", (reason: unknown) => {\n try {\n post({\n type: \"log\",\n shardId,\n level: \"error\",\n message: \"projection worker unhandledRejection\",\n args: [errorToInfo(reason)],\n timestamp: Date.now(),\n });\n } catch {\n // nothing left to do\n }\n });\n\n function startDepthReporter(intervalMs: number): void {\n if (intervalMs <= 0) {\n return;\n }\n depthTimer = setInterval(() => {\n if (!stack) {\n return;\n }\n const depth = stack.getChainDepth();\n if (depth === lastReportedDepth) {\n return;\n }\n lastReportedDepth = depth;\n post({\n type: \"chain-depth\",\n shardId,\n depth,\n timestamp: Date.now(),\n });\n }, intervalMs);\n depthTimer.unref();\n }\n\n function stopDepthReporter(): void {\n if (depthTimer) {\n clearInterval(depthTimer);\n depthTimer = null;\n }\n }\n\n async function handleInit(msg: ProjectionInitMessage): Promise<void> {\n shardId = msg.shardId;\n const createDb = overrides.createDatabase ?? defaultCreateDatabase;\n database = await createDb(msg.db, msg.shardId);\n if (overrides.beforeBuildStack) {\n await overrides.beforeBuildStack(database.kysely);\n }\n stack = await buildProjectionStack({\n init: msg,\n database: database.kysely,\n logger: new ConsoleLogger([`projection-shard:${msg.shardId}`]),\n loadFactory: overrides.loadFactory,\n events: {\n onReadReady: (event: JobReadReadyEvent) => {\n post({\n type: \"read-ready\",\n shardId,\n jobId: event.jobId,\n operations: event.operations,\n });\n },\n onReadModelIndexed: (event: ReadModelIndexedEvent) => {\n post({\n type: \"readmodel-indexed\",\n shardId,\n jobId: event.jobId,\n readModelName: event.readModelName,\n stage: event.stage,\n durationMs: event.durationMs,\n operationCount: event.operationCount,\n success: event.success,\n });\n },\n onBatchCompleted: (event: ReadModelBatchCompletedEvent) => {\n post({\n type: \"readmodel-batch-completed\",\n shardId,\n jobId: event.jobId,\n batchSize: event.batchSize,\n chainWaitDurationMs: event.chainWaitDurationMs,\n preReadyDurationMs: event.preReadyDurationMs,\n emitDurationMs: event.emitDurationMs,\n postReadyDurationMs: event.postReadyDurationMs,\n });\n },\n },\n });\n initCompleted = true;\n startDepthReporter(msg.chainDepthReportIntervalMs);\n if (database.poolInstrumentation) {\n startPoolReporter(database.poolInstrumentation);\n }\n logger.info(\"projection worker initialized: @shardId\", msg.shardId);\n post({ type: \"ready\", correlationId: msg.correlationId, shardId });\n }\n\n async function handleWriteReady(\n msg: Extract<ProjectionParentMessage, { type: \"write-ready\" }>,\n ): Promise<void> {\n if (!stack) {\n logger.warn(\n \"write-ready received before init on shard @shardId\",\n shardId,\n );\n return;\n }\n const event: JobWriteReadyEvent = {\n jobId: msg.jobId,\n operations: msg.operations,\n jobMeta: msg.jobMeta,\n collectionMemberships: msg.collectionMemberships,\n };\n await stack.relayWriteReady(event);\n }\n\n async function handleDrain(correlationId: string): Promise<void> {\n if (stack) {\n await stack.drain();\n }\n post({ type: \"drained\", correlationId, shardId });\n }\n\n async function shutdownStack(): Promise<void> {\n stopDepthReporter();\n stopPoolReporter();\n if (stack) {\n try {\n await stack.drain();\n } catch (error) {\n logger.warn(\n \"projection worker drain failed during shutdown: @error\",\n error,\n );\n }\n try {\n await stack.shutdown();\n } catch (error) {\n logger.warn(\"projection worker stack shutdown failed: @error\", error);\n }\n stack = null;\n }\n if (database) {\n await database.shutdown();\n database = null;\n }\n }\n\n function handleParentMessage(msg: ProjectionParentMessage): void {\n switch (msg.type) {\n case \"init\": {\n // Terminal: the worker has no stack, so it can project nothing. The\n // parent rejects startup on this message and terminates the thread;\n // exiting here would race the message and lose the cause.\n handleInit(msg).catch((err: unknown) => {\n post({\n type: \"init-failed\",\n correlationId: msg.correlationId,\n shardId,\n error: errorToInfo(err),\n });\n });\n break;\n }\n case \"write-ready\": {\n if (!initCompleted) {\n logger.warn(\n \"write-ready received before init on shard @shardId\",\n shardId,\n );\n break;\n }\n handleWriteReady(msg).catch((err: unknown) => {\n post({\n type: \"log\",\n shardId,\n level: \"error\",\n message: \"projection worker write-ready failed\",\n args: [errorToInfo(err)],\n timestamp: Date.now(),\n });\n });\n break;\n }\n case \"drain\": {\n handleDrain(msg.correlationId).catch((err: unknown) => {\n post({\n type: \"log\",\n shardId,\n level: \"error\",\n message: \"projection worker drain failed\",\n args: [errorToInfo(err)],\n timestamp: Date.now(),\n });\n });\n break;\n }\n case \"shutdown\": {\n logger.info(\"projection worker shutting down: @shardId\", shardId);\n void shutdownStack().finally(() => {\n post({\n type: \"log\",\n shardId,\n level: \"info\",\n message: \"projection worker shutdown\",\n args: [],\n timestamp: Date.now(),\n });\n process.exit(0);\n });\n break;\n }\n default: {\n const exhaustive: never = msg;\n void exhaustive;\n break;\n }\n }\n }\n\n parentPort.on(\"message\", handleParentMessage);\n\n const harness = {\n handleParentMessage,\n get initCompleted(): boolean {\n return initCompleted;\n },\n get shardId(): string {\n return shardId;\n },\n };\n (\n parentPort as unknown as { __reactorProjectionWorkerHarness?: unknown }\n ).__reactorProjectionWorkerHarness = harness;\n}\n","import { isMainThread, parentPort } from \"node:worker_threads\";\nimport { runProjectionWorker } from \"./run-projection-worker.js\";\n\nif (isMainThread || parentPort === null) {\n throw new Error(\"projection-worker entry.ts must be run as a worker thread\");\n}\n\nrunProjectionWorker(parentPort);\n"],"mappings":"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"}
|
|
@@ -123,7 +123,8 @@ var ProjectionShardManager = class {
|
|
|
123
123
|
models: this.config.models,
|
|
124
124
|
preReadyKinds: this.config.preReadyKinds,
|
|
125
125
|
postReadyKinds: this.config.postReadyKinds,
|
|
126
|
-
chainDepthReportIntervalMs: reportIntervalMs
|
|
126
|
+
chainDepthReportIntervalMs: reportIntervalMs,
|
|
127
|
+
indexing: this.config.indexing
|
|
127
128
|
};
|
|
128
129
|
transport.postMessage(init);
|
|
129
130
|
initPromises.push(initPromise);
|
|
@@ -437,4 +438,4 @@ var ProjectionShardManager = class {
|
|
|
437
438
|
//#endregion
|
|
438
439
|
export { ProjectionShardManager };
|
|
439
440
|
|
|
440
|
-
//# sourceMappingURL=projection-shard-manager-
|
|
441
|
+
//# sourceMappingURL=projection-shard-manager-CUo4bF4O.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projection-shard-manager-CUo4bF4O.js","names":[],"sources":["../src/projection/projection-shard-manager.ts"],"sourcesContent":["import type { OperationWithContext } from \"@powerhousedao/shared/document-model\";\nimport { childLogger, type ILogger } from \"document-model\";\nimport { randomUUID } from \"node:crypto\";\nimport { fromErrorInfo } from \"../executor/worker/error-info.js\";\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 { IConsistencyTracker } from \"../shared/consistency-tracker.js\";\nimport type { ConsistencyCoordinate } from \"../shared/types.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 ProjectionReadModelIndexedMessage,\n ProjectionWorkerMessage,\n ReadModelIndexingConfig,\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 * Maps operations to the coordinates a consistency tracker is keyed by.\n * Same shape `BaseReadModel.updateConsistencyTracker` produces in-process.\n */\nfunction toConsistencyCoordinates(\n operations: OperationWithContext[],\n): ConsistencyCoordinate[] {\n const coordinates: ConsistencyCoordinate[] = [];\n for (let i = 0; i < operations.length; i++) {\n const item = operations[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 return coordinates;\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 ProjectionShardHooks = {\n /** Takes over the host-bus JOB_READ_READY emit; call `emitReadReady` to emit. */\n onReadReady?: (event: JobReadReadyEvent) => void;\n /**\n * Fired when a shard errors, when it exits after having been ready, and\n * for every JOB_WRITE_READY dropped because its shard is not ready.\n *\n * There is no respawn path: once a shard stops being ready it never\n * projects again, so buffering the dropped work would only grow without\n * bound. A host that cares wires this to its shutdown path, so the process\n * restarts and each read model catches up from `ViewState.lastOrdinal` in\n * `BaseReadModel.init`. May fire repeatedly — handlers must be idempotent.\n */\n onShardFatal?: (shardId: string, reason: Error) => void;\n};\n\nexport type ProjectionShardManagerConfig = ProjectionShardHooks & {\n shardCount: number;\n db: DbConfig;\n models: ModelManifestEntry[];\n preReadyKinds: BuiltInReadModelKind[];\n postReadyKinds: BuiltInReadModelKind[];\n /**\n * Chunking bounds the shards' read models index under. Comes from the same\n * host config the in-process read models get, so the two paths cannot drift.\n */\n indexing: ReadModelIndexingConfig;\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 * The host's consistency trackers for the built-in read models the shards\n * run, keyed by kind. The keys double as read-model names (see\n * `DOCUMENT_VIEW_READ_MODEL` / `DOCUMENT_INDEXER_READ_MODEL` in\n * `read-models/names.js`), which is how a relayed `readmodel-indexed`\n * message is matched to a tracker.\n *\n * The host's copies of these read models are never fed an operation under\n * sharding, so without this every read carrying a consistency token waits\n * on a tracker that can never advance — `ConsistencyTracker.waitFor` arms\n * no timer when `timeoutMs` is undefined, which is what every\n * `document-view` call site passes. The worker committed those rows to the\n * same tables the host reads from, so advancing here is exact, not a fudge.\n */\n consistencyTrackers?: Partial<\n Record<BuiltInReadModelKind, IConsistencyTracker>\n >;\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 /**\n * Consistency coordinates of the jobs this shard is still projecting,\n * keyed by jobId. Relayed `readmodel-indexed` messages carry only a count,\n * so the host keeps the coordinates here to advance its trackers from\n * them; the operations themselves are not retained. Empty unless\n * `consistencyTrackers` is configured; entries live from dispatch to\n * `readmodel-batch-completed`, and are dropped when the shard exits.\n */\n pendingCoordinates: Map<string, ConsistencyCoordinate[]>;\n /**\n * Correlation id of this shard's `init` while it is unsettled; cleared once\n * the shard reports ready, fails, or dies. Lets a transport error or a\n * premature exit settle the shard's own pending init instead of leaving\n * `startup()` to time out on a worker that will never answer.\n */\n initCorrelationId?: string;\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 readonly trackersByReadModelName = new Map<\n string,\n IConsistencyTracker\n >();\n private hostSubscription?: Unsubscribe;\n private isRunning = false;\n private started = false;\n private isShuttingDown = 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 const trackers = config.consistencyTrackers ?? {};\n for (const kind of Object.keys(trackers) as BuiltInReadModelKind[]) {\n const tracker = trackers[kind];\n if (tracker) {\n this.trackersByReadModelName.set(kind, tracker);\n }\n }\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 pendingCoordinates: new Map(),\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 state.initCorrelationId = correlationId;\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 indexing: this.config.indexing,\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 /** Waits on ready shards only; `handleTransportExit` releases one that dies. */\n async drain(): Promise<void> {\n const readyShards = this.shards.filter((s) => s.ready);\n if (readyShards.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(readyShards.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 readyShards) {\n shard.transport.postMessage({ type: \"drain\", correlationId });\n }\n await promise;\n }\n\n /** Emits JOB_READ_READY on the host bus; an `onReadReady` hook awaits this. */\n emitReadReady(event: JobReadReadyEvent): Promise<void> {\n return this.hostBus.emit(ReactorEventTypes.JOB_READ_READY, event);\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 // Workers exit as a consequence of this call, so their `exit` events are\n // expected from here on and must not be reported as fatal.\n this.isShuttingDown = true;\n this.stop();\n // An init timer left armed by a shard that never reported keeps the\n // process alive for the whole init timeout after shutdown returns.\n for (const shard of this.shards) {\n this.failPendingInit(\n shard,\n new Error(\n `projection shard ${shard.shardId} was shut down before becoming ready`,\n ),\n );\n }\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 // No shard can be selected (there is no documentId) and no read model\n // has work to do, but JOB_READ_READY is the job's terminal signal:\n // InMemoryJobTracker and every JobAwaiter hang without it. Mirrors\n // ReadModelCoordinator.emitEmptyReadReady.\n this.relayReadReady({ jobId: event.jobId, operations: [] });\n this.relayBatchCompleted({\n jobId: event.jobId,\n batchSize: 0,\n chainWaitDurationMs: 0,\n preReadyDurationMs: 0,\n emitDurationMs: 0,\n postReadyDurationMs: 0,\n });\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.dropWriteReady(shard, event, documentId);\n return;\n }\n if (this.trackersByReadModelName.size > 0) {\n shard.pendingCoordinates.set(\n event.jobId,\n toConsistencyCoordinates(event.operations),\n );\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 /**\n * A shard stops being ready only when it dies, and nothing respawns it, so\n * this job's projection is genuinely lost. Buffering would grow without\n * bound behind a shard that never comes back, so the batch is dropped —\n * loudly, naming the job and document, and through `onShardFatal` so a host\n * can restart rather than serve stale read models.\n *\n * JOB_FAILED is deliberately not emitted: every other emitter uses it for a\n * job whose operations were *not* written (see\n * `executor/job-result-handler.ts`), and these were written and are\n * durable. Marking the job FAILED would invite the caller to re-submit a\n * write that already landed.\n */\n private dropWriteReady(\n shard: ShardState,\n event: JobWriteReadyEvent,\n documentId: string,\n ): void {\n const reason = new Error(\n `projection shard ${shard.shardId} is not ready: JOB_WRITE_READY for job ${event.jobId} ` +\n `(document ${documentId}, ${event.operations.length} operation(s)) was dropped and will ` +\n `never be projected; the operations are written and durable, but this shard's read models ` +\n `are now behind`,\n );\n this.logger.error(\n \"dropping JOB_WRITE_READY for job @jobId on shard @shardId: @error\",\n event.jobId,\n shard.shardId,\n reason,\n );\n this.config.onShardFatal?.(shard.shardId, reason);\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 \"init-failed\":\n this.logger.error(\n \"projection shard @shardId failed to initialize: @error\",\n shard.shardId,\n msg.error.message,\n );\n this.failPendingInit(shard, fromErrorInfo(msg.error));\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.advanceConsistencyTrackers(shard, msg);\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 shard.pendingCoordinates.delete(msg.jobId);\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 /**\n * Advances the host's tracker for the read model the shard just indexed.\n *\n * The shard writes to the same tables the host reads, so once it reports a\n * successful index the host's read path really is consistent to those\n * coordinates. Gated on `success` for parity with\n * `BaseReadModel.indexOperations`, which updates its tracker only after\n * `commitOperations` returns. The worker posts this before its\n * JOB_READ_READY, matching the in-process ordering.\n */\n private advanceConsistencyTrackers(\n shard: ShardState,\n msg: ProjectionReadModelIndexedMessage,\n ): void {\n if (!msg.success) {\n return;\n }\n const tracker = this.trackersByReadModelName.get(msg.readModelName);\n if (!tracker) {\n return;\n }\n const coordinates = shard.pendingCoordinates.get(msg.jobId);\n if (!coordinates || coordinates.length === 0) {\n return;\n }\n tracker.update(coordinates);\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 /**\n * Settles a shard's unresolved `init` with `error`, clearing its timer.\n * Returns false when the shard has no init outstanding, which is every\n * failure after startup. `startup()` awaits all init promises together, so\n * the rejection is always attached.\n */\n private failPendingInit(shard: ShardState, error: Error): boolean {\n const correlationId = shard.initCorrelationId;\n if (correlationId === undefined) {\n return false;\n }\n shard.initCorrelationId = undefined;\n const pending = this.initPromises.get(correlationId);\n if (!pending) {\n return false;\n }\n this.initPromises.delete(correlationId);\n clearTimeout(pending.timer);\n pending.reject(error);\n return true;\n }\n\n private handleReady(shard: ShardState, correlationId: string): void {\n shard.ready = true;\n shard.initCorrelationId = undefined;\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 this.releaseDrain(msg.correlationId, pending, msg.shardId);\n }\n\n /** Removes `shardId` from a pending drain and settles it once no shard remains. */\n private releaseDrain(\n correlationId: string,\n pending: PendingDrain,\n shardId: string,\n ): void {\n pending.remaining.delete(shardId);\n if (pending.remaining.size > 0) {\n return;\n }\n this.pendingDrains.delete(correlationId);\n clearTimeout(pending.timer);\n pending.resolve();\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 // A shard that dies during startup is reported through `startup()`, not\n // through the fatal hook: the host is still building and its shutdown\n // path (which the hook usually triggers) does not exist yet.\n if (this.failPendingInit(shard, err)) {\n return;\n }\n if (!this.isShuttingDown) {\n this.config.onShardFatal?.(shard.shardId, err);\n }\n }\n\n private handleTransportExit(shard: ShardState, code: number): void {\n const wasReady = shard.ready;\n shard.ready = false;\n // Nothing respawns the shard, so every job it had in flight is lost with\n // it. Release them rather than pin their operations for the process's\n // remaining life.\n const abandoned = [...shard.pendingCoordinates.keys()];\n shard.pendingCoordinates.clear();\n for (const [correlationId, pending] of this.pendingDrains) {\n this.releaseDrain(correlationId, pending, shard.shardId);\n }\n if (\n this.failPendingInit(\n shard,\n new Error(\n `projection shard ${shard.shardId} exited with code ${code} before becoming ready`,\n ),\n )\n ) {\n return;\n }\n if (!wasReady || this.isShuttingDown) {\n return;\n }\n this.logger.error(\n \"projection shard exited unexpectedly @shardId code=@code, abandoning @count in-flight job(s): @jobIds\",\n shard.shardId,\n code,\n abandoned.length,\n abandoned.join(\", \"),\n );\n this.config.onShardFatal?.(\n shard.shardId,\n new Error(\n `projection shard ${shard.shardId} exited with code ${code}; ` +\n `${abandoned.length} in-flight job(s) will never be projected` +\n (abandoned.length > 0 ? `: ${abandoned.join(\", \")}` : \"\"),\n ),\n );\n }\n\n private relayReadReady(event: JobReadReadyEvent): void {\n const hook = this.config.onReadReady;\n if (hook) {\n hook(event);\n return;\n }\n void this.emitReadReady(event).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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"}
|
|
@@ -1 +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
|
+
{"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 * The ids of the actions the caller submitted with this job. `operations`\n * can also carry operations the job merely moved into a new position, so a\n * consumer reporting back to the caller needs this to tell the two apart.\n * Absent on jobs that carry no submitted actions.\n */\n submittedActionIds?: string[];\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 {
|
|
1
|
+
import { nt as __exportAll } from "./drive-container-types-CS5IxDiA.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-
|
|
22
|
+
//# sourceMappingURL=worker-g03lNM42.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker-
|
|
1
|
+
{"version":3,"file":"worker-g03lNM42.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.3-dev.
|
|
3
|
+
"version": "6.2.3-dev.21",
|
|
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,11 +42,11 @@
|
|
|
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.3-dev.
|
|
46
|
-
"document-model": "6.2.3-dev.
|
|
45
|
+
"@powerhousedao/shared": "6.2.3-dev.21",
|
|
46
|
+
"document-model": "6.2.3-dev.21"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
|
-
"@powerhousedao/pglite-fs": "6.2.3-dev.
|
|
49
|
+
"@powerhousedao/pglite-fs": "6.2.3-dev.21",
|
|
50
50
|
"@types/pg": "8.16.0",
|
|
51
51
|
"@vitest/coverage-v8": "4.1.1",
|
|
52
52
|
"prettier": "3.8.1",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"document-indexer-C5Gsa1B3.js","names":["uuidv4","uuidv4"],"sources":["../src/read-models/base-read-model.ts","../src/read-models/coordinator.ts","../src/read-models/names.ts","../src/read-models/document-view.ts","../src/shared/consistency-tracker.ts","../src/shared/collect-all-pages.ts","../src/storage/kysely/document-indexer.ts"],"sourcesContent":["import type { OperationWithContext } from \"@powerhousedao/shared/document-model\";\nimport type { Kysely, Transaction } from \"kysely\";\nimport type { IOperationIndex } from \"../cache/operation-index-types.js\";\nimport type { IWriteCache } from \"../cache/write/interfaces.js\";\nimport type { IConsistencyTracker } from \"../shared/consistency-tracker.js\";\nimport type {\n ConsistencyCoordinate,\n ConsistencyToken,\n} from \"../shared/types.js\";\nimport 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 } else {\n await this.initializeState();\n }\n\n let page = await this.operationIndex.getSinceOrdinal(this.lastOrdinal);\n while (page.results.length > 0) {\n const ops = this.config.rebuildStateOnInit\n ? await this.rebuildStateForOperations(page.results)\n : page.results;\n await this.indexOperations(ops);\n\n if (!page.next) break;\n page = await page.next();\n }\n }\n\n /**\n * 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 let maxOrdinal = 0;\n for (const item of items) {\n maxOrdinal = Math.max(maxOrdinal, item.context.ordinal);\n }\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 {\n ILiveReadModelCoordinator,\n IReadModel,\n ReadModelRegistrationStage,\n} from \"./interfaces.js\";\n\n/**\n * Coordinates read model synchronization by listening to operation write events\n * and updating all registered read models on per-`documentId:scope:branch`\n * serial chains. Cross-key projection runs in parallel; same-key projection is\n * serialized so the executor can return to dispatch without holding ordering\n * implicitly.\n */\nexport class ReadModelCoordinator implements ILiveReadModelCoordinator {\n private unsubscribe?: Unsubscribe;\n private isRunning = false;\n private readonly chains = new Map<string, Promise<void>>();\n private readonly logger: ILogger;\n\n readonly readModels: IReadModel[];\n\n constructor(\n private eventBus: IEventBus,\n public readonly preReady: IReadModel[],\n public readonly postReady: IReadModel[],\n ) {\n this.readModels = [...preReady, ...postReady];\n this.logger = childLogger([\"reactor\", \"read-model-coordinator\"]);\n }\n\n start(): void {\n if (this.isRunning) {\n return;\n }\n\n this.unsubscribe = this.eventBus.subscribe(\n ReactorEventTypes.JOB_WRITE_READY,\n (type, event: JobWriteReadyEvent) => {\n return this.handleWriteReady(event);\n },\n );\n\n this.isRunning = true;\n }\n\n stop(): void {\n if (!this.isRunning) {\n return;\n }\n\n if (this.unsubscribe) {\n this.unsubscribe();\n this.unsubscribe = undefined;\n }\n\n this.isRunning = false;\n }\n\n /**\n * Resolves when every per-queueKey projection chain has flushed. Intended\n * for test fixtures and explicit shutdown; production callers use\n * consistency tokens instead.\n */\n async drain(): Promise<void> {\n while (this.chains.size > 0) {\n const pending = Array.from(this.chains.values());\n await Promise.allSettled(pending);\n }\n }\n\n getChainDepth(): number {\n return this.chains.size;\n }\n\n addReadModel(readModel: IReadModel, stage: ReadModelRegistrationStage): void {\n if (this.readModels.some(({ name }) => name === readModel.name)) {\n throw new Error(`Read model \"${readModel.name}\" is already registered`);\n }\n\n if (stage === \"pre_ready\") {\n this.preReady.push(readModel);\n } else {\n this.postReady.push(readModel);\n }\n this.readModels.push(readModel);\n }\n\n private handleWriteReady(event: JobWriteReadyEvent): void {\n if (event.operations.length === 0) {\n void this.emitEmptyReadReady(event);\n return;\n }\n\n const enqueuedAt = performance.now();\n const key = this.queueKeyFor(event);\n const previous = this.chains.get(key) ?? Promise.resolve();\n const current = previous.then(() => this.runChain(event, enqueuedAt));\n\n this.chains.set(key, current);\n void current.finally(() => {\n if (this.chains.get(key) === current) {\n this.chains.delete(key);\n }\n });\n }\n\n private async emitEmptyReadReady(event: JobWriteReadyEvent): Promise<void> {\n const readyEvent: JobReadReadyEvent = {\n jobId: event.jobId,\n operations: event.operations,\n };\n try {\n await this.eventBus.emit(ReactorEventTypes.JOB_READ_READY, readyEvent);\n } catch (error) {\n this.logger.error(\n \"JOB_READ_READY emit failed for job @jobId: @Error\",\n { jobId: event.jobId },\n error,\n );\n }\n this.emitBatchCompleted({\n jobId: event.jobId,\n batchSize: 0,\n chainWaitDurationMs: 0,\n preReadyDurationMs: 0,\n emitDurationMs: 0,\n postReadyDurationMs: 0,\n });\n }\n\n private async runChain(\n event: JobWriteReadyEvent,\n enqueuedAt: number,\n ): Promise<void> {\n const chainStartedAt = performance.now();\n const chainWaitDurationMs = chainStartedAt - enqueuedAt;\n\n const preReadyStart = performance.now();\n try {\n await Promise.all(\n this.preReady.map((readModel) =>\n this.indexWithTiming(readModel, \"pre_ready\", event),\n ),\n );\n } catch (error) {\n this.logger.error(\n \"Pre-ready read model indexing failed for job @jobId: @Error\",\n { jobId: event.jobId },\n error,\n );\n }\n const preReadyDurationMs = performance.now() - preReadyStart;\n\n const readyEvent: JobReadReadyEvent = {\n jobId: event.jobId,\n operations: event.operations,\n };\n const emitStart = performance.now();\n try {\n await this.eventBus.emit(ReactorEventTypes.JOB_READ_READY, readyEvent);\n } catch (error) {\n this.logger.error(\n \"JOB_READ_READY emit failed for job @jobId: @Error\",\n { jobId: event.jobId },\n error,\n );\n }\n const emitDurationMs = performance.now() - emitStart;\n\n const postReadyStart = performance.now();\n try {\n await Promise.all(\n this.postReady.map((readModel) =>\n this.indexWithTiming(readModel, \"post_ready\", event),\n ),\n );\n } catch (error) {\n this.logger.error(\n \"Post-ready read model indexing failed for job @jobId: @Error\",\n { jobId: event.jobId },\n error,\n );\n }\n const postReadyDurationMs = performance.now() - postReadyStart;\n\n this.emitBatchCompleted({\n jobId: event.jobId,\n batchSize: event.operations.length,\n chainWaitDurationMs,\n preReadyDurationMs,\n emitDurationMs,\n postReadyDurationMs,\n });\n }\n\n private async indexWithTiming(\n readModel: IReadModel,\n stage: ReadModelIndexingStage,\n event: JobWriteReadyEvent,\n ): Promise<void> {\n const start = performance.now();\n let success = false;\n try {\n await readModel.indexOperations(event.operations);\n success = true;\n } finally {\n this.emitReadModelIndexed({\n jobId: event.jobId,\n readModelName: readModel.name,\n stage,\n durationMs: performance.now() - start,\n operationCount: event.operations.length,\n success,\n });\n }\n }\n\n private emitReadModelIndexed(payload: ReadModelIndexedEvent): void {\n void this.eventBus\n .emit(ReactorEventTypes.READMODEL_INDEXED, payload)\n .catch((err: unknown) =>\n this.logger.error(\n \"READMODEL_INDEXED emit failed for job @jobId: @Error\",\n { jobId: payload.jobId },\n err,\n ),\n );\n }\n\n private emitBatchCompleted(payload: ReadModelBatchCompletedEvent): void {\n void this.eventBus\n .emit(ReactorEventTypes.READMODEL_BATCH_COMPLETED, payload)\n .catch((err: unknown) =>\n this.logger.error(\n \"READMODEL_BATCH_COMPLETED emit failed for job @jobId: @Error\",\n { jobId: payload.jobId },\n err,\n ),\n );\n }\n\n private queueKeyFor(event: JobWriteReadyEvent): string {\n const ctx = event.operations[0]!.context;\n return `${ctx.documentId}:${ctx.scope}:${ctx.branch}`;\n }\n}\n","import type { IDocumentIndexer, IDocumentView } from \"../storage/interfaces.js\";\n\n/** `getReadModel` name of the document view; see {@link ReactorReadModels}. */\nexport const DOCUMENT_VIEW_READ_MODEL = \"document-view\";\n/** `getReadModel` name of the document indexer; see {@link ReactorReadModels}. */\nexport const DOCUMENT_INDEXER_READ_MODEL = \"document-indexer\";\n\n/**\n * Read models the reactor registers on every host, keyed by the name passed to\n * `IProcessorHostModule.getReadModel`. Each entry says what the model holds\n * and when a processor should reach for it instead of the reactor client.\n */\nexport interface ReactorReadModels {\n /**\n * Materialized document snapshots — header plus per-scope state — kept\n * current as operations are indexed, so reads never replay operations.\n *\n * Reach for it to read a document's current state from inside a\n * processor: `get` / `getMany` by id, `getByIdOrSlug`, `findByType`,\n * `exists`, and `resolveSlug` / `resolveIdOrSlug`. Every query takes an\n * optional `ConsistencyToken` (from a job result) for read-after-write.\n * Unlike `client.get`, results are the raw snapshot — scopes are not\n * filtered to what the caller may read.\n */\n [DOCUMENT_VIEW_READ_MODEL]: IDocumentView;\n\n /**\n * Directed graph of document relationships, built from ADD_RELATIONSHIP /\n * REMOVE_RELATIONSHIP operations. Edges carry a `relationshipType`;\n * parent/child links (drive → document, folder → file) use `\"child\"`.\n *\n * Reach for it to navigate structure rather than content: `getOutgoing` /\n * `getIncoming` neighbours, `hasRelationship`, `findPath`, and\n * `findAncestors` (e.g. which drive(s) contain a document). Snapshots of\n * the documents themselves come from the document view.\n */\n [DOCUMENT_INDEXER_READ_MODEL]: IDocumentIndexer;\n}\n","import type {\n OperationWithContext,\n PHAuthState,\n PHDocument,\n PHDocumentHeader,\n} from \"@powerhousedao/shared/document-model\";\nimport {\n createAuthState,\n isDenied,\n} from \"@powerhousedao/shared/document-model\";\nimport type { Kysely } from \"kysely\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport type { IOperationIndex } from \"../cache/operation-index-types.js\";\nimport type { IWriteCache } from \"../cache/write/interfaces.js\";\nimport type { IConsistencyTracker } from \"../shared/consistency-tracker.js\";\nimport { DOCUMENT_VIEW_READ_MODEL } from \"./names.js\";\nimport type {\n ConsistencyToken,\n PagedResults,\n PagingOptions,\n} from \"../shared/types.js\";\nimport {\n DocumentExistence,\n type DocumentRevisions,\n type IDocumentView,\n type IOperationStore,\n type ViewFilter,\n} from \"../storage/interfaces.js\";\nimport type { Database as StorageDatabase } from \"../storage/kysely/types.js\";\nimport { 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 * Whether a single-document read serves a deleted document's state as of the\n * deletion rather than hiding it. Only meaningful with `documentDecisions`,\n * which is what makes deletion positional. Listings omit it either way.\n */\n private readonly servesDeletionBoundary: boolean,\n ) {\n super(\n db as unknown as Kysely<DocumentViewDatabase>,\n operationIndex,\n writeCache,\n consistencyTracker,\n { readModelId: DOCUMENT_VIEW_READ_MODEL, rebuildStateOnInit: true },\n );\n this._db = db;\n }\n\n /**\n * Indexes committed operations into DocumentSnapshot rows. CREATE_DOCUMENT\n * only seeds header/document/auth. UPGRADE_DOCUMENT reindexes every scope\n * present in resultingState when the operation vouches for them — a seed\n * carrying initialState or a migration stamped with the __migrated marker\n * — since the upgrade reducer may have reshaped any of them; upgrades\n * without either fall back to header/document/auth, because their sibling\n * echoes may be stale. All other action types index only header and their\n * own scope.\n */\n 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 // A refused operation holds its index and changes nothing, so it must\n // not carry its action type's side effects. A refused DELETE_DOCUMENT\n // is the case that matters: taking the branch below would hide the\n // document from every listing and destroy its slug mapping, with\n // nothing in any path to put either back.\n const denied = isDenied(operation);\n\n // Non-header ops carry a denormalized header echo with possibly stale\n // `meta`; only header-scope ops and CREATE/UPGRADE/DELETE may write it.\n const preserveHeaderMeta =\n denied ||\n (operationType !== \"CREATE_DOCUMENT\" &&\n operationType !== \"UPGRADE_DOCUMENT\" &&\n operationType !== \"DELETE_DOCUMENT\" &&\n scope !== \"header\");\n\n if (operationType === \"DELETE_DOCUMENT\" && !denied) {\n const now = new Date();\n await trx\n .updateTable(\"DocumentSnapshot\")\n .set({\n isDeleted: true,\n deletedAt: now,\n lastOperationIndex: index,\n lastOperationHash: hash,\n lastUpdatedAt: now,\n })\n .where(\"documentId\", \"=\", documentId)\n .where(\"branch\", \"=\", branch)\n .execute();\n\n // The content has to say so too, or a caller served the boundary state\n // would get state claiming the document is live.\n const deletedDocumentScope = (\n fullState as Record<string, unknown> | undefined\n )?.document;\n if (deletedDocumentScope !== undefined) {\n await trx\n .updateTable(\"DocumentSnapshot\")\n .set({ content: deletedDocumentScope })\n .where(\"documentId\", \"=\", documentId)\n .where(\"branch\", \"=\", branch)\n .where(\"scope\", \"=\", \"document\")\n .execute();\n }\n\n await trx\n .deleteFrom(\"SlugMapping\")\n .where(\"documentId\", \"=\", documentId)\n .where(\"branch\", \"=\", branch)\n .execute();\n\n continue;\n }\n\n let scopesToIndex: Array<[string, unknown]>;\n\n if (operationType === \"CREATE_DOCUMENT\") {\n scopesToIndex = Object.entries(fullState).filter(\n ([key]) => key === \"header\" || key === \"document\" || key === \"auth\",\n );\n } else if (operationType === \"UPGRADE_DOCUMENT\") {\n // Sibling-scope echoes are only trustworthy when the operation\n // itself defines them: a seed carrying initialState, or a migration\n // whose executor vouched (via the __migrated marker) that it\n // fetched every scope fresh. Upgrades persisted by executors that\n // never fetched sibling scopes echo whatever stale state the write\n // cache held, and indexing those would clobber good snapshots.\n const upgradeInput = operation.action.input as {\n initialState?: unknown;\n };\n const scopesVouchedFor =\n fullState.__migrated === true ||\n upgradeInput.initialState !== undefined;\n delete fullState.__migrated;\n\n scopesToIndex = scopesVouchedFor\n ? Object.entries(fullState)\n : Object.entries(fullState).filter(\n ([key]) =>\n key === \"header\" || key === \"document\" || key === \"auth\",\n );\n } else {\n scopesToIndex = [];\n\n if (fullState.header !== undefined) {\n scopesToIndex.push([\"header\", fullState.header]);\n }\n\n if (fullState[scope] !== undefined) {\n scopesToIndex.push([scope, fullState[scope]]);\n } else {\n scopesToIndex.push([scope, {}]);\n }\n }\n\n for (const [scopeName, scopeState] of scopesToIndex) {\n 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 // Never for a deleted document. Deleting removes its SlugMapping\n // rows, and resolveIdOrSlug relies on that to keep the slug branch\n // from reaching one: re-inserting here would make the document\n // resolvable by slug again, and the upsert would take the slug from\n // a live document that has since claimed it.\n if (slug && slug !== documentId && !existingSnapshot?.isDeleted) {\n await trx\n .insertInto(\"SlugMapping\")\n .values({\n slug,\n documentId,\n scope: scopeName,\n branch,\n })\n .onConflict((oc) =>\n oc.column(\"slug\").doUpdateSet({\n documentId,\n scope: scopeName,\n branch,\n }),\n )\n .execute();\n }\n }\n\n if (existingSnapshot) {\n await trx\n .updateTable(\"DocumentSnapshot\")\n .set({\n lastOperationIndex: index,\n lastOperationHash: hash,\n 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 existence: DocumentExistence,\n consistencyToken?: ConsistencyToken,\n signal?: AbortSignal,\n ): Promise<boolean[]> {\n if (consistencyToken) {\n await this.waitForConsistency(consistencyToken, undefined, signal);\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n if (documentIds.length === 0) {\n return [];\n }\n\n const existingIds =\n existence === DocumentExistence.IncludingDeleted\n ? await this.idsWithStreams(documentIds, signal)\n : await this.liveIds(documentIds);\n\n return documentIds.map((id) => existingIds.has(id));\n }\n\n async get<TDocument extends PHDocument>(\n documentId: string,\n view?: ViewFilter,\n consistencyToken?: ConsistencyToken,\n signal?: AbortSignal,\n ): Promise<TDocument> {\n if (consistencyToken) {\n await this.waitForConsistency(consistencyToken, undefined, signal);\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const branch = view?.branch || \"main\";\n\n let scopesToQuery: string[];\n if (view?.scopes && view.scopes.length > 0) {\n scopesToQuery = [...new Set([\"header\", \"document\", ...view.scopes])];\n } else {\n scopesToQuery = [];\n }\n\n // Unfiltered when serving the boundary state; `state.document.isDeleted` tells\n // the caller what it holds. Listings keep the filter either way.\n let query = this._db\n .selectFrom(\"DocumentSnapshot\")\n .selectAll()\n .where(\"documentId\", \"=\", documentId)\n .where(\"branch\", \"=\", branch);\n\n if (!this.servesDeletionBoundary) {\n query = query.where(\"isDeleted\", \"=\", false);\n }\n\n if (scopesToQuery.length > 0) {\n query = query.where(\"scope\", \"in\", scopesToQuery);\n }\n\n const snapshots = await query.execute();\n\n if (snapshots.length === 0) {\n throw new Error(`Document not found: ${documentId}`);\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const headerSnapshot = snapshots.find((s) => s.scope === \"header\");\n if (!headerSnapshot) {\n throw new Error(`Document header not found: ${documentId}`);\n }\n\n const header = headerSnapshot.content as PHDocumentHeader;\n\n const revisions = await this.operationStore.getRevisions(\n documentId,\n branch,\n signal,\n );\n header.revision = revisions.revision;\n header.lastModifiedAtUtcIso = revisions.latestTimestamp;\n\n const state: Record<string, unknown> = {};\n for (const snapshot of snapshots) {\n if (snapshot.scope === \"header\") {\n continue;\n }\n\n // snapshot rows written before PHAuthState had a version carry auth: {}\n state[snapshot.scope] =\n snapshot.scope === \"auth\"\n ? createAuthState(snapshot.content as Partial<PHAuthState>)\n : snapshot.content;\n }\n\n const document: PHDocument = {\n header,\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n state: state as any,\n operations: {},\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n initialState: state as any,\n clipboard: [],\n };\n\n return document as TDocument;\n }\n\n async getMany<TDocument extends PHDocument>(\n documentIds: string[],\n view?: ViewFilter,\n consistencyToken?: ConsistencyToken,\n signal?: AbortSignal,\n ): Promise<TDocument[]> {\n if (documentIds.length === 0) {\n return [];\n }\n\n if (consistencyToken) {\n await this.waitForConsistency(consistencyToken, undefined, signal);\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const branch = view?.branch || \"main\";\n\n let scopesToQuery: string[];\n if (view?.scopes && view.scopes.length > 0) {\n scopesToQuery = [...new Set([\"header\", \"document\", ...view.scopes])];\n } else {\n scopesToQuery = [];\n }\n\n let query = this._db\n .selectFrom(\"DocumentSnapshot\")\n .selectAll()\n .where(\"documentId\", \"in\", documentIds)\n .where(\"branch\", \"=\", branch)\n .where(\"isDeleted\", \"=\", false);\n\n if (scopesToQuery.length > 0) {\n query = query.where(\"scope\", \"in\", scopesToQuery);\n }\n\n const allSnapshots = await query.execute();\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const snapshotsByDocId = new Map<string, typeof allSnapshots>();\n for (const snapshot of allSnapshots) {\n const existing = snapshotsByDocId.get(snapshot.documentId) || [];\n existing.push(snapshot);\n snapshotsByDocId.set(snapshot.documentId, existing);\n }\n\n const foundDocumentIds = [...snapshotsByDocId.keys()];\n const revisionResults = await Promise.all(\n foundDocumentIds.map((docId) =>\n this.operationStore.getRevisions(docId, branch, signal),\n ),\n );\n\n const revisionsByDocId = new Map<string, DocumentRevisions>();\n for (let i = 0; i < foundDocumentIds.length; i++) {\n revisionsByDocId.set(foundDocumentIds[i], revisionResults[i]);\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const documents: TDocument[] = [];\n for (const documentId of documentIds) {\n const snapshots = snapshotsByDocId.get(documentId);\n if (!snapshots || snapshots.length === 0) {\n continue;\n }\n\n const headerSnapshot = snapshots.find((s) => s.scope === \"header\");\n if (!headerSnapshot) {\n continue;\n }\n\n const header = headerSnapshot.content as PHDocumentHeader;\n const revisions = revisionsByDocId.get(documentId);\n if (revisions) {\n header.revision = revisions.revision;\n header.lastModifiedAtUtcIso = revisions.latestTimestamp;\n }\n\n const state: Record<string, unknown> = {};\n for (const snapshot of snapshots) {\n if (snapshot.scope === \"header\") {\n continue;\n }\n // snapshot rows written before PHAuthState had a version carry auth: {}\n state[snapshot.scope] =\n snapshot.scope === \"auth\"\n ? createAuthState(snapshot.content as Partial<PHAuthState>)\n : snapshot.content;\n }\n\n const document: PHDocument = {\n header,\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n state: state as any,\n operations: {},\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n initialState: state as any,\n clipboard: [],\n };\n\n documents.push(document as TDocument);\n }\n\n return documents;\n }\n\n async getByIdOrSlug<TDocument extends PHDocument>(\n identifier: string,\n view?: ViewFilter,\n consistencyToken?: ConsistencyToken,\n signal?: AbortSignal,\n ): Promise<TDocument> {\n const documentId = await this.resolveIdOrSlug(\n identifier,\n view,\n consistencyToken,\n signal,\n );\n return this.get<TDocument>(documentId, view, undefined, signal);\n }\n\n async findByType(\n type: string,\n view?: ViewFilter,\n paging?: PagingOptions,\n consistencyToken?: ConsistencyToken,\n signal?: AbortSignal,\n ): Promise<PagedResults<PHDocument>> {\n if (consistencyToken) {\n await this.waitForConsistency(consistencyToken, undefined, signal);\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const branch = view?.branch || \"main\";\n\n const startIndex = paging?.cursor ? parseInt(paging.cursor) : 0;\n const limit = paging?.limit || 100;\n\n const 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 // The id branch relaxes with `get`, or resolution throws before it. The slug\n // branch never does: deleting a document removes its SlugMapping rows.\n let idCheckQuery = this._db\n .selectFrom(\"DocumentSnapshot\")\n .select(\"documentId\")\n .where(\"documentId\", \"=\", identifier)\n .where(\"branch\", \"=\", branch);\n\n if (!this.servesDeletionBoundary) {\n idCheckQuery = idCheckQuery.where(\"isDeleted\", \"=\", false);\n }\n\n const idCheckPromise = idCheckQuery.executeTakeFirst();\n\n const slugCheckPromise = this._db\n .selectFrom(\"SlugMapping\")\n .select(\"documentId\")\n .where(\"slug\", \"=\", identifier)\n .where(\"branch\", \"=\", branch)\n .executeTakeFirst();\n\n const [idMatch, slugMatch] = await Promise.all([\n idCheckPromise,\n slugCheckPromise,\n ]);\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const idMatchDocId = idMatch?.documentId;\n const slugMatchDocId = slugMatch?.documentId;\n\n if (idMatchDocId && slugMatchDocId && idMatchDocId !== slugMatchDocId) {\n throw new Error(\n `Ambiguous identifier \"${identifier}\": matches both document ID \"${idMatchDocId}\" and slug for document ID \"${slugMatchDocId}\". ` +\n `Please use get() for ID or resolveSlug() + get() for slug to be explicit.`,\n );\n }\n\n const resolvedDocumentId = idMatchDocId || slugMatchDocId;\n\n if (!resolvedDocumentId) {\n throw new Error(`Document not found: ${identifier}`);\n }\n\n return resolvedDocumentId;\n }\n\n /**\n * Ids that have at least one stream, asked of the operation store rather than\n * of a table, so the answer holds for any store implementation. A deleted\n * document keeps its stream, so this is the id-is-taken answer, and it does\n * not drift when a snapshot row is missing.\n *\n * Asked on main because that is the branch a create writes: the store this\n * question exists to agree with validates the create against main's head.\n */\n private async idsWithStreams(\n documentIds: string[],\n signal?: AbortSignal,\n ): Promise<Set<string>> {\n const unique = [...new Set(documentIds)];\n const revisions = await Promise.all(\n unique.map((documentId) =>\n this.operationStore.getRevisions(documentId, \"main\", signal),\n ),\n );\n\n const taken = new Set<string>();\n for (let i = 0; i < unique.length; i++) {\n if (Object.keys(revisions[i].revision).length > 0) {\n taken.add(unique[i]);\n }\n }\n return taken;\n }\n\n private async liveIds(documentIds: string[]): Promise<Set<string>> {\n const rows = await this._db\n .selectFrom(\"DocumentSnapshot\")\n .select([\"documentId\"])\n .where(\"documentId\", \"in\", documentIds)\n .where(\"isDeleted\", \"=\", false)\n .distinct()\n .execute();\n\n return new Set(rows.map((row) => row.documentId));\n }\n}\n","import type { ConsistencyCoordinate, ConsistencyKey } from \"./types.js\";\n\nexport interface IConsistencyTracker {\n /**\n * Updates the tracker with new operation indexes.\n * When multiple coordinates have the same key, keeps the highest operationIndex.\n * Resolves any pending waiters whose coordinates are now satisfied.\n */\n update(coordinates: ConsistencyCoordinate[]): void;\n\n /**\n * Returns the latest operation index for a given key, or undefined if not tracked.\n */\n getLatest(key: ConsistencyKey): number | undefined;\n\n /**\n * Returns a promise that resolves when all coordinates are satisfied.\n * Rejects if the timeout is reached or the signal is aborted.\n */\n waitFor(\n coordinates: ConsistencyCoordinate[],\n timeoutMs?: number,\n signal?: AbortSignal,\n ): Promise<void>;\n\n /**\n * Returns a serializable snapshot of the current state.\n */\n serialize(): Array<[ConsistencyKey, number]>;\n\n /**\n * Restores state from a serialized snapshot.\n */\n hydrate(entries: Array<[ConsistencyKey, number]>): void;\n}\n\ntype Waiter = {\n coordinates: ConsistencyCoordinate[];\n resolve: () => void;\n reject: (reason: Error) => void;\n signal?: AbortSignal;\n timeoutId?: NodeJS.Timeout;\n};\n\n/**\n * Creates a consistency key from documentId, scope, and branch.\n */\nexport function makeConsistencyKey(\n documentId: string,\n scope: string,\n branch: string,\n): ConsistencyKey {\n return `${documentId}:${scope}:${branch}`;\n}\n\n/**\n * Tracks operation indexes for documents and provides read-after-write consistency guarantees.\n * Maintains an in-memory map of the latest operation index for each (documentId, scope, branch) tuple.\n */\nexport class ConsistencyTracker implements IConsistencyTracker {\n private state = new Map<ConsistencyKey, number>();\n private waiters: Waiter[] = [];\n\n update(coordinates: ConsistencyCoordinate[]): void {\n const deduplicated = this.deduplicateCoordinates(coordinates);\n\n for (let i = 0; i < deduplicated.length; i++) {\n const coord = deduplicated[i]!;\n const key = makeConsistencyKey(\n coord.documentId,\n coord.scope,\n coord.branch,\n );\n const current = this.state.get(key);\n if (current === undefined || coord.operationIndex > current) {\n this.state.set(key, coord.operationIndex);\n }\n }\n\n this.checkWaiters();\n }\n\n getLatest(key: ConsistencyKey): number | undefined {\n return this.state.get(key);\n }\n\n waitFor(\n coordinates: ConsistencyCoordinate[],\n timeoutMs?: number,\n signal?: AbortSignal,\n ): Promise<void> {\n if (signal?.aborted) {\n return Promise.reject(new Error(\"Operation aborted\"));\n }\n\n if (this.areCoordinatesSatisfied(coordinates)) {\n return Promise.resolve();\n }\n\n return new Promise<void>((resolve, reject) => {\n const waiter: Waiter = {\n coordinates,\n resolve,\n reject,\n signal,\n };\n\n if (timeoutMs !== undefined) {\n waiter.timeoutId = setTimeout(() => {\n this.removeWaiter(waiter);\n reject(new Error(`Consistency wait timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n }\n\n if (signal) {\n const abortHandler = () => {\n this.removeWaiter(waiter);\n reject(new Error(\"Operation aborted\"));\n };\n signal.addEventListener(\"abort\", abortHandler, { once: true });\n }\n\n this.waiters.push(waiter);\n });\n }\n\n serialize(): Array<[ConsistencyKey, number]> {\n return Array.from(this.state.entries());\n }\n\n hydrate(entries: Array<[ConsistencyKey, number]>): void {\n this.state.clear();\n for (const [key, index] of entries) {\n this.state.set(key, index);\n }\n }\n\n private deduplicateCoordinates(\n coordinates: ConsistencyCoordinate[],\n ): ConsistencyCoordinate[] {\n const map = new Map<ConsistencyKey, ConsistencyCoordinate>();\n\n for (let i = 0; i < coordinates.length; i++) {\n const coord = coordinates[i]!;\n const key = makeConsistencyKey(\n coord.documentId,\n coord.scope,\n coord.branch,\n );\n const existing = map.get(key);\n\n if (!existing || coord.operationIndex > existing.operationIndex) {\n map.set(key, coord);\n }\n }\n\n return Array.from(map.values());\n }\n\n private areCoordinatesSatisfied(\n coordinates: ConsistencyCoordinate[],\n ): boolean {\n for (let i = 0; i < coordinates.length; i++) {\n const coord = coordinates[i]!;\n const key = makeConsistencyKey(\n coord.documentId,\n coord.scope,\n coord.branch,\n );\n const latest = this.state.get(key);\n if (latest === undefined || latest < coord.operationIndex) {\n return false;\n }\n }\n return true;\n }\n\n private checkWaiters(): void {\n const satisfiedWaiters: Waiter[] = [];\n const unsatisfiedWaiters: Waiter[] = [];\n\n for (const waiter of this.waiters) {\n if (waiter.signal?.aborted) {\n continue;\n }\n\n if (this.areCoordinatesSatisfied(waiter.coordinates)) {\n satisfiedWaiters.push(waiter);\n } else {\n unsatisfiedWaiters.push(waiter);\n }\n }\n\n this.waiters = unsatisfiedWaiters;\n\n for (const waiter of satisfiedWaiters) {\n if (waiter.timeoutId !== undefined) {\n clearTimeout(waiter.timeoutId);\n }\n waiter.resolve();\n }\n }\n\n private removeWaiter(waiter: Waiter): void {\n const index = this.waiters.indexOf(waiter);\n if (index !== -1) {\n this.waiters.splice(index, 1);\n }\n if (waiter.timeoutId !== undefined) {\n clearTimeout(waiter.timeoutId);\n }\n }\n}\n","import type { PagedResults } from \"./types.js\";\n\n/**\n * Collects all results from a paged result set by following the next() function\n * until all pages have been fetched.\n */\nexport async function collectAllPages<T>(\n firstPage: PagedResults<T>,\n signal?: AbortSignal,\n): Promise<T[]> {\n const allResults: T[] = [...firstPage.results];\n let currentPage = firstPage;\n\n while (currentPage.next) {\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n currentPage = await currentPage.next();\n allResults.push(...currentPage.results);\n }\n\n return allResults;\n}\n","import type {\n Operation,\n OperationWithContext,\n} from \"@powerhousedao/shared/document-model\";\nimport type { Kysely } from \"kysely\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport type { IOperationIndex } from \"../../cache/operation-index-types.js\";\nimport type { IWriteCache } from \"../../cache/write/interfaces.js\";\nimport { BaseReadModel } from \"../../read-models/base-read-model.js\";\nimport { DOCUMENT_INDEXER_READ_MODEL } from \"../../read-models/names.js\";\nimport type { DocumentViewDatabase } from \"../../read-models/types.js\";\nimport { collectAllPages } from \"../../shared/collect-all-pages.js\";\nimport type { IConsistencyTracker } from \"../../shared/consistency-tracker.js\";\nimport type {\n ConsistencyToken,\n PagedResults,\n PagingOptions,\n} from \"../../shared/types.js\";\nimport type {\n DocumentGraphEdge,\n DocumentRelationship,\n IDocumentGraph,\n IDocumentIndexer,\n} from \"../interfaces.js\";\nimport type {\n DocumentIndexerDatabase,\n InsertableDocumentRelationship,\n Database as StorageDatabase,\n} from \"./types.js\";\n\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_READ_MODEL, 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":";;;;;;;;;;;AAsBA,IAAa,gBAAb,MAAiD;CAC/C,cAAgC;CAEhC;CAEA,YACE,IACA,gBACA,YACA,oBACA,QACA;AALU,OAAA,KAAA;AACA,OAAA,iBAAA;AACA,OAAA,aAAA;AACA,OAAA,qBAAA;AACA,OAAA,SAAA;AAEV,OAAK,OAAO,OAAO;;;;;CAMrB,MAAM,OAAsB;EAC1B,MAAM,YAAY,MAAM,KAAK,WAAW;AAExC,MAAI,cAAc,KAAA,EAChB,MAAK,cAAc;MAEnB,OAAM,KAAK,iBAAiB;EAG9B,IAAI,OAAO,MAAM,KAAK,eAAe,gBAAgB,KAAK,YAAY;AACtE,SAAO,KAAK,QAAQ,SAAS,GAAG;GAC9B,MAAM,MAAM,KAAK,OAAO,qBACpB,MAAM,KAAK,0BAA0B,KAAK,QAAQ,GAClD,KAAK;AACT,SAAM,KAAK,gBAAgB,IAAI;AAE/B,OAAI,CAAC,KAAK,KAAM;AAChB,UAAO,MAAM,KAAK,MAAM;;;;;;;CAQ5B,MAAM,gBAAgB,OAA8C;AAClE,MAAI,MAAM,WAAW,EAAG;AAExB,QAAM,KAAK,iBAAiB,MAAM;AAElC,QAAM,KAAK,GAAG,aAAa,CAAC,QAAQ,OAAO,QAAQ;AACjD,SAAM,KAAK,UAAU,KAAK,MAAM;IAChC;AAEF,OAAK,yBAAyB,MAAM;;;;;CAMtC,MAAM,mBACJ,OACA,WACA,QACe;AACf,MAAI,MAAM,YAAY,WAAW,EAC/B;AAEF,QAAM,KAAK,mBAAmB,QAAQ,MAAM,aAAa,WAAW,OAAO;;CAI7E,MAAgB,iBAEd,OACe;;;;CAKjB,MAAgB,0BACd,YACiC;EACjC,MAAM,SAAiC,EAAE;AAEzC,OAAK,MAAM,MAAM,YAAY;GAC3B,MAAM,EAAE,YAAY,OAAO,WAAW,GAAG;GACzC,MAAM,iBAAiB,GAAG,UAAU;GAEpC,MAAM,WAAW,MAAM,KAAK,WAAW,SACrC,YACA,OACA,QACA,eACD;AAED,UAAO,KAAK;IACV,WAAW,GAAG;IACd,SAAS;KACP,GAAG,GAAG;KACN,gBAAgB,KAAK,UAAU,SAAS;KACzC;IACF,CAAC;;AAGJ,SAAO;;;;;;CAOT,MAAgB,YAAyC;AAQvD,UANY,MADQ,KAAK,GAEtB,WAAW,YAAY,CACvB,OAAO,cAAc,CACrB,MAAM,eAAe,KAAK,KAAK,OAAO,YAAY,CAClD,kBAAkB,GAET;;;;;CAMd,MAAgB,kBAAiC;AAE/C,QADoB,KAAK,GAEtB,WAAW,YAAY,CACvB,OAAO;GACN,aAAa,KAAK,OAAO;GACzB,aAAa;GACd,CAAC,CACD,SAAS;;;;;CAMd,MAAgB,UACd,KACA,OACe;EACf,IAAI,aAAa;AACjB,OAAK,MAAM,QAAQ,MACjB,cAAa,KAAK,IAAI,YAAY,KAAK,QAAQ,QAAQ;AAEzD,OAAK,cAAc;AAEnB,QAAM,IACH,YAAY,YAAY,CACxB,IAAI;GACH,aAAa;GACb,wCAAwB,IAAI,MAAM;GACnC,CAAC,CACD,MAAM,eAAe,KAAK,KAAK,OAAO,YAAY,CAClD,SAAS;;;;;CAMd,yBAAmC,OAAqC;EACtE,MAAM,cAAuC,EAAE;AAE/C,OAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;GACrC,MAAM,OAAO,MAAM;AACnB,eAAY,KAAK;IACf,YAAY,KAAK,QAAQ;IACzB,OAAO,KAAK,QAAQ;IACpB,QAAQ,KAAK,QAAQ;IACrB,gBAAgB,KAAK,UAAU;IAChC,CAAC;;AAGJ,OAAK,mBAAmB,OAAO,YAAY;;;;;;;;;;;;AC5K/C,IAAa,uBAAb,MAAuE;CACrE;CACA,YAAoB;CACpB,yBAA0B,IAAI,KAA4B;CAC1D;CAEA;CAEA,YACE,UACA,UACA,WACA;AAHQ,OAAA,WAAA;AACQ,OAAA,WAAA;AACA,OAAA,YAAA;AAEhB,OAAK,aAAa,CAAC,GAAG,UAAU,GAAG,UAAU;AAC7C,OAAK,SAAS,YAAY,CAAC,WAAW,yBAAyB,CAAC;;CAGlE,QAAc;AACZ,MAAI,KAAK,UACP;AAGF,OAAK,cAAc,KAAK,SAAS,UAC/B,kBAAkB,kBACjB,MAAM,UAA8B;AACnC,UAAO,KAAK,iBAAiB,MAAM;IAEtC;AAED,OAAK,YAAY;;CAGnB,OAAa;AACX,MAAI,CAAC,KAAK,UACR;AAGF,MAAI,KAAK,aAAa;AACpB,QAAK,aAAa;AAClB,QAAK,cAAc,KAAA;;AAGrB,OAAK,YAAY;;;;;;;CAQnB,MAAM,QAAuB;AAC3B,SAAO,KAAK,OAAO,OAAO,GAAG;GAC3B,MAAM,UAAU,MAAM,KAAK,KAAK,OAAO,QAAQ,CAAC;AAChD,SAAM,QAAQ,WAAW,QAAQ;;;CAIrC,gBAAwB;AACtB,SAAO,KAAK,OAAO;;CAGrB,aAAa,WAAuB,OAAyC;AAC3E,MAAI,KAAK,WAAW,MAAM,EAAE,WAAW,SAAS,UAAU,KAAK,CAC7D,OAAM,IAAI,MAAM,eAAe,UAAU,KAAK,yBAAyB;AAGzE,MAAI,UAAU,YACZ,MAAK,SAAS,KAAK,UAAU;MAE7B,MAAK,UAAU,KAAK,UAAU;AAEhC,OAAK,WAAW,KAAK,UAAU;;CAGjC,iBAAyB,OAAiC;AACxD,MAAI,MAAM,WAAW,WAAW,GAAG;AAC5B,QAAK,mBAAmB,MAAM;AACnC;;EAGF,MAAM,aAAa,YAAY,KAAK;EACpC,MAAM,MAAM,KAAK,YAAY,MAAM;EAEnC,MAAM,WADW,KAAK,OAAO,IAAI,IAAI,IAAI,QAAQ,SAAS,EACjC,WAAW,KAAK,SAAS,OAAO,WAAW,CAAC;AAErE,OAAK,OAAO,IAAI,KAAK,QAAQ;AACxB,UAAQ,cAAc;AACzB,OAAI,KAAK,OAAO,IAAI,IAAI,KAAK,QAC3B,MAAK,OAAO,OAAO,IAAI;IAEzB;;CAGJ,MAAc,mBAAmB,OAA0C;EACzE,MAAM,aAAgC;GACpC,OAAO,MAAM;GACb,YAAY,MAAM;GACnB;AACD,MAAI;AACF,SAAM,KAAK,SAAS,KAAK,kBAAkB,gBAAgB,WAAW;WAC/D,OAAO;AACd,QAAK,OAAO,MACV,qDACA,EAAE,OAAO,MAAM,OAAO,EACtB,MACD;;AAEH,OAAK,mBAAmB;GACtB,OAAO,MAAM;GACb,WAAW;GACX,qBAAqB;GACrB,oBAAoB;GACpB,gBAAgB;GAChB,qBAAqB;GACtB,CAAC;;CAGJ,MAAc,SACZ,OACA,YACe;EAEf,MAAM,sBADiB,YAAY,KAAK,GACK;EAE7C,MAAM,gBAAgB,YAAY,KAAK;AACvC,MAAI;AACF,SAAM,QAAQ,IACZ,KAAK,SAAS,KAAK,cACjB,KAAK,gBAAgB,WAAW,aAAa,MAAM,CACpD,CACF;WACM,OAAO;AACd,QAAK,OAAO,MACV,+DACA,EAAE,OAAO,MAAM,OAAO,EACtB,MACD;;EAEH,MAAM,qBAAqB,YAAY,KAAK,GAAG;EAE/C,MAAM,aAAgC;GACpC,OAAO,MAAM;GACb,YAAY,MAAM;GACnB;EACD,MAAM,YAAY,YAAY,KAAK;AACnC,MAAI;AACF,SAAM,KAAK,SAAS,KAAK,kBAAkB,gBAAgB,WAAW;WAC/D,OAAO;AACd,QAAK,OAAO,MACV,qDACA,EAAE,OAAO,MAAM,OAAO,EACtB,MACD;;EAEH,MAAM,iBAAiB,YAAY,KAAK,GAAG;EAE3C,MAAM,iBAAiB,YAAY,KAAK;AACxC,MAAI;AACF,SAAM,QAAQ,IACZ,KAAK,UAAU,KAAK,cAClB,KAAK,gBAAgB,WAAW,cAAc,MAAM,CACrD,CACF;WACM,OAAO;AACd,QAAK,OAAO,MACV,gEACA,EAAE,OAAO,MAAM,OAAO,EACtB,MACD;;EAEH,MAAM,sBAAsB,YAAY,KAAK,GAAG;AAEhD,OAAK,mBAAmB;GACtB,OAAO,MAAM;GACb,WAAW,MAAM,WAAW;GAC5B;GACA;GACA;GACA;GACD,CAAC;;CAGJ,MAAc,gBACZ,WACA,OACA,OACe;EACf,MAAM,QAAQ,YAAY,KAAK;EAC/B,IAAI,UAAU;AACd,MAAI;AACF,SAAM,UAAU,gBAAgB,MAAM,WAAW;AACjD,aAAU;YACF;AACR,QAAK,qBAAqB;IACxB,OAAO,MAAM;IACb,eAAe,UAAU;IACzB;IACA,YAAY,YAAY,KAAK,GAAG;IAChC,gBAAgB,MAAM,WAAW;IACjC;IACD,CAAC;;;CAIN,qBAA6B,SAAsC;AAC5D,OAAK,SACP,KAAK,kBAAkB,mBAAmB,QAAQ,CAClD,OAAO,QACN,KAAK,OAAO,MACV,wDACA,EAAE,OAAO,QAAQ,OAAO,EACxB,IACD,CACF;;CAGL,mBAA2B,SAA6C;AACjE,OAAK,SACP,KAAK,kBAAkB,2BAA2B,QAAQ,CAC1D,OAAO,QACN,KAAK,OAAO,MACV,gEACA,EAAE,OAAO,QAAQ,OAAO,EACxB,IACD,CACF;;CAGL,YAAoB,OAAmC;EACrD,MAAM,MAAM,MAAM,WAAW,GAAI;AACjC,SAAO,GAAG,IAAI,WAAW,GAAG,IAAI,MAAM,GAAG,IAAI;;;;;;AC3PjD,MAAa,2BAA2B;;AAExC,MAAa,8BAA8B;;;ACgC3C,IAAa,qBAAb,cAAwC,cAAuC;CAC7E;CAEA,YACE,IACA,gBACA,gBACA,YACA,oBAMA,wBACA;AACA,QACE,IACA,gBACA,YACA,oBACA;GAAE,aAAa;GAA0B,oBAAoB;GAAM,CACpE;AAjBO,OAAA,iBAAA;AASS,OAAA,yBAAA;AASjB,OAAK,MAAM;;;;;;;;;;;;CAab,MAAyB,iBACvB,OACe;AACf,QAAM,KAAK,IAAI,aAAa,CAAC,QAAQ,OAAO,QAAQ;AAClD,QAAK,MAAM,QAAQ,OAAO;IACxB,MAAM,EAAE,WAAW,YAAY;IAC/B,MAAM,EAAE,YAAY,OAAO,QAAQ,cAAc,mBAC/C;IACF,MAAM,EAAE,OAAO,SAAS;AAExB,QAAI,CAAC,eACH,OAAM,IAAI,MACR,mDAAmD,UAAU,MAAM,UAAU,wFAE9E;IAGH,IAAI;AACJ,QAAI;AACF,iBAAY,KAAK,MAAM,eAAe;aAC/B,OAAO;AACd,WAAM,IAAI,MACR,gDAAgD,UAAU,MAAM,UAAU,IAAI,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,IACpI,EAAE,OAAO,OAAO,CACjB;;IAGH,MAAM,gBAAgB,UAAU,OAAO;IAOvC,MAAM,SAAS,SAAS,UAAU;IAIlC,MAAM,qBACJ,UACC,kBAAkB,qBACjB,kBAAkB,sBAClB,kBAAkB,qBAClB,UAAU;AAEd,QAAI,kBAAkB,qBAAqB,CAAC,QAAQ;KAClD,MAAM,sBAAM,IAAI,MAAM;AACtB,WAAM,IACH,YAAY,mBAAmB,CAC/B,IAAI;MACH,WAAW;MACX,WAAW;MACX,oBAAoB;MACpB,mBAAmB;MACnB,eAAe;MAChB,CAAC,CACD,MAAM,cAAc,KAAK,WAAW,CACpC,MAAM,UAAU,KAAK,OAAO,CAC5B,SAAS;KAIZ,MAAM,uBACJ,WACC;AACH,SAAI,yBAAyB,KAAA,EAC3B,OAAM,IACH,YAAY,mBAAmB,CAC/B,IAAI,EAAE,SAAS,sBAAsB,CAAC,CACtC,MAAM,cAAc,KAAK,WAAW,CACpC,MAAM,UAAU,KAAK,OAAO,CAC5B,MAAM,SAAS,KAAK,WAAW,CAC/B,SAAS;AAGd,WAAM,IACH,WAAW,cAAc,CACzB,MAAM,cAAc,KAAK,WAAW,CACpC,MAAM,UAAU,KAAK,OAAO,CAC5B,SAAS;AAEZ;;IAGF,IAAI;AAEJ,QAAI,kBAAkB,kBACpB,iBAAgB,OAAO,QAAQ,UAAU,CAAC,QACvC,CAAC,SAAS,QAAQ,YAAY,QAAQ,cAAc,QAAQ,OAC9D;aACQ,kBAAkB,oBAAoB;KAO/C,MAAM,eAAe,UAAU,OAAO;KAGtC,MAAM,mBACJ,UAAU,eAAe,QACzB,aAAa,iBAAiB,KAAA;AAChC,YAAO,UAAU;AAEjB,qBAAgB,mBACZ,OAAO,QAAQ,UAAU,GACzB,OAAO,QAAQ,UAAU,CAAC,QACvB,CAAC,SACA,QAAQ,YAAY,QAAQ,cAAc,QAAQ,OACrD;WACA;AACL,qBAAgB,EAAE;AAElB,SAAI,UAAU,WAAW,KAAA,EACvB,eAAc,KAAK,CAAC,UAAU,UAAU,OAAO,CAAC;AAGlD,SAAI,UAAU,WAAW,KAAA,EACvB,eAAc,KAAK,CAAC,OAAO,UAAU,OAAO,CAAC;SAE7C,eAAc,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;;AAInC,SAAK,MAAM,CAAC,WAAW,eAAe,eAAe;KACnD,MAAM,mBAAmB,MAAM,IAC5B,WAAW,mBAAmB,CAC9B,WAAW,CACX,MAAM,cAAc,KAAK,WAAW,CACpC,MAAM,SAAS,KAAK,UAAU,CAC9B,MAAM,UAAU,KAAK,OAAO,CAC5B,kBAAkB;KAErB,MAAM,WACJ,OAAO,eAAe,YAAY,eAAe,OAC5C,aACD,EAAE;KAER,IAAI,OAAsB,kBAAkB,QAAQ;KACpD,IAAI,OAAsB,kBAAkB,QAAQ;AAEpD,SAAI,cAAc,UAAU;MAC1B,MAAM,aAAa,SAAS;MAC5B,MAAM,aAAa,SAAS;AAE5B,UAAI,OAAO,eAAe,SACxB,QAAO;AAET,UAAI,OAAO,eAAe,SACxB,QAAO;AAGT,UAAI,sBAAsB,kBAAkB;OAC1C,MAAM,iBAAiB,iBAAiB;AAIxC,WAAI,kBAAkB,UAAU,eAC9B,UAAS,OAAO,eAAe;;AASnC,UAAI,QAAQ,SAAS,cAAc,CAAC,kBAAkB,UACpD,OAAM,IACH,WAAW,cAAc,CACzB,OAAO;OACN;OACA;OACA,OAAO;OACP;OACD,CAAC,CACD,YAAY,OACX,GAAG,OAAO,OAAO,CAAC,YAAY;OAC5B;OACA,OAAO;OACP;OACD,CAAC,CACH,CACA,SAAS;;AAIhB,SAAI,iBACF,OAAM,IACH,YAAY,mBAAmB,CAC/B,IAAI;MACH,oBAAoB;MACpB,mBAAmB;MACnB,+BAAe,IAAI,MAAM;MACzB,iBAAiB,iBAAiB,kBAAkB;MACpD,SAAS;MACT;MACA;MACD,CAAC,CACD,MAAM,cAAc,KAAK,WAAW,CACpC,MAAM,SAAS,KAAK,UAAU,CAC9B,MAAM,UAAU,KAAK,OAAO,CAC5B,SAAS;UACP;MACL,MAAM,WAAuC;OAC3C,IAAIA,IAAQ;OACZ;OACA;OACA;OACA,OAAO;OACP;OACA,SAAS;OACT;OACA,oBAAoB;OACpB,mBAAmB;OACnB,aAAa;OACb,UAAU;OACV,WAAW;OACZ;AAED,YAAM,IAAI,WAAW,mBAAmB,CAAC,OAAO,SAAS,CAAC,SAAS;;;;IAIzE;;CAGJ,MAAM,OACJ,aACA,WACA,kBACA,QACoB;AACpB,MAAI,iBACF,OAAM,KAAK,mBAAmB,kBAAkB,KAAA,GAAW,OAAO;AAGpE,MAAI,QAAQ,QACV,OAAM,IAAI,MAAM,oBAAoB;AAGtC,MAAI,YAAY,WAAW,EACzB,QAAO,EAAE;EAGX,MAAM,cACJ,cAAc,kBAAkB,mBAC5B,MAAM,KAAK,eAAe,aAAa,OAAO,GAC9C,MAAM,KAAK,QAAQ,YAAY;AAErC,SAAO,YAAY,KAAK,OAAO,YAAY,IAAI,GAAG,CAAC;;CAGrD,MAAM,IACJ,YACA,MACA,kBACA,QACoB;AACpB,MAAI,iBACF,OAAM,KAAK,mBAAmB,kBAAkB,KAAA,GAAW,OAAO;AAGpE,MAAI,QAAQ,QACV,OAAM,IAAI,MAAM,oBAAoB;EAGtC,MAAM,SAAS,MAAM,UAAU;EAE/B,IAAI;AACJ,MAAI,MAAM,UAAU,KAAK,OAAO,SAAS,EACvC,iBAAgB,CAAC,GAAG,IAAI,IAAI;GAAC;GAAU;GAAY,GAAG,KAAK;GAAO,CAAC,CAAC;MAEpE,iBAAgB,EAAE;EAKpB,IAAI,QAAQ,KAAK,IACd,WAAW,mBAAmB,CAC9B,WAAW,CACX,MAAM,cAAc,KAAK,WAAW,CACpC,MAAM,UAAU,KAAK,OAAO;AAE/B,MAAI,CAAC,KAAK,uBACR,SAAQ,MAAM,MAAM,aAAa,KAAK,MAAM;AAG9C,MAAI,cAAc,SAAS,EACzB,SAAQ,MAAM,MAAM,SAAS,MAAM,cAAc;EAGnD,MAAM,YAAY,MAAM,MAAM,SAAS;AAEvC,MAAI,UAAU,WAAW,EACvB,OAAM,IAAI,MAAM,uBAAuB,aAAa;AAGtD,MAAI,QAAQ,QACV,OAAM,IAAI,MAAM,oBAAoB;EAGtC,MAAM,iBAAiB,UAAU,MAAM,MAAM,EAAE,UAAU,SAAS;AAClE,MAAI,CAAC,eACH,OAAM,IAAI,MAAM,8BAA8B,aAAa;EAG7D,MAAM,SAAS,eAAe;EAE9B,MAAM,YAAY,MAAM,KAAK,eAAe,aAC1C,YACA,QACA,OACD;AACD,SAAO,WAAW,UAAU;AAC5B,SAAO,uBAAuB,UAAU;EAExC,MAAM,QAAiC,EAAE;AACzC,OAAK,MAAM,YAAY,WAAW;AAChC,OAAI,SAAS,UAAU,SACrB;AAIF,SAAM,SAAS,SACb,SAAS,UAAU,SACf,gBAAgB,SAAS,QAAgC,GACzD,SAAS;;AAajB,SAV6B;GAC3B;GAEO;GACP,YAAY,EAAE;GAEd,cAAc;GACd,WAAW,EAAE;GACd;;CAKH,MAAM,QACJ,aACA,MACA,kBACA,QACsB;AACtB,MAAI,YAAY,WAAW,EACzB,QAAO,EAAE;AAGX,MAAI,iBACF,OAAM,KAAK,mBAAmB,kBAAkB,KAAA,GAAW,OAAO;AAGpE,MAAI,QAAQ,QACV,OAAM,IAAI,MAAM,oBAAoB;EAGtC,MAAM,SAAS,MAAM,UAAU;EAE/B,IAAI;AACJ,MAAI,MAAM,UAAU,KAAK,OAAO,SAAS,EACvC,iBAAgB,CAAC,GAAG,IAAI,IAAI;GAAC;GAAU;GAAY,GAAG,KAAK;GAAO,CAAC,CAAC;MAEpE,iBAAgB,EAAE;EAGpB,IAAI,QAAQ,KAAK,IACd,WAAW,mBAAmB,CAC9B,WAAW,CACX,MAAM,cAAc,MAAM,YAAY,CACtC,MAAM,UAAU,KAAK,OAAO,CAC5B,MAAM,aAAa,KAAK,MAAM;AAEjC,MAAI,cAAc,SAAS,EACzB,SAAQ,MAAM,MAAM,SAAS,MAAM,cAAc;EAGnD,MAAM,eAAe,MAAM,MAAM,SAAS;AAE1C,MAAI,QAAQ,QACV,OAAM,IAAI,MAAM,oBAAoB;EAGtC,MAAM,mCAAmB,IAAI,KAAkC;AAC/D,OAAK,MAAM,YAAY,cAAc;GACnC,MAAM,WAAW,iBAAiB,IAAI,SAAS,WAAW,IAAI,EAAE;AAChE,YAAS,KAAK,SAAS;AACvB,oBAAiB,IAAI,SAAS,YAAY,SAAS;;EAGrD,MAAM,mBAAmB,CAAC,GAAG,iBAAiB,MAAM,CAAC;EACrD,MAAM,kBAAkB,MAAM,QAAQ,IACpC,iBAAiB,KAAK,UACpB,KAAK,eAAe,aAAa,OAAO,QAAQ,OAAO,CACxD,CACF;EAED,MAAM,mCAAmB,IAAI,KAAgC;AAC7D,OAAK,IAAI,IAAI,GAAG,IAAI,iBAAiB,QAAQ,IAC3C,kBAAiB,IAAI,iBAAiB,IAAI,gBAAgB,GAAG;AAG/D,MAAI,QAAQ,QACV,OAAM,IAAI,MAAM,oBAAoB;EAGtC,MAAM,YAAyB,EAAE;AACjC,OAAK,MAAM,cAAc,aAAa;GACpC,MAAM,YAAY,iBAAiB,IAAI,WAAW;AAClD,OAAI,CAAC,aAAa,UAAU,WAAW,EACrC;GAGF,MAAM,iBAAiB,UAAU,MAAM,MAAM,EAAE,UAAU,SAAS;AAClE,OAAI,CAAC,eACH;GAGF,MAAM,SAAS,eAAe;GAC9B,MAAM,YAAY,iBAAiB,IAAI,WAAW;AAClD,OAAI,WAAW;AACb,WAAO,WAAW,UAAU;AAC5B,WAAO,uBAAuB,UAAU;;GAG1C,MAAM,QAAiC,EAAE;AACzC,QAAK,MAAM,YAAY,WAAW;AAChC,QAAI,SAAS,UAAU,SACrB;AAGF,UAAM,SAAS,SACb,SAAS,UAAU,SACf,gBAAgB,SAAS,QAAgC,GACzD,SAAS;;GAGjB,MAAM,WAAuB;IAC3B;IAEO;IACP,YAAY,EAAE;IAEd,cAAc;IACd,WAAW,EAAE;IACd;AAED,aAAU,KAAK,SAAsB;;AAGvC,SAAO;;CAGT,MAAM,cACJ,YACA,MACA,kBACA,QACoB;EACpB,MAAM,aAAa,MAAM,KAAK,gBAC5B,YACA,MACA,kBACA,OACD;AACD,SAAO,KAAK,IAAe,YAAY,MAAM,KAAA,GAAW,OAAO;;CAGjE,MAAM,WACJ,MACA,MACA,QACA,kBACA,QACmC;AACnC,MAAI,iBACF,OAAM,KAAK,mBAAmB,kBAAkB,KAAA,GAAW,OAAO;AAGpE,MAAI,QAAQ,QACV,OAAM,IAAI,MAAM,oBAAoB;EAGtC,MAAM,SAAS,MAAM,UAAU;EAE/B,MAAM,aAAa,QAAQ,SAAS,SAAS,OAAO,OAAO,GAAG;EAC9D,MAAM,QAAQ,QAAQ,SAAS;EAE/B,MAAM,YAA0B,EAAE;EAClC,MAAM,uCAAuB,IAAI,KAAa;EAC9C,MAAM,iBAA2B,EAAE;EAEnC,MAAM,YAAY,MAAM,KAAK,IAC1B,WAAW,mBAAmB,CAC9B,WAAW,CACX,MAAM,gBAAgB,KAAK,KAAK,CAChC,MAAM,UAAU,KAAK,OAAO,CAC5B,MAAM,aAAa,KAAK,MAAM,CAC9B,QAAQ,iBAAiB,OAAO,CAChC,SAAS;AAEZ,MAAI,QAAQ,QACV,OAAM,IAAI,MAAM,oBAAoB;AAGtC,OAAK,MAAM,YAAY,WAAW;AAChC,OAAI,qBAAqB,IAAI,SAAS,WAAW,CAC/C;AAGF,wBAAqB,IAAI,SAAS,WAAW;AAC7C,kBAAe,KAAK,SAAS,WAAW;;EAG1C,MAAM,cAAc,eAAe,MAAM,YAAY,aAAa,MAAM;AAExE,OAAK,MAAM,cAAc,aAAa;AACpC,OAAI,QAAQ,QACV,OAAM,IAAI,MAAM,oBAAoB;AAGtC,OAAI;IACF,MAAM,WAAW,MAAM,KAAK,IAC1B,YACA,MACA,KAAA,GACA,OACD;AACD,cAAU,KAAK,SAAS;WAClB;AACN;;;EAIJ,MAAM,UAAU,eAAe,SAAS,aAAa;EACrD,MAAM,aAAa,UAAU,OAAO,aAAa,MAAM,GAAG,KAAA;AAE1D,SAAO;GACL,SAAS;GACT,SAAS,UAAU;IAAE,QAAQ;IAAK,OAAO;IAAK;GAC9C;GACA,YAAY,eAAe;GAC3B,MAAM,gBAEA,KAAK,WACH,MACA,MACA;IAAE,QAAQ;IAAa;IAAO,EAC9B,kBACA,OACD,GACH,KAAA;GACL;;CAGH,MAAM,YACJ,MAEA,MACA,kBACA,QAC6B;AAC7B,MAAI,iBACF,OAAM,KAAK,mBAAmB,kBAAkB,KAAA,GAAW,OAAO;AAGpE,MAAI,QAAQ,QACV,OAAM,IAAI,MAAM,oBAAoB;EAGtC,MAAM,SAAS,MAAM,UAAU;EAE/B,MAAM,UAAU,MAAM,KAAK,IACxB,WAAW,cAAc,CACzB,OAAO,aAAa,CACpB,MAAM,QAAQ,KAAK,KAAK,CACxB,MAAM,UAAU,KAAK,OAAO,CAC5B,kBAAkB;AAErB,MAAI,CAAC,QACH;AAGF,MAAI,QAAQ,QACV,OAAM,IAAI,MAAM,oBAAoB;AAGtC,MAAI,MAAM,UAAU,KAAK,OAAO,SAAS;OAUnC,CATe,MAAM,KAAK,IAC3B,WAAW,mBAAmB,CAC9B,OAAO,QAAQ,CACf,MAAM,cAAc,KAAK,QAAQ,WAAW,CAC5C,MAAM,UAAU,KAAK,OAAO,CAC5B,MAAM,SAAS,MAAM,KAAK,OAAO,CACjC,MAAM,aAAa,KAAK,MAAM,CAC9B,kBAAkB,CAGnB;;AAIJ,SAAO,QAAQ;;CAIjB,MAAM,aACJ,OAEA,MACA,kBACA,QACmB;AAMnB,UALY,MAAM,QAAQ,IACxB,MAAM,KAAK,SACT,KAAK,YAAY,MAAM,MAAM,kBAAkB,OAAO,CACvD,CACF,EACU,QAAQ,OAAO,OAAO,KAAA,EAAU;;CAG7C,MAAM,gBACJ,YAEA,MACA,kBACA,QACiB;AACjB,MAAI,iBACF,OAAM,KAAK,mBAAmB,kBAAkB,KAAA,GAAW,OAAO;AAGpE,MAAI,QAAQ,QACV,OAAM,IAAI,MAAM,oBAAoB;EAGtC,MAAM,SAAS,MAAM,UAAU;EAI/B,IAAI,eAAe,KAAK,IACrB,WAAW,mBAAmB,CAC9B,OAAO,aAAa,CACpB,MAAM,cAAc,KAAK,WAAW,CACpC,MAAM,UAAU,KAAK,OAAO;AAE/B,MAAI,CAAC,KAAK,uBACR,gBAAe,aAAa,MAAM,aAAa,KAAK,MAAM;EAG5D,MAAM,iBAAiB,aAAa,kBAAkB;EAEtD,MAAM,mBAAmB,KAAK,IAC3B,WAAW,cAAc,CACzB,OAAO,aAAa,CACpB,MAAM,QAAQ,KAAK,WAAW,CAC9B,MAAM,UAAU,KAAK,OAAO,CAC5B,kBAAkB;EAErB,MAAM,CAAC,SAAS,aAAa,MAAM,QAAQ,IAAI,CAC7C,gBACA,iBACD,CAAC;AAEF,MAAI,QAAQ,QACV,OAAM,IAAI,MAAM,oBAAoB;EAGtC,MAAM,eAAe,SAAS;EAC9B,MAAM,iBAAiB,WAAW;AAElC,MAAI,gBAAgB,kBAAkB,iBAAiB,eACrD,OAAM,IAAI,MACR,yBAAyB,WAAW,+BAA+B,aAAa,8BAA8B,eAAe,8EAE9H;EAGH,MAAM,qBAAqB,gBAAgB;AAE3C,MAAI,CAAC,mBACH,OAAM,IAAI,MAAM,uBAAuB,aAAa;AAGtD,SAAO;;;;;;;;;;;CAYT,MAAc,eACZ,aACA,QACsB;EACtB,MAAM,SAAS,CAAC,GAAG,IAAI,IAAI,YAAY,CAAC;EACxC,MAAM,YAAY,MAAM,QAAQ,IAC9B,OAAO,KAAK,eACV,KAAK,eAAe,aAAa,YAAY,QAAQ,OAAO,CAC7D,CACF;EAED,MAAM,wBAAQ,IAAI,KAAa;AAC/B,OAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,IACjC,KAAI,OAAO,KAAK,UAAU,GAAG,SAAS,CAAC,SAAS,EAC9C,OAAM,IAAI,OAAO,GAAG;AAGxB,SAAO;;CAGT,MAAc,QAAQ,aAA6C;EACjE,MAAM,OAAO,MAAM,KAAK,IACrB,WAAW,mBAAmB,CAC9B,OAAO,CAAC,aAAa,CAAC,CACtB,MAAM,cAAc,MAAM,YAAY,CACtC,MAAM,aAAa,KAAK,MAAM,CAC9B,UAAU,CACV,SAAS;AAEZ,SAAO,IAAI,IAAI,KAAK,KAAK,QAAQ,IAAI,WAAW,CAAC;;;;;;;;ACpvBrD,SAAgB,mBACd,YACA,OACA,QACgB;AAChB,QAAO,GAAG,WAAW,GAAG,MAAM,GAAG;;;;;;AAOnC,IAAa,qBAAb,MAA+D;CAC7D,wBAAgB,IAAI,KAA6B;CACjD,UAA4B,EAAE;CAE9B,OAAO,aAA4C;EACjD,MAAM,eAAe,KAAK,uBAAuB,YAAY;AAE7D,OAAK,IAAI,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK;GAC5C,MAAM,QAAQ,aAAa;GAC3B,MAAM,MAAM,mBACV,MAAM,YACN,MAAM,OACN,MAAM,OACP;GACD,MAAM,UAAU,KAAK,MAAM,IAAI,IAAI;AACnC,OAAI,YAAY,KAAA,KAAa,MAAM,iBAAiB,QAClD,MAAK,MAAM,IAAI,KAAK,MAAM,eAAe;;AAI7C,OAAK,cAAc;;CAGrB,UAAU,KAAyC;AACjD,SAAO,KAAK,MAAM,IAAI,IAAI;;CAG5B,QACE,aACA,WACA,QACe;AACf,MAAI,QAAQ,QACV,QAAO,QAAQ,uBAAO,IAAI,MAAM,oBAAoB,CAAC;AAGvD,MAAI,KAAK,wBAAwB,YAAY,CAC3C,QAAO,QAAQ,SAAS;AAG1B,SAAO,IAAI,SAAe,SAAS,WAAW;GAC5C,MAAM,SAAiB;IACrB;IACA;IACA;IACA;IACD;AAED,OAAI,cAAc,KAAA,EAChB,QAAO,YAAY,iBAAiB;AAClC,SAAK,aAAa,OAAO;AACzB,2BAAO,IAAI,MAAM,oCAAoC,UAAU,IAAI,CAAC;MACnE,UAAU;AAGf,OAAI,QAAQ;IACV,MAAM,qBAAqB;AACzB,UAAK,aAAa,OAAO;AACzB,4BAAO,IAAI,MAAM,oBAAoB,CAAC;;AAExC,WAAO,iBAAiB,SAAS,cAAc,EAAE,MAAM,MAAM,CAAC;;AAGhE,QAAK,QAAQ,KAAK,OAAO;IACzB;;CAGJ,YAA6C;AAC3C,SAAO,MAAM,KAAK,KAAK,MAAM,SAAS,CAAC;;CAGzC,QAAQ,SAAgD;AACtD,OAAK,MAAM,OAAO;AAClB,OAAK,MAAM,CAAC,KAAK,UAAU,QACzB,MAAK,MAAM,IAAI,KAAK,MAAM;;CAI9B,uBACE,aACyB;EACzB,MAAM,sBAAM,IAAI,KAA4C;AAE5D,OAAK,IAAI,IAAI,GAAG,IAAI,YAAY,QAAQ,KAAK;GAC3C,MAAM,QAAQ,YAAY;GAC1B,MAAM,MAAM,mBACV,MAAM,YACN,MAAM,OACN,MAAM,OACP;GACD,MAAM,WAAW,IAAI,IAAI,IAAI;AAE7B,OAAI,CAAC,YAAY,MAAM,iBAAiB,SAAS,eAC/C,KAAI,IAAI,KAAK,MAAM;;AAIvB,SAAO,MAAM,KAAK,IAAI,QAAQ,CAAC;;CAGjC,wBACE,aACS;AACT,OAAK,IAAI,IAAI,GAAG,IAAI,YAAY,QAAQ,KAAK;GAC3C,MAAM,QAAQ,YAAY;GAC1B,MAAM,MAAM,mBACV,MAAM,YACN,MAAM,OACN,MAAM,OACP;GACD,MAAM,SAAS,KAAK,MAAM,IAAI,IAAI;AAClC,OAAI,WAAW,KAAA,KAAa,SAAS,MAAM,eACzC,QAAO;;AAGX,SAAO;;CAGT,eAA6B;EAC3B,MAAM,mBAA6B,EAAE;EACrC,MAAM,qBAA+B,EAAE;AAEvC,OAAK,MAAM,UAAU,KAAK,SAAS;AACjC,OAAI,OAAO,QAAQ,QACjB;AAGF,OAAI,KAAK,wBAAwB,OAAO,YAAY,CAClD,kBAAiB,KAAK,OAAO;OAE7B,oBAAmB,KAAK,OAAO;;AAInC,OAAK,UAAU;AAEf,OAAK,MAAM,UAAU,kBAAkB;AACrC,OAAI,OAAO,cAAc,KAAA,EACvB,cAAa,OAAO,UAAU;AAEhC,UAAO,SAAS;;;CAIpB,aAAqB,QAAsB;EACzC,MAAM,QAAQ,KAAK,QAAQ,QAAQ,OAAO;AAC1C,MAAI,UAAU,GACZ,MAAK,QAAQ,OAAO,OAAO,EAAE;AAE/B,MAAI,OAAO,cAAc,KAAA,EACvB,cAAa,OAAO,UAAU;;;;;;;;;AC3MpC,eAAsB,gBACpB,WACA,QACc;CACd,MAAM,aAAkB,CAAC,GAAG,UAAU,QAAQ;CAC9C,IAAI,cAAc;AAElB,QAAO,YAAY,MAAM;AACvB,MAAI,QAAQ,QACV,OAAM,IAAI,MAAM,oBAAoB;AAEtC,gBAAc,MAAM,YAAY,MAAM;AACtC,aAAW,KAAK,GAAG,YAAY,QAAQ;;AAGzC,QAAO;;;;ACaT,IAAa,wBAAb,cACU,cAEV;CACE;CAEA,YACE,IACA,gBACA,YACA,oBACA;AACA,QACE,IACA,gBACA,YACA,oBACA;GAAE,aAAa;GAA6B,oBAAoB;GAAO,CACxE;AACD,OAAK,MAAM;;CAGb,MAAyB,iBACvB,OACe;AACf,QAAM,KAAK,IAAI,aAAa,CAAC,QAAQ,OAAO,QAAQ;AAClD,QAAK,MAAM,QAAQ,OAAO;IACxB,MAAM,EAAE,cAAc;IACtB,MAAM,aAAa,UAAU,OAAO;AAEpC,QAAI,eAAe,mBACjB,OAAM,KAAK,sBAAsB,KAAK,UAAU;aACvC,eAAe,sBACxB,OAAM,KAAK,yBAAyB,KAAK,UAAU;aAC1C,eAAe,sBACxB,OAAM,KAAK,yBAAyB,KAAK,UAAU;;IAGvD;;CAGJ,MAAM,YACJ,YACA,OACA,QACA,kBACA,QAC6C;AAC7C,MAAI,iBACF,OAAM,KAAK,mBAAmB,kBAAkB,KAAA,GAAW,OAAO;AAEpE,MAAI,QAAQ,QACV,OAAM,IAAI,MAAM,oBAAoB;EAGtC,MAAM,aAAa,QAAQ,SAAS,SAAS,OAAO,OAAO,GAAG;EAC9D,MAAM,QAAQ,QAAQ,SAAS;EAE/B,IAAI,QAAQ,KAAK,IACd,WAAW,uBAAuB,CAClC,WAAW,CACX,MAAM,YAAY,KAAK,WAAW;AAErC,MAAI,SAAS,MAAM,SAAS,EAC1B,SAAQ,MAAM,MAAM,oBAAoB,MAAM,MAAM;EAGtD,MAAM,OAAO,MAAM,MAChB,QAAQ,aAAa,MAAM,CAC3B,QAAQ,MAAM,MAAM,CACpB,OAAO,WAAW,CAClB,MAAM,QAAQ,EAAE,CAChB,SAAS;EAEZ,MAAM,UAAU,KAAK,SAAS;EAC9B,MAAM,UAAU,UAAU,KAAK,MAAM,GAAG,MAAM,GAAG;EACjD,MAAM,aAAa,UAAU,OAAO,aAAa,MAAM,GAAG,KAAA;AAE1D,SAAO;GACL,SAAS,QAAQ,KAAK,SAAS;IAC7B,UAAU,IAAI;IACd,UAAU,IAAI;IACd,kBAAkB,IAAI;IACtB,UAAU,IAAI,WACT,IAAI,WACL,KAAA;IACJ,WAAW,IAAI;IACf,WAAW,IAAI;IAChB,EAAE;GACH,SAAS,UAAU;IAAE,QAAQ;IAAK,OAAO;IAAK;GAC9C;GACA,MAAM,gBAEA,KAAK,YACH,YACA,OACA;IAAE,QAAQ;IAAa;IAAO,EAC9B,kBACA,OACD,GACH,KAAA;GACL;;CAGH,MAAM,YACJ,YACA,OACA,QACA,kBACA,QAC6C;AAC7C,MAAI,iBACF,OAAM,KAAK,mBAAmB,kBAAkB,KAAA,GAAW,OAAO;AAGpE,MAAI,QAAQ,QACV,OAAM,IAAI,MAAM,oBAAoB;EAGtC,MAAM,aAAa,QAAQ,SAAS,SAAS,OAAO,OAAO,GAAG;EAC9D,MAAM,QAAQ,QAAQ,SAAS;EAE/B,IAAI,QAAQ,KAAK,IACd,WAAW,uBAAuB,CAClC,WAAW,CACX,MAAM,YAAY,KAAK,WAAW;AAErC,MAAI,SAAS,MAAM,SAAS,EAC1B,SAAQ,MAAM,MAAM,oBAAoB,MAAM,MAAM;EAGtD,MAAM,OAAO,MAAM,MAChB,QAAQ,aAAa,MAAM,CAC3B,QAAQ,MAAM,MAAM,CACpB,OAAO,WAAW,CAClB,MAAM,QAAQ,EAAE,CAChB,SAAS;EAEZ,MAAM,UAAU,KAAK,SAAS;EAC9B,MAAM,UAAU,UAAU,KAAK,MAAM,GAAG,MAAM,GAAG;EACjD,MAAM,aAAa,UAAU,OAAO,aAAa,MAAM,GAAG,KAAA;AAE1D,SAAO;GACL,SAAS,QAAQ,KAAK,SAAS;IAC7B,UAAU,IAAI;IACd,UAAU,IAAI;IACd,kBAAkB,IAAI;IACtB,UAAU,IAAI,WACT,IAAI,WACL,KAAA;IACJ,WAAW,IAAI;IACf,WAAW,IAAI;IAChB,EAAE;GACH,SAAS,UAAU;IAAE,QAAQ;IAAK,OAAO;IAAK;GAC9C;GACA,MAAM,gBAEA,KAAK,YACH,YACA,OACA;IAAE,QAAQ;IAAa;IAAO,EAC9B,kBACA,OACD,GACH,KAAA;GACL;;CAGH,MAAM,oBACJ,WACA,OACA,QACmB;AACnB,MAAI,UAAU,WAAW,EACvB,QAAO,EAAE;AAEX,MAAI,QAAQ,QACV,OAAM,IAAI,MAAM,oBAAoB;EAGtC,IAAI,QAAQ,KAAK,IACd,WAAW,4BAA4B,CACvC,OAAO,aAAa,CACpB,UAAU,CACV,MAAM,cAAc,MAAM,UAAU,CACpC,OAAO,OACN,GAAG,IACD,GAAG,OACD,GACG,WAAW,gCAAgC,CAC3C,OAAO,WAAW,CAClB,SAAS,kBAAkB,KAAK,aAAa,CAC7C,MAAM,kBAAkB,UAAU,UAAU,CAChD,CACF,CACF;AAEH,MAAI,SAAS,MAAM,SAAS,EAC1B,SAAQ,MAAM,MAAM,sBAAsB,MAAM,MAAM;AAIxD,UADa,MAAM,MAAM,SAAS,EACtB,KAAK,QAAQ,IAAI,SAAS;;CAGxC,MAAM,gBACJ,UACA,UACA,OACA,kBACA,QACkB;AAClB,MAAI,iBACF,OAAM,KAAK,mBAAmB,kBAAkB,KAAA,GAAW,OAAO;AAGpE,MAAI,QAAQ,QACV,OAAM,IAAI,MAAM,oBAAoB;EAGtC,IAAI,QAAQ,KAAK,IACd,WAAW,uBAAuB,CAClC,OAAO,KAAK,CACZ,MAAM,YAAY,KAAK,SAAS,CAChC,MAAM,YAAY,KAAK,SAAS;AAEnC,MAAI,SAAS,MAAM,SAAS,EAC1B,SAAQ,MAAM,MAAM,oBAAoB,MAAM,MAAM;AAKtD,SAFe,MAAM,MAAM,kBAAkB,KAE3B,KAAA;;CAGpB,MAAM,2BACJ,GACA,GACA,OACA,QACA,kBACA,QAC6C;AAC7C,MAAI,iBACF,OAAM,KAAK,mBAAmB,kBAAkB,KAAA,GAAW,OAAO;AAGpE,MAAI,QAAQ,QACV,OAAM,IAAI,MAAM,oBAAoB;EAGtC,MAAM,aAAa,QAAQ,SAAS,SAAS,OAAO,OAAO,GAAG;EAC9D,MAAM,QAAQ,QAAQ,SAAS;EAE/B,IAAI,QAAQ,KAAK,IACd,WAAW,uBAAuB,CAClC,WAAW,CACX,OAAO,OACN,GAAG,GAAG,CACJ,GAAG,IAAI,CAAC,GAAG,YAAY,KAAK,EAAE,EAAE,GAAG,YAAY,KAAK,EAAE,CAAC,CAAC,EACxD,GAAG,IAAI,CAAC,GAAG,YAAY,KAAK,EAAE,EAAE,GAAG,YAAY,KAAK,EAAE,CAAC,CAAC,CACzD,CAAC,CACH;AAEH,MAAI,SAAS,MAAM,SAAS,EAC1B,SAAQ,MAAM,MAAM,oBAAoB,MAAM,MAAM;EAGtD,MAAM,OAAO,MAAM,MAChB,QAAQ,aAAa,MAAM,CAC3B,QAAQ,MAAM,MAAM,CACpB,OAAO,WAAW,CAClB,MAAM,QAAQ,EAAE,CAChB,SAAS;EAEZ,MAAM,UAAU,KAAK,SAAS;EAC9B,MAAM,UAAU,UAAU,KAAK,MAAM,GAAG,MAAM,GAAG;EACjD,MAAM,aAAa,UAAU,OAAO,aAAa,MAAM,GAAG,KAAA;AAE1D,SAAO;GACL,SAAS,QAAQ,KAAK,SAAS;IAC7B,UAAU,IAAI;IACd,UAAU,IAAI;IACd,kBAAkB,IAAI;IACtB,UAAU,IAAI,WACT,IAAI,WACL,KAAA;IACJ,WAAW,IAAI;IACf,WAAW,IAAI;IAChB,EAAE;GACH,SAAS,UAAU;IAAE,QAAQ;IAAK,OAAO;IAAK;GAC9C;GACA,MAAM,gBAEA,KAAK,2BACH,GACA,GACA,OACA;IAAE,QAAQ;IAAa;IAAO,EAC9B,kBACA,OACD,GACH,KAAA;GACL;;CAGH,MAAM,yBACJ,UACA,UACA,OACA,QACA,kBACA,QAC6C;AAC7C,MAAI,iBACF,OAAM,KAAK,mBAAmB,kBAAkB,KAAA,GAAW,OAAO;AAGpE,MAAI,QAAQ,QACV,OAAM,IAAI,MAAM,oBAAoB;EAGtC,MAAM,aAAa,QAAQ,SAAS,SAAS,OAAO,OAAO,GAAG;EAC9D,MAAM,QAAQ,QAAQ,SAAS;EAE/B,IAAI,QAAQ,KAAK,IACd,WAAW,uBAAuB,CAClC,WAAW,CACX,MAAM,YAAY,KAAK,SAAS,CAChC,MAAM,YAAY,KAAK,SAAS;AAEnC,MAAI,SAAS,MAAM,SAAS,EAC1B,SAAQ,MAAM,MAAM,oBAAoB,MAAM,MAAM;EAGtD,MAAM,OAAO,MAAM,MAChB,QAAQ,aAAa,MAAM,CAC3B,QAAQ,MAAM,MAAM,CACpB,OAAO,WAAW,CAClB,MAAM,QAAQ,EAAE,CAChB,SAAS;EAEZ,MAAM,UAAU,KAAK,SAAS;EAC9B,MAAM,UAAU,UAAU,KAAK,MAAM,GAAG,MAAM,GAAG;EACjD,MAAM,aAAa,UAAU,OAAO,aAAa,MAAM,GAAG,KAAA;AAE1D,SAAO;GACL,SAAS,QAAQ,KAAK,SAAS;IAC7B,UAAU,IAAI;IACd,UAAU,IAAI;IACd,kBAAkB,IAAI;IACtB,UAAU,IAAI,WACT,IAAI,WACL,KAAA;IACJ,WAAW,IAAI;IACf,WAAW,IAAI;IAChB,EAAE;GACH,SAAS,UAAU;IAAE,QAAQ;IAAK,OAAO;IAAK;GAC9C;GACA,MAAM,gBAEA,KAAK,yBACH,UACA,UACA,OACA;IAAE,QAAQ;IAAa;IAAO,EAC9B,kBACA,OACD,GACH,KAAA;GACL;;CAGH,MAAM,SACJ,UACA,UACA,OACA,kBACA,QAC0B;AAC1B,MAAI,iBACF,OAAM,KAAK,mBAAmB,kBAAkB,KAAA,GAAW,OAAO;AAGpE,MAAI,QAAQ,QACV,OAAM,IAAI,MAAM,oBAAoB;AAGtC,MAAI,aAAa,SACf,QAAO,CAAC,SAAS;EAGnB,MAAM,0BAAU,IAAI,KAAa;EACjC,MAAM,QAA+C,CACnD;GAAE,IAAI;GAAU,MAAM,CAAC,SAAS;GAAE,CACnC;AAED,SAAO,MAAM,SAAS,GAAG;GACvB,MAAM,UAAU,MAAM,OAAO;AAE7B,OAAI,QAAQ,OAAO,SACjB,QAAO,QAAQ;AAGjB,OAAI,QAAQ,IAAI,QAAQ,GAAG,CACzB;AAGF,WAAQ,IAAI,QAAQ,GAAG;GASvB,MAAM,wBAAwB,MAAM,gBAPf,MAAM,KAAK,YAC9B,QAAQ,IACR,OACA,KAAA,GACA,kBACA,OACD,EACiE,OAAO;AAEzE,QAAK,MAAM,OAAO,sBAChB,KAAI,CAAC,QAAQ,IAAI,IAAI,SAAS,CAC5B,OAAM,KAAK;IACT,IAAI,IAAI;IACR,MAAM,CAAC,GAAG,QAAQ,MAAM,IAAI,SAAS;IACtC,CAAC;;AAKR,SAAO;;CAGT,MAAM,cACJ,YACA,OACA,kBACA,QACyB;AACzB,MAAI,iBACF,OAAM,KAAK,mBAAmB,kBAAkB,KAAA,GAAW,OAAO;AAGpE,MAAI,QAAQ,QACV,OAAM,IAAI,MAAM,oBAAoB;EAGtC,MAAM,QAAQ,IAAI,IAAY,CAAC,WAAW,CAAC;EAC3C,MAAM,QAA6B,EAAE;EACrC,MAAM,QAAkB,CAAC,WAAW;EACpC,MAAM,0BAAU,IAAI,KAAa;AAEjC,SAAO,MAAM,SAAS,GAAG;GACvB,MAAM,YAAY,MAAM,OAAO;AAE/B,OAAI,QAAQ,IAAI,UAAU,CACxB;AAGF,WAAQ,IAAI,UAAU;GAStB,MAAM,wBAAwB,MAAM,gBAPf,MAAM,KAAK,YAC9B,WACA,OACA,KAAA,GACA,kBACA,OACD,EACiE,OAAO;AAEzE,QAAK,MAAM,OAAO,uBAAuB;AACvC,UAAM,IAAI,IAAI,SAAS;AACvB,UAAM,KAAK;KACT,MAAM,IAAI;KACV,IAAI,IAAI;KACR,MAAM,IAAI;KACX,CAAC;AAEF,QAAI,CAAC,QAAQ,IAAI,IAAI,SAAS,CAC5B,OAAM,KAAK,IAAI,SAAS;;;AAK9B,SAAO;GACL,OAAO,MAAM,KAAK,MAAM;GACxB;GACD;;CAGH,MAAM,qBACJ,kBACA,QACmB;AACnB,MAAI,iBACF,OAAM,KAAK,mBAAmB,kBAAkB,KAAA,GAAW,OAAO;AAGpE,MAAI,QAAQ,QACV,OAAM,IAAI,MAAM,oBAAoB;AAStC,UANa,MAAM,KAAK,IACrB,WAAW,uBAAuB,CAClC,OAAO,mBAAmB,CAC1B,UAAU,CACV,SAAS,EAEA,KAAK,QAAQ,IAAI,iBAAiB;;CAGhD,MAAc,sBACZ,KACA,WACe;EACf,MAAM,QAAQ,UAAU,OAAO;AAa/B,MAAI,CANgB,MAAM,IACvB,WAAW,WAAW,CACtB,OAAO,KAAK,CACZ,MAAM,MAAM,KAAK,MAAM,SAAS,CAChC,kBAAkB,CAGnB,OAAM,IACH,WAAW,WAAW,CACtB,OAAO,EACN,IAAI,MAAM,UACX,CAAC,CACD,SAAS;AASd,MAAI,CANsB,MAAM,IAC7B,WAAW,WAAW,CACtB,OAAO,KAAK,CACZ,MAAM,MAAM,KAAK,MAAM,SAAS,CAChC,kBAAkB,CAGnB,OAAM,IACH,WAAW,WAAW,CACtB,OAAO,EACN,IAAI,MAAM,UACX,CAAC,CACD,SAAS;AAWd,MAAI,CARgB,MAAM,IACvB,WAAW,uBAAuB,CAClC,OAAO,KAAK,CACZ,MAAM,YAAY,KAAK,MAAM,SAAS,CACtC,MAAM,YAAY,KAAK,MAAM,SAAS,CACtC,MAAM,oBAAoB,KAAK,MAAM,iBAAiB,CACtD,kBAAkB,EAEH;GAChB,MAAM,eAA+C;IACnD,IAAIC,IAAQ;IACZ,UAAU,MAAM;IAChB,UAAU,MAAM;IAChB,kBAAkB,MAAM;IACxB,UAAU,MAAM,YAAY;IAC7B;AAED,SAAM,IACH,WAAW,uBAAuB,CAClC,OAAO,aAAa,CACpB,SAAS;;;CAIhB,MAAc,yBACZ,KACA,WACe;EACf,MAAM,QAAQ,UAAU,OAAO;AAM/B,QAAM,IACH,WAAW,uBAAuB,CAClC,MAAM,YAAY,KAAK,MAAM,SAAS,CACtC,MAAM,YAAY,KAAK,MAAM,SAAS,CACtC,MAAM,oBAAoB,KAAK,MAAM,iBAAiB,CACtD,SAAS;;CAGd,MAAc,yBACZ,KACA,WACe;EACf,MAAM,QAAQ,UAAU,OAAO;AAO/B,QAAM,IACH,YAAY,uBAAuB,CACnC,IAAI;GACH,UAAU,MAAM;GAChB,2BAAW,IAAI,MAAM;GACtB,CAAC,CACD,MAAM,YAAY,KAAK,MAAM,SAAS,CACtC,MAAM,YAAY,KAAK,MAAM,SAAS,CACtC,MAAM,oBAAoB,KAAK,MAAM,iBAAiB,CACtD,SAAS"}
|