@powerhousedao/reactor 6.2.2-dev.2 → 6.2.2-dev.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { S as CollectionMembershipCache, _ as KyselyWriteCache, d as KyselyKeyframeStore, f as DocumentModelRegistry, g as EventBus, h as KyselyExecutionScope, n as REACTOR_SCHEMA, p as SimpleJobExecutor, s as KyselyOperationStore, t as DEFAULT_DRIVE_CONTAINER_TYPES, v as KyselyOperationIndex, y as DocumentMetaCache } from "./drive-container-types-DpJp2AmE.js";
1
+ import { c as KyselyKeyframeStore, f as KyselyExecutionScope, g as DocumentMetaCache, h as KyselyOperationIndex, l as DocumentModelRegistry, m as KyselyWriteCache, n as REACTOR_SCHEMA, p as EventBus, s as KyselyOperationStore, t as DEFAULT_DRIVE_CONTAINER_TYPES, u as SimpleJobExecutor, y as CollectionMembershipCache } from "./drive-container-types-DrQMmpCa.js";
2
2
  import { n as ReactorEventTypes } from "./types-DMKLa0Ok.js";
3
3
  //#region src/executor/worker/build-worker-executor.ts
4
4
  async function defaultLoadFactory(spec) {
@@ -39,7 +39,7 @@ async function buildWorkerExecutor(options) {
39
39
  const registry = new DocumentModelRegistry();
40
40
  await loadModelManifest(init.models, loadFactory, registry, logger);
41
41
  let signatureVerifier;
42
- try {
42
+ if (init.signatureVerifier) try {
43
43
  signatureVerifier = await loadFactory(init.signatureVerifier);
44
44
  } catch (error) {
45
45
  logger.error("worker failed to load signature verifier: @spec @error", init.signatureVerifier, error);
@@ -80,4 +80,4 @@ async function buildWorkerExecutor(options) {
80
80
  //#endregion
81
81
  export { defaultLoadFactory as n, buildWorkerExecutor as t };
82
82
 
83
- //# sourceMappingURL=build-worker-executor-BDrPlCJl.js.map
83
+ //# sourceMappingURL=build-worker-executor-CE-Yyezb.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"build-worker-executor-BDrPlCJl.js","names":[],"sources":["../src/executor/worker/build-worker-executor.ts"],"sourcesContent":["import type {\n DocumentModelModule,\n OperationWithContext,\n} from \"@powerhousedao/shared/document-model\";\nimport type { ILogger } from \"document-model\";\nimport type { Kysely } from \"kysely\";\nimport { CollectionMembershipCache } from \"../../cache/collection-membership-cache.js\";\nimport { DocumentMetaCache } from \"../../cache/document-meta-cache.js\";\nimport { KyselyOperationIndex } from \"../../cache/kysely-operation-index.js\";\nimport { KyselyWriteCache } from \"../../cache/kysely-write-cache.js\";\nimport type { WriteCacheConfig } from \"../../cache/write-cache-types.js\";\nimport { DEFAULT_DRIVE_CONTAINER_TYPES } from \"../../core/drive-container-types.js\";\nimport type { Database } from \"../../core/types.js\";\nimport { EventBus } from \"../../events/event-bus.js\";\nimport {\n ReactorEventTypes,\n type JobWriteReadyEvent,\n} from \"../../events/types.js\";\nimport { DocumentModelRegistry } from \"../../registry/implementation.js\";\nimport type { JobMeta } from \"../../shared/types.js\";\nimport type { SignatureVerificationHandler } from \"../../signer/types.js\";\nimport { KyselyKeyframeStore } from \"../../storage/kysely/keyframe-store.js\";\nimport { KyselyOperationStore } from \"../../storage/kysely/store.js\";\nimport type { Database as StorageDatabase } from \"../../storage/kysely/types.js\";\nimport { REACTOR_SCHEMA } from \"../../storage/migrations/migrator.js\";\nimport { KyselyExecutionScope } from \"../execution-scope.js\";\nimport { SimpleJobExecutor } from \"../simple-job-executor.js\";\nimport type { JobExecutorConfig } from \"../types.js\";\nimport type {\n FactorySpec,\n InitMessage,\n ModelManifestEntry,\n} from \"./protocol.js\";\n\n/**\n * In-worker capture of the JOB_WRITE_READY event emitted by the executor.\n * The worker forwards `operations` and `jobMeta` back to the parent; the\n * parent re-enriches `collectionMemberships` at emit time.\n */\nexport type WorkerWriteReadyCapture = {\n operations: OperationWithContext[];\n jobMeta: JobMeta;\n};\n\nexport type WorkerExecutorStack = {\n executor: SimpleJobExecutor;\n registry: DocumentModelRegistry;\n /**\n * Synchronously pops the most-recent JOB_WRITE_READY captured on this\n * worker's local event bus and clears it. Returns null if the executor\n * did not produce one for this job.\n */\n takeLastWriteReady(): WorkerWriteReadyCapture | null;\n};\n\nexport type BuildWorkerExecutorOptions = {\n init: InitMessage;\n database: Kysely<Database>;\n logger: ILogger;\n executorConfig?: JobExecutorConfig;\n driveContainerTypes?: ReadonlySet<string>;\n /**\n * Override the module loader used to materialize factory specs. Tests\n * can inject a deterministic resolver instead of touching the real\n * Node module loader.\n */\n loadFactory?: (spec: FactorySpec) => Promise<unknown>;\n};\n\nexport async function defaultLoadFactory(spec: FactorySpec): Promise<unknown> {\n const ref = spec.module;\n const specifier =\n \"filePath\" in ref\n ? new URL(`file://${ref.filePath}`).href\n : ref.packageName;\n const mod = (await import(specifier)) as Record<string, unknown>;\n const exported = mod[ref.exportName];\n if (typeof exported === \"function\") {\n return (exported as (args: unknown) => unknown)(spec.initArgs);\n }\n return exported;\n}\n\nasync function loadModelManifest(\n entries: ModelManifestEntry[],\n loadFactory: (spec: FactorySpec) => Promise<unknown>,\n registry: DocumentModelRegistry,\n logger: ILogger,\n): Promise<void> {\n for (const entry of entries) {\n let module: DocumentModelModule;\n try {\n module = (await loadFactory(entry.spec)) as DocumentModelModule;\n } catch (error) {\n logger.error(\n \"worker failed to load document model: @entry @error\",\n entry,\n error,\n );\n throw error;\n }\n const [result] = registry.registerModules(module);\n if (result.status === \"error\") {\n logger.error(\n \"worker failed to register document model: @entry @error\",\n entry,\n result.error,\n );\n throw result.error;\n }\n }\n}\n\n/**\n * Assembles the in-worker storage stack plus a {@link SimpleJobExecutor}\n * bound to a pre-built Kysely instance. The parent owns the wire protocol\n * and routing; the worker owns everything below `SimpleJobExecutor`.\n *\n * The local event bus exists only to satisfy the executor's contract: its\n * JOB_WRITE_READY emissions are captured here and shipped to the parent\n * via {@link WorkerExecutorStack.takeLastWriteReady}.\n */\nexport async function buildWorkerExecutor(\n options: BuildWorkerExecutorOptions,\n): Promise<WorkerExecutorStack> {\n const { init, database: baseDatabase, logger } = options;\n const driveContainerTypes =\n options.driveContainerTypes ?? DEFAULT_DRIVE_CONTAINER_TYPES;\n const loadFactory = options.loadFactory ?? defaultLoadFactory;\n\n const registry = new DocumentModelRegistry();\n await loadModelManifest(init.models, loadFactory, registry, logger);\n\n let signatureVerifier: SignatureVerificationHandler | undefined;\n try {\n signatureVerifier = (await loadFactory(\n init.signatureVerifier,\n )) as SignatureVerificationHandler;\n } catch (error) {\n logger.error(\n \"worker failed to load signature verifier: @spec @error\",\n init.signatureVerifier,\n error,\n );\n throw error;\n }\n\n const database = baseDatabase.withSchema(REACTOR_SCHEMA);\n const operationStore = new KyselyOperationStore(\n database as unknown as Kysely<StorageDatabase>,\n );\n const keyframeStore = new KyselyKeyframeStore(\n database as unknown as Kysely<StorageDatabase>,\n );\n\n const cacheConfig: WriteCacheConfig = {\n maxDocuments: 100,\n ringBufferSize: 10,\n keyframeInterval: 10,\n };\n const writeCache = new KyselyWriteCache(\n keyframeStore,\n operationStore,\n registry,\n cacheConfig,\n );\n await writeCache.startup();\n\n const operationIndex = new KyselyOperationIndex(\n database as unknown as Kysely<StorageDatabase>,\n );\n\n const documentMetaCache = new DocumentMetaCache(operationStore, {\n maxDocuments: 1000,\n });\n await documentMetaCache.startup();\n\n const collectionMembershipCache = new CollectionMembershipCache(\n operationIndex,\n );\n\n const executionScope = new KyselyExecutionScope(\n database as unknown as Kysely<StorageDatabase>,\n operationStore,\n operationIndex,\n keyframeStore,\n writeCache,\n documentMetaCache,\n collectionMembershipCache,\n );\n\n const eventBus = new EventBus();\n let lastWriteReady: WorkerWriteReadyCapture | null = null;\n eventBus.subscribe(\n ReactorEventTypes.JOB_WRITE_READY,\n (_t: number, event: JobWriteReadyEvent) => {\n lastWriteReady = {\n operations: event.operations,\n jobMeta: event.jobMeta,\n };\n },\n );\n\n const executorConfig = options.executorConfig ?? {};\n const executor = new SimpleJobExecutor(\n logger,\n registry,\n operationStore,\n eventBus,\n writeCache,\n operationIndex,\n documentMetaCache,\n collectionMembershipCache,\n driveContainerTypes,\n executorConfig,\n signatureVerifier,\n executionScope,\n );\n\n return {\n executor,\n registry,\n takeLastWriteReady(): WorkerWriteReadyCapture | null {\n const captured = lastWriteReady;\n lastWriteReady = null;\n return captured;\n },\n };\n}\n"],"mappings":";;;AAqEA,eAAsB,mBAAmB,MAAqC;CAC5E,MAAM,MAAM,KAAK;CAMjB,MAAM,YADO,OAHX,cAAc,MAAA,OACV,IAAI,IAAI,UAAU,IAAI,WAAW,CAAC,QAAA,OAClC,IAAI,eAEW,IAAI;AACzB,KAAI,OAAO,aAAa,WACtB,QAAQ,SAAwC,KAAK,SAAS;AAEhE,QAAO;;AAGT,eAAe,kBACb,SACA,aACA,UACA,QACe;AACf,MAAK,MAAM,SAAS,SAAS;EAC3B,IAAI;AACJ,MAAI;AACF,YAAU,MAAM,YAAY,MAAM,KAAK;WAChC,OAAO;AACd,UAAO,MACL,uDACA,OACA,MACD;AACD,SAAM;;EAER,MAAM,CAAC,UAAU,SAAS,gBAAgB,OAAO;AACjD,MAAI,OAAO,WAAW,SAAS;AAC7B,UAAO,MACL,2DACA,OACA,OAAO,MACR;AACD,SAAM,OAAO;;;;;;;;;;;;;AAcnB,eAAsB,oBACpB,SAC8B;CAC9B,MAAM,EAAE,MAAM,UAAU,cAAc,WAAW;CACjD,MAAM,sBACJ,QAAQ,uBAAuB;CACjC,MAAM,cAAc,QAAQ,eAAe;CAE3C,MAAM,WAAW,IAAI,uBAAuB;AAC5C,OAAM,kBAAkB,KAAK,QAAQ,aAAa,UAAU,OAAO;CAEnE,IAAI;AACJ,KAAI;AACF,sBAAqB,MAAM,YACzB,KAAK,kBACN;UACM,OAAO;AACd,SAAO,MACL,0DACA,KAAK,mBACL,MACD;AACD,QAAM;;CAGR,MAAM,WAAW,aAAa,WAAW,eAAe;CACxD,MAAM,iBAAiB,IAAI,qBACzB,SACD;CACD,MAAM,gBAAgB,IAAI,oBACxB,SACD;CAOD,MAAM,aAAa,IAAI,iBACrB,eACA,gBACA,UARoC;EACpC,cAAc;EACd,gBAAgB;EAChB,kBAAkB;EACnB,CAMA;AACD,OAAM,WAAW,SAAS;CAE1B,MAAM,iBAAiB,IAAI,qBACzB,SACD;CAED,MAAM,oBAAoB,IAAI,kBAAkB,gBAAgB,EAC9D,cAAc,KACf,CAAC;AACF,OAAM,kBAAkB,SAAS;CAEjC,MAAM,4BAA4B,IAAI,0BACpC,eACD;CAED,MAAM,iBAAiB,IAAI,qBACzB,UACA,gBACA,gBACA,eACA,YACA,mBACA,0BACD;CAED,MAAM,WAAW,IAAI,UAAU;CAC/B,IAAI,iBAAiD;AACrD,UAAS,UACP,kBAAkB,kBACjB,IAAY,UAA8B;AACzC,mBAAiB;GACf,YAAY,MAAM;GAClB,SAAS,MAAM;GAChB;GAEJ;AAkBD,QAAO;EACL,UAhBe,IAAI,kBACnB,QACA,UACA,gBACA,UACA,YACA,gBACA,mBACA,2BACA,qBAVqB,QAAQ,kBAAkB,EAAE,EAYjD,mBACA,eACD;EAIC;EACA,qBAAqD;GACnD,MAAM,WAAW;AACjB,oBAAiB;AACjB,UAAO;;EAEV"}
1
+ {"version":3,"file":"build-worker-executor-CE-Yyezb.js","names":[],"sources":["../src/executor/worker/build-worker-executor.ts"],"sourcesContent":["import type {\n DocumentModelModule,\n OperationWithContext,\n} from \"@powerhousedao/shared/document-model\";\nimport type { ILogger } from \"document-model\";\nimport type { Kysely } from \"kysely\";\nimport { CollectionMembershipCache } from \"../../cache/collection-membership-cache.js\";\nimport { DocumentMetaCache } from \"../../cache/document-meta-cache.js\";\nimport { KyselyOperationIndex } from \"../../cache/kysely-operation-index.js\";\nimport { KyselyWriteCache } from \"../../cache/kysely-write-cache.js\";\nimport type { WriteCacheConfig } from \"../../cache/write-cache-types.js\";\nimport { DEFAULT_DRIVE_CONTAINER_TYPES } from \"../../core/drive-container-types.js\";\nimport type { Database } from \"../../core/types.js\";\nimport { EventBus } from \"../../events/event-bus.js\";\nimport {\n ReactorEventTypes,\n type JobWriteReadyEvent,\n} from \"../../events/types.js\";\nimport { DocumentModelRegistry } from \"../../registry/implementation.js\";\nimport type { JobMeta } from \"../../shared/types.js\";\nimport type { SignatureVerificationHandler } from \"../../signer/types.js\";\nimport { KyselyKeyframeStore } from \"../../storage/kysely/keyframe-store.js\";\nimport { KyselyOperationStore } from \"../../storage/kysely/store.js\";\nimport type { Database as StorageDatabase } from \"../../storage/kysely/types.js\";\nimport { REACTOR_SCHEMA } from \"../../storage/migrations/migrator.js\";\nimport { KyselyExecutionScope } from \"../execution-scope.js\";\nimport { SimpleJobExecutor } from \"../simple-job-executor.js\";\nimport type { JobExecutorConfig } from \"../types.js\";\nimport type {\n FactorySpec,\n InitMessage,\n ModelManifestEntry,\n} from \"./protocol.js\";\n\n/**\n * In-worker capture of the JOB_WRITE_READY event emitted by the executor.\n * The worker forwards `operations` and `jobMeta` back to the parent; the\n * parent re-enriches `collectionMemberships` at emit time.\n */\nexport type WorkerWriteReadyCapture = {\n operations: OperationWithContext[];\n jobMeta: JobMeta;\n};\n\nexport type WorkerExecutorStack = {\n executor: SimpleJobExecutor;\n registry: DocumentModelRegistry;\n /**\n * Synchronously pops the most-recent JOB_WRITE_READY captured on this\n * worker's local event bus and clears it. Returns null if the executor\n * did not produce one for this job.\n */\n takeLastWriteReady(): WorkerWriteReadyCapture | null;\n};\n\nexport type BuildWorkerExecutorOptions = {\n init: InitMessage;\n database: Kysely<Database>;\n logger: ILogger;\n executorConfig?: JobExecutorConfig;\n driveContainerTypes?: ReadonlySet<string>;\n /**\n * Override the module loader used to materialize factory specs. Tests\n * can inject a deterministic resolver instead of touching the real\n * Node module loader.\n */\n loadFactory?: (spec: FactorySpec) => Promise<unknown>;\n};\n\nexport async function defaultLoadFactory(spec: FactorySpec): Promise<unknown> {\n const ref = spec.module;\n const specifier =\n \"filePath\" in ref\n ? new URL(`file://${ref.filePath}`).href\n : ref.packageName;\n const mod = (await import(specifier)) as Record<string, unknown>;\n const exported = mod[ref.exportName];\n if (typeof exported === \"function\") {\n return (exported as (args: unknown) => unknown)(spec.initArgs);\n }\n return exported;\n}\n\nasync function loadModelManifest(\n entries: ModelManifestEntry[],\n loadFactory: (spec: FactorySpec) => Promise<unknown>,\n registry: DocumentModelRegistry,\n logger: ILogger,\n): Promise<void> {\n for (const entry of entries) {\n let module: DocumentModelModule;\n try {\n module = (await loadFactory(entry.spec)) as DocumentModelModule;\n } catch (error) {\n logger.error(\n \"worker failed to load document model: @entry @error\",\n entry,\n error,\n );\n throw error;\n }\n const [result] = registry.registerModules(module);\n if (result.status === \"error\") {\n logger.error(\n \"worker failed to register document model: @entry @error\",\n entry,\n result.error,\n );\n throw result.error;\n }\n }\n}\n\n/**\n * Assembles the in-worker storage stack plus a {@link SimpleJobExecutor}\n * bound to a pre-built Kysely instance. The parent owns the wire protocol\n * and routing; the worker owns everything below `SimpleJobExecutor`.\n *\n * The local event bus exists only to satisfy the executor's contract: its\n * JOB_WRITE_READY emissions are captured here and shipped to the parent\n * via {@link WorkerExecutorStack.takeLastWriteReady}.\n */\nexport async function buildWorkerExecutor(\n options: BuildWorkerExecutorOptions,\n): Promise<WorkerExecutorStack> {\n const { init, database: baseDatabase, logger } = options;\n const driveContainerTypes =\n options.driveContainerTypes ?? DEFAULT_DRIVE_CONTAINER_TYPES;\n const loadFactory = options.loadFactory ?? defaultLoadFactory;\n\n const registry = new DocumentModelRegistry();\n await loadModelManifest(init.models, loadFactory, registry, logger);\n\n let signatureVerifier: SignatureVerificationHandler | undefined;\n if (init.signatureVerifier) {\n try {\n signatureVerifier = (await loadFactory(\n init.signatureVerifier,\n )) as SignatureVerificationHandler;\n } catch (error) {\n logger.error(\n \"worker failed to load signature verifier: @spec @error\",\n init.signatureVerifier,\n error,\n );\n throw error;\n }\n }\n\n const database = baseDatabase.withSchema(REACTOR_SCHEMA);\n const operationStore = new KyselyOperationStore(\n database as unknown as Kysely<StorageDatabase>,\n );\n const keyframeStore = new KyselyKeyframeStore(\n database as unknown as Kysely<StorageDatabase>,\n );\n\n const cacheConfig: WriteCacheConfig = {\n maxDocuments: 100,\n ringBufferSize: 10,\n keyframeInterval: 10,\n };\n const writeCache = new KyselyWriteCache(\n keyframeStore,\n operationStore,\n registry,\n cacheConfig,\n );\n await writeCache.startup();\n\n const operationIndex = new KyselyOperationIndex(\n database as unknown as Kysely<StorageDatabase>,\n );\n\n const documentMetaCache = new DocumentMetaCache(operationStore, {\n maxDocuments: 1000,\n });\n await documentMetaCache.startup();\n\n const collectionMembershipCache = new CollectionMembershipCache(\n operationIndex,\n );\n\n const executionScope = new KyselyExecutionScope(\n database as unknown as Kysely<StorageDatabase>,\n operationStore,\n operationIndex,\n keyframeStore,\n writeCache,\n documentMetaCache,\n collectionMembershipCache,\n );\n\n const eventBus = new EventBus();\n let lastWriteReady: WorkerWriteReadyCapture | null = null;\n eventBus.subscribe(\n ReactorEventTypes.JOB_WRITE_READY,\n (_t: number, event: JobWriteReadyEvent) => {\n lastWriteReady = {\n operations: event.operations,\n jobMeta: event.jobMeta,\n };\n },\n );\n\n const executorConfig = options.executorConfig ?? {};\n const executor = new SimpleJobExecutor(\n logger,\n registry,\n operationStore,\n eventBus,\n writeCache,\n operationIndex,\n documentMetaCache,\n collectionMembershipCache,\n driveContainerTypes,\n executorConfig,\n signatureVerifier,\n executionScope,\n );\n\n return {\n executor,\n registry,\n takeLastWriteReady(): WorkerWriteReadyCapture | null {\n const captured = lastWriteReady;\n lastWriteReady = null;\n return captured;\n },\n };\n}\n"],"mappings":";;;AAqEA,eAAsB,mBAAmB,MAAqC;CAC5E,MAAM,MAAM,KAAK;CAMjB,MAAM,YADO,OAHX,cAAc,MAAA,OACV,IAAI,IAAI,UAAU,IAAI,WAAW,CAAC,QAAA,OAClC,IAAI,eAEW,IAAI;AACzB,KAAI,OAAO,aAAa,WACtB,QAAQ,SAAwC,KAAK,SAAS;AAEhE,QAAO;;AAGT,eAAe,kBACb,SACA,aACA,UACA,QACe;AACf,MAAK,MAAM,SAAS,SAAS;EAC3B,IAAI;AACJ,MAAI;AACF,YAAU,MAAM,YAAY,MAAM,KAAK;WAChC,OAAO;AACd,UAAO,MACL,uDACA,OACA,MACD;AACD,SAAM;;EAER,MAAM,CAAC,UAAU,SAAS,gBAAgB,OAAO;AACjD,MAAI,OAAO,WAAW,SAAS;AAC7B,UAAO,MACL,2DACA,OACA,OAAO,MACR;AACD,SAAM,OAAO;;;;;;;;;;;;;AAcnB,eAAsB,oBACpB,SAC8B;CAC9B,MAAM,EAAE,MAAM,UAAU,cAAc,WAAW;CACjD,MAAM,sBACJ,QAAQ,uBAAuB;CACjC,MAAM,cAAc,QAAQ,eAAe;CAE3C,MAAM,WAAW,IAAI,uBAAuB;AAC5C,OAAM,kBAAkB,KAAK,QAAQ,aAAa,UAAU,OAAO;CAEnE,IAAI;AACJ,KAAI,KAAK,kBACP,KAAI;AACF,sBAAqB,MAAM,YACzB,KAAK,kBACN;UACM,OAAO;AACd,SAAO,MACL,0DACA,KAAK,mBACL,MACD;AACD,QAAM;;CAIV,MAAM,WAAW,aAAa,WAAW,eAAe;CACxD,MAAM,iBAAiB,IAAI,qBACzB,SACD;CACD,MAAM,gBAAgB,IAAI,oBACxB,SACD;CAOD,MAAM,aAAa,IAAI,iBACrB,eACA,gBACA,UARoC;EACpC,cAAc;EACd,gBAAgB;EAChB,kBAAkB;EACnB,CAMA;AACD,OAAM,WAAW,SAAS;CAE1B,MAAM,iBAAiB,IAAI,qBACzB,SACD;CAED,MAAM,oBAAoB,IAAI,kBAAkB,gBAAgB,EAC9D,cAAc,KACf,CAAC;AACF,OAAM,kBAAkB,SAAS;CAEjC,MAAM,4BAA4B,IAAI,0BACpC,eACD;CAED,MAAM,iBAAiB,IAAI,qBACzB,UACA,gBACA,gBACA,eACA,YACA,mBACA,0BACD;CAED,MAAM,WAAW,IAAI,UAAU;CAC/B,IAAI,iBAAiD;AACrD,UAAS,UACP,kBAAkB,kBACjB,IAAY,UAA8B;AACzC,mBAAiB;GACf,YAAY,MAAM;GAClB,SAAS,MAAM;GAChB;GAEJ;AAkBD,QAAO;EACL,UAhBe,IAAI,kBACnB,QACA,UACA,gBACA,UACA,YACA,gBACA,mBACA,2BACA,qBAVqB,QAAQ,kBAAkB,EAAE,EAYjD,mBACA,eACD;EAIC;EACA,qBAAqD;GACnD,MAAM,WAAW;AACjB,oBAAiB;AACjB,UAAO;;EAEV"}
@@ -1,4 +1,5 @@
1
1
  import { n as ReactorEventTypes } from "./types-DMKLa0Ok.js";
2
+ import { createAuthState } from "@powerhousedao/shared/document-model";
2
3
  import { v4 } from "uuid";
3
4
  import { childLogger } from "document-model";
4
5
  //#region src/read-models/base-read-model.ts
@@ -168,6 +169,12 @@ var ReadModelCoordinator = class {
168
169
  getChainDepth() {
169
170
  return this.chains.size;
170
171
  }
172
+ addReadModel(readModel, stage) {
173
+ if (this.readModels.some(({ name }) => name === readModel.name)) throw new Error(`Read model "${readModel.name}" is already registered`);
174
+ if (stage === "pre_ready") this.preReady.push(readModel);
175
+ else this.postReady.push(readModel);
176
+ this.readModels.push(readModel);
177
+ }
171
178
  handleWriteReady(event) {
172
179
  if (event.operations.length === 0) {
173
180
  this.emitEmptyReadReady(event);
@@ -414,7 +421,7 @@ var KyselyDocumentView = class extends BaseReadModel {
414
421
  const state = {};
415
422
  for (const snapshot of snapshots) {
416
423
  if (snapshot.scope === "header") continue;
417
- state[snapshot.scope] = snapshot.content;
424
+ state[snapshot.scope] = snapshot.scope === "auth" ? createAuthState(snapshot.content) : snapshot.content;
418
425
  }
419
426
  return {
420
427
  header,
@@ -466,7 +473,7 @@ var KyselyDocumentView = class extends BaseReadModel {
466
473
  const state = {};
467
474
  for (const snapshot of snapshots) {
468
475
  if (snapshot.scope === "header") continue;
469
- state[snapshot.scope] = snapshot.content;
476
+ state[snapshot.scope] = snapshot.scope === "auth" ? createAuthState(snapshot.content) : snapshot.content;
470
477
  }
471
478
  const document = {
472
479
  header,
@@ -914,4 +921,4 @@ var KyselyDocumentIndexer = class extends BaseReadModel {
914
921
  //#endregion
915
922
  export { ReadModelCoordinator as a, KyselyDocumentView as i, ConsistencyTracker as n, BaseReadModel as o, makeConsistencyKey as r, KyselyDocumentIndexer as t };
916
923
 
917
- //# sourceMappingURL=document-indexer-FGJmRAdX.js.map
924
+ //# sourceMappingURL=document-indexer-BY-mPXJF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document-indexer-BY-mPXJF.js","names":["uuidv4","uuidv4"],"sources":["../src/read-models/base-read-model.ts","../src/read-models/coordinator.ts","../src/read-models/document-view.ts","../src/shared/consistency-tracker.ts","../src/shared/collect-all-pages.ts","../src/storage/kysely/document-indexer.ts"],"sourcesContent":["import type { OperationWithContext } from \"@powerhousedao/shared/document-model\";\nimport type { Kysely, Transaction } from \"kysely\";\nimport type { IOperationIndex } from \"../cache/operation-index-types.js\";\nimport type { IWriteCache } from \"../cache/write/interfaces.js\";\nimport type { IConsistencyTracker } from \"../shared/consistency-tracker.js\";\nimport type {\n ConsistencyCoordinate,\n ConsistencyToken,\n} from \"../shared/types.js\";\nimport type { IReadModel } from \"./interfaces.js\";\nimport type { DocumentViewDatabase } from \"./types.js\";\n\nexport type BaseReadModelConfig = {\n readModelId: string;\n rebuildStateOnInit: boolean;\n};\n\n/**\n * Base class for read models that provides catch-up/rewind functionality.\n * Handles initialization, state tracking via ViewState table, and consistency tracking.\n * Subclasses override commitOperations() with their specific domain logic.\n */\nexport class BaseReadModel implements IReadModel {\n protected lastOrdinal: number = 0;\n\n readonly name: string;\n\n constructor(\n protected db: Kysely<DocumentViewDatabase>,\n protected operationIndex: IOperationIndex,\n protected writeCache: IWriteCache,\n protected consistencyTracker: IConsistencyTracker,\n protected config: BaseReadModelConfig,\n ) {\n this.name = config.readModelId;\n }\n\n /**\n * Initializes the read model by loading state and catching up on missed operations.\n */\n async init(): Promise<void> {\n const viewState = await this.loadState();\n\n if (viewState !== undefined) {\n this.lastOrdinal = viewState;\n } else {\n await this.initializeState();\n }\n\n let page = await this.operationIndex.getSinceOrdinal(this.lastOrdinal);\n while (page.results.length > 0) {\n const ops = this.config.rebuildStateOnInit\n ? await this.rebuildStateForOperations(page.results)\n : page.results;\n await this.indexOperations(ops);\n\n if (!page.next) break;\n page = await page.next();\n }\n }\n\n /**\n * Template method: runs domain-specific commitOperations, then persists\n * state and updates consistency tracking.\n */\n async indexOperations(items: OperationWithContext[]): Promise<void> {\n if (items.length === 0) return;\n\n await this.commitOperations(items);\n\n await this.db.transaction().execute(async (trx) => {\n await this.saveState(trx, items);\n });\n\n this.updateConsistencyTracker(items);\n }\n\n /**\n * Waits for the read model to reach the specified consistency level.\n */\n async waitForConsistency(\n token: ConsistencyToken,\n timeoutMs?: number,\n signal?: AbortSignal,\n ): Promise<void> {\n if (token.coordinates.length === 0) {\n return;\n }\n await this.consistencyTracker.waitFor(token.coordinates, timeoutMs, signal);\n }\n\n // Subclass does domain-specific work here (snapshots, relationships, processor routing, etc.).\n protected async commitOperations(\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n items: OperationWithContext[],\n ): Promise<void> {}\n\n /**\n * Rebuilds document state for each operation using the write cache.\n */\n protected async rebuildStateForOperations(\n operations: OperationWithContext[],\n ): Promise<OperationWithContext[]> {\n const result: OperationWithContext[] = [];\n\n for (const op of operations) {\n const { documentId, scope, branch } = op.context;\n const targetRevision = op.operation.index;\n\n const document = await this.writeCache.getState(\n documentId,\n scope,\n branch,\n targetRevision,\n );\n\n result.push({\n operation: op.operation,\n context: {\n ...op.context,\n resultingState: JSON.stringify(document),\n },\n });\n }\n\n return result;\n }\n\n /**\n * Loads the last processed ordinal from the ViewState table.\n * Returns undefined if no state exists for this read model.\n */\n protected async loadState(): Promise<number | undefined> {\n const viewStateDb = this.db as unknown as Kysely<DocumentViewDatabase>;\n const row = await viewStateDb\n .selectFrom(\"ViewState\")\n .select(\"lastOrdinal\")\n .where(\"readModelId\", \"=\", this.config.readModelId)\n .executeTakeFirst();\n\n return row?.lastOrdinal;\n }\n\n /**\n * Initializes the ViewState row for this read model.\n */\n protected async initializeState(): Promise<void> {\n const viewStateDb = this.db as unknown as Kysely<DocumentViewDatabase>;\n await viewStateDb\n .insertInto(\"ViewState\")\n .values({\n readModelId: this.config.readModelId,\n lastOrdinal: 0,\n })\n .execute();\n }\n\n /**\n * Saves the last processed ordinal to the ViewState table.\n */\n protected async saveState(\n trx: Transaction<DocumentViewDatabase>,\n items: OperationWithContext[],\n ): Promise<void> {\n let maxOrdinal = 0;\n for (const item of items) {\n maxOrdinal = Math.max(maxOrdinal, item.context.ordinal);\n }\n this.lastOrdinal = maxOrdinal;\n\n await trx\n .updateTable(\"ViewState\")\n .set({\n lastOrdinal: maxOrdinal,\n lastOperationTimestamp: new Date(),\n })\n .where(\"readModelId\", \"=\", this.config.readModelId)\n .execute();\n }\n\n /**\n * Updates the consistency tracker with the processed operations.\n */\n protected updateConsistencyTracker(items: OperationWithContext[]): void {\n const coordinates: ConsistencyCoordinate[] = [];\n\n for (let i = 0; i < items.length; i++) {\n const item = items[i]!;\n coordinates.push({\n documentId: item.context.documentId,\n scope: item.context.scope,\n branch: item.context.branch,\n operationIndex: item.operation.index,\n });\n }\n\n this.consistencyTracker.update(coordinates);\n }\n}\n","import { childLogger, type ILogger } from \"document-model\";\nimport type { IEventBus } from \"../events/interfaces.js\";\nimport {\n ReactorEventTypes,\n type JobReadReadyEvent,\n type JobWriteReadyEvent,\n type ReadModelBatchCompletedEvent,\n type ReadModelIndexedEvent,\n type ReadModelIndexingStage,\n type Unsubscribe,\n} from \"../events/types.js\";\nimport type {\n ILiveReadModelCoordinator,\n IReadModel,\n ReadModelRegistrationStage,\n} from \"./interfaces.js\";\n\n/**\n * Coordinates read model synchronization by listening to operation write events\n * and updating all registered read models on per-`documentId:scope:branch`\n * serial chains. Cross-key projection runs in parallel; same-key projection is\n * serialized so the executor can return to dispatch without holding ordering\n * implicitly.\n */\nexport class ReadModelCoordinator implements ILiveReadModelCoordinator {\n private unsubscribe?: Unsubscribe;\n private isRunning = false;\n private readonly chains = new Map<string, Promise<void>>();\n private readonly logger: ILogger;\n\n readonly readModels: IReadModel[];\n\n constructor(\n private eventBus: IEventBus,\n public readonly preReady: IReadModel[],\n public readonly postReady: IReadModel[],\n ) {\n this.readModels = [...preReady, ...postReady];\n this.logger = childLogger([\"reactor\", \"read-model-coordinator\"]);\n }\n\n start(): void {\n if (this.isRunning) {\n return;\n }\n\n this.unsubscribe = this.eventBus.subscribe(\n ReactorEventTypes.JOB_WRITE_READY,\n (type, event: JobWriteReadyEvent) => {\n return this.handleWriteReady(event);\n },\n );\n\n this.isRunning = true;\n }\n\n stop(): void {\n if (!this.isRunning) {\n return;\n }\n\n if (this.unsubscribe) {\n this.unsubscribe();\n this.unsubscribe = undefined;\n }\n\n this.isRunning = false;\n }\n\n /**\n * Resolves when every per-queueKey projection chain has flushed. Intended\n * for test fixtures and explicit shutdown; production callers use\n * consistency tokens instead.\n */\n async drain(): Promise<void> {\n while (this.chains.size > 0) {\n const pending = Array.from(this.chains.values());\n await Promise.allSettled(pending);\n }\n }\n\n getChainDepth(): number {\n return this.chains.size;\n }\n\n addReadModel(readModel: IReadModel, stage: ReadModelRegistrationStage): void {\n if (this.readModels.some(({ name }) => name === readModel.name)) {\n throw new Error(`Read model \"${readModel.name}\" is already registered`);\n }\n\n if (stage === \"pre_ready\") {\n this.preReady.push(readModel);\n } else {\n this.postReady.push(readModel);\n }\n this.readModels.push(readModel);\n }\n\n private handleWriteReady(event: JobWriteReadyEvent): void {\n if (event.operations.length === 0) {\n void this.emitEmptyReadReady(event);\n return;\n }\n\n const enqueuedAt = performance.now();\n const key = this.queueKeyFor(event);\n const previous = this.chains.get(key) ?? Promise.resolve();\n const current = previous.then(() => this.runChain(event, enqueuedAt));\n\n this.chains.set(key, current);\n void current.finally(() => {\n if (this.chains.get(key) === current) {\n this.chains.delete(key);\n }\n });\n }\n\n private async emitEmptyReadReady(event: JobWriteReadyEvent): Promise<void> {\n const readyEvent: JobReadReadyEvent = {\n jobId: event.jobId,\n operations: event.operations,\n };\n try {\n await this.eventBus.emit(ReactorEventTypes.JOB_READ_READY, readyEvent);\n } catch (error) {\n this.logger.error(\n \"JOB_READ_READY emit failed for job @jobId: @Error\",\n { jobId: event.jobId },\n error,\n );\n }\n this.emitBatchCompleted({\n jobId: event.jobId,\n batchSize: 0,\n chainWaitDurationMs: 0,\n preReadyDurationMs: 0,\n emitDurationMs: 0,\n postReadyDurationMs: 0,\n });\n }\n\n private async runChain(\n event: JobWriteReadyEvent,\n enqueuedAt: number,\n ): Promise<void> {\n const chainStartedAt = performance.now();\n const chainWaitDurationMs = chainStartedAt - enqueuedAt;\n\n const preReadyStart = performance.now();\n try {\n await Promise.all(\n this.preReady.map((readModel) =>\n this.indexWithTiming(readModel, \"pre_ready\", event),\n ),\n );\n } catch (error) {\n this.logger.error(\n \"Pre-ready read model indexing failed for job @jobId: @Error\",\n { jobId: event.jobId },\n error,\n );\n }\n const preReadyDurationMs = performance.now() - preReadyStart;\n\n const readyEvent: JobReadReadyEvent = {\n jobId: event.jobId,\n operations: event.operations,\n };\n const emitStart = performance.now();\n try {\n await this.eventBus.emit(ReactorEventTypes.JOB_READ_READY, readyEvent);\n } catch (error) {\n this.logger.error(\n \"JOB_READ_READY emit failed for job @jobId: @Error\",\n { jobId: event.jobId },\n error,\n );\n }\n const emitDurationMs = performance.now() - emitStart;\n\n const postReadyStart = performance.now();\n try {\n await Promise.all(\n this.postReady.map((readModel) =>\n this.indexWithTiming(readModel, \"post_ready\", event),\n ),\n );\n } catch (error) {\n this.logger.error(\n \"Post-ready read model indexing failed for job @jobId: @Error\",\n { jobId: event.jobId },\n error,\n );\n }\n const postReadyDurationMs = performance.now() - postReadyStart;\n\n this.emitBatchCompleted({\n jobId: event.jobId,\n batchSize: event.operations.length,\n chainWaitDurationMs,\n preReadyDurationMs,\n emitDurationMs,\n postReadyDurationMs,\n });\n }\n\n private async indexWithTiming(\n readModel: IReadModel,\n stage: ReadModelIndexingStage,\n event: JobWriteReadyEvent,\n ): Promise<void> {\n const start = performance.now();\n let success = false;\n try {\n await readModel.indexOperations(event.operations);\n success = true;\n } finally {\n this.emitReadModelIndexed({\n jobId: event.jobId,\n readModelName: readModel.name,\n stage,\n durationMs: performance.now() - start,\n operationCount: event.operations.length,\n success,\n });\n }\n }\n\n private emitReadModelIndexed(payload: ReadModelIndexedEvent): void {\n void this.eventBus\n .emit(ReactorEventTypes.READMODEL_INDEXED, payload)\n .catch((err: unknown) =>\n this.logger.error(\n \"READMODEL_INDEXED emit failed for job @jobId: @Error\",\n { jobId: payload.jobId },\n err,\n ),\n );\n }\n\n private emitBatchCompleted(payload: ReadModelBatchCompletedEvent): void {\n void this.eventBus\n .emit(ReactorEventTypes.READMODEL_BATCH_COMPLETED, payload)\n .catch((err: unknown) =>\n this.logger.error(\n \"READMODEL_BATCH_COMPLETED emit failed for job @jobId: @Error\",\n { jobId: payload.jobId },\n err,\n ),\n );\n }\n\n private queueKeyFor(event: JobWriteReadyEvent): string {\n const ctx = event.operations[0]!.context;\n return `${ctx.documentId}:${ctx.scope}:${ctx.branch}`;\n }\n}\n","import type {\n OperationWithContext,\n PHAuthState,\n PHDocument,\n PHDocumentHeader,\n} from \"@powerhousedao/shared/document-model\";\nimport { createAuthState } from \"@powerhousedao/shared/document-model\";\nimport type { Kysely } from \"kysely\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport type { IOperationIndex } from \"../cache/operation-index-types.js\";\nimport type { IWriteCache } from \"../cache/write/interfaces.js\";\nimport type { IConsistencyTracker } from \"../shared/consistency-tracker.js\";\nimport type {\n ConsistencyToken,\n PagedResults,\n PagingOptions,\n} from \"../shared/types.js\";\nimport type {\n DocumentRevisions,\n IDocumentView,\n IOperationStore,\n ViewFilter,\n} from \"../storage/interfaces.js\";\nimport type { Database as StorageDatabase } from \"../storage/kysely/types.js\";\nimport { BaseReadModel } from \"./base-read-model.js\";\nimport type {\n DocumentViewDatabase,\n InsertableDocumentSnapshot,\n} from \"./types.js\";\n\ntype Database = StorageDatabase & DocumentViewDatabase;\n\nexport class KyselyDocumentView extends BaseReadModel implements IDocumentView {\n private _db: Kysely<Database>;\n\n constructor(\n db: Kysely<Database>,\n private operationStore: IOperationStore,\n operationIndex: IOperationIndex,\n writeCache: IWriteCache,\n consistencyTracker: IConsistencyTracker,\n ) {\n super(\n db as unknown as Kysely<DocumentViewDatabase>,\n operationIndex,\n writeCache,\n consistencyTracker,\n { readModelId: \"document-view\", rebuildStateOnInit: true },\n );\n this._db = db;\n }\n\n protected override async commitOperations(\n items: OperationWithContext[],\n ): Promise<void> {\n await this._db.transaction().execute(async (trx) => {\n for (const item of items) {\n const { operation, context } = item;\n const { documentId, scope, branch, documentType, resultingState } =\n context;\n const { index, hash } = operation;\n\n if (!resultingState) {\n throw new Error(\n `Missing resultingState in context for operation ${operation.id || \"unknown\"}. ` +\n `IDocumentView requires resultingState from upstream - it does not rebuild documents.`,\n );\n }\n\n let fullState: Record<string, unknown>;\n try {\n fullState = JSON.parse(resultingState) as Record<string, unknown>;\n } catch (error) {\n throw new Error(\n `Failed to parse resultingState for operation ${operation.id || \"unknown\"}: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n }\n\n const operationType = operation.action.type;\n\n // Non-header ops carry a denormalized header echo with possibly stale\n // `meta`; only header-scope ops and CREATE/UPGRADE/DELETE may write it.\n const preserveHeaderMeta =\n operationType !== \"CREATE_DOCUMENT\" &&\n operationType !== \"UPGRADE_DOCUMENT\" &&\n operationType !== \"DELETE_DOCUMENT\" &&\n scope !== \"header\";\n\n if (operationType === \"DELETE_DOCUMENT\") {\n const now = new Date();\n await trx\n .updateTable(\"DocumentSnapshot\")\n .set({\n isDeleted: true,\n deletedAt: now,\n lastOperationIndex: index,\n lastOperationHash: hash,\n lastUpdatedAt: now,\n })\n .where(\"documentId\", \"=\", documentId)\n .where(\"branch\", \"=\", branch)\n .execute();\n\n await trx\n .deleteFrom(\"SlugMapping\")\n .where(\"documentId\", \"=\", documentId)\n .where(\"branch\", \"=\", branch)\n .execute();\n\n continue;\n }\n\n let scopesToIndex: Array<[string, unknown]>;\n\n if (operationType === \"CREATE_DOCUMENT\") {\n scopesToIndex = Object.entries(fullState).filter(\n ([key]) => key === \"header\" || key === \"document\" || key === \"auth\",\n );\n } else if (operationType === \"UPGRADE_DOCUMENT\") {\n const scopeStatesToIndex: Array<[string, unknown]> = [];\n\n for (const [scopeName, scopeState] of Object.entries(fullState)) {\n if (scopeName === \"header\") {\n scopeStatesToIndex.push([scopeName, scopeState]);\n continue;\n }\n\n if (scopeName === scope) {\n scopeStatesToIndex.push([scopeName, scopeState]);\n continue;\n }\n\n const existingSnapshot = await trx\n .selectFrom(\"DocumentSnapshot\")\n .select(\"scope\")\n .where(\"documentId\", \"=\", documentId)\n .where(\"scope\", \"=\", scopeName)\n .where(\"branch\", \"=\", branch)\n .executeTakeFirst();\n\n if (!existingSnapshot) {\n scopeStatesToIndex.push([scopeName, scopeState]);\n }\n }\n\n scopesToIndex = scopeStatesToIndex;\n } else {\n scopesToIndex = [];\n\n if (fullState.header !== undefined) {\n scopesToIndex.push([\"header\", fullState.header]);\n }\n\n if (fullState[scope] !== undefined) {\n scopesToIndex.push([scope, fullState[scope]]);\n } else {\n scopesToIndex.push([scope, {}]);\n }\n }\n\n for (const [scopeName, scopeState] of scopesToIndex) {\n const existingSnapshot = await trx\n .selectFrom(\"DocumentSnapshot\")\n .selectAll()\n .where(\"documentId\", \"=\", documentId)\n .where(\"scope\", \"=\", scopeName)\n .where(\"branch\", \"=\", branch)\n .executeTakeFirst();\n\n const newState =\n typeof scopeState === \"object\" && scopeState !== null\n ? (scopeState as Record<string, unknown>)\n : {};\n\n let slug: string | null = existingSnapshot?.slug ?? null;\n let name: string | null = existingSnapshot?.name ?? null;\n\n if (scopeName === \"header\") {\n const headerSlug = newState.slug;\n const headerName = newState.name;\n\n if (typeof headerSlug === \"string\") {\n slug = headerSlug;\n }\n if (typeof headerName === \"string\") {\n name = headerName;\n }\n\n if (preserveHeaderMeta && existingSnapshot) {\n const existingHeader = existingSnapshot.content as Record<\n string,\n unknown\n >;\n if (existingHeader && \"meta\" in existingHeader) {\n newState.meta = existingHeader.meta;\n }\n }\n\n if (slug && slug !== documentId) {\n await trx\n .insertInto(\"SlugMapping\")\n .values({\n slug,\n documentId,\n scope: scopeName,\n branch,\n })\n .onConflict((oc) =>\n oc.column(\"slug\").doUpdateSet({\n documentId,\n scope: scopeName,\n branch,\n }),\n )\n .execute();\n }\n }\n\n if (existingSnapshot) {\n await trx\n .updateTable(\"DocumentSnapshot\")\n .set({\n lastOperationIndex: index,\n lastOperationHash: hash,\n lastUpdatedAt: new Date(),\n snapshotVersion: existingSnapshot.snapshotVersion + 1,\n content: newState,\n slug,\n name,\n })\n .where(\"documentId\", \"=\", documentId)\n .where(\"scope\", \"=\", scopeName)\n .where(\"branch\", \"=\", branch)\n .execute();\n } else {\n const snapshot: InsertableDocumentSnapshot = {\n id: uuidv4(),\n documentId,\n slug,\n name,\n scope: scopeName,\n branch,\n content: newState,\n documentType,\n lastOperationIndex: index,\n lastOperationHash: hash,\n identifiers: null,\n metadata: null,\n deletedAt: null,\n };\n\n await trx.insertInto(\"DocumentSnapshot\").values(snapshot).execute();\n }\n }\n }\n });\n }\n\n async exists(\n documentIds: string[],\n consistencyToken?: ConsistencyToken,\n signal?: AbortSignal,\n ): Promise<boolean[]> {\n if (consistencyToken) {\n await this.waitForConsistency(consistencyToken, undefined, signal);\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n if (documentIds.length === 0) {\n return [];\n }\n\n const snapshots = await this._db\n .selectFrom(\"DocumentSnapshot\")\n .select([\"documentId\"])\n .where(\"documentId\", \"in\", documentIds)\n .where(\"isDeleted\", \"=\", false)\n .distinct()\n .execute();\n\n const existingIds = new Set(snapshots.map((s) => s.documentId));\n\n return documentIds.map((id) => existingIds.has(id));\n }\n\n async get<TDocument extends PHDocument>(\n documentId: string,\n view?: ViewFilter,\n consistencyToken?: ConsistencyToken,\n signal?: AbortSignal,\n ): Promise<TDocument> {\n if (consistencyToken) {\n await this.waitForConsistency(consistencyToken, undefined, signal);\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const branch = view?.branch || \"main\";\n\n let scopesToQuery: string[];\n if (view?.scopes && view.scopes.length > 0) {\n scopesToQuery = [...new Set([\"header\", \"document\", ...view.scopes])];\n } else {\n scopesToQuery = [];\n }\n\n let query = this._db\n .selectFrom(\"DocumentSnapshot\")\n .selectAll()\n .where(\"documentId\", \"=\", documentId)\n .where(\"branch\", \"=\", branch)\n .where(\"isDeleted\", \"=\", false);\n\n if (scopesToQuery.length > 0) {\n query = query.where(\"scope\", \"in\", scopesToQuery);\n }\n\n const snapshots = await query.execute();\n\n if (snapshots.length === 0) {\n throw new Error(`Document not found: ${documentId}`);\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const headerSnapshot = snapshots.find((s) => s.scope === \"header\");\n if (!headerSnapshot) {\n throw new Error(`Document header not found: ${documentId}`);\n }\n\n const header = headerSnapshot.content as PHDocumentHeader;\n\n const revisions = await this.operationStore.getRevisions(\n documentId,\n branch,\n signal,\n );\n header.revision = revisions.revision;\n header.lastModifiedAtUtcIso = revisions.latestTimestamp;\n\n const state: Record<string, unknown> = {};\n for (const snapshot of snapshots) {\n if (snapshot.scope === \"header\") {\n continue;\n }\n\n // snapshot rows written before PHAuthState had a version carry auth: {}\n state[snapshot.scope] =\n snapshot.scope === \"auth\"\n ? createAuthState(snapshot.content as Partial<PHAuthState>)\n : snapshot.content;\n }\n\n const document: PHDocument = {\n header,\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n state: state as any,\n operations: {},\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n initialState: state as any,\n clipboard: [],\n };\n\n return document as TDocument;\n }\n\n async getMany<TDocument extends PHDocument>(\n documentIds: string[],\n view?: ViewFilter,\n consistencyToken?: ConsistencyToken,\n signal?: AbortSignal,\n ): Promise<TDocument[]> {\n if (documentIds.length === 0) {\n return [];\n }\n\n if (consistencyToken) {\n await this.waitForConsistency(consistencyToken, undefined, signal);\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const branch = view?.branch || \"main\";\n\n let scopesToQuery: string[];\n if (view?.scopes && view.scopes.length > 0) {\n scopesToQuery = [...new Set([\"header\", \"document\", ...view.scopes])];\n } else {\n scopesToQuery = [];\n }\n\n let query = this._db\n .selectFrom(\"DocumentSnapshot\")\n .selectAll()\n .where(\"documentId\", \"in\", documentIds)\n .where(\"branch\", \"=\", branch)\n .where(\"isDeleted\", \"=\", false);\n\n if (scopesToQuery.length > 0) {\n query = query.where(\"scope\", \"in\", scopesToQuery);\n }\n\n const allSnapshots = await query.execute();\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const snapshotsByDocId = new Map<string, typeof allSnapshots>();\n for (const snapshot of allSnapshots) {\n const existing = snapshotsByDocId.get(snapshot.documentId) || [];\n existing.push(snapshot);\n snapshotsByDocId.set(snapshot.documentId, existing);\n }\n\n const foundDocumentIds = [...snapshotsByDocId.keys()];\n const revisionResults = await Promise.all(\n foundDocumentIds.map((docId) =>\n this.operationStore.getRevisions(docId, branch, signal),\n ),\n );\n\n const revisionsByDocId = new Map<string, DocumentRevisions>();\n for (let i = 0; i < foundDocumentIds.length; i++) {\n revisionsByDocId.set(foundDocumentIds[i], revisionResults[i]);\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const documents: TDocument[] = [];\n for (const documentId of documentIds) {\n const snapshots = snapshotsByDocId.get(documentId);\n if (!snapshots || snapshots.length === 0) {\n continue;\n }\n\n const headerSnapshot = snapshots.find((s) => s.scope === \"header\");\n if (!headerSnapshot) {\n continue;\n }\n\n const header = headerSnapshot.content as PHDocumentHeader;\n const revisions = revisionsByDocId.get(documentId);\n if (revisions) {\n header.revision = revisions.revision;\n header.lastModifiedAtUtcIso = revisions.latestTimestamp;\n }\n\n const state: Record<string, unknown> = {};\n for (const snapshot of snapshots) {\n if (snapshot.scope === \"header\") {\n continue;\n }\n // snapshot rows written before PHAuthState had a version carry auth: {}\n state[snapshot.scope] =\n snapshot.scope === \"auth\"\n ? createAuthState(snapshot.content as Partial<PHAuthState>)\n : snapshot.content;\n }\n\n const document: PHDocument = {\n header,\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n state: state as any,\n operations: {},\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n initialState: state as any,\n clipboard: [],\n };\n\n documents.push(document as TDocument);\n }\n\n return documents;\n }\n\n async getByIdOrSlug<TDocument extends PHDocument>(\n identifier: string,\n view?: ViewFilter,\n consistencyToken?: ConsistencyToken,\n signal?: AbortSignal,\n ): Promise<TDocument> {\n const documentId = await this.resolveIdOrSlug(\n identifier,\n view,\n consistencyToken,\n signal,\n );\n return this.get<TDocument>(documentId, view, undefined, signal);\n }\n\n async findByType(\n type: string,\n view?: ViewFilter,\n paging?: PagingOptions,\n consistencyToken?: ConsistencyToken,\n signal?: AbortSignal,\n ): Promise<PagedResults<PHDocument>> {\n if (consistencyToken) {\n await this.waitForConsistency(consistencyToken, undefined, signal);\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const branch = view?.branch || \"main\";\n\n const startIndex = paging?.cursor ? parseInt(paging.cursor) : 0;\n const limit = paging?.limit || 100;\n\n const documents: PHDocument[] = [];\n const processedDocumentIds = new Set<string>();\n const allDocumentIds: string[] = [];\n\n const snapshots = await this._db\n .selectFrom(\"DocumentSnapshot\")\n .selectAll()\n .where(\"documentType\", \"=\", type)\n .where(\"branch\", \"=\", branch)\n .where(\"isDeleted\", \"=\", false)\n .orderBy(\"lastUpdatedAt\", \"desc\")\n .execute();\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n for (const snapshot of snapshots) {\n if (processedDocumentIds.has(snapshot.documentId)) {\n continue;\n }\n\n processedDocumentIds.add(snapshot.documentId);\n allDocumentIds.push(snapshot.documentId);\n }\n\n const docsToFetch = allDocumentIds.slice(startIndex, startIndex + limit);\n\n for (const documentId of docsToFetch) {\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n try {\n const document = await this.get<PHDocument>(\n documentId,\n view,\n undefined,\n signal,\n );\n documents.push(document);\n } catch {\n continue;\n }\n }\n\n const hasMore = allDocumentIds.length > startIndex + limit;\n const nextCursor = hasMore ? String(startIndex + limit) : undefined;\n\n return {\n results: documents,\n options: paging || { cursor: \"0\", limit: 100 },\n nextCursor,\n totalCount: allDocumentIds.length,\n next: hasMore\n ? () =>\n this.findByType(\n type,\n view,\n { cursor: nextCursor!, limit },\n consistencyToken,\n signal,\n )\n : undefined,\n };\n }\n\n async resolveSlug(\n slug: string,\n // TODO: this should only be branch\n view?: ViewFilter,\n consistencyToken?: ConsistencyToken,\n signal?: AbortSignal,\n ): Promise<string | undefined> {\n if (consistencyToken) {\n await this.waitForConsistency(consistencyToken, undefined, signal);\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const branch = view?.branch || \"main\";\n\n const mapping = await this._db\n .selectFrom(\"SlugMapping\")\n .select(\"documentId\")\n .where(\"slug\", \"=\", slug)\n .where(\"branch\", \"=\", branch)\n .executeTakeFirst();\n\n if (!mapping) {\n return undefined;\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n if (view?.scopes && view.scopes.length > 0) {\n const scopeCheck = await this._db\n .selectFrom(\"DocumentSnapshot\")\n .select(\"scope\")\n .where(\"documentId\", \"=\", mapping.documentId)\n .where(\"branch\", \"=\", branch)\n .where(\"scope\", \"in\", view.scopes)\n .where(\"isDeleted\", \"=\", false)\n .executeTakeFirst();\n\n if (!scopeCheck) {\n return undefined;\n }\n }\n\n return mapping.documentId;\n }\n\n // TODO: fix\n async resolveSlugs(\n slugs: string[],\n // TODO: this should only be branch\n view?: ViewFilter,\n consistencyToken?: ConsistencyToken,\n signal?: AbortSignal,\n ): Promise<string[]> {\n const ids = await Promise.all(\n slugs.map((slug) =>\n this.resolveSlug(slug, view, consistencyToken, signal),\n ),\n );\n return ids.filter((id) => id !== undefined) as string[];\n }\n\n async resolveIdOrSlug(\n identifier: string,\n // TODO: this should only be branch\n view?: ViewFilter,\n consistencyToken?: ConsistencyToken,\n signal?: AbortSignal,\n ): Promise<string> {\n if (consistencyToken) {\n await this.waitForConsistency(consistencyToken, undefined, signal);\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const branch = view?.branch || \"main\";\n\n const idCheckPromise = this._db\n .selectFrom(\"DocumentSnapshot\")\n .select(\"documentId\")\n .where(\"documentId\", \"=\", identifier)\n .where(\"branch\", \"=\", branch)\n .where(\"isDeleted\", \"=\", false)\n .executeTakeFirst();\n\n const slugCheckPromise = this._db\n .selectFrom(\"SlugMapping\")\n .select(\"documentId\")\n .where(\"slug\", \"=\", identifier)\n .where(\"branch\", \"=\", branch)\n .executeTakeFirst();\n\n const [idMatch, slugMatch] = await Promise.all([\n idCheckPromise,\n slugCheckPromise,\n ]);\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const idMatchDocId = idMatch?.documentId;\n const slugMatchDocId = slugMatch?.documentId;\n\n if (idMatchDocId && slugMatchDocId && idMatchDocId !== slugMatchDocId) {\n throw new Error(\n `Ambiguous identifier \"${identifier}\": matches both document ID \"${idMatchDocId}\" and slug for document ID \"${slugMatchDocId}\". ` +\n `Please use get() for ID or resolveSlug() + get() for slug to be explicit.`,\n );\n }\n\n const resolvedDocumentId = idMatchDocId || slugMatchDocId;\n\n if (!resolvedDocumentId) {\n throw new Error(`Document not found: ${identifier}`);\n }\n\n return resolvedDocumentId;\n }\n}\n","import type { ConsistencyCoordinate, ConsistencyKey } from \"./types.js\";\n\nexport interface IConsistencyTracker {\n /**\n * Updates the tracker with new operation indexes.\n * When multiple coordinates have the same key, keeps the highest operationIndex.\n * Resolves any pending waiters whose coordinates are now satisfied.\n */\n update(coordinates: ConsistencyCoordinate[]): void;\n\n /**\n * Returns the latest operation index for a given key, or undefined if not tracked.\n */\n getLatest(key: ConsistencyKey): number | undefined;\n\n /**\n * Returns a promise that resolves when all coordinates are satisfied.\n * Rejects if the timeout is reached or the signal is aborted.\n */\n waitFor(\n coordinates: ConsistencyCoordinate[],\n timeoutMs?: number,\n signal?: AbortSignal,\n ): Promise<void>;\n\n /**\n * Returns a serializable snapshot of the current state.\n */\n serialize(): Array<[ConsistencyKey, number]>;\n\n /**\n * Restores state from a serialized snapshot.\n */\n hydrate(entries: Array<[ConsistencyKey, number]>): void;\n}\n\ntype Waiter = {\n coordinates: ConsistencyCoordinate[];\n resolve: () => void;\n reject: (reason: Error) => void;\n signal?: AbortSignal;\n timeoutId?: NodeJS.Timeout;\n};\n\n/**\n * Creates a consistency key from documentId, scope, and branch.\n */\nexport function makeConsistencyKey(\n documentId: string,\n scope: string,\n branch: string,\n): ConsistencyKey {\n return `${documentId}:${scope}:${branch}`;\n}\n\n/**\n * Tracks operation indexes for documents and provides read-after-write consistency guarantees.\n * Maintains an in-memory map of the latest operation index for each (documentId, scope, branch) tuple.\n */\nexport class ConsistencyTracker implements IConsistencyTracker {\n private state = new Map<ConsistencyKey, number>();\n private waiters: Waiter[] = [];\n\n update(coordinates: ConsistencyCoordinate[]): void {\n const deduplicated = this.deduplicateCoordinates(coordinates);\n\n for (let i = 0; i < deduplicated.length; i++) {\n const coord = deduplicated[i]!;\n const key = makeConsistencyKey(\n coord.documentId,\n coord.scope,\n coord.branch,\n );\n const current = this.state.get(key);\n if (current === undefined || coord.operationIndex > current) {\n this.state.set(key, coord.operationIndex);\n }\n }\n\n this.checkWaiters();\n }\n\n getLatest(key: ConsistencyKey): number | undefined {\n return this.state.get(key);\n }\n\n waitFor(\n coordinates: ConsistencyCoordinate[],\n timeoutMs?: number,\n signal?: AbortSignal,\n ): Promise<void> {\n if (signal?.aborted) {\n return Promise.reject(new Error(\"Operation aborted\"));\n }\n\n if (this.areCoordinatesSatisfied(coordinates)) {\n return Promise.resolve();\n }\n\n return new Promise<void>((resolve, reject) => {\n const waiter: Waiter = {\n coordinates,\n resolve,\n reject,\n signal,\n };\n\n if (timeoutMs !== undefined) {\n waiter.timeoutId = setTimeout(() => {\n this.removeWaiter(waiter);\n reject(new Error(`Consistency wait timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n }\n\n if (signal) {\n const abortHandler = () => {\n this.removeWaiter(waiter);\n reject(new Error(\"Operation aborted\"));\n };\n signal.addEventListener(\"abort\", abortHandler, { once: true });\n }\n\n this.waiters.push(waiter);\n });\n }\n\n serialize(): Array<[ConsistencyKey, number]> {\n return Array.from(this.state.entries());\n }\n\n hydrate(entries: Array<[ConsistencyKey, number]>): void {\n this.state.clear();\n for (const [key, index] of entries) {\n this.state.set(key, index);\n }\n }\n\n private deduplicateCoordinates(\n coordinates: ConsistencyCoordinate[],\n ): ConsistencyCoordinate[] {\n const map = new Map<ConsistencyKey, ConsistencyCoordinate>();\n\n for (let i = 0; i < coordinates.length; i++) {\n const coord = coordinates[i]!;\n const key = makeConsistencyKey(\n coord.documentId,\n coord.scope,\n coord.branch,\n );\n const existing = map.get(key);\n\n if (!existing || coord.operationIndex > existing.operationIndex) {\n map.set(key, coord);\n }\n }\n\n return Array.from(map.values());\n }\n\n private areCoordinatesSatisfied(\n coordinates: ConsistencyCoordinate[],\n ): boolean {\n for (let i = 0; i < coordinates.length; i++) {\n const coord = coordinates[i]!;\n const key = makeConsistencyKey(\n coord.documentId,\n coord.scope,\n coord.branch,\n );\n const latest = this.state.get(key);\n if (latest === undefined || latest < coord.operationIndex) {\n return false;\n }\n }\n return true;\n }\n\n private checkWaiters(): void {\n const satisfiedWaiters: Waiter[] = [];\n const unsatisfiedWaiters: Waiter[] = [];\n\n for (const waiter of this.waiters) {\n if (waiter.signal?.aborted) {\n continue;\n }\n\n if (this.areCoordinatesSatisfied(waiter.coordinates)) {\n satisfiedWaiters.push(waiter);\n } else {\n unsatisfiedWaiters.push(waiter);\n }\n }\n\n this.waiters = unsatisfiedWaiters;\n\n for (const waiter of satisfiedWaiters) {\n if (waiter.timeoutId !== undefined) {\n clearTimeout(waiter.timeoutId);\n }\n waiter.resolve();\n }\n }\n\n private removeWaiter(waiter: Waiter): void {\n const index = this.waiters.indexOf(waiter);\n if (index !== -1) {\n this.waiters.splice(index, 1);\n }\n if (waiter.timeoutId !== undefined) {\n clearTimeout(waiter.timeoutId);\n }\n }\n}\n","import type { PagedResults } from \"./types.js\";\n\n/**\n * Collects all results from a paged result set by following the next() function\n * until all pages have been fetched.\n */\nexport async function collectAllPages<T>(\n firstPage: PagedResults<T>,\n signal?: AbortSignal,\n): Promise<T[]> {\n const allResults: T[] = [...firstPage.results];\n let currentPage = firstPage;\n\n while (currentPage.next) {\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n currentPage = await currentPage.next();\n allResults.push(...currentPage.results);\n }\n\n return allResults;\n}\n","import type {\n Operation,\n OperationWithContext,\n} from \"@powerhousedao/shared/document-model\";\nimport type { Kysely } from \"kysely\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport type { IOperationIndex } from \"../../cache/operation-index-types.js\";\nimport type { IWriteCache } from \"../../cache/write/interfaces.js\";\nimport { BaseReadModel } from \"../../read-models/base-read-model.js\";\nimport type { DocumentViewDatabase } from \"../../read-models/types.js\";\nimport { collectAllPages } from \"../../shared/collect-all-pages.js\";\nimport type { IConsistencyTracker } from \"../../shared/consistency-tracker.js\";\nimport type {\n ConsistencyToken,\n PagedResults,\n PagingOptions,\n} from \"../../shared/types.js\";\nimport type {\n DocumentGraphEdge,\n DocumentRelationship,\n IDocumentGraph,\n IDocumentIndexer,\n} from \"../interfaces.js\";\nimport type {\n DocumentIndexerDatabase,\n InsertableDocumentRelationship,\n Database as StorageDatabase,\n} from \"./types.js\";\n\nexport type IndexerDatabase = StorageDatabase &\n DocumentIndexerDatabase &\n DocumentViewDatabase;\n\nexport class KyselyDocumentIndexer\n extends BaseReadModel\n implements IDocumentIndexer\n{\n private _db: Kysely<IndexerDatabase>;\n\n constructor(\n db: Kysely<IndexerDatabase>,\n operationIndex: IOperationIndex,\n writeCache: IWriteCache,\n consistencyTracker: IConsistencyTracker,\n ) {\n super(\n db as unknown as Kysely<DocumentViewDatabase>,\n operationIndex,\n writeCache,\n consistencyTracker,\n { readModelId: \"document-indexer\", rebuildStateOnInit: false },\n );\n this._db = db;\n }\n\n protected override async commitOperations(\n items: OperationWithContext[],\n ): Promise<void> {\n await this._db.transaction().execute(async (trx) => {\n for (const item of items) {\n const { operation } = item;\n const actionType = operation.action.type;\n\n if (actionType === \"ADD_RELATIONSHIP\") {\n await this.handleAddRelationship(trx, operation);\n } else if (actionType === \"REMOVE_RELATIONSHIP\") {\n await this.handleRemoveRelationship(trx, operation);\n } else if (actionType === \"UPDATE_RELATIONSHIP\") {\n await this.handleUpdateRelationship(trx, operation);\n }\n }\n });\n }\n\n async getOutgoing(\n documentId: string,\n types?: string[],\n paging?: PagingOptions,\n consistencyToken?: ConsistencyToken,\n signal?: AbortSignal,\n ): Promise<PagedResults<DocumentRelationship>> {\n if (consistencyToken) {\n await this.waitForConsistency(consistencyToken, undefined, signal);\n }\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const startIndex = paging?.cursor ? parseInt(paging.cursor) : 0;\n const limit = paging?.limit || 100;\n\n let query = this._db\n .selectFrom(\"DocumentRelationship\")\n .selectAll()\n .where(\"sourceId\", \"=\", documentId);\n\n if (types && types.length > 0) {\n query = query.where(\"relationshipType\", \"in\", types);\n }\n\n const rows = await query\n .orderBy(\"createdAt\", \"asc\")\n .orderBy(\"id\", \"asc\")\n .offset(startIndex)\n .limit(limit + 1)\n .execute();\n\n const hasMore = rows.length > limit;\n const results = hasMore ? rows.slice(0, limit) : rows;\n const nextCursor = hasMore ? String(startIndex + limit) : undefined;\n\n return {\n results: results.map((row) => ({\n sourceId: row.sourceId,\n targetId: row.targetId,\n relationshipType: row.relationshipType,\n metadata: row.metadata\n ? (row.metadata as Record<string, unknown>)\n : undefined,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n })),\n options: paging || { cursor: \"0\", limit: 100 },\n nextCursor,\n next: hasMore\n ? () =>\n this.getOutgoing(\n documentId,\n types,\n { cursor: nextCursor!, limit },\n consistencyToken,\n signal,\n )\n : undefined,\n };\n }\n\n async getIncoming(\n documentId: string,\n types?: string[],\n paging?: PagingOptions,\n consistencyToken?: ConsistencyToken,\n signal?: AbortSignal,\n ): Promise<PagedResults<DocumentRelationship>> {\n if (consistencyToken) {\n await this.waitForConsistency(consistencyToken, undefined, signal);\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const startIndex = paging?.cursor ? parseInt(paging.cursor) : 0;\n const limit = paging?.limit || 100;\n\n let query = this._db\n .selectFrom(\"DocumentRelationship\")\n .selectAll()\n .where(\"targetId\", \"=\", documentId);\n\n if (types && types.length > 0) {\n query = query.where(\"relationshipType\", \"in\", types);\n }\n\n const rows = await query\n .orderBy(\"createdAt\", \"asc\")\n .orderBy(\"id\", \"asc\")\n .offset(startIndex)\n .limit(limit + 1)\n .execute();\n\n const hasMore = rows.length > limit;\n const results = hasMore ? rows.slice(0, limit) : rows;\n const nextCursor = hasMore ? String(startIndex + limit) : undefined;\n\n return {\n results: results.map((row) => ({\n sourceId: row.sourceId,\n targetId: row.targetId,\n relationshipType: row.relationshipType,\n metadata: row.metadata\n ? (row.metadata as Record<string, unknown>)\n : undefined,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n })),\n options: paging || { cursor: \"0\", limit: 100 },\n nextCursor,\n next: hasMore\n ? () =>\n this.getIncoming(\n documentId,\n types,\n { cursor: nextCursor!, limit },\n consistencyToken,\n signal,\n )\n : undefined,\n };\n }\n\n async getOrphanedChildren(\n parentIds: string[],\n types?: string[],\n signal?: AbortSignal,\n ): Promise<string[]> {\n if (parentIds.length === 0) {\n return [];\n }\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n let query = this._db\n .selectFrom(\"DocumentRelationship as r\")\n .select(\"r.targetId\")\n .distinct()\n .where(\"r.sourceId\", \"in\", parentIds)\n .where((eb) =>\n eb.not(\n eb.exists(\n eb\n .selectFrom(\"DocumentRelationship as other\")\n .select(\"other.id\")\n .whereRef(\"other.targetId\", \"=\", \"r.targetId\")\n .where(\"other.sourceId\", \"not in\", parentIds),\n ),\n ),\n );\n\n if (types && types.length > 0) {\n query = query.where(\"r.relationshipType\", \"in\", types);\n }\n\n const rows = await query.execute();\n return rows.map((row) => row.targetId);\n }\n\n async hasRelationship(\n sourceId: string,\n targetId: string,\n types?: string[],\n consistencyToken?: ConsistencyToken,\n signal?: AbortSignal,\n ): Promise<boolean> {\n if (consistencyToken) {\n await this.waitForConsistency(consistencyToken, undefined, signal);\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n let query = this._db\n .selectFrom(\"DocumentRelationship\")\n .select(\"id\")\n .where(\"sourceId\", \"=\", sourceId)\n .where(\"targetId\", \"=\", targetId);\n\n if (types && types.length > 0) {\n query = query.where(\"relationshipType\", \"in\", types);\n }\n\n const result = await query.executeTakeFirst();\n\n return result !== undefined;\n }\n\n async getUndirectedRelationships(\n a: string,\n b: string,\n types?: string[],\n paging?: PagingOptions,\n consistencyToken?: ConsistencyToken,\n signal?: AbortSignal,\n ): Promise<PagedResults<DocumentRelationship>> {\n if (consistencyToken) {\n await this.waitForConsistency(consistencyToken, undefined, signal);\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const startIndex = paging?.cursor ? parseInt(paging.cursor) : 0;\n const limit = paging?.limit || 100;\n\n let query = this._db\n .selectFrom(\"DocumentRelationship\")\n .selectAll()\n .where((eb) =>\n eb.or([\n eb.and([eb(\"sourceId\", \"=\", a), eb(\"targetId\", \"=\", b)]),\n eb.and([eb(\"sourceId\", \"=\", b), eb(\"targetId\", \"=\", a)]),\n ]),\n );\n\n if (types && types.length > 0) {\n query = query.where(\"relationshipType\", \"in\", types);\n }\n\n const rows = await query\n .orderBy(\"createdAt\", \"asc\")\n .orderBy(\"id\", \"asc\")\n .offset(startIndex)\n .limit(limit + 1)\n .execute();\n\n const hasMore = rows.length > limit;\n const results = hasMore ? rows.slice(0, limit) : rows;\n const nextCursor = hasMore ? String(startIndex + limit) : undefined;\n\n return {\n results: results.map((row) => ({\n sourceId: row.sourceId,\n targetId: row.targetId,\n relationshipType: row.relationshipType,\n metadata: row.metadata\n ? (row.metadata as Record<string, unknown>)\n : undefined,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n })),\n options: paging || { cursor: \"0\", limit: 100 },\n nextCursor,\n next: hasMore\n ? () =>\n this.getUndirectedRelationships(\n a,\n b,\n types,\n { cursor: nextCursor!, limit },\n consistencyToken,\n signal,\n )\n : undefined,\n };\n }\n\n async getDirectedRelationships(\n sourceId: string,\n targetId: string,\n types?: string[],\n paging?: PagingOptions,\n consistencyToken?: ConsistencyToken,\n signal?: AbortSignal,\n ): Promise<PagedResults<DocumentRelationship>> {\n if (consistencyToken) {\n await this.waitForConsistency(consistencyToken, undefined, signal);\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const startIndex = paging?.cursor ? parseInt(paging.cursor) : 0;\n const limit = paging?.limit || 100;\n\n let query = this._db\n .selectFrom(\"DocumentRelationship\")\n .selectAll()\n .where(\"sourceId\", \"=\", sourceId)\n .where(\"targetId\", \"=\", targetId);\n\n if (types && types.length > 0) {\n query = query.where(\"relationshipType\", \"in\", types);\n }\n\n const rows = await query\n .orderBy(\"createdAt\", \"asc\")\n .orderBy(\"id\", \"asc\")\n .offset(startIndex)\n .limit(limit + 1)\n .execute();\n\n const hasMore = rows.length > limit;\n const results = hasMore ? rows.slice(0, limit) : rows;\n const nextCursor = hasMore ? String(startIndex + limit) : undefined;\n\n return {\n results: results.map((row) => ({\n sourceId: row.sourceId,\n targetId: row.targetId,\n relationshipType: row.relationshipType,\n metadata: row.metadata\n ? (row.metadata as Record<string, unknown>)\n : undefined,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n })),\n options: paging || { cursor: \"0\", limit: 100 },\n nextCursor,\n next: hasMore\n ? () =>\n this.getDirectedRelationships(\n sourceId,\n targetId,\n types,\n { cursor: nextCursor!, limit },\n consistencyToken,\n signal,\n )\n : undefined,\n };\n }\n\n async findPath(\n sourceId: string,\n targetId: string,\n types?: string[],\n consistencyToken?: ConsistencyToken,\n signal?: AbortSignal,\n ): Promise<string[] | null> {\n if (consistencyToken) {\n await this.waitForConsistency(consistencyToken, undefined, signal);\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n if (sourceId === targetId) {\n return [sourceId];\n }\n\n const visited = new Set<string>();\n const queue: Array<{ id: string; path: string[] }> = [\n { id: sourceId, path: [sourceId] },\n ];\n\n while (queue.length > 0) {\n const current = queue.shift()!;\n\n if (current.id === targetId) {\n return current.path;\n }\n\n if (visited.has(current.id)) {\n continue;\n }\n\n visited.add(current.id);\n\n const outgoingPage = await this.getOutgoing(\n current.id,\n types,\n undefined,\n consistencyToken,\n signal,\n );\n const outgoingRelationships = await collectAllPages(outgoingPage, signal);\n\n for (const rel of outgoingRelationships) {\n if (!visited.has(rel.targetId)) {\n queue.push({\n id: rel.targetId,\n path: [...current.path, rel.targetId],\n });\n }\n }\n }\n\n return null;\n }\n\n async findAncestors(\n documentId: string,\n types?: string[],\n consistencyToken?: ConsistencyToken,\n signal?: AbortSignal,\n ): Promise<IDocumentGraph> {\n if (consistencyToken) {\n await this.waitForConsistency(consistencyToken, undefined, signal);\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const nodes = new Set<string>([documentId]);\n const edges: DocumentGraphEdge[] = [];\n const queue: string[] = [documentId];\n const visited = new Set<string>();\n\n while (queue.length > 0) {\n const currentId = queue.shift()!;\n\n if (visited.has(currentId)) {\n continue;\n }\n\n visited.add(currentId);\n\n const incomingPage = await this.getIncoming(\n currentId,\n types,\n undefined,\n consistencyToken,\n signal,\n );\n const incomingRelationships = await collectAllPages(incomingPage, signal);\n\n for (const rel of incomingRelationships) {\n nodes.add(rel.sourceId);\n edges.push({\n from: rel.sourceId,\n to: rel.targetId,\n type: rel.relationshipType,\n });\n\n if (!visited.has(rel.sourceId)) {\n queue.push(rel.sourceId);\n }\n }\n }\n\n return {\n nodes: Array.from(nodes),\n edges,\n };\n }\n\n async getRelationshipTypes(\n consistencyToken?: ConsistencyToken,\n signal?: AbortSignal,\n ): Promise<string[]> {\n if (consistencyToken) {\n await this.waitForConsistency(consistencyToken, undefined, signal);\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const rows = await this._db\n .selectFrom(\"DocumentRelationship\")\n .select(\"relationshipType\")\n .distinct()\n .execute();\n\n return rows.map((row) => row.relationshipType);\n }\n\n private async handleAddRelationship(\n trx: Kysely<IndexerDatabase>,\n operation: Operation,\n ): Promise<void> {\n const input = operation.action.input as {\n sourceId: string;\n targetId: string;\n relationshipType: string;\n metadata?: Record<string, unknown>;\n };\n\n const existingDoc = await trx\n .selectFrom(\"Document\")\n .select(\"id\")\n .where(\"id\", \"=\", input.sourceId)\n .executeTakeFirst();\n\n if (!existingDoc) {\n await trx\n .insertInto(\"Document\")\n .values({\n id: input.sourceId,\n })\n .execute();\n }\n\n const existingTargetDoc = await trx\n .selectFrom(\"Document\")\n .select(\"id\")\n .where(\"id\", \"=\", input.targetId)\n .executeTakeFirst();\n\n if (!existingTargetDoc) {\n await trx\n .insertInto(\"Document\")\n .values({\n id: input.targetId,\n })\n .execute();\n }\n\n const existingRel = await trx\n .selectFrom(\"DocumentRelationship\")\n .select(\"id\")\n .where(\"sourceId\", \"=\", input.sourceId)\n .where(\"targetId\", \"=\", input.targetId)\n .where(\"relationshipType\", \"=\", input.relationshipType)\n .executeTakeFirst();\n\n if (!existingRel) {\n const relationship: InsertableDocumentRelationship = {\n id: uuidv4(),\n sourceId: input.sourceId,\n targetId: input.targetId,\n relationshipType: input.relationshipType,\n metadata: input.metadata || null,\n };\n\n await trx\n .insertInto(\"DocumentRelationship\")\n .values(relationship)\n .execute();\n }\n }\n\n private async handleRemoveRelationship(\n trx: Kysely<IndexerDatabase>,\n operation: Operation,\n ): Promise<void> {\n const input = operation.action.input as {\n sourceId: string;\n targetId: string;\n relationshipType: string;\n };\n\n await trx\n .deleteFrom(\"DocumentRelationship\")\n .where(\"sourceId\", \"=\", input.sourceId)\n .where(\"targetId\", \"=\", input.targetId)\n .where(\"relationshipType\", \"=\", input.relationshipType)\n .execute();\n }\n\n private async handleUpdateRelationship(\n trx: Kysely<IndexerDatabase>,\n operation: Operation,\n ): Promise<void> {\n const input = operation.action.input as {\n sourceId: string;\n targetId: string;\n relationshipType: string;\n metadata: Record<string, unknown> | null;\n };\n\n await trx\n .updateTable(\"DocumentRelationship\")\n .set({\n metadata: input.metadata,\n updatedAt: new Date(),\n })\n .where(\"sourceId\", \"=\", input.sourceId)\n .where(\"targetId\", \"=\", input.targetId)\n .where(\"relationshipType\", \"=\", input.relationshipType)\n .execute();\n }\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { n as ReactorEventTypes, t as EventBusAggregateError } from "./types-DMKLa0Ok.js";
2
- import { DowngradeNotSupportedError, applyDeleteDocumentAction, applyDeleteDocumentAction as applyDeleteDocumentAction$1, applyUpgradeDocumentAction, applyUpgradeDocumentAction as applyUpgradeDocumentAction$1, createPresignedHeader, defaultBaseState, deriveOperationId, isUndoRedo } from "@powerhousedao/shared/document-model";
2
+ import { DowngradeNotSupportedError, applyDeleteDocumentAction, applyDeleteDocumentAction as applyDeleteDocumentAction$1, applyUpgradeDocumentAction, applyUpgradeDocumentAction as applyUpgradeDocumentAction$1, createPresignedHeader, decide, defaultBaseState, deriveOperationId, isUndoRedo } from "@powerhousedao/shared/document-model";
3
3
  import { v4 } from "uuid";
4
4
  import { Migrator, sql } from "kysely";
5
5
  //#region \0rolldown/runtime.js
@@ -72,6 +72,27 @@ var DocumentDeletedError = class DocumentDeletedError extends Error {
72
72
  }
73
73
  };
74
74
  /**
75
+ * Error thrown when the auth policy denies an action at the executor gate.
76
+ */
77
+ var AuthorizationDeniedError = class AuthorizationDeniedError extends Error {
78
+ documentId;
79
+ scope;
80
+ operation;
81
+ subject;
82
+ constructor(documentId, scope, operation, subject) {
83
+ super(`Authorization denied: ${subject ?? "anonymous"} may not execute ${operation} in scope "${scope}" of document ${documentId}`);
84
+ this.name = "AuthorizationDeniedError";
85
+ this.documentId = documentId;
86
+ this.scope = scope;
87
+ this.operation = operation;
88
+ this.subject = subject;
89
+ Error.captureStackTrace(this, AuthorizationDeniedError);
90
+ }
91
+ static isError(error) {
92
+ return Error.isError(error) && error.name === "AuthorizationDeniedError";
93
+ }
94
+ };
95
+ /**
75
96
  * Error thrown when an operation has an invalid signature.
76
97
  */
77
98
  var InvalidSignatureError = class InvalidSignatureError extends Error {
@@ -181,6 +202,51 @@ var InvalidUpgradeStepError = class extends Error {
181
202
  }
182
203
  };
183
204
  //#endregion
205
+ //#region src/storage/interfaces.ts
206
+ /**
207
+ * Thrown when an operation with the same identity already exists in the store.
208
+ */
209
+ var DuplicateOperationError = class extends Error {
210
+ constructor(description) {
211
+ super(`Duplicate operation: ${description}`);
212
+ this.name = "DuplicateOperationError";
213
+ }
214
+ };
215
+ /**
216
+ * Thrown when a concurrent write conflict is detected during an atomic apply.
217
+ */
218
+ var OptimisticLockError = class extends Error {
219
+ constructor(message) {
220
+ super(message);
221
+ this.name = "OptimisticLockError";
222
+ }
223
+ };
224
+ /**
225
+ * Thrown when the caller-provided revision does not match the current
226
+ * stored revision, indicating a stale read.
227
+ */
228
+ var RevisionMismatchError = class extends Error {
229
+ constructor(expected, actual) {
230
+ super(`Revision mismatch: expected ${expected}, got ${actual}`);
231
+ this.name = "RevisionMismatchError";
232
+ }
233
+ };
234
+ /**
235
+ * A read-set stream grew before the append committed. A concurrency
236
+ * conflict, not a fault: the caller retries against the new stream heads.
237
+ */
238
+ var AppendConditionFailedError = class extends Error {
239
+ constructor(condition) {
240
+ const streams = condition.streams.map((s) => `${s.documentId}:${s.scope}:${s.branch}@${s.revision}`).join(", ");
241
+ super(`Append condition failed: a read-set stream advanced [${streams}]`);
242
+ this.condition = condition;
243
+ this.name = "AppendConditionFailedError";
244
+ }
245
+ static isError(error) {
246
+ return Error.isError(error) && error.name === "AppendConditionFailedError";
247
+ }
248
+ };
249
+ //#endregion
184
250
  //#region src/cache/collection-membership-cache.ts
185
251
  var CollectionMembershipCache = class CollectionMembershipCache {
186
252
  cache = /* @__PURE__ */ new Map();
@@ -1746,10 +1812,11 @@ var DocumentActionHandler = class {
1746
1812
  } catch (error) {
1747
1813
  this.logger.error("Error writing @Operation to IOperationStore: @Error", operation, error);
1748
1814
  stores.writeCache.invalidate(documentId, scope, branch);
1815
+ if (AppendConditionFailedError.isError(error)) for (const stream of error.condition.streams) stores.writeCache.invalidate(stream.documentId, stream.scope, stream.branch);
1749
1816
  return {
1750
1817
  job,
1751
1818
  success: false,
1752
- error: /* @__PURE__ */ new Error(`Failed to write operation to IOperationStore: ${error instanceof Error ? error.message : String(error)}`),
1819
+ error: AppendConditionFailedError.isError(error) ? error : /* @__PURE__ */ new Error(`Failed to write operation to IOperationStore: ${error instanceof Error ? error.message : String(error)}`),
1753
1820
  duration: Date.now() - startTime
1754
1821
  };
1755
1822
  }
@@ -1995,6 +2062,17 @@ var SimpleJobExecutor = class {
1995
2062
  } catch (error) {
1996
2063
  return buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
1997
2064
  }
2065
+ if (job.kind !== "load") {
2066
+ const subject = {
2067
+ address: action.context?.signer?.user.address,
2068
+ key: action.context?.signer?.app.key
2069
+ };
2070
+ if (decide(document.state.auth, subject, {
2071
+ verb: "execute",
2072
+ scope: action.scope,
2073
+ operation: action.type
2074
+ }) === "deny") return buildErrorResult(job, new AuthorizationDeniedError(job.documentId, action.scope, action.type, subject.address), startTime);
2075
+ }
1998
2076
  let module;
1999
2077
  try {
2000
2078
  const moduleVersion = docMeta.state.version === 0 ? void 0 : docMeta.state.version;
@@ -2039,10 +2117,11 @@ var SimpleJobExecutor = class {
2039
2117
  } catch (error) {
2040
2118
  this.logger.error("Error writing @Operation to IOperationStore: @Error", newOperation, error);
2041
2119
  stores.writeCache.invalidate(job.documentId, scope, job.branch);
2120
+ if (AppendConditionFailedError.isError(error)) for (const stream of error.condition.streams) stores.writeCache.invalidate(stream.documentId, stream.scope, stream.branch);
2042
2121
  return {
2043
2122
  job,
2044
2123
  success: false,
2045
- error: /* @__PURE__ */ new Error(`Failed to write operation to IOperationStore: ${error instanceof Error ? error.message : String(error)}`),
2124
+ error: AppendConditionFailedError.isError(error) ? error : /* @__PURE__ */ new Error(`Failed to write operation to IOperationStore: ${error instanceof Error ? error.message : String(error)}`),
2046
2125
  duration: Date.now() - startTime
2047
2126
  };
2048
2127
  }
@@ -2419,36 +2498,6 @@ function paginateRows(rows, paging, cursorOf, toItem, refetch) {
2419
2498
  };
2420
2499
  }
2421
2500
  //#endregion
2422
- //#region src/storage/interfaces.ts
2423
- /**
2424
- * Thrown when an operation with the same identity already exists in the store.
2425
- */
2426
- var DuplicateOperationError = class extends Error {
2427
- constructor(description) {
2428
- super(`Duplicate operation: ${description}`);
2429
- this.name = "DuplicateOperationError";
2430
- }
2431
- };
2432
- /**
2433
- * Thrown when a concurrent write conflict is detected during an atomic apply.
2434
- */
2435
- var OptimisticLockError = class extends Error {
2436
- constructor(message) {
2437
- super(message);
2438
- this.name = "OptimisticLockError";
2439
- }
2440
- };
2441
- /**
2442
- * Thrown when the caller-provided revision does not match the current
2443
- * stored revision, indicating a stale read.
2444
- */
2445
- var RevisionMismatchError = class extends Error {
2446
- constructor(expected, actual) {
2447
- super(`Revision mismatch: expected ${expected}, got ${actual}`);
2448
- this.name = "RevisionMismatchError";
2449
- }
2450
- };
2451
- //#endregion
2452
2501
  //#region src/storage/txn.ts
2453
2502
  var AtomicTransaction = class {
2454
2503
  operations = [];
@@ -2506,12 +2555,12 @@ var KyselyOperationStore = class KyselyOperationStore {
2506
2555
  instance.trx = trx;
2507
2556
  return instance;
2508
2557
  }
2509
- async apply(documentId, documentType, scope, branch, revision, fn, signal) {
2558
+ async apply(documentId, documentType, scope, branch, revision, fn, signal, condition) {
2510
2559
  if (this.trx) {
2511
2560
  let executeResult = null;
2512
2561
  let uniqueCtx = null;
2513
2562
  try {
2514
- executeResult = await this.executeApply(this.trx, documentId, documentType, scope, branch, revision, fn, signal);
2563
+ executeResult = await this.executeApply(this.trx, documentId, documentType, scope, branch, revision, fn, signal, condition);
2515
2564
  } catch (error) {
2516
2565
  if (error instanceof _UniqueConstraintContext) uniqueCtx = error;
2517
2566
  else throw error;
@@ -2523,7 +2572,7 @@ var KyselyOperationStore = class KyselyOperationStore {
2523
2572
  let uniqueCtx = null;
2524
2573
  try {
2525
2574
  transactionResult = await this.db.transaction().execute(async (trx) => {
2526
- return this.executeApply(trx, documentId, documentType, scope, branch, revision, fn, signal);
2575
+ return this.executeApply(trx, documentId, documentType, scope, branch, revision, fn, signal, condition);
2527
2576
  });
2528
2577
  } catch (error) {
2529
2578
  if (error instanceof _UniqueConstraintContext) uniqueCtx = error;
@@ -2542,12 +2591,13 @@ var KyselyOperationStore = class KyselyOperationStore {
2542
2591
  const op = ctx.stagedOps[0];
2543
2592
  throw new DuplicateOperationError(`${op.opId} at index ${op.index} with skip ${op.skip}`);
2544
2593
  }
2545
- async executeApply(trx, documentId, documentType, scope, branch, revision, fn, signal) {
2594
+ async executeApply(trx, documentId, documentType, scope, branch, revision, fn, signal, condition) {
2546
2595
  throwIfAborted(signal);
2547
2596
  const atomicTxn = new AtomicTransaction(documentId, documentType, scope, branch, revision);
2548
2597
  await fn(atomicTxn);
2549
2598
  const operations = atomicTxn.getOperations();
2550
2599
  if (operations.length === 0) return [];
2600
+ if (condition) await this.acquireStreamLocks(trx, documentId, scope, branch, condition);
2551
2601
  const latestOp = await trx.selectFrom("Operation").selectAll().where("documentId", "=", documentId).where("scope", "=", scope).where("branch", "=", branch).orderBy("index", "desc").limit(1).executeTakeFirst();
2552
2602
  const currentRevision = latestOp ? latestOp.index : -1;
2553
2603
  if (currentRevision !== revision - 1) {
@@ -2563,12 +2613,15 @@ var KyselyOperationStore = class KyselyOperationStore {
2563
2613
  op.prevOpId = prevOpId;
2564
2614
  prevOpId = op.opId;
2565
2615
  }
2616
+ let insertedCount = operations.length;
2566
2617
  try {
2567
- await trx.insertInto("Operation").values(operations).execute();
2618
+ if (condition && condition.streams.length > 0) insertedCount = await this.insertGuarded(trx, operations, condition);
2619
+ else await trx.insertInto("Operation").values(operations).execute();
2568
2620
  } catch (error) {
2569
2621
  if (error instanceof Error && error.message.includes("unique constraint")) throw new _UniqueConstraintContext(documentId, scope, branch, revision, operations);
2570
2622
  throw error;
2571
2623
  }
2624
+ if (insertedCount !== operations.length) throw new AppendConditionFailedError(condition);
2572
2625
  return operations.map((op) => ({
2573
2626
  index: op.index,
2574
2627
  timestampUtcMs: op.timestampUtcMs.toISOString(),
@@ -2579,6 +2632,69 @@ var KyselyOperationStore = class KyselyOperationStore {
2579
2632
  action: JSON.parse(op.action)
2580
2633
  }));
2581
2634
  }
2635
+ /**
2636
+ * Locks the written stream and every read-set stream, in sorted key order
2637
+ * so that overlapping concurrent appends serialize rather than deadlock.
2638
+ * The locks are still taken one row at a time, so the query preserves that
2639
+ * order. It must stay separate from the guarded insert, which would
2640
+ * otherwise read a snapshot taken before the locks were held.
2641
+ */
2642
+ async acquireStreamLocks(trx, documentId, scope, branch, condition) {
2643
+ const keys = new Set([`${documentId}:${scope}:${branch}`]);
2644
+ for (const stream of condition.streams) keys.add(`${stream.documentId}:${stream.scope}:${stream.branch}`);
2645
+ await sql`
2646
+ with ordered as materialized (
2647
+ select key
2648
+ from unnest(array[${sql.join([...keys].sort())}]::text[]) with ordinality as t(key, ord)
2649
+ order by ord
2650
+ )
2651
+ select pg_advisory_xact_lock(hashtext(key)) from ordered
2652
+ `.execute(trx);
2653
+ }
2654
+ /**
2655
+ * Inserts the staged operations with the condition compiled in as a WHERE
2656
+ * NOT EXISTS guard, making the check and the append one statement. Returns
2657
+ * the rows inserted; zero means the guard failed and nothing was written.
2658
+ */
2659
+ async insertGuarded(trx, operations, condition) {
2660
+ const branches = operations.map((op) => trx.selectNoFrom([
2661
+ sql`${op.jobId}::text`.as("jobId"),
2662
+ sql`${op.opId}::text`.as("opId"),
2663
+ sql`${op.prevOpId}::text`.as("prevOpId"),
2664
+ sql`${op.documentId}::text`.as("documentId"),
2665
+ sql`${op.documentType}::text`.as("documentType"),
2666
+ sql`${op.scope}::text`.as("scope"),
2667
+ sql`${op.branch}::text`.as("branch"),
2668
+ sql`${op.timestampUtcMs}::timestamptz`.as("timestampUtcMs"),
2669
+ sql`${op.index}::integer`.as("index"),
2670
+ sql`${op.action}::jsonb`.as("action"),
2671
+ sql`${op.skip}::integer`.as("skip"),
2672
+ sql`${op.error ?? null}::text`.as("error"),
2673
+ sql`${op.hash}::text`.as("hash")
2674
+ ]).where((eb) => eb.not(eb.exists(eb.selectFrom("Operation").select("Operation.id").where((web) => web.or(condition.streams.map((s) => web.and([
2675
+ web("Operation.documentId", "=", s.documentId),
2676
+ web("Operation.scope", "=", s.scope),
2677
+ web("Operation.branch", "=", s.branch),
2678
+ web("Operation.index", ">", s.revision)
2679
+ ]))))))));
2680
+ let expression = branches[0];
2681
+ for (let i = 1; i < branches.length; i++) expression = expression.unionAll(branches[i]);
2682
+ return (await trx.insertInto("Operation").columns([
2683
+ "jobId",
2684
+ "opId",
2685
+ "prevOpId",
2686
+ "documentId",
2687
+ "documentType",
2688
+ "scope",
2689
+ "branch",
2690
+ "timestampUtcMs",
2691
+ "index",
2692
+ "action",
2693
+ "skip",
2694
+ "error",
2695
+ "hash"
2696
+ ]).expression(expression).returning("id").execute()).length;
2697
+ }
2582
2698
  async findIdempotentReplay(executor, documentId, scope, branch, revision, stagedOps) {
2583
2699
  const minIndex = revision;
2584
2700
  const maxIndex = revision + stagedOps.length - 1;
@@ -2979,6 +3095,6 @@ async function getMigrationStatus(db, schema = REACTOR_SCHEMA) {
2979
3095
  //#region src/core/drive-container-types.ts
2980
3096
  const DEFAULT_DRIVE_CONTAINER_TYPES = new Set(["powerhouse/document-drive", "powerhouse/reactor-drive"]);
2981
3097
  //#endregion
2982
- export { parsePagingOptions as A, DuplicateManifestError as C, DocumentDeletedError as D, ModuleNotFoundError as E, __exportAll as M, DocumentNotFoundError as O, CollectionMembershipCache as S, InvalidModuleError as T, KyselyWriteCache as _, createForwardingPoolInstrumentation as a, createConsistencyToken as b, DuplicateOperationError as c, KyselyKeyframeStore as d, DocumentModelRegistry as f, EventBus as g, KyselyExecutionScope as h, runMigrations as i, throwIfAborted as j, matchesScope as k, OptimisticLockError as l, DriveCollectionId as m, REACTOR_SCHEMA as n, instrumentPgPool as o, SimpleJobExecutor as p, getMigrationStatus as r, KyselyOperationStore as s, DEFAULT_DRIVE_CONTAINER_TYPES as t, RevisionMismatchError as u, KyselyOperationIndex as v, DuplicateModuleError as w, createEmptyConsistencyToken as x, DocumentMetaCache as y };
3098
+ export { DocumentNotFoundError as A, RevisionMismatchError as C, ModuleNotFoundError as D, InvalidModuleError as E, parsePagingOptions as M, throwIfAborted as N, AuthorizationDeniedError as O, __exportAll as P, OptimisticLockError as S, DuplicateModuleError as T, createConsistencyToken as _, createForwardingPoolInstrumentation as a, AppendConditionFailedError as b, KyselyKeyframeStore as c, DriveCollectionId as d, KyselyExecutionScope as f, DocumentMetaCache as g, KyselyOperationIndex as h, runMigrations as i, matchesScope as j, DocumentDeletedError as k, DocumentModelRegistry as l, KyselyWriteCache as m, REACTOR_SCHEMA as n, instrumentPgPool as o, EventBus as p, getMigrationStatus as r, KyselyOperationStore as s, DEFAULT_DRIVE_CONTAINER_TYPES as t, SimpleJobExecutor as u, createEmptyConsistencyToken as v, DuplicateManifestError as w, DuplicateOperationError as x, CollectionMembershipCache as y };
2983
3099
 
2984
- //# sourceMappingURL=drive-container-types-DpJp2AmE.js.map
3100
+ //# sourceMappingURL=drive-container-types-DrQMmpCa.js.map