@powerhousedao/reactor 6.2.3-dev.33 → 6.2.3-dev.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{build-worker-executor-BonPsKdx.js → build-worker-executor-B9IfcRij.js} +8 -2
- package/dist/build-worker-executor-B9IfcRij.js.map +1 -0
- package/dist/{document-indexer-I6i_YzsR.js → document-indexer-CZqIpdz2.js} +124 -26
- package/dist/document-indexer-CZqIpdz2.js.map +1 -0
- package/dist/entry.js +5 -2
- package/dist/entry.js.map +1 -1
- package/dist/{forwarding-logger-BkwlvD9W.js → forwarding-logger-DMqDs9tE.js} +980 -148
- package/dist/forwarding-logger-DMqDs9tE.js.map +1 -0
- package/dist/index.d.ts +1006 -517
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1593 -215
- package/dist/index.js.map +1 -1
- package/dist/projection-entry.js +42 -6
- package/dist/projection-entry.js.map +1 -1
- package/dist/{projection-shard-manager-CJIdjsd8.js → projection-shard-manager-BSK-GEyP.js} +2 -2
- package/dist/{projection-shard-manager-CJIdjsd8.js.map → projection-shard-manager-BSK-GEyP.js.map} +1 -1
- package/dist/{worker-handle-DjbEQkq-.js → worker-handle-weNGCZme.js} +9 -2
- package/dist/worker-handle-weNGCZme.js.map +1 -0
- package/package.json +4 -4
- package/dist/build-worker-executor-BonPsKdx.js.map +0 -1
- package/dist/document-indexer-I6i_YzsR.js.map +0 -1
- package/dist/forwarding-logger-BkwlvD9W.js.map +0 -1
- package/dist/worker-handle-DjbEQkq-.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { C as KyselyWriteCache, I as DocumentMetaCache, L as CollectionMembershipCache, S as EventBus, _ as KyselyExecutionScope, a as DEFAULT_DRIVE_CONTAINER_TYPES, d as KyselyOperationStore, f as KyselyKeyframeStore, kt as ReactorEventTypes, m as SimpleJobExecutor, o as REACTOR_SCHEMA, p as DocumentModelRegistry, w as KyselyOperationIndex } from "./forwarding-logger-DMqDs9tE.js";
|
|
2
2
|
//#region src/executor/worker/build-worker-executor.ts
|
|
3
3
|
async function defaultLoadFactory(spec) {
|
|
4
4
|
const ref = spec.module;
|
|
@@ -90,10 +90,16 @@ async function buildWorkerExecutor(options) {
|
|
|
90
90
|
const captured = signatureRefusals;
|
|
91
91
|
signatureRefusals = [];
|
|
92
92
|
return captured;
|
|
93
|
+
},
|
|
94
|
+
evictPurged(documentIds) {
|
|
95
|
+
for (const documentId of documentIds) {
|
|
96
|
+
writeCache.invalidate(documentId);
|
|
97
|
+
documentMetaCache.invalidate(documentId);
|
|
98
|
+
}
|
|
93
99
|
}
|
|
94
100
|
};
|
|
95
101
|
}
|
|
96
102
|
//#endregion
|
|
97
103
|
export { defaultLoadFactory as n, buildWorkerExecutor as t };
|
|
98
104
|
|
|
99
|
-
//# sourceMappingURL=build-worker-executor-
|
|
105
|
+
//# sourceMappingURL=build-worker-executor-B9IfcRij.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-worker-executor-B9IfcRij.js","names":[],"sources":["../src/executor/worker/build-worker-executor.ts"],"sourcesContent":["import type {\n DocumentModelModule,\n ISigner,\n OperationWithContext,\n} from \"@powerhousedao/shared/document-model\";\nimport type { ILogger } from \"document-model\";\nimport type { Kysely } from \"kysely\";\nimport { CollectionMembershipCache } from \"../../cache/collection-membership-cache.js\";\nimport { DocumentMetaCache } from \"../../cache/document-meta-cache.js\";\nimport { KyselyOperationIndex } from \"../../cache/kysely-operation-index.js\";\nimport { KyselyWriteCache } from \"../../cache/kysely-write-cache.js\";\nimport type { WriteCacheConfig } from \"../../cache/write-cache-types.js\";\nimport { DEFAULT_DRIVE_CONTAINER_TYPES } from \"../../core/drive-container-types.js\";\nimport type { Database } from \"../../core/types.js\";\nimport { EventBus } from \"../../events/event-bus.js\";\nimport {\n ReactorEventTypes,\n type JobWriteReadyEvent,\n type SignatureRefusedEvent,\n} from \"../../events/types.js\";\nimport { DocumentModelRegistry } from \"../../registry/implementation.js\";\nimport type { JobMeta } from \"../../shared/types.js\";\nimport { KyselyKeyframeStore } from \"../../storage/kysely/keyframe-store.js\";\nimport { KyselyOperationStore } from \"../../storage/kysely/store.js\";\nimport type { Database as StorageDatabase } from \"../../storage/kysely/types.js\";\nimport { REACTOR_SCHEMA } from \"../../storage/migrations/migrator.js\";\nimport { KyselyExecutionScope } from \"../execution-scope.js\";\nimport { SimpleJobExecutor } from \"../simple-job-executor.js\";\nimport type { SignatureTrustPolicy } from \"../../signer/types.js\";\nimport type { JobExecutorConfig } from \"../types.js\";\nimport type {\n FactorySpec,\n InitMessage,\n ModelManifestEntry,\n} from \"./protocol.js\";\n\n/**\n * In-worker capture of the JOB_WRITE_READY event emitted by the executor.\n * The worker forwards `operations` and `jobMeta` back to the parent; the\n * parent re-enriches `collectionMemberships` at emit time.\n */\nexport type WorkerWriteReadyCapture = {\n operations: OperationWithContext[];\n jobMeta: JobMeta;\n};\n\nexport type WorkerExecutorStack = {\n executor: SimpleJobExecutor;\n registry: DocumentModelRegistry;\n /**\n * Synchronously pops the most-recent JOB_WRITE_READY captured on this\n * worker's local event bus and clears it. Returns null if the executor\n * did not produce one for this job.\n */\n takeLastWriteReady(): WorkerWriteReadyCapture | null;\n /** Drains the refusals this worker's executor emitted since the last call. */\n takeSignatureRefusals(): SignatureRefusedEvent[];\n /** Drops purged ids from this worker's write and meta caches. */\n evictPurged(documentIds: string[]): void;\n};\n\nexport type BuildWorkerExecutorOptions = {\n init: InitMessage;\n database: Kysely<Database>;\n logger: ILogger;\n executorConfig?: JobExecutorConfig;\n driveContainerTypes?: ReadonlySet<string>;\n /**\n * Override the module loader used to materialize factory specs. Tests\n * can inject a deterministic resolver instead of touching the real\n * Node module loader.\n */\n loadFactory?: (spec: FactorySpec) => Promise<unknown>;\n};\n\nexport async function defaultLoadFactory(spec: FactorySpec): Promise<unknown> {\n const ref = spec.module;\n const specifier =\n \"filePath\" in ref\n ? new URL(`file://${ref.filePath}`).href\n : ref.packageName;\n const mod = (await import(specifier)) as Record<string, unknown>;\n const exported = mod[ref.exportName];\n if (typeof exported === \"function\") {\n return (exported as (args: unknown) => unknown)(spec.initArgs);\n }\n return exported;\n}\n\nasync function loadModelManifest(\n entries: ModelManifestEntry[],\n loadFactory: (spec: FactorySpec) => Promise<unknown>,\n registry: DocumentModelRegistry,\n logger: ILogger,\n): Promise<void> {\n for (const entry of entries) {\n let module: DocumentModelModule;\n try {\n module = (await loadFactory(entry.spec)) as DocumentModelModule;\n } catch (error) {\n logger.error(\n \"worker failed to load document model: @entry @error\",\n entry,\n error,\n );\n throw error;\n }\n const [result] = registry.registerModules(module);\n if (result.status === \"error\") {\n logger.error(\n \"worker failed to register document model: @entry @error\",\n entry,\n result.error,\n );\n throw result.error;\n }\n }\n}\n\n/**\n * Assembles the in-worker storage stack plus a {@link SimpleJobExecutor}\n * bound to a pre-built Kysely instance. The parent owns the wire protocol\n * and routing; the worker owns everything below `SimpleJobExecutor`.\n *\n * The local event bus exists only to satisfy the executor's contract: its\n * JOB_WRITE_READY emissions are captured here and shipped to the parent\n * via {@link WorkerExecutorStack.takeLastWriteReady}.\n */\nexport async function buildWorkerExecutor(\n options: BuildWorkerExecutorOptions,\n): Promise<WorkerExecutorStack> {\n const { init, database: baseDatabase, logger } = options;\n const driveContainerTypes =\n options.driveContainerTypes ?? DEFAULT_DRIVE_CONTAINER_TYPES;\n const loadFactory = options.loadFactory ?? defaultLoadFactory;\n\n const registry = new DocumentModelRegistry();\n await loadModelManifest(init.models, loadFactory, registry, logger);\n\n let signer: ISigner | undefined;\n if (init.signer) {\n try {\n signer = (await loadFactory(init.signer)) as ISigner;\n } catch (error) {\n logger.error(\n \"worker failed to load signer: @spec @error\",\n init.signer.module,\n error,\n );\n throw error;\n }\n }\n\n let trustPolicy: SignatureTrustPolicy | undefined;\n if (init.trustPolicy) {\n try {\n trustPolicy = (await loadFactory(\n init.trustPolicy,\n )) as SignatureTrustPolicy;\n } catch (error) {\n logger.error(\n \"worker failed to load trust policy: @spec @error\",\n init.trustPolicy.module,\n error,\n );\n throw error;\n }\n }\n\n const database = baseDatabase.withSchema(REACTOR_SCHEMA);\n const operationStore = new KyselyOperationStore(\n database as unknown as Kysely<StorageDatabase>,\n );\n const keyframeStore = new KyselyKeyframeStore(\n database as unknown as Kysely<StorageDatabase>,\n );\n\n const cacheConfig: WriteCacheConfig = {\n maxDocuments: 100,\n ringBufferSize: 10,\n keyframeInterval: 10,\n };\n const writeCache = new KyselyWriteCache(\n keyframeStore,\n operationStore,\n registry,\n cacheConfig,\n );\n await writeCache.startup();\n\n const operationIndex = new KyselyOperationIndex(\n database as unknown as Kysely<StorageDatabase>,\n );\n\n const documentMetaCache = new DocumentMetaCache(operationStore, {\n maxDocuments: 1000,\n });\n await documentMetaCache.startup();\n\n const collectionMembershipCache = new CollectionMembershipCache(\n operationIndex,\n );\n\n const executionScope = new KyselyExecutionScope(\n database as unknown as Kysely<StorageDatabase>,\n operationStore,\n operationIndex,\n keyframeStore,\n writeCache,\n documentMetaCache,\n collectionMembershipCache,\n );\n\n const eventBus = new EventBus();\n let lastWriteReady: WorkerWriteReadyCapture | null = null;\n eventBus.subscribe(\n ReactorEventTypes.JOB_WRITE_READY,\n (_t: number, event: JobWriteReadyEvent) => {\n lastWriteReady = {\n operations: event.operations,\n jobMeta: event.jobMeta,\n };\n },\n );\n\n let signatureRefusals: SignatureRefusedEvent[] = [];\n eventBus.subscribe(\n ReactorEventTypes.SIGNATURE_REFUSED,\n (_t: number, event: SignatureRefusedEvent) => {\n signatureRefusals.push(event);\n },\n );\n\n const executorConfig = options.executorConfig ?? {};\n const executor = new SimpleJobExecutor(\n logger,\n registry,\n operationStore,\n eventBus,\n writeCache,\n operationIndex,\n documentMetaCache,\n collectionMembershipCache,\n driveContainerTypes,\n executorConfig,\n executionScope,\n signer,\n trustPolicy,\n );\n\n return {\n executor,\n registry,\n takeLastWriteReady(): WorkerWriteReadyCapture | null {\n const captured = lastWriteReady;\n lastWriteReady = null;\n return captured;\n },\n takeSignatureRefusals(): SignatureRefusedEvent[] {\n const captured = signatureRefusals;\n signatureRefusals = [];\n return captured;\n },\n evictPurged(documentIds: string[]): void {\n for (const documentId of documentIds) {\n writeCache.invalidate(documentId);\n documentMetaCache.invalidate(documentId);\n }\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { createAuthState, isDenied } from "@powerhousedao/shared/document-model";
|
|
1
|
+
import { D as acquirePurgeLocks, M as createKyselyWatermarkProbe, Nt as yieldToMain, O as findPurged, gt as DocumentNotFoundError, j as SettledWatermark, kt as ReactorEventTypes } from "./forwarding-logger-DMqDs9tE.js";
|
|
2
|
+
import { createAuthState, isDenied, isPurgeMarker } from "@powerhousedao/shared/document-model";
|
|
3
3
|
import { v4 } from "uuid";
|
|
4
4
|
import { childLogger } from "document-model";
|
|
5
5
|
//#region src/catch-up/scheduler.ts
|
|
@@ -336,11 +336,27 @@ function mergeByOrdinal(...lists) {
|
|
|
336
336
|
for (const list of lists) for (const item of list) byOrdinal.set(ordinalOf(item), item);
|
|
337
337
|
return [...byOrdinal.values()].sort((a, b) => ordinalOf(a) - ordinalOf(b));
|
|
338
338
|
}
|
|
339
|
+
/** The ids whose purge an operation's rows must not outlive. */
|
|
340
|
+
function purgeFenceIds(items) {
|
|
341
|
+
const ids = /* @__PURE__ */ new Set();
|
|
342
|
+
for (const { operation, context } of items) {
|
|
343
|
+
ids.add(context.documentId);
|
|
344
|
+
if (operation.action.type !== "ADD_RELATIONSHIP") continue;
|
|
345
|
+
const input = operation.action.input;
|
|
346
|
+
if (typeof input.sourceId === "string") ids.add(input.sourceId);
|
|
347
|
+
if (typeof input.targetId === "string") ids.add(input.targetId);
|
|
348
|
+
}
|
|
349
|
+
return [...ids].sort();
|
|
350
|
+
}
|
|
351
|
+
function dropPurged(items, purged) {
|
|
352
|
+
if (purged.size === 0) return items;
|
|
353
|
+
return items.filter((item) => isPurgeMarker(item.operation) || !purged.has(item.context.documentId));
|
|
354
|
+
}
|
|
339
355
|
function errorMessage(error) {
|
|
340
356
|
return error instanceof Error ? error.message : String(error);
|
|
341
357
|
}
|
|
342
358
|
/** A read model whose cursor only sweeps and boot replay advance. */
|
|
343
|
-
var BaseReadModel = class {
|
|
359
|
+
var BaseReadModel = class BaseReadModel {
|
|
344
360
|
db;
|
|
345
361
|
operationIndex;
|
|
346
362
|
writeCache;
|
|
@@ -372,7 +388,10 @@ var BaseReadModel = class {
|
|
|
372
388
|
]);
|
|
373
389
|
this.indexing = normalizeIndexingConfig(config.indexing ?? defaultReadModelIndexingConfig);
|
|
374
390
|
this.cursor = new ContiguousCursor(0, this.maxTrackedAboveCursor);
|
|
391
|
+
if ((config.purgeFence ?? "locked") === "locked" && this.commitOperations !== BaseReadModel.prototype.commitOperations && !this.constructor.commitsInFenceTransaction) throw new Error(`Read model ${config.readModelId}: purgeFence "locked" commits in a transaction its commitOperations must write through. Write through the trx argument and set static commitsInFenceTransaction to true, or set purgeFence "skip" (rows on another handle) or "none" (no rows here, or its own transaction).`);
|
|
375
392
|
}
|
|
393
|
+
/** Set by a subclass whose commitOperations writes through its trx argument. */
|
|
394
|
+
static commitsInFenceTransaction = false;
|
|
376
395
|
get consumerId() {
|
|
377
396
|
return this.config.readModelId;
|
|
378
397
|
}
|
|
@@ -504,7 +523,39 @@ var BaseReadModel = class {
|
|
|
504
523
|
}
|
|
505
524
|
/** Called after the cursor advances. */
|
|
506
525
|
onCursorAdvanced(appliedThrough) {}
|
|
507
|
-
|
|
526
|
+
/** Writes through `trx` when given: the purge fence's transaction. */
|
|
527
|
+
async commitOperations(items, trx) {}
|
|
528
|
+
/** The handle document_purges is read through; `db` may be the fence trx. */
|
|
529
|
+
purgeLookup(db) {
|
|
530
|
+
return db;
|
|
531
|
+
}
|
|
532
|
+
/** False when the batch writes no rows, so the fence opens no transaction. */
|
|
533
|
+
writesRows(items) {
|
|
534
|
+
return true;
|
|
535
|
+
}
|
|
536
|
+
/** Every delivery commits through here. */
|
|
537
|
+
async commitFenced(items) {
|
|
538
|
+
const fence = this.config.purgeFence ?? "locked";
|
|
539
|
+
if (fence === "none") {
|
|
540
|
+
await this.commitOperations(items);
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
if (items.length === 0 || !this.writesRows(items)) {
|
|
544
|
+
if (items.length > 0) await this.commitOperations(items);
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
const ids = purgeFenceIds(items);
|
|
548
|
+
if (fence === "skip") {
|
|
549
|
+
const live = dropPurged(items, await findPurged(this.purgeLookup(this.db), ids));
|
|
550
|
+
if (live.length > 0) await this.commitOperations(live);
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
await this.db.transaction().execute(async (trx) => {
|
|
554
|
+
await acquirePurgeLocks(trx, ids, "shared");
|
|
555
|
+
const live = dropPurged(items, await findPurged(this.purgeLookup(trx), ids));
|
|
556
|
+
if (live.length > 0) await this.commitOperations(live, trx);
|
|
557
|
+
});
|
|
558
|
+
}
|
|
508
559
|
/** Rebuilds resultingState as the executor writes it: scopes plus header. */
|
|
509
560
|
async rebuildStateForOperations(operations) {
|
|
510
561
|
const result = [];
|
|
@@ -615,7 +666,7 @@ var BaseReadModel = class {
|
|
|
615
666
|
}
|
|
616
667
|
const chunk = items.slice(start, start + commitChunkSize);
|
|
617
668
|
try {
|
|
618
|
-
await this.
|
|
669
|
+
await this.commitFenced(chunk);
|
|
619
670
|
} catch (error) {
|
|
620
671
|
this.failedItem = chunk[0];
|
|
621
672
|
const prefix = items.slice(0, committed);
|
|
@@ -722,7 +773,7 @@ var BaseReadModel = class {
|
|
|
722
773
|
};
|
|
723
774
|
}
|
|
724
775
|
try {
|
|
725
|
-
await this.
|
|
776
|
+
await this.commitFenced(rebuilt.items);
|
|
726
777
|
} catch (error) {
|
|
727
778
|
this.cursor.settle(owned, false);
|
|
728
779
|
return {
|
|
@@ -741,7 +792,7 @@ var BaseReadModel = class {
|
|
|
741
792
|
reapplied: rebuilt.items.length - replayed
|
|
742
793
|
};
|
|
743
794
|
}
|
|
744
|
-
/** A document gone from the write cache
|
|
795
|
+
/** A document gone from the write cache, or purged, is absent. */
|
|
745
796
|
async rebuildIfConfigured(items) {
|
|
746
797
|
if (!this.config.rebuildStateOnInit) return {
|
|
747
798
|
items,
|
|
@@ -750,11 +801,15 @@ var BaseReadModel = class {
|
|
|
750
801
|
const rebuilt = [];
|
|
751
802
|
const absent = [];
|
|
752
803
|
for (const item of items) {
|
|
804
|
+
if (isPurgeMarker(item.operation)) {
|
|
805
|
+
rebuilt.push(item);
|
|
806
|
+
continue;
|
|
807
|
+
}
|
|
753
808
|
let result;
|
|
754
809
|
try {
|
|
755
810
|
result = await this.rebuildStateForOperations([item]);
|
|
756
811
|
} catch (error) {
|
|
757
|
-
if (!(error
|
|
812
|
+
if (!DocumentNotFoundError.isError(error) && !await this.isPurged(item.context.documentId)) throw error;
|
|
758
813
|
this.catchUpLogger.warn("@consumer dropped ordinal @ordinal: document @documentId is gone", this.consumerId, ordinalOf(item), item.context.documentId);
|
|
759
814
|
absent.push(ordinalOf(item));
|
|
760
815
|
continue;
|
|
@@ -766,6 +821,14 @@ var BaseReadModel = class {
|
|
|
766
821
|
absent
|
|
767
822
|
};
|
|
768
823
|
}
|
|
824
|
+
/** A failed lookup is no answer: the caller's own error stands. */
|
|
825
|
+
async isPurged(documentId) {
|
|
826
|
+
try {
|
|
827
|
+
return (await findPurged(this.purgeLookup(this.db), [documentId])).has(documentId);
|
|
828
|
+
} catch {
|
|
829
|
+
return false;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
769
832
|
block(item, error) {
|
|
770
833
|
const blockedAt = {
|
|
771
834
|
ordinal: ordinalOf(item),
|
|
@@ -1044,13 +1107,15 @@ let DeletedDocumentRead = /* @__PURE__ */ function(DeletedDocumentRead) {
|
|
|
1044
1107
|
var KyselyDocumentView = class extends BaseReadModel {
|
|
1045
1108
|
operationStore;
|
|
1046
1109
|
deletedDocumentRead;
|
|
1110
|
+
static commitsInFenceTransaction = true;
|
|
1047
1111
|
_db;
|
|
1048
1112
|
constructor(db, operationStore, operationIndex, writeCache, consistencyTracker, deletedDocumentRead, indexing = defaultReadModelIndexingConfig) {
|
|
1049
1113
|
super(db, operationIndex, writeCache, consistencyTracker, {
|
|
1050
1114
|
readModelId: DOCUMENT_VIEW_READ_MODEL,
|
|
1051
1115
|
rebuildStateOnInit: true,
|
|
1052
1116
|
indexing,
|
|
1053
|
-
replayStreamSuffix: false
|
|
1117
|
+
replayStreamSuffix: false,
|
|
1118
|
+
purgeFence: "locked"
|
|
1054
1119
|
});
|
|
1055
1120
|
this.operationStore = operationStore;
|
|
1056
1121
|
this.deletedDocumentRead = deletedDocumentRead;
|
|
@@ -1070,10 +1135,11 @@ var KyselyDocumentView = class extends BaseReadModel {
|
|
|
1070
1135
|
* at least the one the row already carries, so an older duplicate from a
|
|
1071
1136
|
* sweep, boot replay or chunk interleave never rolls a scope back.
|
|
1072
1137
|
*/
|
|
1073
|
-
async commitOperations(items) {
|
|
1074
|
-
|
|
1138
|
+
async commitOperations(items, fenced) {
|
|
1139
|
+
const write = async (trx) => {
|
|
1075
1140
|
for (const item of items) {
|
|
1076
1141
|
const { operation, context } = item;
|
|
1142
|
+
if (isPurgeMarker(operation)) continue;
|
|
1077
1143
|
const { documentId, scope, branch, documentType, resultingState, ordinal } = context;
|
|
1078
1144
|
const { index, hash } = operation;
|
|
1079
1145
|
if (!resultingState) throw new Error(`Missing resultingState in context for operation ${operation.id || "unknown"}. IDocumentView requires resultingState from upstream - it does not rebuild documents.`);
|
|
@@ -1178,7 +1244,12 @@ var KyselyDocumentView = class extends BaseReadModel {
|
|
|
1178
1244
|
}
|
|
1179
1245
|
}
|
|
1180
1246
|
}
|
|
1181
|
-
}
|
|
1247
|
+
};
|
|
1248
|
+
if (fenced) {
|
|
1249
|
+
await write(fenced);
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
await this._db.transaction().execute(write);
|
|
1182
1253
|
}
|
|
1183
1254
|
async exists(documentIds, existence, consistencyToken, signal) {
|
|
1184
1255
|
if (consistencyToken) await this.waitForConsistency(consistencyToken, void 0, signal);
|
|
@@ -1202,7 +1273,7 @@ var KyselyDocumentView = class extends BaseReadModel {
|
|
|
1202
1273
|
if (this.deletedDocumentRead === "NotFound") query = query.where("isDeleted", "=", false);
|
|
1203
1274
|
if (scopesToQuery.length > 0) query = query.where("scope", "in", scopesToQuery);
|
|
1204
1275
|
const snapshots = await query.execute();
|
|
1205
|
-
if (snapshots.length === 0) throw new
|
|
1276
|
+
if (snapshots.length === 0) throw new DocumentNotFoundError(documentId, `Document not found: ${documentId}`);
|
|
1206
1277
|
if (signal?.aborted) throw new Error("Operation aborted");
|
|
1207
1278
|
const headerSnapshot = snapshots.find((s) => s.scope === "header");
|
|
1208
1279
|
if (!headerSnapshot) throw new Error(`Document header not found: ${documentId}`);
|
|
@@ -1345,7 +1416,7 @@ var KyselyDocumentView = class extends BaseReadModel {
|
|
|
1345
1416
|
const slugMatchDocId = slugMatch?.documentId;
|
|
1346
1417
|
if (idMatchDocId && slugMatchDocId && idMatchDocId !== slugMatchDocId) throw new Error(`Ambiguous identifier "${identifier}": matches both document ID "${idMatchDocId}" and slug for document ID "${slugMatchDocId}". Please use get() for ID or resolveSlug() + get() for slug to be explicit.`);
|
|
1347
1418
|
const resolvedDocumentId = idMatchDocId || slugMatchDocId;
|
|
1348
|
-
if (!resolvedDocumentId) throw new
|
|
1419
|
+
if (!resolvedDocumentId) throw new DocumentNotFoundError(identifier, `Document not found: ${identifier}`);
|
|
1349
1420
|
return resolvedDocumentId;
|
|
1350
1421
|
}
|
|
1351
1422
|
/**
|
|
@@ -1488,29 +1559,56 @@ async function collectAllPages(firstPage, signal) {
|
|
|
1488
1559
|
function isRelationshipAction(actionType) {
|
|
1489
1560
|
return actionType === "ADD_RELATIONSHIP" || actionType === "REMOVE_RELATIONSHIP" || actionType === "UPDATE_RELATIONSHIP";
|
|
1490
1561
|
}
|
|
1562
|
+
function addRelationshipIds(items) {
|
|
1563
|
+
const ids = [];
|
|
1564
|
+
for (const { operation } of items) {
|
|
1565
|
+
if (operation.action.type !== "ADD_RELATIONSHIP") continue;
|
|
1566
|
+
const { sourceId, targetId } = operation.action.input;
|
|
1567
|
+
if (typeof sourceId === "string") ids.push(sourceId);
|
|
1568
|
+
if (typeof targetId === "string") ids.push(targetId);
|
|
1569
|
+
}
|
|
1570
|
+
return ids;
|
|
1571
|
+
}
|
|
1572
|
+
function touchesPurged(operation, purged) {
|
|
1573
|
+
const input = operation.action.input;
|
|
1574
|
+
return purged.has(input.sourceId) || purged.has(input.targetId);
|
|
1575
|
+
}
|
|
1491
1576
|
var KyselyDocumentIndexer = class extends BaseReadModel {
|
|
1577
|
+
static commitsInFenceTransaction = true;
|
|
1492
1578
|
_db;
|
|
1493
1579
|
constructor(db, operationIndex, writeCache, consistencyTracker, indexing = defaultReadModelIndexingConfig) {
|
|
1494
1580
|
super(db, operationIndex, writeCache, consistencyTracker, {
|
|
1495
1581
|
readModelId: DOCUMENT_INDEXER_READ_MODEL,
|
|
1496
1582
|
rebuildStateOnInit: false,
|
|
1497
|
-
indexing
|
|
1583
|
+
indexing,
|
|
1584
|
+
purgeFence: "locked"
|
|
1498
1585
|
});
|
|
1499
1586
|
this._db = db;
|
|
1500
1587
|
}
|
|
1588
|
+
writesRows(items) {
|
|
1589
|
+
return items.some((item) => isRelationshipAction(item.operation.action.type));
|
|
1590
|
+
}
|
|
1501
1591
|
/** Opens no transaction for a batch carrying no relationship operation. */
|
|
1502
|
-
async commitOperations(items) {
|
|
1592
|
+
async commitOperations(items, fenced) {
|
|
1503
1593
|
const relationshipOps = items.filter((item) => isRelationshipAction(item.operation.action.type));
|
|
1504
1594
|
if (relationshipOps.length === 0) return;
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1595
|
+
if (fenced) {
|
|
1596
|
+
await this.writeRelationships(relationshipOps, fenced);
|
|
1597
|
+
return;
|
|
1598
|
+
}
|
|
1599
|
+
await this._db.transaction().execute((trx) => this.writeRelationships(relationshipOps, trx));
|
|
1600
|
+
}
|
|
1601
|
+
async writeRelationships(relationshipOps, trx) {
|
|
1602
|
+
const purged = await findPurged(trx, addRelationshipIds(relationshipOps));
|
|
1603
|
+
for (const item of relationshipOps) {
|
|
1604
|
+
const { operation } = item;
|
|
1605
|
+
const actionType = operation.action.type;
|
|
1606
|
+
if (actionType === "ADD_RELATIONSHIP") {
|
|
1607
|
+
if (touchesPurged(operation, purged)) continue;
|
|
1608
|
+
await this.handleAddRelationship(trx, operation);
|
|
1609
|
+
} else if (actionType === "REMOVE_RELATIONSHIP") await this.handleRemoveRelationship(trx, operation);
|
|
1610
|
+
else if (actionType === "UPDATE_RELATIONSHIP") await this.handleUpdateRelationship(trx, operation);
|
|
1611
|
+
}
|
|
1514
1612
|
}
|
|
1515
1613
|
async getOutgoing(documentId, types, paging, consistencyToken, signal) {
|
|
1516
1614
|
if (consistencyToken) await this.waitForConsistency(consistencyToken, void 0, signal);
|
|
@@ -1734,4 +1832,4 @@ var KyselyDocumentIndexer = class extends BaseReadModel {
|
|
|
1734
1832
|
//#endregion
|
|
1735
1833
|
export { ContiguousCursor as _, KyselyDocumentView as a, isCatchUpConsumer as b, ReadModelCoordinator as c, reserveBatch as d, BaseReadModel as f, unchunkedReadModelIndexingConfig as g, defaultReadModelIndexingConfig as h, DeletedDocumentRead as i, indexReserved as l, DEFAULT_READ_MODEL_YIELD_DEADLINE_MS as m, ConsistencyTracker as n, DOCUMENT_INDEXER_READ_MODEL as o, DEFAULT_COMMIT_CHUNK_SIZE as p, makeConsistencyKey as r, DOCUMENT_VIEW_READ_MODEL as s, KyselyDocumentIndexer as t, releaseBatch as u, defaultCatchUpConfig as v, CatchUpScheduler as y };
|
|
1736
1834
|
|
|
1737
|
-
//# sourceMappingURL=document-indexer-
|
|
1835
|
+
//# sourceMappingURL=document-indexer-CZqIpdz2.js.map
|