@powerhousedao/reactor 6.2.3-dev.49 → 6.2.3-dev.50
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/{document-indexer-Dc_Sv6yi.js → document-indexer-DwAMDVRI.js} +2 -2
- package/dist/{document-indexer-Dc_Sv6yi.js.map → document-indexer-DwAMDVRI.js.map} +1 -1
- package/dist/{drive-container-types-D380mcB3.js → drive-container-types-Ci1pdv4X.js} +9 -9
- package/dist/{drive-container-types-D380mcB3.js.map → drive-container-types-Ci1pdv4X.js.map} +1 -1
- package/dist/entry.js +3 -2
- package/dist/entry.js.map +1 -1
- package/dist/{load-spec-D9lnje-H.js → forwarding-logger-ByfBFgMS.js} +9 -248
- package/dist/forwarding-logger-ByfBFgMS.js.map +1 -0
- package/dist/{index-Dq2f7oAC.d.ts → index-CNhT69De.d.ts} +34 -5
- package/dist/index-CNhT69De.d.ts.map +1 -0
- package/dist/index.d.ts +126 -26
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1065 -157
- package/dist/index.js.map +1 -1
- package/dist/load-spec-ZLjuyhxz.js +246 -0
- package/dist/load-spec-ZLjuyhxz.js.map +1 -0
- package/dist/projection-entry.js +3 -2
- package/dist/projection-entry.js.map +1 -1
- package/dist/{projection-shard-manager-MRJ9BjVM.js → projection-shard-manager-CQ5FwQmv.js} +2 -2
- package/dist/{projection-shard-manager-MRJ9BjVM.js.map → projection-shard-manager-CQ5FwQmv.js.map} +1 -1
- package/dist/rpc.d.ts +2 -2
- package/dist/rpc.js +3 -2
- package/dist/rpc.js.map +1 -1
- package/dist/{worker-handle-B48V1hDm.js → worker-handle-CN2-oRv-.js} +2 -2
- package/dist/{worker-handle-B48V1hDm.js.map → worker-handle-CN2-oRv-.js.map} +1 -1
- package/package.json +13 -13
- package/dist/index-Dq2f7oAC.d.ts.map +0 -1
- package/dist/load-spec-D9lnje-H.js.map +0 -1
package/dist/{projection-shard-manager-MRJ9BjVM.js.map → projection-shard-manager-CQ5FwQmv.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"projection-shard-manager-MRJ9BjVM.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 type { CatchUpConfig, CatchUpStatus } from \"../catch-up/types.js\";\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 catchUp: CatchUpConfig;\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 catchUpStatus?: CatchUpStatus;\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 catchUp: this.config.catchUp,\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 indexedReadModels(): readonly IReadModel[] {\n return [];\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 /** The last catch-up status each ready shard reported. */\n catchUpStatuses(): CatchUpStatus[] {\n const statuses: CatchUpStatus[] = [];\n for (const shard of this.shards) {\n if (shard.catchUpStatus) statuses.push(shard.catchUpStatus);\n }\n return statuses;\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 case \"readmodel-swept\":\n this.trackersByReadModelName\n .get(msg.readModelName)\n ?.update(msg.coordinates);\n void this.hostBus\n .emit(ReactorEventTypes.CATCHUP_SWEPT, {\n ...msg.result,\n thread: \"projection\",\n })\n .catch(() => {});\n return;\n case \"catchup-status\":\n shard.catchUpStatus = msg.status;\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
|
+
{"version":3,"file":"projection-shard-manager-CQ5FwQmv.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 type { CatchUpConfig, CatchUpStatus } from \"../catch-up/types.js\";\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 catchUp: CatchUpConfig;\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 catchUpStatus?: CatchUpStatus;\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 catchUp: this.config.catchUp,\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 indexedReadModels(): readonly IReadModel[] {\n return [];\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 /** The last catch-up status each ready shard reported. */\n catchUpStatuses(): CatchUpStatus[] {\n const statuses: CatchUpStatus[] = [];\n for (const shard of this.shards) {\n if (shard.catchUpStatus) statuses.push(shard.catchUpStatus);\n }\n return statuses;\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 case \"readmodel-swept\":\n this.trackersByReadModelName\n .get(msg.readModelName)\n ?.update(msg.coordinates);\n void this.hostBus\n .emit(ReactorEventTypes.CATCHUP_SWEPT, {\n ...msg.result,\n thread: \"projection\",\n })\n .catch(() => {});\n return;\n case \"catchup-status\":\n shard.catchUpStatus = msg.status;\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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"}
|
package/dist/rpc.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Tr as IDocumentModelRegistry, mt as IReactorClient, s as loadDocumentModelSpec, t as DocumentModelRegistry } from "./index-CNhT69De.js";
|
|
2
2
|
//#region src/rpc/protocol.d.ts
|
|
3
3
|
type CorrelationId = string;
|
|
4
4
|
type ErrorInfo = {
|
|
@@ -327,6 +327,6 @@ interface CorrelatedConfig<Sub, EK extends OwnerMessage["k"], ErrK extends Owner
|
|
|
327
327
|
}
|
|
328
328
|
export declare function createCorrelatedSubscriptions<Sub, EK extends OwnerMessage["k"], ErrK extends OwnerMessage["k"]>(router: MessageRouter, config: CorrelatedConfig<Sub, EK, ErrK>): ICorrelatedSubscriptions<Sub>;
|
|
329
329
|
//#endregion
|
|
330
|
-
export type
|
|
330
|
+
export { type ClientMessage, type CorrelationId, DocumentModelRegistry, type ErrorInfo, type ICorrelatedSubscriptions, type IDocumentModelRegistry, type IHostResponder, type IRpcTransport, type MethodCallMessage, type OpKind, type OwnerMessage, type ReactorIdentity, type RpcAbort, type RpcAdmin, type RpcBusEvent, type RpcDbOp, type RpcError, type RpcEvent, type RpcHello, type RpcIdentity, type RpcInspectorOp, type RpcLiveError, type RpcLiveEvent, type RpcLiveSubscribe, type RpcLiveUnsub, type RpcMessage, type RpcMigration, type RpcNextPage, type RpcPing, type RpcPong, type RpcPoster, type RpcRegisterPackages, type RpcReload, type RpcRequest, type RpcRequestOptions, type RpcResponse, type RpcSubError, type RpcSubscribe, type RpcSyncOp, type RpcUnregisterPackages, type RpcUnsub, type VersionFingerprint, type WorkerInspectorInfo, type WorkerMigrationState, type WorkerPackageSource, loadDocumentModelSpec };
|
|
331
331
|
export {};
|
|
332
332
|
//# sourceMappingURL=rpc.d.ts.map
|
package/dist/rpc.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as loadDocumentModelSpec, r as DocumentModelRegistry } from "./load-spec-ZLjuyhxz.js";
|
|
2
|
+
import { normalizeDocumentModelVersion } from "@powerhousedao/shared/document-model/version";
|
|
2
3
|
//#region src/rpc/error-info.ts
|
|
3
4
|
const MAX_STACK_LENGTH = 8192;
|
|
4
5
|
const MAX_CAUSE_DEPTH = 8;
|
|
@@ -565,6 +566,6 @@ function createPortTransport(port) {
|
|
|
565
566
|
};
|
|
566
567
|
}
|
|
567
568
|
//#endregion
|
|
568
|
-
export { KeyedListeners, Listeners, MessageRouter, RPC_PROTOCOL_VERSION, ReactorHostServer, RpcCorrelator, SubscriptionStore, createCorrelatedSubscriptions, createPortTransport, createReactorClientProxy, fromErrorInfo, hostResponder, toErrorInfo };
|
|
569
|
+
export { DocumentModelRegistry, KeyedListeners, Listeners, MessageRouter, RPC_PROTOCOL_VERSION, ReactorHostServer, RpcCorrelator, SubscriptionStore, createCorrelatedSubscriptions, createPortTransport, createReactorClientProxy, fromErrorInfo, hostResponder, loadDocumentModelSpec, toErrorInfo };
|
|
569
570
|
|
|
570
571
|
//# sourceMappingURL=rpc.js.map
|
package/dist/rpc.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc.js","names":[],"sources":["../src/rpc/error-info.ts","../src/rpc/paging.ts","../src/rpc/subscription.ts","../src/rpc/client-proxy.ts","../src/rpc/protocol.ts","../src/rpc/host-reply.ts","../src/rpc/host-server.ts","../src/rpc/listeners.ts","../src/rpc/rpc-correlator.ts","../src/rpc/message-router.ts","../src/rpc/transport.ts"],"sourcesContent":["import type { ErrorInfo } from \"./protocol.js\";\n\nconst MAX_STACK_LENGTH = 8 * 1024;\nconst MAX_CAUSE_DEPTH = 8;\n\nexport function toErrorInfo(err: unknown, depth = 0): ErrorInfo {\n if (depth > MAX_CAUSE_DEPTH) {\n return { name: \"Error\", message: \"cause chain truncated\" };\n }\n if (err instanceof Error) {\n const info: ErrorInfo = { name: err.name || \"Error\", message: err.message };\n if (typeof err.stack === \"string\") {\n info.stack =\n err.stack.length > MAX_STACK_LENGTH\n ? err.stack.slice(0, MAX_STACK_LENGTH)\n : err.stack;\n }\n if (err.cause !== undefined) {\n info.cause = toErrorInfo(err.cause, depth + 1);\n }\n return info;\n }\n if (typeof err === \"string\") {\n return { name: \"Error\", message: err };\n }\n if (err && typeof err === \"object\") {\n const record = err as { name?: unknown; message?: unknown };\n return {\n name: typeof record.name === \"string\" ? record.name : \"Error\",\n message:\n typeof record.message === \"string\"\n ? record.message\n : safeStringify(err),\n };\n }\n return { name: \"Error\", message: safeStringify(err) };\n}\n\nfunction safeStringify(value: unknown): string {\n if (value === null || value === undefined) {\n return String(value);\n }\n if (\n typeof value === \"string\" ||\n typeof value === \"number\" ||\n typeof value === \"boolean\"\n ) {\n return String(value);\n }\n try {\n return JSON.stringify(value);\n } catch {\n return \"[unserializable]\";\n }\n}\n\nexport function fromErrorInfo(info: ErrorInfo): Error {\n const err = new Error(info.message);\n Object.defineProperty(err, \"name\", {\n value: info.name,\n configurable: true,\n writable: true,\n });\n if (info.stack !== undefined) {\n err.stack = info.stack;\n }\n if (info.cause !== undefined) {\n Object.defineProperty(err, \"cause\", {\n value: fromErrorInfo(info.cause),\n configurable: true,\n writable: true,\n });\n }\n return err;\n}\n","export type PageableResult = Record<string, unknown> & {\n next: () => Promise<unknown>;\n};\n\nexport type TokenizedPage = Record<string, unknown> & { nextToken: string };\n\nexport function isPageableResult(value: unknown): value is PageableResult {\n return (\n value !== null &&\n typeof value === \"object\" &&\n typeof (value as { next?: unknown }).next === \"function\"\n );\n}\n\nexport function hasNextToken(value: unknown): value is TokenizedPage {\n return (\n value !== null &&\n typeof value === \"object\" &&\n typeof (value as { nextToken?: unknown }).nextToken === \"string\"\n );\n}\n\nexport function dehydratePage(\n value: unknown,\n registerCursor: (next: () => Promise<unknown>) => string,\n): unknown {\n if (!isPageableResult(value)) {\n return value;\n }\n const { next, ...rest } = value;\n return { ...rest, nextToken: registerCursor(next) };\n}\n\nexport function rehydratePage(\n value: unknown,\n fetchPage: (token: string) => Promise<unknown>,\n): unknown {\n if (!hasNextToken(value)) {\n return value;\n }\n const { nextToken, ...rest } = value;\n return { ...rest, next: () => fetchPage(nextToken) };\n}\n","import type { MessageRouter } from \"./message-router.js\";\nimport type { ClientMessage, CorrelationId, OwnerMessage } from \"./protocol.js\";\n\nexport class SubscriptionStore {\n private readonly map = new Map<CorrelationId, () => void>();\n\n set(id: CorrelationId, unsubscribe: () => void): void {\n this.map.set(id, unsubscribe);\n }\n\n get(id: CorrelationId): (() => void) | undefined {\n return this.map.get(id);\n }\n\n delete(id: CorrelationId): void {\n this.map.delete(id);\n }\n\n end(id: CorrelationId): void {\n const unsubscribe = this.map.get(id);\n if (unsubscribe) {\n this.map.delete(id);\n unsubscribe();\n }\n }\n\n drain(): void {\n for (const unsubscribe of this.map.values()) {\n unsubscribe();\n }\n this.map.clear();\n }\n}\n\nexport interface ICorrelatedSubscriptions<Sub> {\n subscribe(sub: Sub, open: (id: CorrelationId) => ClientMessage): () => void;\n}\n\nexport interface CorrelatedConfig<\n Sub,\n EK extends OwnerMessage[\"k\"],\n ErrK extends OwnerMessage[\"k\"],\n> {\n idPrefix: string;\n eventKind: EK;\n errKind: ErrK;\n onEvent: (sub: Sub, message: Extract<OwnerMessage, { k: EK }>) => void;\n onError: (sub: Sub, message: Extract<OwnerMessage, { k: ErrK }>) => void;\n close: (id: CorrelationId) => ClientMessage;\n}\n\nexport function createCorrelatedSubscriptions<\n Sub,\n EK extends OwnerMessage[\"k\"],\n ErrK extends OwnerMessage[\"k\"],\n>(\n router: MessageRouter,\n config: CorrelatedConfig<Sub, EK, ErrK>,\n): ICorrelatedSubscriptions<Sub> {\n let counter = 0;\n const subs = new Map<CorrelationId, Sub>();\n\n router.on(config.eventKind, (message) => {\n const sub = subs.get((message as { id: CorrelationId }).id);\n if (sub) {\n config.onEvent(sub, message);\n }\n });\n router.on(config.errKind, (message) => {\n const id = (message as { id: CorrelationId }).id;\n const sub = subs.get(id);\n if (sub) {\n subs.delete(id);\n router.post(config.close(id));\n config.onError(sub, message);\n }\n });\n\n return {\n subscribe(sub, open) {\n const id: CorrelationId = `${config.idPrefix}${++counter}`;\n subs.set(id, sub);\n router.post(open(id));\n return () => {\n subs.delete(id);\n router.post(config.close(id));\n };\n },\n };\n}\n","import type { DocumentChangeEvent, IReactorClient } from \"../client/types.js\";\nimport type { IDocumentModelRegistry } from \"../registry/index.js\";\nimport type {\n PagingOptions,\n SearchFilter,\n ViewFilter,\n} from \"../shared/types.js\";\nimport type { PHDocument } from \"@powerhousedao/shared/document-model\";\nimport { normalizeDocumentModelVersion } from \"@powerhousedao/shared/document-model\";\nimport { fromErrorInfo } from \"./error-info.js\";\nimport type { MessageRouter } from \"./message-router.js\";\nimport { rehydratePage } from \"./paging.js\";\nimport { createCorrelatedSubscriptions } from \"./subscription.js\";\n\ntype Forwarder = (...args: unknown[]) => Promise<unknown>;\n\ntype ChangeCallback = (event: DocumentChangeEvent) => void;\n\nfunction isAbortSignal(value: unknown): value is AbortSignal {\n return (\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as AbortSignal).aborted === \"boolean\" &&\n typeof (value as { addEventListener?: unknown }).addEventListener ===\n \"function\" &&\n // also require removeEventListener — call() attaches a listener it later detaches\n typeof (value as { removeEventListener?: unknown }).removeEventListener ===\n \"function\"\n );\n}\n\nexport type ReactorClientProxyOptions = {\n onReload?: (reason: string, workerGen?: string) => void;\n /** Tab-local registry for module lookups (reducers/editors can't cross the worker boundary). */\n registry?: IDocumentModelRegistry;\n};\n\nexport function createReactorClientProxy(\n router: MessageRouter,\n options: ReactorClientProxyOptions = {},\n): IReactorClient {\n const registry = options.registry;\n\n function fetchPage(token: string): Promise<unknown> {\n return router.request((id) => ({ k: \"page\", id, token }), {\n transform: rehydrate,\n });\n }\n\n function rehydrate(value: unknown): unknown {\n return rehydratePage(value, fetchPage);\n }\n\n const changeSubs = createCorrelatedSubscriptions<\n ChangeCallback,\n \"event\",\n \"sub-err\"\n >(router, {\n idPrefix: \"sub\",\n eventKind: \"event\",\n errKind: \"sub-err\",\n onEvent: (callback, msg) => callback(msg.change as DocumentChangeEvent),\n onError: (_callback, msg) =>\n console.error(\"Reactor subscription failed:\", fromErrorInfo(msg.error)),\n close: (id) => ({ k: \"unsub\", id }),\n });\n router.on(\"reload\", (msg) => {\n options.onReload?.(msg.reason, msg.workerGen);\n });\n\n const call = (method: string, args: unknown[]): Promise<unknown> => {\n let abortAt: number | undefined;\n for (let i = 0; i < args.length; i++) {\n if (isAbortSignal(args[i])) {\n abortAt = i;\n }\n }\n let wire = args;\n if (abortAt !== undefined) {\n wire = args.slice();\n wire[abortAt] = undefined;\n }\n return router.request(\n (id) => ({ k: \"req\", id, method, args: wire, abortAt }),\n {\n transform: rehydrate,\n setup: (id) => {\n if (abortAt === undefined) {\n return;\n }\n const signal = args[abortAt] as AbortSignal;\n const sendAbort = () => router.post({ k: \"abort\", targetId: id });\n if (signal.aborted) {\n sendAbort();\n return;\n }\n signal.addEventListener(\"abort\", sendAbort, { once: true });\n return () => signal.removeEventListener(\"abort\", sendAbort);\n },\n },\n );\n };\n\n const subscribe = (\n search: SearchFilter,\n callback: ChangeCallback,\n view?: ViewFilter,\n ): (() => void) =>\n changeSubs.subscribe(callback, (id) => ({ k: \"sub\", id, search, view }));\n\n const forwarders = new Map<string, Forwarder>();\n const forwarder = (method: string): Forwarder => {\n let fn = forwarders.get(method);\n if (!fn) {\n fn = (...args: unknown[]) => call(method, args);\n forwarders.set(method, fn);\n }\n return fn;\n };\n\n const drives = new Proxy(\n {},\n {\n get: (_target, prop) =>\n typeof prop === \"string\" ? forwarder(`drives.${prop}`) : undefined,\n },\n );\n\n const handler: ProxyHandler<Record<string, unknown>> = {\n get(_target, prop) {\n if (typeof prop !== \"string\" || prop === \"then\") {\n return undefined;\n }\n if (prop === \"drives\") {\n return drives;\n }\n if (prop === \"subscribe\") {\n return subscribe;\n }\n if (registry) {\n if (prop === \"getDocumentModelModule\") {\n return (documentType: string) =>\n Promise.resolve().then(() => registry.getModule(documentType));\n }\n if (prop === \"getDocumentModelModuleForDocument\") {\n return (document: PHDocument) =>\n Promise.resolve().then(() =>\n registry.getModule(\n document.header.documentType,\n normalizeDocumentModelVersion(\n (document.state as Partial<typeof document.state>).document\n ?.version,\n ),\n ),\n );\n }\n if (prop === \"getDocumentModelModules\") {\n return (_namespace?: string, paging?: PagingOptions) =>\n Promise.resolve({\n results: registry.getAllModules(),\n options: paging ?? { cursor: \"\", limit: Number.MAX_SAFE_INTEGER },\n });\n }\n }\n return forwarder(prop);\n },\n };\n\n return new Proxy({}, handler) as unknown as IReactorClient;\n}\n","export type CorrelationId = string;\n\nexport type ErrorInfo = {\n name: string;\n message: string;\n stack?: string;\n cause?: ErrorInfo;\n};\n\nexport type RpcRequest = {\n k: \"req\";\n id: CorrelationId;\n method: string;\n args: unknown[];\n abortAt?: number;\n};\n\nexport type RpcResponse = { k: \"res\"; id: CorrelationId; value: unknown };\n\nexport type RpcError = { k: \"err\"; id: CorrelationId; error: ErrorInfo };\n\n/** Failed document subscribe; routed to the change-subscription registry. */\nexport type RpcSubError = { k: \"sub-err\"; id: CorrelationId; error: ErrorInfo };\n\n/** Failed live query; routed to the live-query registry. */\nexport type RpcLiveError = {\n k: \"live-err\";\n id: CorrelationId;\n error: ErrorInfo;\n};\n\nexport type RpcAbort = { k: \"abort\"; targetId: CorrelationId };\n\nexport type RpcSubscribe = {\n k: \"sub\";\n id: CorrelationId;\n search: unknown;\n view?: unknown;\n};\n\nexport type RpcEvent = { k: \"event\"; id: CorrelationId; change: unknown };\n\nexport type RpcUnsub = { k: \"unsub\"; id: CorrelationId };\n\nexport type RpcNextPage = { k: \"page\"; id: CorrelationId; token: string };\n\n// Bumped when the tab<->owner wire protocol changes incompatibly; a tab whose\n// version differs from the owner's baseline is told to reload.\nexport const RPC_PROTOCOL_VERSION = 3;\n\n/**\n * A package the worker loads by URL rather than by registry spec: local\n * project packages, whose models the registry does not serve. The URL must be\n * a worker-importable models entry - the dev server's transformed\n * `document-models/index.js` in dev, a prebuilt bundle under\n * `__reactor_worker__/packages/` in production.\n */\nexport type WorkerPackageSource = {\n name: string;\n version?: string;\n url: string;\n};\n\nexport type VersionFingerprint = {\n appBuildId: string;\n rpcProtocolVersion: number;\n models: { id: string; version: number }[];\n // Enabled enforcement flags, sorted and joined. In the fingerprint so a\n // worker cannot keep enforcing the set it booted with after a config change.\n featureFlags?: string;\n};\n\nexport type RpcHello = {\n k: \"hello\";\n id: CorrelationId;\n version: VersionFingerprint;\n construct?: unknown;\n packages?: string[];\n};\n\nexport type RpcRegisterPackages = {\n k: \"register-packages\";\n id: CorrelationId;\n specs: string[];\n /** URL-addressed packages; the worker REPLACES a source it already loaded. */\n sources?: WorkerPackageSource[];\n};\n\nexport type RpcUnregisterPackages = {\n k: \"unregister-packages\";\n id: CorrelationId;\n names: string[];\n};\n\n// `workerGen` (present on restart-driven reloads) is the new SharedWorker name\n// suffix every tab should adopt so they converge on one fresh worker.\nexport type RpcReload = { k: \"reload\"; reason: string; workerGen?: string };\n\n// Worker lifecycle/admin channel, separate from the IReactorClient RPC surface.\nexport type RpcAdmin = {\n k: \"admin\";\n id: CorrelationId;\n method: \"info\" | \"restart\" | \"clearStorage\" | \"migrate\";\n};\n\nexport type WorkerMigrationState = {\n status: \"idle\" | \"needed\" | \"migrating\" | \"failed\";\n legacyMajor?: number;\n phase?: \"clone\" | \"dump\" | \"restore\";\n error?: string;\n};\n\n// Owner -> tab push of the worker's migration state (initial seed on connect +\n// on every change), so tabs can drive the migration banner from one flag.\nexport type RpcMigration = { k: \"migration\"; state: WorkerMigrationState };\n\nexport type WorkerInspectorInfo = {\n namespace: string;\n ownerId: string;\n bootedAtMs: number;\n connectedClients: number;\n appBuildId: string;\n rpcProtocolVersion: number;\n /** Flags this worker is enforcing, from the fingerprint that built it. */\n featureFlags?: string;\n};\n\n// Cloneable subset of renown's user (matches UserActionSigner); token minting + attribution.\nexport type ReactorIdentity = {\n address: string;\n chainId: number;\n networkId: string;\n};\n\nexport type RpcIdentity = { k: \"identity\"; user: ReactorIdentity | null };\n\n// Distributed EventBus: worker -> all tabs, fire-and-forget. `eventType` is a\n// reactor IEventBus numeric type; `event` is the (cloneable) payload.\nexport type RpcBusEvent = { k: \"bus-event\"; eventType: number; event: unknown };\n\nexport type OpKind = \"sync-op\" | \"db-op\" | \"inspector-op\";\n\nexport type MethodCallMessage<K extends OpKind> = {\n k: K;\n id: CorrelationId;\n method: string;\n args: unknown[];\n};\n\n// syncManager commands (add/remove/triggerPull/list); the reply value is the\n// reactor's RemoteMeta (its DriveCollectionId degrades to plain\n// {driveId,branch} over postMessage and is rehydrated tab-side).\nexport type RpcSyncOp = MethodCallMessage<\"sync-op\">;\n\nexport type RpcDbOp = MethodCallMessage<\"db-op\">;\n\nexport type RpcInspectorOp = MethodCallMessage<\"inspector-op\">;\n\nexport type RpcLiveSubscribe = {\n k: \"sub-live\";\n id: CorrelationId;\n sql: string;\n params: unknown[];\n};\n\nexport type RpcLiveEvent = {\n k: \"event-live\";\n id: CorrelationId;\n results: unknown;\n};\n\nexport type RpcLiveUnsub = { k: \"unsub-live\"; id: CorrelationId };\n\n// Liveness heartbeat. The tab pings; the owner answers synchronously (even\n// mid-build/mid-migration) so a silently-evicted worker is detectable. The pong\n// echoes ownerId/bootedAtMs so a respawned (different) instance is identifiable.\nexport type RpcPing = { k: \"ping\"; id: CorrelationId };\n\nexport type RpcPong = {\n k: \"pong\";\n id: CorrelationId;\n ownerId: string;\n bootedAtMs: number;\n};\n\nexport type ClientMessage =\n | RpcRequest\n | RpcAbort\n | RpcSubscribe\n | RpcUnsub\n | RpcNextPage\n | RpcHello\n | RpcRegisterPackages\n | RpcUnregisterPackages\n | RpcIdentity\n | RpcAdmin\n | RpcSyncOp\n | RpcDbOp\n | RpcInspectorOp\n | RpcLiveSubscribe\n | RpcLiveUnsub\n | RpcPing;\n\nexport type OwnerMessage =\n | RpcResponse\n | RpcError\n | RpcSubError\n | RpcLiveError\n | RpcEvent\n | RpcReload\n | RpcBusEvent\n | RpcLiveEvent\n | RpcMigration\n | RpcPong;\n\nexport type RpcMessage = ClientMessage | OwnerMessage;\n\n/** Error reply kind for a client message kind, so it reaches the right registry. */\nexport function responseErrorKind(\n k: ClientMessage[\"k\"],\n): \"err\" | \"sub-err\" | \"live-err\" {\n if (k === \"sub\") {\n return \"sub-err\";\n }\n if (k === \"sub-live\") {\n return \"live-err\";\n }\n return \"err\";\n}\n","import { toErrorInfo } from \"./error-info.js\";\nimport { responseErrorKind } from \"./protocol.js\";\nimport type { ClientMessage, CorrelationId, OwnerMessage } from \"./protocol.js\";\nimport type { IRpcTransport } from \"./transport.js\";\n\nexport interface IHostResponder {\n ok(id: CorrelationId, value?: unknown): void;\n err(id: CorrelationId, error: unknown): void;\n errForKind(message: ClientMessage, error: unknown): void;\n run(\n id: CorrelationId,\n body: () => Promise<unknown>,\n mapOk?: (value: unknown) => unknown,\n ): Promise<void>;\n}\n\nconst defaultOk = (): unknown => ({ ok: true });\n\nexport function hostResponder(transport: IRpcTransport): IHostResponder {\n const ok = (id: CorrelationId, value: unknown = { ok: true }): void => {\n transport.post({ k: \"res\", id, value });\n };\n const err = (id: CorrelationId, error: unknown): void => {\n transport.post({ k: \"err\", id, error: toErrorInfo(error) });\n };\n return {\n ok,\n err,\n errForKind(message, error) {\n if (!(\"id\" in message)) {\n return;\n }\n transport.post({\n k: responseErrorKind(message.k),\n id: message.id,\n error: toErrorInfo(error),\n } as OwnerMessage);\n },\n async run(id, body, mapOk = defaultOk) {\n try {\n ok(id, mapOk(await body()));\n } catch (error) {\n err(id, error);\n }\n },\n };\n}\n","import type { DocumentChangeEvent, IReactorClient } from \"../client/types.js\";\nimport type { SearchFilter, ViewFilter } from \"../shared/types.js\";\nimport { hostResponder, type IHostResponder } from \"./host-reply.js\";\nimport { dehydratePage } from \"./paging.js\";\nimport { SubscriptionStore } from \"./subscription.js\";\nimport type {\n ClientMessage,\n RpcNextPage,\n RpcRequest,\n RpcSubscribe,\n} from \"./protocol.js\";\nimport type { IRpcTransport } from \"./transport.js\";\n\ntype AnyMethod = (...args: unknown[]) => unknown;\ntype NextPage = () => Promise<unknown>;\n\n// Bound the page-continuation cache; an evicted token falls back to the \"expired cursor\" error.\nconst MAX_PENDING_PAGES = 1000;\n\nexport class ReactorHostServer {\n private readonly client: IReactorClient;\n private readonly transport: IRpcTransport;\n private readonly reply: IHostResponder;\n private readonly aborters = new Map<string, AbortController>();\n private readonly subscriptions = new SubscriptionStore();\n private readonly pages = new Map<string, NextPage>();\n private pageCounter = 0;\n private detach: () => void = () => {};\n\n constructor(client: IReactorClient, transport: IRpcTransport) {\n this.client = client;\n this.transport = transport;\n this.reply = hostResponder(transport);\n }\n\n start(): void {\n this.detach = this.transport.onMessage((message) => {\n void this.handleMessage(message as ClientMessage);\n });\n }\n\n stop(): void {\n this.detach();\n this.subscriptions.drain();\n this.aborters.clear();\n this.pages.clear();\n }\n\n async handleMessage(message: ClientMessage): Promise<void> {\n try {\n switch (message.k) {\n case \"req\":\n await this.handleRequest(message);\n return;\n case \"page\":\n await this.handlePage(message);\n return;\n case \"abort\":\n this.aborters.get(message.targetId)?.abort();\n return;\n case \"sub\":\n this.handleSubscribe(message);\n return;\n case \"unsub\":\n this.subscriptions.end(message.id);\n return;\n }\n } catch (error) {\n this.reply.errForKind(message, error);\n }\n }\n\n private async handleRequest(message: RpcRequest): Promise<void> {\n let args = message.args;\n let controller: AbortController | undefined;\n if (message.abortAt !== undefined) {\n controller = new AbortController();\n this.aborters.set(message.id, controller);\n args = message.args.slice();\n args[message.abortAt] = controller.signal;\n }\n try {\n const method = this.resolveMethod(message.method);\n const value = await method(...args);\n this.reply.ok(message.id, this.prepareResult(value));\n } catch (error) {\n this.reply.err(message.id, error);\n } finally {\n if (controller) {\n this.aborters.delete(message.id);\n }\n }\n }\n\n private async handlePage(message: RpcNextPage): Promise<void> {\n const next = this.pages.get(message.token);\n this.pages.delete(message.token);\n if (!next) {\n this.reply.err(\n message.id,\n new Error(\"Paged cursor already consumed or expired\"),\n );\n return;\n }\n try {\n const value = await next();\n this.reply.ok(message.id, this.prepareResult(value));\n } catch (error) {\n this.reply.err(message.id, error);\n }\n }\n\n private handleSubscribe(message: RpcSubscribe): void {\n const unsubscribe = this.client.subscribe(\n message.search as SearchFilter,\n (change: DocumentChangeEvent) =>\n this.transport.post({ k: \"event\", id: message.id, change }),\n message.view as ViewFilter | undefined,\n );\n this.subscriptions.set(message.id, unsubscribe);\n }\n\n private prepareResult(value: unknown): unknown {\n return dehydratePage(value, (next) => {\n const token = `p${++this.pageCounter}`;\n this.pages.set(token, next);\n if (this.pages.size > MAX_PENDING_PAGES) {\n const oldest = this.pages.keys().next().value;\n if (oldest !== undefined) {\n this.pages.delete(oldest);\n }\n }\n return token;\n });\n }\n\n private resolveMethod(path: string): AnyMethod {\n if (path.startsWith(\"drives.\")) {\n const name = path.slice(\"drives.\".length);\n const drives = this.client.drives as unknown as Record<string, AnyMethod>;\n const fn = drives[name];\n if (typeof fn !== \"function\") {\n throw new Error(`Unknown drive method: ${name}`);\n }\n return fn.bind(this.client.drives);\n }\n const client = this.client as unknown as Record<string, AnyMethod>;\n const fn = client[path];\n if (typeof fn !== \"function\") {\n throw new Error(`Unknown reactor method: ${path}`);\n }\n return fn.bind(this.client);\n }\n}\n","export class KeyedListeners<K, A extends unknown[] = []> {\n private readonly map = new Map<K, Set<(...a: A) => void>>();\n\n add(key: K, fn: (...a: A) => void): () => void {\n let set = this.map.get(key);\n if (!set) {\n set = new Set();\n this.map.set(key, set);\n }\n set.add(fn);\n return () => {\n const current = this.map.get(key);\n if (!current) {\n return;\n }\n current.delete(fn);\n if (current.size === 0) {\n this.map.delete(key);\n }\n };\n }\n\n emit(key: K, ...a: A): void {\n const set = this.map.get(key);\n if (!set) {\n return;\n }\n for (const fn of [...set]) {\n fn(...a);\n }\n }\n\n emitAll(...a: A): void {\n for (const set of [...this.map.values()]) {\n for (const fn of [...set]) {\n fn(...a);\n }\n }\n }\n}\n\nexport class Listeners<A extends unknown[] = []> {\n private readonly keyed = new KeyedListeners<null, A>();\n\n add(fn: (...a: A) => void): () => void {\n return this.keyed.add(null, fn);\n }\n\n emit(...a: A): void {\n this.keyed.emit(null, ...a);\n }\n}\n","import { fromErrorInfo } from \"./error-info.js\";\nimport type { ClientMessage, CorrelationId, OwnerMessage } from \"./protocol.js\";\n\ntype Pending = {\n resolve: (value: unknown) => void;\n reject: (error: unknown) => void;\n timer?: ReturnType<typeof setTimeout>;\n cleanup?: () => void;\n transform?: (value: unknown) => unknown;\n};\n\nexport type RpcRequestOptions = {\n /** Reject if no response arrives within this many ms; omit for no timeout. */\n timeoutMs?: number;\n /** Maps a successful response value (e.g. paged-result rehydration). */\n transform?: (value: unknown) => unknown;\n /** Runs after the post; may return a cleanup invoked when the request settles. */\n setup?: (id: CorrelationId) => (() => void) | void;\n};\n\n/** Anything that can send a client message (the router). */\nexport type RpcPoster = { post(message: ClientMessage): void };\n\n/**\n * Request/response correlation shared by every RPC proxy: one pending map,\n * res/err settlement, and per-request timeout/transform/cleanup.\n */\nexport class RpcCorrelator {\n private counter = 0;\n private readonly pending = new Map<CorrelationId, Pending>();\n\n constructor(\n private readonly poster: RpcPoster,\n private readonly prefix = \"r\",\n ) {}\n\n nextId(): CorrelationId {\n return `${this.prefix}${++this.counter}`;\n }\n\n /** Settle the pending request a res/err refers to; a missing id is a no-op. */\n handleMessage(message: OwnerMessage): void {\n if (message.k !== \"res\" && message.k !== \"err\") {\n return;\n }\n const entry = this.pending.get(message.id);\n if (!entry) {\n return;\n }\n this.pending.delete(message.id);\n if (entry.timer) {\n clearTimeout(entry.timer);\n }\n entry.cleanup?.();\n if (message.k === \"res\") {\n entry.resolve(\n entry.transform ? entry.transform(message.value) : message.value,\n );\n } else {\n entry.reject(fromErrorInfo(message.error));\n }\n }\n\n /** Send a request, resolving on its res/err. `build` stamps the generated id. */\n request(\n build: (id: CorrelationId) => ClientMessage,\n options: RpcRequestOptions = {},\n ): Promise<unknown> {\n const id = this.nextId();\n const message = build(id);\n const method = (message as { method?: unknown }).method;\n const label =\n typeof method === \"string\" ? `${message.k} \"${method}\"` : message.k;\n const promise = new Promise<unknown>((resolve, reject) => {\n let timer: ReturnType<typeof setTimeout> | undefined;\n if (options.timeoutMs !== undefined) {\n const timeoutMs = options.timeoutMs;\n timer = setTimeout(() => {\n const entry = this.pending.get(id);\n if (entry) {\n this.pending.delete(id);\n entry.cleanup?.();\n reject(\n new Error(\n `Reactor worker did not respond to ${label} within ${timeoutMs}ms; the worker may have failed to load`,\n ),\n );\n }\n }, timeoutMs);\n }\n this.pending.set(id, {\n resolve,\n reject,\n timer,\n transform: options.transform,\n });\n });\n this.poster.post(message);\n const cleanup = options.setup?.(id);\n if (cleanup) {\n const entry = this.pending.get(id);\n if (entry) {\n entry.cleanup = cleanup;\n } else {\n cleanup();\n }\n }\n return promise;\n }\n}\n","import type { ClientMessage, CorrelationId, OwnerMessage } from \"./protocol.js\";\nimport { RpcCorrelator, type RpcRequestOptions } from \"./rpc-correlator.js\";\nimport type { IRpcTransport } from \"./transport.js\";\n\ntype OwnerKind = OwnerMessage[\"k\"];\ntype RouteHandler = (message: OwnerMessage) => void;\n\n/**\n * Owns the single transport.onMessage and dispatches each message to the one\n * consumer that owns its kind. Composes the shared RpcCorrelator (res/err) and\n * exposes request/nextId; attach/detach swap the transport for reconnect.\n */\nexport class MessageRouter {\n private readonly handlers = new Map<OwnerKind, RouteHandler>();\n private readonly correlator: RpcCorrelator;\n private transport: IRpcTransport | null = null;\n private detachTransport: () => void = () => {};\n\n constructor(prefix = \"r\") {\n this.correlator = new RpcCorrelator(this, prefix);\n const settle: RouteHandler = (message) =>\n this.correlator.handleMessage(message);\n this.handlers.set(\"res\", settle);\n this.handlers.set(\"err\", settle);\n }\n\n attach(transport: IRpcTransport): void {\n this.detachTransport();\n this.transport = transport;\n this.detachTransport = transport.onMessage((message) => {\n const msg = message as OwnerMessage;\n this.handlers.get(msg.k)?.(msg);\n });\n }\n\n detach(): void {\n this.detachTransport();\n this.detachTransport = () => {};\n this.transport = null;\n }\n\n post(message: ClientMessage): void {\n if (!this.transport) {\n throw new Error(\"MessageRouter.post called before attach\");\n }\n this.transport.post(message);\n }\n\n /** Register the sole owner of a message kind; throws on a duplicate. */\n on<K extends OwnerKind>(\n kind: K,\n handler: (message: Extract<OwnerMessage, { k: K }>) => void,\n ): () => void {\n if (this.handlers.has(kind)) {\n throw new Error(`MessageRouter already has a handler for \"${kind}\"`);\n }\n const route = handler as RouteHandler;\n this.handlers.set(kind, route);\n return () => {\n if (this.handlers.get(kind) === route) {\n this.handlers.delete(kind);\n }\n };\n }\n\n request(\n build: (id: CorrelationId) => ClientMessage,\n options?: RpcRequestOptions,\n ): Promise<unknown> {\n return this.correlator.request(build, options);\n }\n\n nextId(): CorrelationId {\n return this.correlator.nextId();\n }\n}\n","import type { RpcMessage } from \"./protocol.js\";\n\nexport interface IRpcTransport {\n post(message: RpcMessage): void;\n onMessage(listener: (message: RpcMessage) => void): () => void;\n close(): void;\n}\n\nexport function createPortTransport(port: MessagePort): IRpcTransport {\n port.start();\n port.addEventListener(\"messageerror\", (event) => {\n console.error(\"[rpc transport] failed to deserialize message\", event);\n });\n return {\n post(message) {\n port.postMessage(message);\n },\n onMessage(listener) {\n const handler = (event: MessageEvent) => {\n listener(event.data as RpcMessage);\n };\n port.addEventListener(\"message\", handler);\n return () => port.removeEventListener(\"message\", handler);\n },\n close() {\n port.close();\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"rpc.js","names":[],"sources":["../src/rpc/error-info.ts","../src/rpc/paging.ts","../src/rpc/subscription.ts","../src/rpc/client-proxy.ts","../src/rpc/protocol.ts","../src/rpc/host-reply.ts","../src/rpc/host-server.ts","../src/rpc/listeners.ts","../src/rpc/rpc-correlator.ts","../src/rpc/message-router.ts","../src/rpc/transport.ts"],"sourcesContent":["import type { ErrorInfo } from \"./protocol.js\";\n\nconst MAX_STACK_LENGTH = 8 * 1024;\nconst MAX_CAUSE_DEPTH = 8;\n\nexport function toErrorInfo(err: unknown, depth = 0): ErrorInfo {\n if (depth > MAX_CAUSE_DEPTH) {\n return { name: \"Error\", message: \"cause chain truncated\" };\n }\n if (err instanceof Error) {\n const info: ErrorInfo = { name: err.name || \"Error\", message: err.message };\n if (typeof err.stack === \"string\") {\n info.stack =\n err.stack.length > MAX_STACK_LENGTH\n ? err.stack.slice(0, MAX_STACK_LENGTH)\n : err.stack;\n }\n if (err.cause !== undefined) {\n info.cause = toErrorInfo(err.cause, depth + 1);\n }\n return info;\n }\n if (typeof err === \"string\") {\n return { name: \"Error\", message: err };\n }\n if (err && typeof err === \"object\") {\n const record = err as { name?: unknown; message?: unknown };\n return {\n name: typeof record.name === \"string\" ? record.name : \"Error\",\n message:\n typeof record.message === \"string\"\n ? record.message\n : safeStringify(err),\n };\n }\n return { name: \"Error\", message: safeStringify(err) };\n}\n\nfunction safeStringify(value: unknown): string {\n if (value === null || value === undefined) {\n return String(value);\n }\n if (\n typeof value === \"string\" ||\n typeof value === \"number\" ||\n typeof value === \"boolean\"\n ) {\n return String(value);\n }\n try {\n return JSON.stringify(value);\n } catch {\n return \"[unserializable]\";\n }\n}\n\nexport function fromErrorInfo(info: ErrorInfo): Error {\n const err = new Error(info.message);\n Object.defineProperty(err, \"name\", {\n value: info.name,\n configurable: true,\n writable: true,\n });\n if (info.stack !== undefined) {\n err.stack = info.stack;\n }\n if (info.cause !== undefined) {\n Object.defineProperty(err, \"cause\", {\n value: fromErrorInfo(info.cause),\n configurable: true,\n writable: true,\n });\n }\n return err;\n}\n","export type PageableResult = Record<string, unknown> & {\n next: () => Promise<unknown>;\n};\n\nexport type TokenizedPage = Record<string, unknown> & { nextToken: string };\n\nexport function isPageableResult(value: unknown): value is PageableResult {\n return (\n value !== null &&\n typeof value === \"object\" &&\n typeof (value as { next?: unknown }).next === \"function\"\n );\n}\n\nexport function hasNextToken(value: unknown): value is TokenizedPage {\n return (\n value !== null &&\n typeof value === \"object\" &&\n typeof (value as { nextToken?: unknown }).nextToken === \"string\"\n );\n}\n\nexport function dehydratePage(\n value: unknown,\n registerCursor: (next: () => Promise<unknown>) => string,\n): unknown {\n if (!isPageableResult(value)) {\n return value;\n }\n const { next, ...rest } = value;\n return { ...rest, nextToken: registerCursor(next) };\n}\n\nexport function rehydratePage(\n value: unknown,\n fetchPage: (token: string) => Promise<unknown>,\n): unknown {\n if (!hasNextToken(value)) {\n return value;\n }\n const { nextToken, ...rest } = value;\n return { ...rest, next: () => fetchPage(nextToken) };\n}\n","import type { MessageRouter } from \"./message-router.js\";\nimport type { ClientMessage, CorrelationId, OwnerMessage } from \"./protocol.js\";\n\nexport class SubscriptionStore {\n private readonly map = new Map<CorrelationId, () => void>();\n\n set(id: CorrelationId, unsubscribe: () => void): void {\n this.map.set(id, unsubscribe);\n }\n\n get(id: CorrelationId): (() => void) | undefined {\n return this.map.get(id);\n }\n\n delete(id: CorrelationId): void {\n this.map.delete(id);\n }\n\n end(id: CorrelationId): void {\n const unsubscribe = this.map.get(id);\n if (unsubscribe) {\n this.map.delete(id);\n unsubscribe();\n }\n }\n\n drain(): void {\n for (const unsubscribe of this.map.values()) {\n unsubscribe();\n }\n this.map.clear();\n }\n}\n\nexport interface ICorrelatedSubscriptions<Sub> {\n subscribe(sub: Sub, open: (id: CorrelationId) => ClientMessage): () => void;\n}\n\nexport interface CorrelatedConfig<\n Sub,\n EK extends OwnerMessage[\"k\"],\n ErrK extends OwnerMessage[\"k\"],\n> {\n idPrefix: string;\n eventKind: EK;\n errKind: ErrK;\n onEvent: (sub: Sub, message: Extract<OwnerMessage, { k: EK }>) => void;\n onError: (sub: Sub, message: Extract<OwnerMessage, { k: ErrK }>) => void;\n close: (id: CorrelationId) => ClientMessage;\n}\n\nexport function createCorrelatedSubscriptions<\n Sub,\n EK extends OwnerMessage[\"k\"],\n ErrK extends OwnerMessage[\"k\"],\n>(\n router: MessageRouter,\n config: CorrelatedConfig<Sub, EK, ErrK>,\n): ICorrelatedSubscriptions<Sub> {\n let counter = 0;\n const subs = new Map<CorrelationId, Sub>();\n\n router.on(config.eventKind, (message) => {\n const sub = subs.get((message as { id: CorrelationId }).id);\n if (sub) {\n config.onEvent(sub, message);\n }\n });\n router.on(config.errKind, (message) => {\n const id = (message as { id: CorrelationId }).id;\n const sub = subs.get(id);\n if (sub) {\n subs.delete(id);\n router.post(config.close(id));\n config.onError(sub, message);\n }\n });\n\n return {\n subscribe(sub, open) {\n const id: CorrelationId = `${config.idPrefix}${++counter}`;\n subs.set(id, sub);\n router.post(open(id));\n return () => {\n subs.delete(id);\n router.post(config.close(id));\n };\n },\n };\n}\n","import type { DocumentChangeEvent, IReactorClient } from \"../client/types.js\";\nimport type { IDocumentModelRegistry } from \"../registry/index.js\";\nimport type {\n PagingOptions,\n SearchFilter,\n ViewFilter,\n} from \"../shared/types.js\";\nimport type { PHDocument } from \"@powerhousedao/shared/document-model\";\nimport { normalizeDocumentModelVersion } from \"@powerhousedao/shared/document-model/version\";\nimport { fromErrorInfo } from \"./error-info.js\";\nimport type { MessageRouter } from \"./message-router.js\";\nimport { rehydratePage } from \"./paging.js\";\nimport { createCorrelatedSubscriptions } from \"./subscription.js\";\n\ntype Forwarder = (...args: unknown[]) => Promise<unknown>;\n\ntype ChangeCallback = (event: DocumentChangeEvent) => void;\n\nfunction isAbortSignal(value: unknown): value is AbortSignal {\n return (\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as AbortSignal).aborted === \"boolean\" &&\n typeof (value as { addEventListener?: unknown }).addEventListener ===\n \"function\" &&\n // also require removeEventListener — call() attaches a listener it later detaches\n typeof (value as { removeEventListener?: unknown }).removeEventListener ===\n \"function\"\n );\n}\n\nexport type ReactorClientProxyOptions = {\n onReload?: (reason: string, workerGen?: string) => void;\n /** Tab-local registry for module lookups (reducers/editors can't cross the worker boundary). */\n registry?: IDocumentModelRegistry;\n};\n\nexport function createReactorClientProxy(\n router: MessageRouter,\n options: ReactorClientProxyOptions = {},\n): IReactorClient {\n const registry = options.registry;\n\n function fetchPage(token: string): Promise<unknown> {\n return router.request((id) => ({ k: \"page\", id, token }), {\n transform: rehydrate,\n });\n }\n\n function rehydrate(value: unknown): unknown {\n return rehydratePage(value, fetchPage);\n }\n\n const changeSubs = createCorrelatedSubscriptions<\n ChangeCallback,\n \"event\",\n \"sub-err\"\n >(router, {\n idPrefix: \"sub\",\n eventKind: \"event\",\n errKind: \"sub-err\",\n onEvent: (callback, msg) => callback(msg.change as DocumentChangeEvent),\n onError: (_callback, msg) =>\n console.error(\"Reactor subscription failed:\", fromErrorInfo(msg.error)),\n close: (id) => ({ k: \"unsub\", id }),\n });\n router.on(\"reload\", (msg) => {\n options.onReload?.(msg.reason, msg.workerGen);\n });\n\n const call = (method: string, args: unknown[]): Promise<unknown> => {\n let abortAt: number | undefined;\n for (let i = 0; i < args.length; i++) {\n if (isAbortSignal(args[i])) {\n abortAt = i;\n }\n }\n let wire = args;\n if (abortAt !== undefined) {\n wire = args.slice();\n wire[abortAt] = undefined;\n }\n return router.request(\n (id) => ({ k: \"req\", id, method, args: wire, abortAt }),\n {\n transform: rehydrate,\n setup: (id) => {\n if (abortAt === undefined) {\n return;\n }\n const signal = args[abortAt] as AbortSignal;\n const sendAbort = () => router.post({ k: \"abort\", targetId: id });\n if (signal.aborted) {\n sendAbort();\n return;\n }\n signal.addEventListener(\"abort\", sendAbort, { once: true });\n return () => signal.removeEventListener(\"abort\", sendAbort);\n },\n },\n );\n };\n\n const subscribe = (\n search: SearchFilter,\n callback: ChangeCallback,\n view?: ViewFilter,\n ): (() => void) =>\n changeSubs.subscribe(callback, (id) => ({ k: \"sub\", id, search, view }));\n\n const forwarders = new Map<string, Forwarder>();\n const forwarder = (method: string): Forwarder => {\n let fn = forwarders.get(method);\n if (!fn) {\n fn = (...args: unknown[]) => call(method, args);\n forwarders.set(method, fn);\n }\n return fn;\n };\n\n const drives = new Proxy(\n {},\n {\n get: (_target, prop) =>\n typeof prop === \"string\" ? forwarder(`drives.${prop}`) : undefined,\n },\n );\n\n const handler: ProxyHandler<Record<string, unknown>> = {\n get(_target, prop) {\n if (typeof prop !== \"string\" || prop === \"then\") {\n return undefined;\n }\n if (prop === \"drives\") {\n return drives;\n }\n if (prop === \"subscribe\") {\n return subscribe;\n }\n if (registry) {\n if (prop === \"getDocumentModelModule\") {\n return (documentType: string) =>\n Promise.resolve().then(() => registry.getModule(documentType));\n }\n if (prop === \"getDocumentModelModuleForDocument\") {\n return (document: PHDocument) =>\n Promise.resolve().then(() =>\n registry.getModule(\n document.header.documentType,\n normalizeDocumentModelVersion(\n (document.state as Partial<typeof document.state>).document\n ?.version,\n ),\n ),\n );\n }\n if (prop === \"getDocumentModelModules\") {\n return (_namespace?: string, paging?: PagingOptions) =>\n Promise.resolve({\n results: registry.getAllModules(),\n options: paging ?? { cursor: \"\", limit: Number.MAX_SAFE_INTEGER },\n });\n }\n }\n return forwarder(prop);\n },\n };\n\n return new Proxy({}, handler) as unknown as IReactorClient;\n}\n","export type CorrelationId = string;\n\nexport type ErrorInfo = {\n name: string;\n message: string;\n stack?: string;\n cause?: ErrorInfo;\n};\n\nexport type RpcRequest = {\n k: \"req\";\n id: CorrelationId;\n method: string;\n args: unknown[];\n abortAt?: number;\n};\n\nexport type RpcResponse = { k: \"res\"; id: CorrelationId; value: unknown };\n\nexport type RpcError = { k: \"err\"; id: CorrelationId; error: ErrorInfo };\n\n/** Failed document subscribe; routed to the change-subscription registry. */\nexport type RpcSubError = { k: \"sub-err\"; id: CorrelationId; error: ErrorInfo };\n\n/** Failed live query; routed to the live-query registry. */\nexport type RpcLiveError = {\n k: \"live-err\";\n id: CorrelationId;\n error: ErrorInfo;\n};\n\nexport type RpcAbort = { k: \"abort\"; targetId: CorrelationId };\n\nexport type RpcSubscribe = {\n k: \"sub\";\n id: CorrelationId;\n search: unknown;\n view?: unknown;\n};\n\nexport type RpcEvent = { k: \"event\"; id: CorrelationId; change: unknown };\n\nexport type RpcUnsub = { k: \"unsub\"; id: CorrelationId };\n\nexport type RpcNextPage = { k: \"page\"; id: CorrelationId; token: string };\n\n// Bumped when the tab<->owner wire protocol changes incompatibly; a tab whose\n// version differs from the owner's baseline is told to reload.\nexport const RPC_PROTOCOL_VERSION = 3;\n\n/**\n * A package the worker loads by URL rather than by registry spec: local\n * project packages, whose models the registry does not serve. The URL must be\n * a worker-importable models entry - the dev server's transformed\n * `document-models/index.js` in dev, a prebuilt bundle under\n * `__reactor_worker__/packages/` in production.\n */\nexport type WorkerPackageSource = {\n name: string;\n version?: string;\n url: string;\n};\n\nexport type VersionFingerprint = {\n appBuildId: string;\n rpcProtocolVersion: number;\n models: { id: string; version: number }[];\n // Enabled enforcement flags, sorted and joined. In the fingerprint so a\n // worker cannot keep enforcing the set it booted with after a config change.\n featureFlags?: string;\n};\n\nexport type RpcHello = {\n k: \"hello\";\n id: CorrelationId;\n version: VersionFingerprint;\n construct?: unknown;\n packages?: string[];\n};\n\nexport type RpcRegisterPackages = {\n k: \"register-packages\";\n id: CorrelationId;\n specs: string[];\n /** URL-addressed packages; the worker REPLACES a source it already loaded. */\n sources?: WorkerPackageSource[];\n};\n\nexport type RpcUnregisterPackages = {\n k: \"unregister-packages\";\n id: CorrelationId;\n names: string[];\n};\n\n// `workerGen` (present on restart-driven reloads) is the new SharedWorker name\n// suffix every tab should adopt so they converge on one fresh worker.\nexport type RpcReload = { k: \"reload\"; reason: string; workerGen?: string };\n\n// Worker lifecycle/admin channel, separate from the IReactorClient RPC surface.\nexport type RpcAdmin = {\n k: \"admin\";\n id: CorrelationId;\n method: \"info\" | \"restart\" | \"clearStorage\" | \"migrate\";\n};\n\nexport type WorkerMigrationState = {\n status: \"idle\" | \"needed\" | \"migrating\" | \"failed\";\n legacyMajor?: number;\n phase?: \"clone\" | \"dump\" | \"restore\";\n error?: string;\n};\n\n// Owner -> tab push of the worker's migration state (initial seed on connect +\n// on every change), so tabs can drive the migration banner from one flag.\nexport type RpcMigration = { k: \"migration\"; state: WorkerMigrationState };\n\nexport type WorkerInspectorInfo = {\n namespace: string;\n ownerId: string;\n bootedAtMs: number;\n connectedClients: number;\n appBuildId: string;\n rpcProtocolVersion: number;\n /** Flags this worker is enforcing, from the fingerprint that built it. */\n featureFlags?: string;\n};\n\n// Cloneable subset of renown's user (matches UserActionSigner); token minting + attribution.\nexport type ReactorIdentity = {\n address: string;\n chainId: number;\n networkId: string;\n};\n\nexport type RpcIdentity = { k: \"identity\"; user: ReactorIdentity | null };\n\n// Distributed EventBus: worker -> all tabs, fire-and-forget. `eventType` is a\n// reactor IEventBus numeric type; `event` is the (cloneable) payload.\nexport type RpcBusEvent = { k: \"bus-event\"; eventType: number; event: unknown };\n\nexport type OpKind = \"sync-op\" | \"db-op\" | \"inspector-op\";\n\nexport type MethodCallMessage<K extends OpKind> = {\n k: K;\n id: CorrelationId;\n method: string;\n args: unknown[];\n};\n\n// syncManager commands (add/remove/triggerPull/list); the reply value is the\n// reactor's RemoteMeta (its DriveCollectionId degrades to plain\n// {driveId,branch} over postMessage and is rehydrated tab-side).\nexport type RpcSyncOp = MethodCallMessage<\"sync-op\">;\n\nexport type RpcDbOp = MethodCallMessage<\"db-op\">;\n\nexport type RpcInspectorOp = MethodCallMessage<\"inspector-op\">;\n\nexport type RpcLiveSubscribe = {\n k: \"sub-live\";\n id: CorrelationId;\n sql: string;\n params: unknown[];\n};\n\nexport type RpcLiveEvent = {\n k: \"event-live\";\n id: CorrelationId;\n results: unknown;\n};\n\nexport type RpcLiveUnsub = { k: \"unsub-live\"; id: CorrelationId };\n\n// Liveness heartbeat. The tab pings; the owner answers synchronously (even\n// mid-build/mid-migration) so a silently-evicted worker is detectable. The pong\n// echoes ownerId/bootedAtMs so a respawned (different) instance is identifiable.\nexport type RpcPing = { k: \"ping\"; id: CorrelationId };\n\nexport type RpcPong = {\n k: \"pong\";\n id: CorrelationId;\n ownerId: string;\n bootedAtMs: number;\n};\n\nexport type ClientMessage =\n | RpcRequest\n | RpcAbort\n | RpcSubscribe\n | RpcUnsub\n | RpcNextPage\n | RpcHello\n | RpcRegisterPackages\n | RpcUnregisterPackages\n | RpcIdentity\n | RpcAdmin\n | RpcSyncOp\n | RpcDbOp\n | RpcInspectorOp\n | RpcLiveSubscribe\n | RpcLiveUnsub\n | RpcPing;\n\nexport type OwnerMessage =\n | RpcResponse\n | RpcError\n | RpcSubError\n | RpcLiveError\n | RpcEvent\n | RpcReload\n | RpcBusEvent\n | RpcLiveEvent\n | RpcMigration\n | RpcPong;\n\nexport type RpcMessage = ClientMessage | OwnerMessage;\n\n/** Error reply kind for a client message kind, so it reaches the right registry. */\nexport function responseErrorKind(\n k: ClientMessage[\"k\"],\n): \"err\" | \"sub-err\" | \"live-err\" {\n if (k === \"sub\") {\n return \"sub-err\";\n }\n if (k === \"sub-live\") {\n return \"live-err\";\n }\n return \"err\";\n}\n","import { toErrorInfo } from \"./error-info.js\";\nimport { responseErrorKind } from \"./protocol.js\";\nimport type { ClientMessage, CorrelationId, OwnerMessage } from \"./protocol.js\";\nimport type { IRpcTransport } from \"./transport.js\";\n\nexport interface IHostResponder {\n ok(id: CorrelationId, value?: unknown): void;\n err(id: CorrelationId, error: unknown): void;\n errForKind(message: ClientMessage, error: unknown): void;\n run(\n id: CorrelationId,\n body: () => Promise<unknown>,\n mapOk?: (value: unknown) => unknown,\n ): Promise<void>;\n}\n\nconst defaultOk = (): unknown => ({ ok: true });\n\nexport function hostResponder(transport: IRpcTransport): IHostResponder {\n const ok = (id: CorrelationId, value: unknown = { ok: true }): void => {\n transport.post({ k: \"res\", id, value });\n };\n const err = (id: CorrelationId, error: unknown): void => {\n transport.post({ k: \"err\", id, error: toErrorInfo(error) });\n };\n return {\n ok,\n err,\n errForKind(message, error) {\n if (!(\"id\" in message)) {\n return;\n }\n transport.post({\n k: responseErrorKind(message.k),\n id: message.id,\n error: toErrorInfo(error),\n } as OwnerMessage);\n },\n async run(id, body, mapOk = defaultOk) {\n try {\n ok(id, mapOk(await body()));\n } catch (error) {\n err(id, error);\n }\n },\n };\n}\n","import type { DocumentChangeEvent, IReactorClient } from \"../client/types.js\";\nimport type { SearchFilter, ViewFilter } from \"../shared/types.js\";\nimport { hostResponder, type IHostResponder } from \"./host-reply.js\";\nimport { dehydratePage } from \"./paging.js\";\nimport { SubscriptionStore } from \"./subscription.js\";\nimport type {\n ClientMessage,\n RpcNextPage,\n RpcRequest,\n RpcSubscribe,\n} from \"./protocol.js\";\nimport type { IRpcTransport } from \"./transport.js\";\n\ntype AnyMethod = (...args: unknown[]) => unknown;\ntype NextPage = () => Promise<unknown>;\n\n// Bound the page-continuation cache; an evicted token falls back to the \"expired cursor\" error.\nconst MAX_PENDING_PAGES = 1000;\n\nexport class ReactorHostServer {\n private readonly client: IReactorClient;\n private readonly transport: IRpcTransport;\n private readonly reply: IHostResponder;\n private readonly aborters = new Map<string, AbortController>();\n private readonly subscriptions = new SubscriptionStore();\n private readonly pages = new Map<string, NextPage>();\n private pageCounter = 0;\n private detach: () => void = () => {};\n\n constructor(client: IReactorClient, transport: IRpcTransport) {\n this.client = client;\n this.transport = transport;\n this.reply = hostResponder(transport);\n }\n\n start(): void {\n this.detach = this.transport.onMessage((message) => {\n void this.handleMessage(message as ClientMessage);\n });\n }\n\n stop(): void {\n this.detach();\n this.subscriptions.drain();\n this.aborters.clear();\n this.pages.clear();\n }\n\n async handleMessage(message: ClientMessage): Promise<void> {\n try {\n switch (message.k) {\n case \"req\":\n await this.handleRequest(message);\n return;\n case \"page\":\n await this.handlePage(message);\n return;\n case \"abort\":\n this.aborters.get(message.targetId)?.abort();\n return;\n case \"sub\":\n this.handleSubscribe(message);\n return;\n case \"unsub\":\n this.subscriptions.end(message.id);\n return;\n }\n } catch (error) {\n this.reply.errForKind(message, error);\n }\n }\n\n private async handleRequest(message: RpcRequest): Promise<void> {\n let args = message.args;\n let controller: AbortController | undefined;\n if (message.abortAt !== undefined) {\n controller = new AbortController();\n this.aborters.set(message.id, controller);\n args = message.args.slice();\n args[message.abortAt] = controller.signal;\n }\n try {\n const method = this.resolveMethod(message.method);\n const value = await method(...args);\n this.reply.ok(message.id, this.prepareResult(value));\n } catch (error) {\n this.reply.err(message.id, error);\n } finally {\n if (controller) {\n this.aborters.delete(message.id);\n }\n }\n }\n\n private async handlePage(message: RpcNextPage): Promise<void> {\n const next = this.pages.get(message.token);\n this.pages.delete(message.token);\n if (!next) {\n this.reply.err(\n message.id,\n new Error(\"Paged cursor already consumed or expired\"),\n );\n return;\n }\n try {\n const value = await next();\n this.reply.ok(message.id, this.prepareResult(value));\n } catch (error) {\n this.reply.err(message.id, error);\n }\n }\n\n private handleSubscribe(message: RpcSubscribe): void {\n const unsubscribe = this.client.subscribe(\n message.search as SearchFilter,\n (change: DocumentChangeEvent) =>\n this.transport.post({ k: \"event\", id: message.id, change }),\n message.view as ViewFilter | undefined,\n );\n this.subscriptions.set(message.id, unsubscribe);\n }\n\n private prepareResult(value: unknown): unknown {\n return dehydratePage(value, (next) => {\n const token = `p${++this.pageCounter}`;\n this.pages.set(token, next);\n if (this.pages.size > MAX_PENDING_PAGES) {\n const oldest = this.pages.keys().next().value;\n if (oldest !== undefined) {\n this.pages.delete(oldest);\n }\n }\n return token;\n });\n }\n\n private resolveMethod(path: string): AnyMethod {\n if (path.startsWith(\"drives.\")) {\n const name = path.slice(\"drives.\".length);\n const drives = this.client.drives as unknown as Record<string, AnyMethod>;\n const fn = drives[name];\n if (typeof fn !== \"function\") {\n throw new Error(`Unknown drive method: ${name}`);\n }\n return fn.bind(this.client.drives);\n }\n const client = this.client as unknown as Record<string, AnyMethod>;\n const fn = client[path];\n if (typeof fn !== \"function\") {\n throw new Error(`Unknown reactor method: ${path}`);\n }\n return fn.bind(this.client);\n }\n}\n","export class KeyedListeners<K, A extends unknown[] = []> {\n private readonly map = new Map<K, Set<(...a: A) => void>>();\n\n add(key: K, fn: (...a: A) => void): () => void {\n let set = this.map.get(key);\n if (!set) {\n set = new Set();\n this.map.set(key, set);\n }\n set.add(fn);\n return () => {\n const current = this.map.get(key);\n if (!current) {\n return;\n }\n current.delete(fn);\n if (current.size === 0) {\n this.map.delete(key);\n }\n };\n }\n\n emit(key: K, ...a: A): void {\n const set = this.map.get(key);\n if (!set) {\n return;\n }\n for (const fn of [...set]) {\n fn(...a);\n }\n }\n\n emitAll(...a: A): void {\n for (const set of [...this.map.values()]) {\n for (const fn of [...set]) {\n fn(...a);\n }\n }\n }\n}\n\nexport class Listeners<A extends unknown[] = []> {\n private readonly keyed = new KeyedListeners<null, A>();\n\n add(fn: (...a: A) => void): () => void {\n return this.keyed.add(null, fn);\n }\n\n emit(...a: A): void {\n this.keyed.emit(null, ...a);\n }\n}\n","import { fromErrorInfo } from \"./error-info.js\";\nimport type { ClientMessage, CorrelationId, OwnerMessage } from \"./protocol.js\";\n\ntype Pending = {\n resolve: (value: unknown) => void;\n reject: (error: unknown) => void;\n timer?: ReturnType<typeof setTimeout>;\n cleanup?: () => void;\n transform?: (value: unknown) => unknown;\n};\n\nexport type RpcRequestOptions = {\n /** Reject if no response arrives within this many ms; omit for no timeout. */\n timeoutMs?: number;\n /** Maps a successful response value (e.g. paged-result rehydration). */\n transform?: (value: unknown) => unknown;\n /** Runs after the post; may return a cleanup invoked when the request settles. */\n setup?: (id: CorrelationId) => (() => void) | void;\n};\n\n/** Anything that can send a client message (the router). */\nexport type RpcPoster = { post(message: ClientMessage): void };\n\n/**\n * Request/response correlation shared by every RPC proxy: one pending map,\n * res/err settlement, and per-request timeout/transform/cleanup.\n */\nexport class RpcCorrelator {\n private counter = 0;\n private readonly pending = new Map<CorrelationId, Pending>();\n\n constructor(\n private readonly poster: RpcPoster,\n private readonly prefix = \"r\",\n ) {}\n\n nextId(): CorrelationId {\n return `${this.prefix}${++this.counter}`;\n }\n\n /** Settle the pending request a res/err refers to; a missing id is a no-op. */\n handleMessage(message: OwnerMessage): void {\n if (message.k !== \"res\" && message.k !== \"err\") {\n return;\n }\n const entry = this.pending.get(message.id);\n if (!entry) {\n return;\n }\n this.pending.delete(message.id);\n if (entry.timer) {\n clearTimeout(entry.timer);\n }\n entry.cleanup?.();\n if (message.k === \"res\") {\n entry.resolve(\n entry.transform ? entry.transform(message.value) : message.value,\n );\n } else {\n entry.reject(fromErrorInfo(message.error));\n }\n }\n\n /** Send a request, resolving on its res/err. `build` stamps the generated id. */\n request(\n build: (id: CorrelationId) => ClientMessage,\n options: RpcRequestOptions = {},\n ): Promise<unknown> {\n const id = this.nextId();\n const message = build(id);\n const method = (message as { method?: unknown }).method;\n const label =\n typeof method === \"string\" ? `${message.k} \"${method}\"` : message.k;\n const promise = new Promise<unknown>((resolve, reject) => {\n let timer: ReturnType<typeof setTimeout> | undefined;\n if (options.timeoutMs !== undefined) {\n const timeoutMs = options.timeoutMs;\n timer = setTimeout(() => {\n const entry = this.pending.get(id);\n if (entry) {\n this.pending.delete(id);\n entry.cleanup?.();\n reject(\n new Error(\n `Reactor worker did not respond to ${label} within ${timeoutMs}ms; the worker may have failed to load`,\n ),\n );\n }\n }, timeoutMs);\n }\n this.pending.set(id, {\n resolve,\n reject,\n timer,\n transform: options.transform,\n });\n });\n this.poster.post(message);\n const cleanup = options.setup?.(id);\n if (cleanup) {\n const entry = this.pending.get(id);\n if (entry) {\n entry.cleanup = cleanup;\n } else {\n cleanup();\n }\n }\n return promise;\n }\n}\n","import type { ClientMessage, CorrelationId, OwnerMessage } from \"./protocol.js\";\nimport { RpcCorrelator, type RpcRequestOptions } from \"./rpc-correlator.js\";\nimport type { IRpcTransport } from \"./transport.js\";\n\ntype OwnerKind = OwnerMessage[\"k\"];\ntype RouteHandler = (message: OwnerMessage) => void;\n\n/**\n * Owns the single transport.onMessage and dispatches each message to the one\n * consumer that owns its kind. Composes the shared RpcCorrelator (res/err) and\n * exposes request/nextId; attach/detach swap the transport for reconnect.\n */\nexport class MessageRouter {\n private readonly handlers = new Map<OwnerKind, RouteHandler>();\n private readonly correlator: RpcCorrelator;\n private transport: IRpcTransport | null = null;\n private detachTransport: () => void = () => {};\n\n constructor(prefix = \"r\") {\n this.correlator = new RpcCorrelator(this, prefix);\n const settle: RouteHandler = (message) =>\n this.correlator.handleMessage(message);\n this.handlers.set(\"res\", settle);\n this.handlers.set(\"err\", settle);\n }\n\n attach(transport: IRpcTransport): void {\n this.detachTransport();\n this.transport = transport;\n this.detachTransport = transport.onMessage((message) => {\n const msg = message as OwnerMessage;\n this.handlers.get(msg.k)?.(msg);\n });\n }\n\n detach(): void {\n this.detachTransport();\n this.detachTransport = () => {};\n this.transport = null;\n }\n\n post(message: ClientMessage): void {\n if (!this.transport) {\n throw new Error(\"MessageRouter.post called before attach\");\n }\n this.transport.post(message);\n }\n\n /** Register the sole owner of a message kind; throws on a duplicate. */\n on<K extends OwnerKind>(\n kind: K,\n handler: (message: Extract<OwnerMessage, { k: K }>) => void,\n ): () => void {\n if (this.handlers.has(kind)) {\n throw new Error(`MessageRouter already has a handler for \"${kind}\"`);\n }\n const route = handler as RouteHandler;\n this.handlers.set(kind, route);\n return () => {\n if (this.handlers.get(kind) === route) {\n this.handlers.delete(kind);\n }\n };\n }\n\n request(\n build: (id: CorrelationId) => ClientMessage,\n options?: RpcRequestOptions,\n ): Promise<unknown> {\n return this.correlator.request(build, options);\n }\n\n nextId(): CorrelationId {\n return this.correlator.nextId();\n }\n}\n","import type { RpcMessage } from \"./protocol.js\";\n\nexport interface IRpcTransport {\n post(message: RpcMessage): void;\n onMessage(listener: (message: RpcMessage) => void): () => void;\n close(): void;\n}\n\nexport function createPortTransport(port: MessagePort): IRpcTransport {\n port.start();\n port.addEventListener(\"messageerror\", (event) => {\n console.error(\"[rpc transport] failed to deserialize message\", event);\n });\n return {\n post(message) {\n port.postMessage(message);\n },\n onMessage(listener) {\n const handler = (event: MessageEvent) => {\n listener(event.data as RpcMessage);\n };\n port.addEventListener(\"message\", handler);\n return () => port.removeEventListener(\"message\", handler);\n },\n close() {\n port.close();\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as fromErrorInfo } from "./forwarding-logger-ByfBFgMS.js";
|
|
2
2
|
import { a as WorkerLoadModelFailedError, i as WorkerInitFailedError, n as WorkerBusyError, o as WorkerShutdownTimeoutError, r as WorkerExitedError, t as WorkerAbortTimeoutError } from "./errors-BM_ULVWd.js";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
//#region src/executor/worker/worker-handle.ts
|
|
@@ -394,4 +394,4 @@ var WorkerHandle = class {
|
|
|
394
394
|
//#endregion
|
|
395
395
|
export { WorkerHandle };
|
|
396
396
|
|
|
397
|
-
//# sourceMappingURL=worker-handle-
|
|
397
|
+
//# sourceMappingURL=worker-handle-CN2-oRv-.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker-handle-B48V1hDm.js","names":[],"sources":["../src/executor/worker/worker-handle.ts"],"sourcesContent":["/**\n * Parent-side IPC wrapper around a single executor worker.\n *\n * Implements {@link IExecutorWorker} on top of an injectable\n * {@link IWorkerTransport}. The handle owns one worker's lifecycle (init ->\n * ready -> execute/result rounds -> abort/shutdown -> terminate) and bounds\n * its in-flight slot to a single job — `SimpleJobExecutor` is single-threaded\n * inside the worker, and concurrent dispatches would race its caches.\n *\n * @see Executor Worker Pool Design wiki page\n * (Powerhouse board wiki id: d400d711-f07e-4389-a226-4e9fdd4fa8ba)\n */\n\nimport type { ILogger } from \"document-model\";\nimport { randomUUID } from \"node:crypto\";\nimport type {\n IExecutorWorker,\n WorkerExecutionOutcome,\n WorkerInFlightSnapshot,\n} from \"../interfaces.js\";\nimport type { Job } from \"../../queue/types.js\";\nimport type { JobExecutorConfig } from \"../types.js\";\nimport { fromErrorInfo } from \"./error-info.js\";\nimport {\n WorkerAbortTimeoutError,\n WorkerBusyError,\n WorkerExitedError,\n WorkerInitFailedError,\n WorkerLoadModelFailedError,\n WorkerShutdownTimeoutError,\n} from \"./errors.js\";\nimport type { ForwardingPoolInstrumentation } from \"../../storage/pool-instrumentation.js\";\nimport type {\n DbConfig,\n FactorySpec,\n HeartbeatMessage,\n LogMessage,\n MetricsMessage,\n ModelLoadFailedMessage,\n ModelLoadedMessage,\n ModelManifestEntry,\n PoolAcquireSamplesMessage,\n ResultMessage,\n WorkerMessage,\n WorkerPoolConfig,\n} from \"./protocol.js\";\nimport type { IWorkerTransport, WorkerTransportListener } from \"./transport.js\";\n\nconst DEFAULT_ABORT_GRACE_MS = 5_000;\nconst DEFAULT_SHUTDOWN_GRACE_MS = 5_000;\n\n/**\n * Payload the parent ships in the `init` message (everything except the\n * envelope fields the handle fills in itself).\n */\nexport type WorkerInitPayload = {\n poolConfig: WorkerPoolConfig;\n db: DbConfig;\n models: ModelManifestEntry[];\n /** Omitted = the worker builds its executor with the built-in defaults. */\n executorConfig?: JobExecutorConfig;\n /** Builds the worker's signer for synthesized operations. */\n signer?: FactorySpec;\n /** Builds the worker's signature trust policy. */\n trustPolicy?: FactorySpec;\n};\n\nexport type WorkerHandleOptions = {\n workerId: string;\n index: number;\n transport: IWorkerTransport;\n initPayload: WorkerInitPayload;\n logger: ILogger;\n /** Grace period before an outstanding `abort` escalates to terminate. */\n abortGraceMs?: number;\n /** Default grace period applied to graceful `shutdown` when not overridden. */\n defaultShutdownGraceMs?: number;\n /**\n * Forwarding instrumentation the host registers per worker. The handle\n * pumps `pool-acquire-samples` messages into this object so the host's\n * OpenTelemetry instrumentation records acquire-wait latencies as if the\n * worker's pool were local.\n */\n poolInstrumentation?: ForwardingPoolInstrumentation;\n};\n\ntype PendingEntry =\n | {\n kind: \"init\";\n resolve: () => void;\n reject: (err: Error) => void;\n }\n | {\n kind: \"execute\";\n jobId: string;\n resolve: (outcome: WorkerExecutionOutcome) => void;\n reject: (err: Error) => void;\n }\n | {\n kind: \"shutdown\";\n resolve: () => void;\n reject: (err: Error) => void;\n }\n | {\n kind: \"load-model\";\n documentType: string;\n version: string;\n resolve: () => void;\n reject: (err: Error) => void;\n };\n\ntype Phase = \"fresh\" | \"starting\" | \"ready\" | \"shutting-down\" | \"terminated\";\n\nexport class WorkerHandle implements IExecutorWorker {\n readonly workerId: string;\n readonly index: number;\n\n private readonly transport: IWorkerTransport;\n private readonly initPayload: WorkerInitPayload;\n private readonly logger: ILogger;\n private readonly abortGraceMs: number;\n private readonly defaultShutdownGraceMs: number;\n private readonly poolInstrumentation?: ForwardingPoolInstrumentation;\n\n private readonly pending = new Map<string, PendingEntry>();\n private inFlight: { correlationId: string; jobId: string } | null = null;\n private phase: Phase = \"fresh\";\n private lastExitError: Error | null = null;\n private lastCorrelationId: string | null = null;\n private abortTimer: NodeJS.Timeout | null = null;\n private lastHeartbeatAt: number | null = null;\n private lastMetrics: {\n counters: Record<string, number>;\n gauges: Record<string, number>;\n timestamp: number;\n } | null = null;\n\n private readonly onMessage: WorkerTransportListener<\"message\">;\n private readonly onError: WorkerTransportListener<\"error\">;\n private readonly onExit: WorkerTransportListener<\"exit\">;\n\n constructor(options: WorkerHandleOptions) {\n this.workerId = options.workerId;\n this.index = options.index;\n this.transport = options.transport;\n this.initPayload = options.initPayload;\n this.logger = options.logger;\n this.abortGraceMs = options.abortGraceMs ?? DEFAULT_ABORT_GRACE_MS;\n this.defaultShutdownGraceMs =\n options.defaultShutdownGraceMs ?? DEFAULT_SHUTDOWN_GRACE_MS;\n this.poolInstrumentation = options.poolInstrumentation;\n\n this.onMessage = (msg) => this.handleMessage(msg);\n this.onError = (err) => this.handleTransportError(err);\n this.onExit = (code) => this.handleExit(code);\n\n this.transport.on(\"message\", this.onMessage);\n this.transport.on(\"error\", this.onError);\n this.transport.on(\"exit\", this.onExit);\n }\n\n public async start(): Promise<void> {\n if (this.phase !== \"fresh\") {\n throw new WorkerInitFailedError(\n this.workerId,\n `cannot start worker in phase ${this.phase}`,\n );\n }\n this.phase = \"starting\";\n const correlationId = this.nextCorrelationId();\n const ready = new Promise<void>((resolve, reject) => {\n this.pending.set(correlationId, {\n kind: \"init\",\n resolve,\n reject,\n });\n });\n this.transport.postMessage({\n type: \"init\",\n correlationId,\n workerId: this.workerId,\n poolConfig: this.initPayload.poolConfig,\n db: this.initPayload.db,\n models: this.initPayload.models,\n executorConfig: this.initPayload.executorConfig,\n signer: this.initPayload.signer,\n trustPolicy: this.initPayload.trustPolicy,\n });\n await ready;\n this.phase = \"ready\";\n }\n\n public execute(\n job: Job,\n signal?: AbortSignal,\n ): Promise<WorkerExecutionOutcome> {\n if (this.phase === \"terminated\") {\n return Promise.reject(\n this.lastExitError ??\n new WorkerExitedError(this.workerId, -1, this.lastCorrelationId),\n );\n }\n if (this.phase !== \"ready\") {\n return Promise.reject(\n new WorkerInitFailedError(\n this.workerId,\n `worker not ready (phase=${this.phase})`,\n ),\n );\n }\n if (this.inFlight !== null) {\n return Promise.reject(new WorkerBusyError(this.workerId, job.id));\n }\n\n const correlationId = this.nextCorrelationId();\n this.lastCorrelationId = correlationId;\n this.inFlight = { correlationId, jobId: job.id };\n\n let abortListener: (() => void) | null = null;\n const detachSignal = () => {\n if (signal && abortListener) {\n signal.removeEventListener(\"abort\", abortListener);\n abortListener = null;\n }\n };\n\n const promise = new Promise<WorkerExecutionOutcome>((resolve, reject) => {\n this.pending.set(correlationId, {\n kind: \"execute\",\n jobId: job.id,\n resolve: (outcome) => {\n detachSignal();\n resolve(outcome);\n },\n reject: (err) => {\n detachSignal();\n reject(err);\n },\n });\n });\n\n if (signal) {\n if (signal.aborted) {\n this.abort(correlationId, \"aborted before dispatch\");\n } else {\n abortListener = () => this.abort(correlationId, \"caller signal\");\n signal.addEventListener(\"abort\", abortListener, { once: true });\n }\n }\n\n this.transport.postMessage({\n type: \"execute\",\n correlationId,\n job,\n });\n\n return promise;\n }\n\n public abort(correlationId: string, reason?: string): void {\n if (this.phase === \"terminated\") {\n return;\n }\n if (!this.inFlight || this.inFlight.correlationId !== correlationId) {\n return;\n }\n if (this.abortTimer) {\n return;\n }\n this.transport.postMessage({\n type: \"abort\",\n correlationId: this.nextCorrelationId(),\n targetCorrelationId: correlationId,\n reason,\n });\n this.abortTimer = setTimeout(() => {\n this.abortTimer = null;\n const entry = this.pending.get(correlationId);\n if (!entry || entry.kind !== \"execute\") {\n return;\n }\n const err = new WorkerAbortTimeoutError(\n this.workerId,\n correlationId,\n this.abortGraceMs,\n );\n this.failPending(correlationId, err);\n this.inFlight = null;\n void this.forceTerminate(err);\n }, this.abortGraceMs);\n }\n\n public async shutdown(graceful: boolean, graceMs?: number): Promise<void> {\n if (this.phase === \"terminated\") {\n return;\n }\n if (!graceful) {\n this.phase = \"shutting-down\";\n await this.forceTerminate(null);\n return;\n }\n\n const effectiveGrace = graceMs ?? this.defaultShutdownGraceMs;\n this.phase = \"shutting-down\";\n const correlationId = this.nextCorrelationId();\n const drained = new Promise<void>((resolve, reject) => {\n this.pending.set(correlationId, {\n kind: \"shutdown\",\n resolve,\n reject,\n });\n });\n this.transport.postMessage({\n type: \"shutdown\",\n correlationId,\n graceMs: effectiveGrace,\n });\n\n let timer!: NodeJS.Timeout;\n const timeout = new Promise<void>((_, reject) => {\n timer = setTimeout(() => {\n reject(new WorkerShutdownTimeoutError(this.workerId, effectiveGrace));\n }, effectiveGrace);\n });\n\n try {\n await Promise.race([drained, timeout]);\n } catch (err) {\n this.logger.warn(\n `worker ${this.workerId} shutdown timed out after ${effectiveGrace}ms`,\n err,\n );\n } finally {\n clearTimeout(timer);\n }\n\n await this.forceTerminate(null);\n }\n\n public loadModel(\n entry: ModelManifestEntry,\n signal?: AbortSignal,\n ): Promise<void> {\n if (this.phase === \"terminated\") {\n return Promise.reject(\n this.lastExitError ??\n new WorkerExitedError(this.workerId, -1, this.lastCorrelationId),\n );\n }\n if (this.phase !== \"ready\") {\n return Promise.reject(\n new WorkerInitFailedError(\n this.workerId,\n `worker not ready (phase=${this.phase})`,\n ),\n );\n }\n\n const correlationId = this.nextCorrelationId();\n let abortListener: (() => void) | null = null;\n const detachSignal = () => {\n if (signal && abortListener) {\n signal.removeEventListener(\"abort\", abortListener);\n abortListener = null;\n }\n };\n\n const promise = new Promise<void>((resolve, reject) => {\n this.pending.set(correlationId, {\n kind: \"load-model\",\n documentType: entry.documentType,\n version: entry.version,\n resolve: () => {\n detachSignal();\n resolve();\n },\n reject: (err) => {\n detachSignal();\n reject(err);\n },\n });\n });\n\n if (signal) {\n if (signal.aborted) {\n this.failPending(\n correlationId,\n new WorkerLoadModelFailedError(\n this.workerId,\n entry.documentType,\n entry.version,\n \"aborted before dispatch\",\n ),\n );\n return promise;\n }\n abortListener = () => {\n this.failPending(\n correlationId,\n new WorkerLoadModelFailedError(\n this.workerId,\n entry.documentType,\n entry.version,\n \"caller signal\",\n ),\n );\n };\n signal.addEventListener(\"abort\", abortListener, { once: true });\n }\n\n this.transport.postMessage({\n type: \"load-model\",\n correlationId,\n model: entry,\n });\n\n return promise;\n }\n\n public evictPurged(documentIds: string[]): void {\n if (this.phase !== \"ready\" || documentIds.length === 0) {\n return;\n }\n this.transport.postMessage({ type: \"evict-purged\", documentIds });\n }\n\n public isIdle(): boolean {\n return this.inFlight === null && this.phase === \"ready\";\n }\n\n public getInFlight(): WorkerInFlightSnapshot | null {\n if (!this.inFlight) {\n return null;\n }\n return {\n correlationId: this.inFlight.correlationId,\n jobId: this.inFlight.jobId,\n };\n }\n\n private nextCorrelationId(): string {\n return randomUUID();\n }\n\n private handleMessage(msg: WorkerMessage): void {\n switch (msg.type) {\n case \"ready\":\n this.handleReady(msg.correlationId);\n return;\n case \"result\":\n this.handleResult(msg);\n return;\n case \"model-loaded\":\n this.handleModelLoaded(msg);\n return;\n case \"model-load-failed\":\n this.handleModelLoadFailed(msg);\n return;\n case \"log\":\n this.handleLog(msg);\n return;\n case \"heartbeat\":\n this.handleHeartbeat(msg);\n return;\n case \"metrics\":\n this.handleMetrics(msg);\n return;\n case \"pool-acquire-samples\":\n this.handlePoolAcquireSamples(msg);\n return;\n default: {\n const exhaustive: never = msg;\n void exhaustive;\n }\n }\n }\n\n private handlePoolAcquireSamples(msg: PoolAcquireSamplesMessage): void {\n if (!this.poolInstrumentation) {\n return;\n }\n this.poolInstrumentation.updateStats({\n size: msg.size,\n idle: msg.idle,\n waiting: msg.waiting,\n });\n this.poolInstrumentation.pushSamples(msg.durations);\n }\n\n private handleReady(correlationId: string): void {\n const entry = this.pending.get(correlationId);\n if (!entry || entry.kind !== \"init\") {\n return;\n }\n this.pending.delete(correlationId);\n entry.resolve();\n }\n\n private handleResult(msg: ResultMessage): void {\n const entry = this.pending.get(msg.correlationId);\n if (!entry || entry.kind !== \"execute\") {\n return;\n }\n this.pending.delete(msg.correlationId);\n if (this.inFlight?.correlationId === msg.correlationId) {\n this.inFlight = null;\n }\n if (this.abortTimer) {\n clearTimeout(this.abortTimer);\n this.abortTimer = null;\n }\n\n const outcome: WorkerExecutionOutcome = msg.error\n ? {\n result: {\n ...msg.result,\n success: false,\n error: fromErrorInfo(msg.error),\n },\n signatureRefusals: msg.signatureRefusals,\n }\n : {\n result: msg.result,\n writeReady: msg.writeReady,\n signatureRefusals: msg.signatureRefusals,\n };\n entry.resolve(outcome);\n\n if (this.phase === \"shutting-down\") {\n const shutdownEntry = [...this.pending.values()].find(\n (e) => e.kind === \"shutdown\",\n );\n if (shutdownEntry) {\n shutdownEntry.resolve();\n }\n }\n }\n\n private handleModelLoaded(msg: ModelLoadedMessage): void {\n const entry = this.pending.get(msg.correlationId);\n if (!entry || entry.kind !== \"load-model\") {\n return;\n }\n this.pending.delete(msg.correlationId);\n entry.resolve();\n }\n\n private handleModelLoadFailed(msg: ModelLoadFailedMessage): void {\n const entry = this.pending.get(msg.correlationId);\n if (!entry || entry.kind !== \"load-model\") {\n return;\n }\n this.pending.delete(msg.correlationId);\n entry.reject(\n new WorkerLoadModelFailedError(\n this.workerId,\n msg.documentType,\n msg.version,\n msg.error.message,\n { cause: fromErrorInfo(msg.error) },\n ),\n );\n }\n\n private handleLog(msg: LogMessage): void {\n const args = msg.args;\n switch (msg.level) {\n case \"debug\":\n this.logger.debug(msg.message, ...args);\n return;\n case \"info\":\n this.logger.info(msg.message, ...args);\n return;\n case \"warn\":\n this.logger.warn(msg.message, ...args);\n return;\n case \"error\":\n this.logger.error(msg.message, ...args);\n return;\n default: {\n const exhaustive: never = msg.level;\n void exhaustive;\n }\n }\n }\n\n private handleHeartbeat(msg: HeartbeatMessage): void {\n this.lastHeartbeatAt = msg.timestamp;\n }\n\n private handleMetrics(msg: MetricsMessage): void {\n this.lastMetrics = {\n counters: msg.counters,\n gauges: msg.gauges,\n timestamp: msg.timestamp,\n };\n }\n\n private handleTransportError(err: Error): void {\n this.logger.error(\n `worker ${this.workerId} transport error: @message`,\n err.message,\n err,\n );\n }\n\n private handleExit(code: number): void {\n if (this.phase === \"terminated\") {\n return;\n }\n const wasGraceful = this.phase === \"shutting-down\" && code === 0;\n this.phase = \"terminated\";\n const err = wasGraceful\n ? null\n : new WorkerExitedError(this.workerId, code, this.lastCorrelationId);\n this.lastExitError = err;\n if (this.abortTimer) {\n clearTimeout(this.abortTimer);\n this.abortTimer = null;\n }\n this.detachTransportListeners();\n this.drainPending(err);\n this.inFlight = null;\n }\n\n private detachTransportListeners(): void {\n this.transport.off(\"message\", this.onMessage);\n this.transport.off(\"error\", this.onError);\n this.transport.off(\"exit\", this.onExit);\n }\n\n private drainPending(err: Error | null): void {\n for (const [, entry] of this.pending) {\n if (err) {\n entry.reject(err);\n } else if (entry.kind === \"shutdown\") {\n entry.resolve();\n } else if (entry.kind === \"init\") {\n entry.reject(new WorkerInitFailedError(this.workerId, \"worker exited\"));\n } else if (entry.kind === \"load-model\") {\n entry.reject(\n new WorkerLoadModelFailedError(\n this.workerId,\n entry.documentType,\n entry.version,\n \"worker exited before responding\",\n ),\n );\n } else {\n entry.reject(\n new WorkerExitedError(this.workerId, 0, this.lastCorrelationId),\n );\n }\n }\n this.pending.clear();\n }\n\n private failPending(correlationId: string, err: Error): void {\n const entry = this.pending.get(correlationId);\n if (!entry) {\n return;\n }\n this.pending.delete(correlationId);\n entry.reject(err);\n }\n\n private async forceTerminate(reason: Error | null): Promise<void> {\n if (this.phase === \"terminated\") {\n return;\n }\n try {\n await this.transport.terminate();\n } catch (err) {\n this.logger.warn(\n `worker ${this.workerId} terminate threw: ${(err as Error).message}`,\n );\n }\n const phaseAfter = this.phase as Phase;\n if (phaseAfter === \"terminated\") {\n return;\n }\n this.phase = \"terminated\";\n this.lastExitError = reason;\n this.detachTransportListeners();\n this.drainPending(reason);\n this.inFlight = null;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"worker-handle-CN2-oRv-.js","names":[],"sources":["../src/executor/worker/worker-handle.ts"],"sourcesContent":["/**\n * Parent-side IPC wrapper around a single executor worker.\n *\n * Implements {@link IExecutorWorker} on top of an injectable\n * {@link IWorkerTransport}. The handle owns one worker's lifecycle (init ->\n * ready -> execute/result rounds -> abort/shutdown -> terminate) and bounds\n * its in-flight slot to a single job — `SimpleJobExecutor` is single-threaded\n * inside the worker, and concurrent dispatches would race its caches.\n *\n * @see Executor Worker Pool Design wiki page\n * (Powerhouse board wiki id: d400d711-f07e-4389-a226-4e9fdd4fa8ba)\n */\n\nimport type { ILogger } from \"document-model\";\nimport { randomUUID } from \"node:crypto\";\nimport type {\n IExecutorWorker,\n WorkerExecutionOutcome,\n WorkerInFlightSnapshot,\n} from \"../interfaces.js\";\nimport type { Job } from \"../../queue/types.js\";\nimport type { JobExecutorConfig } from \"../types.js\";\nimport { fromErrorInfo } from \"./error-info.js\";\nimport {\n WorkerAbortTimeoutError,\n WorkerBusyError,\n WorkerExitedError,\n WorkerInitFailedError,\n WorkerLoadModelFailedError,\n WorkerShutdownTimeoutError,\n} from \"./errors.js\";\nimport type { ForwardingPoolInstrumentation } from \"../../storage/pool-instrumentation.js\";\nimport type {\n DbConfig,\n FactorySpec,\n HeartbeatMessage,\n LogMessage,\n MetricsMessage,\n ModelLoadFailedMessage,\n ModelLoadedMessage,\n ModelManifestEntry,\n PoolAcquireSamplesMessage,\n ResultMessage,\n WorkerMessage,\n WorkerPoolConfig,\n} from \"./protocol.js\";\nimport type { IWorkerTransport, WorkerTransportListener } from \"./transport.js\";\n\nconst DEFAULT_ABORT_GRACE_MS = 5_000;\nconst DEFAULT_SHUTDOWN_GRACE_MS = 5_000;\n\n/**\n * Payload the parent ships in the `init` message (everything except the\n * envelope fields the handle fills in itself).\n */\nexport type WorkerInitPayload = {\n poolConfig: WorkerPoolConfig;\n db: DbConfig;\n models: ModelManifestEntry[];\n /** Omitted = the worker builds its executor with the built-in defaults. */\n executorConfig?: JobExecutorConfig;\n /** Builds the worker's signer for synthesized operations. */\n signer?: FactorySpec;\n /** Builds the worker's signature trust policy. */\n trustPolicy?: FactorySpec;\n};\n\nexport type WorkerHandleOptions = {\n workerId: string;\n index: number;\n transport: IWorkerTransport;\n initPayload: WorkerInitPayload;\n logger: ILogger;\n /** Grace period before an outstanding `abort` escalates to terminate. */\n abortGraceMs?: number;\n /** Default grace period applied to graceful `shutdown` when not overridden. */\n defaultShutdownGraceMs?: number;\n /**\n * Forwarding instrumentation the host registers per worker. The handle\n * pumps `pool-acquire-samples` messages into this object so the host's\n * OpenTelemetry instrumentation records acquire-wait latencies as if the\n * worker's pool were local.\n */\n poolInstrumentation?: ForwardingPoolInstrumentation;\n};\n\ntype PendingEntry =\n | {\n kind: \"init\";\n resolve: () => void;\n reject: (err: Error) => void;\n }\n | {\n kind: \"execute\";\n jobId: string;\n resolve: (outcome: WorkerExecutionOutcome) => void;\n reject: (err: Error) => void;\n }\n | {\n kind: \"shutdown\";\n resolve: () => void;\n reject: (err: Error) => void;\n }\n | {\n kind: \"load-model\";\n documentType: string;\n version: string;\n resolve: () => void;\n reject: (err: Error) => void;\n };\n\ntype Phase = \"fresh\" | \"starting\" | \"ready\" | \"shutting-down\" | \"terminated\";\n\nexport class WorkerHandle implements IExecutorWorker {\n readonly workerId: string;\n readonly index: number;\n\n private readonly transport: IWorkerTransport;\n private readonly initPayload: WorkerInitPayload;\n private readonly logger: ILogger;\n private readonly abortGraceMs: number;\n private readonly defaultShutdownGraceMs: number;\n private readonly poolInstrumentation?: ForwardingPoolInstrumentation;\n\n private readonly pending = new Map<string, PendingEntry>();\n private inFlight: { correlationId: string; jobId: string } | null = null;\n private phase: Phase = \"fresh\";\n private lastExitError: Error | null = null;\n private lastCorrelationId: string | null = null;\n private abortTimer: NodeJS.Timeout | null = null;\n private lastHeartbeatAt: number | null = null;\n private lastMetrics: {\n counters: Record<string, number>;\n gauges: Record<string, number>;\n timestamp: number;\n } | null = null;\n\n private readonly onMessage: WorkerTransportListener<\"message\">;\n private readonly onError: WorkerTransportListener<\"error\">;\n private readonly onExit: WorkerTransportListener<\"exit\">;\n\n constructor(options: WorkerHandleOptions) {\n this.workerId = options.workerId;\n this.index = options.index;\n this.transport = options.transport;\n this.initPayload = options.initPayload;\n this.logger = options.logger;\n this.abortGraceMs = options.abortGraceMs ?? DEFAULT_ABORT_GRACE_MS;\n this.defaultShutdownGraceMs =\n options.defaultShutdownGraceMs ?? DEFAULT_SHUTDOWN_GRACE_MS;\n this.poolInstrumentation = options.poolInstrumentation;\n\n this.onMessage = (msg) => this.handleMessage(msg);\n this.onError = (err) => this.handleTransportError(err);\n this.onExit = (code) => this.handleExit(code);\n\n this.transport.on(\"message\", this.onMessage);\n this.transport.on(\"error\", this.onError);\n this.transport.on(\"exit\", this.onExit);\n }\n\n public async start(): Promise<void> {\n if (this.phase !== \"fresh\") {\n throw new WorkerInitFailedError(\n this.workerId,\n `cannot start worker in phase ${this.phase}`,\n );\n }\n this.phase = \"starting\";\n const correlationId = this.nextCorrelationId();\n const ready = new Promise<void>((resolve, reject) => {\n this.pending.set(correlationId, {\n kind: \"init\",\n resolve,\n reject,\n });\n });\n this.transport.postMessage({\n type: \"init\",\n correlationId,\n workerId: this.workerId,\n poolConfig: this.initPayload.poolConfig,\n db: this.initPayload.db,\n models: this.initPayload.models,\n executorConfig: this.initPayload.executorConfig,\n signer: this.initPayload.signer,\n trustPolicy: this.initPayload.trustPolicy,\n });\n await ready;\n this.phase = \"ready\";\n }\n\n public execute(\n job: Job,\n signal?: AbortSignal,\n ): Promise<WorkerExecutionOutcome> {\n if (this.phase === \"terminated\") {\n return Promise.reject(\n this.lastExitError ??\n new WorkerExitedError(this.workerId, -1, this.lastCorrelationId),\n );\n }\n if (this.phase !== \"ready\") {\n return Promise.reject(\n new WorkerInitFailedError(\n this.workerId,\n `worker not ready (phase=${this.phase})`,\n ),\n );\n }\n if (this.inFlight !== null) {\n return Promise.reject(new WorkerBusyError(this.workerId, job.id));\n }\n\n const correlationId = this.nextCorrelationId();\n this.lastCorrelationId = correlationId;\n this.inFlight = { correlationId, jobId: job.id };\n\n let abortListener: (() => void) | null = null;\n const detachSignal = () => {\n if (signal && abortListener) {\n signal.removeEventListener(\"abort\", abortListener);\n abortListener = null;\n }\n };\n\n const promise = new Promise<WorkerExecutionOutcome>((resolve, reject) => {\n this.pending.set(correlationId, {\n kind: \"execute\",\n jobId: job.id,\n resolve: (outcome) => {\n detachSignal();\n resolve(outcome);\n },\n reject: (err) => {\n detachSignal();\n reject(err);\n },\n });\n });\n\n if (signal) {\n if (signal.aborted) {\n this.abort(correlationId, \"aborted before dispatch\");\n } else {\n abortListener = () => this.abort(correlationId, \"caller signal\");\n signal.addEventListener(\"abort\", abortListener, { once: true });\n }\n }\n\n this.transport.postMessage({\n type: \"execute\",\n correlationId,\n job,\n });\n\n return promise;\n }\n\n public abort(correlationId: string, reason?: string): void {\n if (this.phase === \"terminated\") {\n return;\n }\n if (!this.inFlight || this.inFlight.correlationId !== correlationId) {\n return;\n }\n if (this.abortTimer) {\n return;\n }\n this.transport.postMessage({\n type: \"abort\",\n correlationId: this.nextCorrelationId(),\n targetCorrelationId: correlationId,\n reason,\n });\n this.abortTimer = setTimeout(() => {\n this.abortTimer = null;\n const entry = this.pending.get(correlationId);\n if (!entry || entry.kind !== \"execute\") {\n return;\n }\n const err = new WorkerAbortTimeoutError(\n this.workerId,\n correlationId,\n this.abortGraceMs,\n );\n this.failPending(correlationId, err);\n this.inFlight = null;\n void this.forceTerminate(err);\n }, this.abortGraceMs);\n }\n\n public async shutdown(graceful: boolean, graceMs?: number): Promise<void> {\n if (this.phase === \"terminated\") {\n return;\n }\n if (!graceful) {\n this.phase = \"shutting-down\";\n await this.forceTerminate(null);\n return;\n }\n\n const effectiveGrace = graceMs ?? this.defaultShutdownGraceMs;\n this.phase = \"shutting-down\";\n const correlationId = this.nextCorrelationId();\n const drained = new Promise<void>((resolve, reject) => {\n this.pending.set(correlationId, {\n kind: \"shutdown\",\n resolve,\n reject,\n });\n });\n this.transport.postMessage({\n type: \"shutdown\",\n correlationId,\n graceMs: effectiveGrace,\n });\n\n let timer!: NodeJS.Timeout;\n const timeout = new Promise<void>((_, reject) => {\n timer = setTimeout(() => {\n reject(new WorkerShutdownTimeoutError(this.workerId, effectiveGrace));\n }, effectiveGrace);\n });\n\n try {\n await Promise.race([drained, timeout]);\n } catch (err) {\n this.logger.warn(\n `worker ${this.workerId} shutdown timed out after ${effectiveGrace}ms`,\n err,\n );\n } finally {\n clearTimeout(timer);\n }\n\n await this.forceTerminate(null);\n }\n\n public loadModel(\n entry: ModelManifestEntry,\n signal?: AbortSignal,\n ): Promise<void> {\n if (this.phase === \"terminated\") {\n return Promise.reject(\n this.lastExitError ??\n new WorkerExitedError(this.workerId, -1, this.lastCorrelationId),\n );\n }\n if (this.phase !== \"ready\") {\n return Promise.reject(\n new WorkerInitFailedError(\n this.workerId,\n `worker not ready (phase=${this.phase})`,\n ),\n );\n }\n\n const correlationId = this.nextCorrelationId();\n let abortListener: (() => void) | null = null;\n const detachSignal = () => {\n if (signal && abortListener) {\n signal.removeEventListener(\"abort\", abortListener);\n abortListener = null;\n }\n };\n\n const promise = new Promise<void>((resolve, reject) => {\n this.pending.set(correlationId, {\n kind: \"load-model\",\n documentType: entry.documentType,\n version: entry.version,\n resolve: () => {\n detachSignal();\n resolve();\n },\n reject: (err) => {\n detachSignal();\n reject(err);\n },\n });\n });\n\n if (signal) {\n if (signal.aborted) {\n this.failPending(\n correlationId,\n new WorkerLoadModelFailedError(\n this.workerId,\n entry.documentType,\n entry.version,\n \"aborted before dispatch\",\n ),\n );\n return promise;\n }\n abortListener = () => {\n this.failPending(\n correlationId,\n new WorkerLoadModelFailedError(\n this.workerId,\n entry.documentType,\n entry.version,\n \"caller signal\",\n ),\n );\n };\n signal.addEventListener(\"abort\", abortListener, { once: true });\n }\n\n this.transport.postMessage({\n type: \"load-model\",\n correlationId,\n model: entry,\n });\n\n return promise;\n }\n\n public evictPurged(documentIds: string[]): void {\n if (this.phase !== \"ready\" || documentIds.length === 0) {\n return;\n }\n this.transport.postMessage({ type: \"evict-purged\", documentIds });\n }\n\n public isIdle(): boolean {\n return this.inFlight === null && this.phase === \"ready\";\n }\n\n public getInFlight(): WorkerInFlightSnapshot | null {\n if (!this.inFlight) {\n return null;\n }\n return {\n correlationId: this.inFlight.correlationId,\n jobId: this.inFlight.jobId,\n };\n }\n\n private nextCorrelationId(): string {\n return randomUUID();\n }\n\n private handleMessage(msg: WorkerMessage): void {\n switch (msg.type) {\n case \"ready\":\n this.handleReady(msg.correlationId);\n return;\n case \"result\":\n this.handleResult(msg);\n return;\n case \"model-loaded\":\n this.handleModelLoaded(msg);\n return;\n case \"model-load-failed\":\n this.handleModelLoadFailed(msg);\n return;\n case \"log\":\n this.handleLog(msg);\n return;\n case \"heartbeat\":\n this.handleHeartbeat(msg);\n return;\n case \"metrics\":\n this.handleMetrics(msg);\n return;\n case \"pool-acquire-samples\":\n this.handlePoolAcquireSamples(msg);\n return;\n default: {\n const exhaustive: never = msg;\n void exhaustive;\n }\n }\n }\n\n private handlePoolAcquireSamples(msg: PoolAcquireSamplesMessage): void {\n if (!this.poolInstrumentation) {\n return;\n }\n this.poolInstrumentation.updateStats({\n size: msg.size,\n idle: msg.idle,\n waiting: msg.waiting,\n });\n this.poolInstrumentation.pushSamples(msg.durations);\n }\n\n private handleReady(correlationId: string): void {\n const entry = this.pending.get(correlationId);\n if (!entry || entry.kind !== \"init\") {\n return;\n }\n this.pending.delete(correlationId);\n entry.resolve();\n }\n\n private handleResult(msg: ResultMessage): void {\n const entry = this.pending.get(msg.correlationId);\n if (!entry || entry.kind !== \"execute\") {\n return;\n }\n this.pending.delete(msg.correlationId);\n if (this.inFlight?.correlationId === msg.correlationId) {\n this.inFlight = null;\n }\n if (this.abortTimer) {\n clearTimeout(this.abortTimer);\n this.abortTimer = null;\n }\n\n const outcome: WorkerExecutionOutcome = msg.error\n ? {\n result: {\n ...msg.result,\n success: false,\n error: fromErrorInfo(msg.error),\n },\n signatureRefusals: msg.signatureRefusals,\n }\n : {\n result: msg.result,\n writeReady: msg.writeReady,\n signatureRefusals: msg.signatureRefusals,\n };\n entry.resolve(outcome);\n\n if (this.phase === \"shutting-down\") {\n const shutdownEntry = [...this.pending.values()].find(\n (e) => e.kind === \"shutdown\",\n );\n if (shutdownEntry) {\n shutdownEntry.resolve();\n }\n }\n }\n\n private handleModelLoaded(msg: ModelLoadedMessage): void {\n const entry = this.pending.get(msg.correlationId);\n if (!entry || entry.kind !== \"load-model\") {\n return;\n }\n this.pending.delete(msg.correlationId);\n entry.resolve();\n }\n\n private handleModelLoadFailed(msg: ModelLoadFailedMessage): void {\n const entry = this.pending.get(msg.correlationId);\n if (!entry || entry.kind !== \"load-model\") {\n return;\n }\n this.pending.delete(msg.correlationId);\n entry.reject(\n new WorkerLoadModelFailedError(\n this.workerId,\n msg.documentType,\n msg.version,\n msg.error.message,\n { cause: fromErrorInfo(msg.error) },\n ),\n );\n }\n\n private handleLog(msg: LogMessage): void {\n const args = msg.args;\n switch (msg.level) {\n case \"debug\":\n this.logger.debug(msg.message, ...args);\n return;\n case \"info\":\n this.logger.info(msg.message, ...args);\n return;\n case \"warn\":\n this.logger.warn(msg.message, ...args);\n return;\n case \"error\":\n this.logger.error(msg.message, ...args);\n return;\n default: {\n const exhaustive: never = msg.level;\n void exhaustive;\n }\n }\n }\n\n private handleHeartbeat(msg: HeartbeatMessage): void {\n this.lastHeartbeatAt = msg.timestamp;\n }\n\n private handleMetrics(msg: MetricsMessage): void {\n this.lastMetrics = {\n counters: msg.counters,\n gauges: msg.gauges,\n timestamp: msg.timestamp,\n };\n }\n\n private handleTransportError(err: Error): void {\n this.logger.error(\n `worker ${this.workerId} transport error: @message`,\n err.message,\n err,\n );\n }\n\n private handleExit(code: number): void {\n if (this.phase === \"terminated\") {\n return;\n }\n const wasGraceful = this.phase === \"shutting-down\" && code === 0;\n this.phase = \"terminated\";\n const err = wasGraceful\n ? null\n : new WorkerExitedError(this.workerId, code, this.lastCorrelationId);\n this.lastExitError = err;\n if (this.abortTimer) {\n clearTimeout(this.abortTimer);\n this.abortTimer = null;\n }\n this.detachTransportListeners();\n this.drainPending(err);\n this.inFlight = null;\n }\n\n private detachTransportListeners(): void {\n this.transport.off(\"message\", this.onMessage);\n this.transport.off(\"error\", this.onError);\n this.transport.off(\"exit\", this.onExit);\n }\n\n private drainPending(err: Error | null): void {\n for (const [, entry] of this.pending) {\n if (err) {\n entry.reject(err);\n } else if (entry.kind === \"shutdown\") {\n entry.resolve();\n } else if (entry.kind === \"init\") {\n entry.reject(new WorkerInitFailedError(this.workerId, \"worker exited\"));\n } else if (entry.kind === \"load-model\") {\n entry.reject(\n new WorkerLoadModelFailedError(\n this.workerId,\n entry.documentType,\n entry.version,\n \"worker exited before responding\",\n ),\n );\n } else {\n entry.reject(\n new WorkerExitedError(this.workerId, 0, this.lastCorrelationId),\n );\n }\n }\n this.pending.clear();\n }\n\n private failPending(correlationId: string, err: Error): void {\n const entry = this.pending.get(correlationId);\n if (!entry) {\n return;\n }\n this.pending.delete(correlationId);\n entry.reject(err);\n }\n\n private async forceTerminate(reason: Error | null): Promise<void> {\n if (this.phase === \"terminated\") {\n return;\n }\n try {\n await this.transport.terminate();\n } catch (err) {\n this.logger.warn(\n `worker ${this.workerId} terminate threw: ${(err as Error).message}`,\n );\n }\n const phaseAfter = this.phase as Phase;\n if (phaseAfter === \"terminated\") {\n return;\n }\n this.phase = \"terminated\";\n this.lastExitError = reason;\n this.detachTransportListeners();\n this.drainPending(reason);\n this.inFlight = null;\n }\n}\n"],"mappings":"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"}
|