@powerhousedao/reactor 6.2.2-dev.31 → 6.2.2-dev.32
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-CO2_-cnA.js → build-worker-executor-DisavYJh.js} +2 -2
- package/dist/{build-worker-executor-CO2_-cnA.js.map → build-worker-executor-DisavYJh.js.map} +1 -1
- package/dist/{drive-container-types-BNwEHEXP.js → drive-container-types-CPQDOggp.js} +165 -57
- package/dist/drive-container-types-CPQDOggp.js.map +1 -0
- package/dist/entry.js +2 -2
- package/dist/index.d.ts +16 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +27 -3
- package/dist/index.js.map +1 -1
- package/dist/projection-entry.js +2 -2
- package/dist/{worker-DNfxv5Ir.js → worker-B2JPT0sg.js} +2 -2
- package/dist/{worker-DNfxv5Ir.js.map → worker-B2JPT0sg.js.map} +1 -1
- package/package.json +4 -4
- package/dist/drive-container-types-BNwEHEXP.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _ as DocumentMetaCache, b as CollectionMembershipCache, c as KyselyKeyframeStore, g as KyselyOperationIndex, h as KyselyWriteCache, l as DocumentModelRegistry, m as EventBus, n as REACTOR_SCHEMA, p as KyselyExecutionScope, s as KyselyOperationStore, t as DEFAULT_DRIVE_CONTAINER_TYPES, u as SimpleJobExecutor } from "./drive-container-types-
|
|
1
|
+
import { _ as DocumentMetaCache, b as CollectionMembershipCache, c as KyselyKeyframeStore, g as KyselyOperationIndex, h as KyselyWriteCache, l as DocumentModelRegistry, m as EventBus, n as REACTOR_SCHEMA, p as KyselyExecutionScope, s as KyselyOperationStore, t as DEFAULT_DRIVE_CONTAINER_TYPES, u as SimpleJobExecutor } from "./drive-container-types-CPQDOggp.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) {
|
|
@@ -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-
|
|
83
|
+
//# sourceMappingURL=build-worker-executor-DisavYJh.js.map
|
package/dist/{build-worker-executor-CO2_-cnA.js.map → build-worker-executor-DisavYJh.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-worker-executor-
|
|
1
|
+
{"version":3,"file":"build-worker-executor-DisavYJh.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"}
|
|
@@ -1743,7 +1743,7 @@ function documentDecisionModel(target) {
|
|
|
1743
1743
|
scope: "document"
|
|
1744
1744
|
}
|
|
1745
1745
|
} },
|
|
1746
|
-
|
|
1746
|
+
evaluatesScope() {
|
|
1747
1747
|
return true;
|
|
1748
1748
|
},
|
|
1749
1749
|
decide(model) {
|
|
@@ -1767,6 +1767,7 @@ function comparePositions(a, b) {
|
|
|
1767
1767
|
const aTime = Date.parse(a.operation.timestampUtcMs);
|
|
1768
1768
|
const bTime = Date.parse(b.operation.timestampUtcMs);
|
|
1769
1769
|
if (aTime !== bTime) return aTime - bTime;
|
|
1770
|
+
if (a.streamKey === b.streamKey) return a.operation.index - b.operation.index;
|
|
1770
1771
|
const actionIds = (a.operation.action.id ?? "").localeCompare(b.operation.action.id ?? "");
|
|
1771
1772
|
if (actionIds !== 0) return actionIds;
|
|
1772
1773
|
return (a.operation.id ?? "").localeCompare(b.operation.id ?? "");
|
|
@@ -1799,6 +1800,23 @@ function retractionSkip(nextIndex, firstRetractedIndex) {
|
|
|
1799
1800
|
//#endregion
|
|
1800
1801
|
//#region src/decision/walk.ts
|
|
1801
1802
|
/**
|
|
1803
|
+
* A single forward pass is only correct while a stream's effective operations
|
|
1804
|
+
* are ordered.
|
|
1805
|
+
*/
|
|
1806
|
+
function assertPositionOrder(streamKey, operations) {
|
|
1807
|
+
for (let i = 1; i < operations.length; i++) {
|
|
1808
|
+
const previous = operations[i - 1];
|
|
1809
|
+
const current = operations[i];
|
|
1810
|
+
if (comparePositions({
|
|
1811
|
+
streamKey,
|
|
1812
|
+
operation: previous
|
|
1813
|
+
}, {
|
|
1814
|
+
streamKey,
|
|
1815
|
+
operation: current
|
|
1816
|
+
}) > 0) throw new Error(`Stream ${streamKey} is out of position order: index ${previous.index} at ${previous.timestampUtcMs} precedes index ${current.index} at ${current.timestampUtcMs}`);
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
/**
|
|
1802
1820
|
* Visits every operation in the read-set once, in the order their positions
|
|
1803
1821
|
* fall, and hands back the state each stream held just before it. That state is
|
|
1804
1822
|
* what a decision at that operation reads: everything ahead of it has been
|
|
@@ -1810,10 +1828,14 @@ function retractionSkip(nextIndex, firstRetractedIndex) {
|
|
|
1810
1828
|
* A denied operation is visited but not applied.
|
|
1811
1829
|
*/
|
|
1812
1830
|
function* walkByPosition(streams) {
|
|
1813
|
-
const merged = mergeByPosition(streams.map((stream) =>
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1831
|
+
const merged = mergeByPosition(streams.map((stream) => {
|
|
1832
|
+
const operations = garbageCollect(sortOperations([...stream.operations]));
|
|
1833
|
+
assertPositionOrder(stream.streamKey, operations);
|
|
1834
|
+
return {
|
|
1835
|
+
streamKey: stream.streamKey,
|
|
1836
|
+
operations
|
|
1837
|
+
};
|
|
1838
|
+
}));
|
|
1817
1839
|
const states = new Map(streams.map((stream) => [stream.streamKey, stream.document]));
|
|
1818
1840
|
for (const { streamKey, operation } of merged) {
|
|
1819
1841
|
yield {
|
|
@@ -1829,9 +1851,9 @@ function* walkByPosition(streams) {
|
|
|
1829
1851
|
}
|
|
1830
1852
|
}
|
|
1831
1853
|
//#endregion
|
|
1832
|
-
//#region src/decision/deletion-
|
|
1854
|
+
//#region src/decision/deletion-evaluation.ts
|
|
1833
1855
|
const WRITTEN = "written";
|
|
1834
|
-
/** Whether a read stream counts this action as one that changes
|
|
1856
|
+
/** Whether a read stream counts this action as one that changes an evaluation. */
|
|
1835
1857
|
function canRefuseOthers(operation, readSet) {
|
|
1836
1858
|
return readSet.some((stream) => stream.decidingActions.includes(operation.action.type));
|
|
1837
1859
|
}
|
|
@@ -1849,16 +1871,17 @@ function firstDeleted(candidates) {
|
|
|
1849
1871
|
* and leaves the earlier ones alone. A deletion among the ones passed in does
|
|
1850
1872
|
* the same to those after it.
|
|
1851
1873
|
*/
|
|
1852
|
-
async function
|
|
1874
|
+
async function evaluateDeletionsByPosition(documentId, scope, branch, operations, writeCache, operationStore, signal) {
|
|
1853
1875
|
const definition = documentDecisionModel({
|
|
1854
1876
|
documentId,
|
|
1855
1877
|
branch
|
|
1856
1878
|
});
|
|
1857
1879
|
const readSet = staticReadSet(definition);
|
|
1858
|
-
if (!definition.
|
|
1880
|
+
if (!definition.evaluatesScope(scope)) return operations.map(() => void 0);
|
|
1881
|
+
const evaluating = new Set(operations.map((operation) => operation.id));
|
|
1859
1882
|
const readStreams = await Promise.all(readSet.map(async (stream) => ({
|
|
1860
1883
|
stream,
|
|
1861
|
-
operations: (await operationStore.getSince(stream.query.documentId, stream.query.scope, stream.query.branch, 0, { actionTypes: stream.decidingActions }, void 0, signal)).results
|
|
1884
|
+
operations: (await operationStore.getSince(stream.query.documentId, stream.query.scope, stream.query.branch, 0, { actionTypes: stream.decidingActions }, void 0, signal)).results.filter((operation) => !evaluating.has(operation.id))
|
|
1862
1885
|
})));
|
|
1863
1886
|
const decidingWritten = operations.filter((operation) => canRefuseOthers(operation, readSet));
|
|
1864
1887
|
if (readStreams.every((read) => read.operations.length === 0) && decidingWritten.length === 0) return operations.map(() => void 0);
|
|
@@ -1947,17 +1970,60 @@ var DocumentActionHandler = class {
|
|
|
1947
1970
|
this.logger = logger;
|
|
1948
1971
|
this.driveContainerTypes = driveContainerTypes;
|
|
1949
1972
|
}
|
|
1950
|
-
async execute(job, action, startTime, indexTxn, stores, skip = 0, sourceRemote = "", signal,
|
|
1973
|
+
async execute(job, action, startTime, indexTxn, stores, skip = 0, sourceRemote = "", signal, alreadyEvaluated = false, deniedReason) {
|
|
1974
|
+
if (deniedReason !== void 0) return this.writeDenied(job, action, startTime, indexTxn, stores, skip, sourceRemote, deniedReason, signal);
|
|
1951
1975
|
switch (action.type) {
|
|
1952
1976
|
case "CREATE_DOCUMENT": return this.executeCreate(job, action, startTime, indexTxn, stores, skip, sourceRemote, signal);
|
|
1953
|
-
case "DELETE_DOCUMENT": return this.executeDelete(job, action, startTime, indexTxn, stores, sourceRemote, signal,
|
|
1954
|
-
case "UPGRADE_DOCUMENT": return this.executeUpgrade(job, action, startTime, indexTxn, stores, skip, sourceRemote, signal,
|
|
1977
|
+
case "DELETE_DOCUMENT": return this.executeDelete(job, action, startTime, indexTxn, stores, skip, sourceRemote, signal, alreadyEvaluated);
|
|
1978
|
+
case "UPGRADE_DOCUMENT": return this.executeUpgrade(job, action, startTime, indexTxn, stores, skip, sourceRemote, signal, alreadyEvaluated);
|
|
1955
1979
|
case "ADD_RELATIONSHIP": return this.executeAddRelationship(job, action, startTime, indexTxn, stores, sourceRemote, signal);
|
|
1956
1980
|
case "REMOVE_RELATIONSHIP": return this.executeRemoveRelationship(job, action, startTime, indexTxn, stores, sourceRemote, signal);
|
|
1957
1981
|
case "UPDATE_RELATIONSHIP": return this.executeUpdateRelationship(job, action, startTime, indexTxn, stores, sourceRemote, signal);
|
|
1958
1982
|
default: return buildErrorResult(job, /* @__PURE__ */ new Error(`Unknown document action type: ${action.type}`), startTime);
|
|
1959
1983
|
}
|
|
1960
1984
|
}
|
|
1985
|
+
/** A refused operation holds a position in the stream but changes nothing. */
|
|
1986
|
+
async writeDenied(job, action, startTime, indexTxn, stores, skip, sourceRemote, deniedReason, signal) {
|
|
1987
|
+
let document;
|
|
1988
|
+
try {
|
|
1989
|
+
document = await stores.writeCache.getState(job.documentId, job.scope, job.branch, void 0, signal);
|
|
1990
|
+
} catch (error) {
|
|
1991
|
+
return buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
|
|
1992
|
+
}
|
|
1993
|
+
let operation = createOperation(action, getNextIndexForScope(document, job.scope), skip, {
|
|
1994
|
+
documentId: job.documentId,
|
|
1995
|
+
scope: job.scope,
|
|
1996
|
+
branch: job.branch
|
|
1997
|
+
});
|
|
1998
|
+
operation.deniedReason = deniedReason;
|
|
1999
|
+
operation.hash = hashDocumentStateForScope(document, job.scope);
|
|
2000
|
+
const writeResult = await this.writeOperationToStore(job.documentId, document.header.documentType, job.scope, job.branch, operation, job, startTime, stores, signal);
|
|
2001
|
+
if (!Array.isArray(writeResult)) return writeResult;
|
|
2002
|
+
operation = writeResult[0];
|
|
2003
|
+
updateDocumentRevision(document, job.scope, operation.index);
|
|
2004
|
+
document.operations = {
|
|
2005
|
+
...document.operations,
|
|
2006
|
+
[job.scope]: [...document.operations[job.scope] ?? [], operation]
|
|
2007
|
+
};
|
|
2008
|
+
stores.writeCache.putState(job.documentId, job.scope, job.branch, operation.index, document, SnapshotPosition.Head);
|
|
2009
|
+
indexTxn.write([{
|
|
2010
|
+
...operation,
|
|
2011
|
+
documentId: job.documentId,
|
|
2012
|
+
documentType: document.header.documentType,
|
|
2013
|
+
branch: job.branch,
|
|
2014
|
+
scope: job.scope,
|
|
2015
|
+
sourceRemote
|
|
2016
|
+
}]);
|
|
2017
|
+
stores.documentMetaCache.putDocumentMeta(job.documentId, job.branch, {
|
|
2018
|
+
state: document.state.document,
|
|
2019
|
+
documentType: document.header.documentType,
|
|
2020
|
+
documentScopeRevision: operation.index + 1
|
|
2021
|
+
});
|
|
2022
|
+
return buildSuccessResult(job, operation, job.documentId, document.header.documentType, JSON.stringify({
|
|
2023
|
+
header: document.header,
|
|
2024
|
+
document: document.state.document
|
|
2025
|
+
}), startTime);
|
|
2026
|
+
}
|
|
1961
2027
|
async executeCreate(job, action, startTime, indexTxn, stores, skip = 0, sourceRemote = "", signal) {
|
|
1962
2028
|
if (job.scope !== "document") return {
|
|
1963
2029
|
job,
|
|
@@ -2005,7 +2071,7 @@ var DocumentActionHandler = class {
|
|
|
2005
2071
|
});
|
|
2006
2072
|
return buildSuccessResult(job, operation, document.header.id, document.header.documentType, resultingState, startTime);
|
|
2007
2073
|
}
|
|
2008
|
-
async executeDelete(job, action, startTime, indexTxn, stores, sourceRemote = "", signal,
|
|
2074
|
+
async executeDelete(job, action, startTime, indexTxn, stores, skip = 0, sourceRemote = "", signal, alreadyEvaluated = false) {
|
|
2009
2075
|
const input = action.input;
|
|
2010
2076
|
if (!input.documentId) return buildErrorResult(job, /* @__PURE__ */ new Error("DELETE_DOCUMENT action requires a documentId in input"), startTime);
|
|
2011
2077
|
const documentId = input.documentId;
|
|
@@ -2016,8 +2082,8 @@ var DocumentActionHandler = class {
|
|
|
2016
2082
|
return buildErrorResult(job, /* @__PURE__ */ new Error(`Failed to fetch document before deletion: ${error instanceof Error ? error.message : String(error)}`), startTime);
|
|
2017
2083
|
}
|
|
2018
2084
|
const documentState = document.state.document;
|
|
2019
|
-
if (documentState.isDeleted && !
|
|
2020
|
-
let operation = createOperation(action, getNextIndexForScope(document, job.scope),
|
|
2085
|
+
if (documentState.isDeleted && !alreadyEvaluated) return buildErrorResult(job, new DocumentDeletedError(documentId, documentState.deletedAtUtcIso), startTime);
|
|
2086
|
+
let operation = createOperation(action, getNextIndexForScope(document, job.scope), skip, {
|
|
2021
2087
|
documentId,
|
|
2022
2088
|
scope: job.scope,
|
|
2023
2089
|
branch: job.branch
|
|
@@ -2052,7 +2118,7 @@ var DocumentActionHandler = class {
|
|
|
2052
2118
|
});
|
|
2053
2119
|
return buildSuccessResult(job, operation, documentId, document.header.documentType, resultingState, startTime);
|
|
2054
2120
|
}
|
|
2055
|
-
async executeUpgrade(job, action, startTime, indexTxn, stores, skip = 0, sourceRemote = "", signal,
|
|
2121
|
+
async executeUpgrade(job, action, startTime, indexTxn, stores, skip = 0, sourceRemote = "", signal, alreadyEvaluated = false) {
|
|
2056
2122
|
const input = action.input;
|
|
2057
2123
|
if (!input.documentId) return buildErrorResult(job, /* @__PURE__ */ new Error("UPGRADE_DOCUMENT action requires a documentId in input"), startTime);
|
|
2058
2124
|
const documentId = input.documentId;
|
|
@@ -2065,7 +2131,7 @@ var DocumentActionHandler = class {
|
|
|
2065
2131
|
return buildErrorResult(job, /* @__PURE__ */ new Error(`Failed to fetch document for upgrade: ${error instanceof Error ? error.message : String(error)}`), startTime);
|
|
2066
2132
|
}
|
|
2067
2133
|
const documentState = document.state.document;
|
|
2068
|
-
if (documentState.isDeleted && !
|
|
2134
|
+
if (documentState.isDeleted && !alreadyEvaluated) return buildErrorResult(job, new DocumentDeletedError(documentId, documentState.deletedAtUtcIso), startTime);
|
|
2069
2135
|
const nextIndex = getNextIndexForScope(document, job.scope);
|
|
2070
2136
|
let upgradePath;
|
|
2071
2137
|
if (fromVersion > 0 && fromVersion < toVersion) try {
|
|
@@ -2360,6 +2426,22 @@ var SimpleJobExecutor = class {
|
|
|
2360
2426
|
scope: owc.context.scope,
|
|
2361
2427
|
branch: owc.context.branch
|
|
2362
2428
|
});
|
|
2429
|
+
const reevaluationError = await this.reevaluateIfCriteriaMet({
|
|
2430
|
+
scope: job.scope,
|
|
2431
|
+
operations: actionResult.generatedOperations
|
|
2432
|
+
}, {
|
|
2433
|
+
job,
|
|
2434
|
+
startTime,
|
|
2435
|
+
indexTxn,
|
|
2436
|
+
stores,
|
|
2437
|
+
signal
|
|
2438
|
+
});
|
|
2439
|
+
if (reevaluationError) return {
|
|
2440
|
+
job,
|
|
2441
|
+
success: false,
|
|
2442
|
+
error: reevaluationError,
|
|
2443
|
+
duration: Date.now() - startTime
|
|
2444
|
+
};
|
|
2363
2445
|
const ordinals = await stores.operationIndex.commit(indexTxn, signal);
|
|
2364
2446
|
if (actionResult.operationsWithContext.length > 0) {
|
|
2365
2447
|
for (let i = 0; i < actionResult.operationsWithContext.length; i++) actionResult.operationsWithContext[i].context.ordinal = ordinals[i];
|
|
@@ -2415,12 +2497,13 @@ var SimpleJobExecutor = class {
|
|
|
2415
2497
|
error: /* @__PURE__ */ new Error(`Invalid timestamp "${action.timestampUtcMs}" on action ${action.type} (id: ${action.id})`)
|
|
2416
2498
|
};
|
|
2417
2499
|
let lastYield = performance.now();
|
|
2500
|
+
const replayingAcceptedHistory = job.kind === "load" || deniedReasons !== void 0;
|
|
2418
2501
|
for (let actionIndex = 0; actionIndex < actions.length; actionIndex++) {
|
|
2419
2502
|
const action = actions[actionIndex];
|
|
2420
2503
|
const skip = skipValues?.[actionIndex] ?? 0;
|
|
2421
2504
|
const sourceOperation = sourceOperations?.[actionIndex];
|
|
2422
2505
|
const deniedReason = deniedReasons?.[actionIndex];
|
|
2423
|
-
const result = documentScopeActions.includes(action.type) ? await this.documentActionHandler.execute(job, action, startTime, indexTxn, stores, skip, sourceRemote, signal, this.featureFlags.documentDecisions &&
|
|
2506
|
+
const result = documentScopeActions.includes(action.type) ? await this.documentActionHandler.execute(job, action, startTime, indexTxn, stores, skip, sourceRemote, signal, this.featureFlags.documentDecisions && replayingAcceptedHistory, deniedReason) : await this.executeRegularAction(job, action, startTime, indexTxn, stores, skip, sourceOperation, sourceRemote, signal, deniedReason, replayingAcceptedHistory);
|
|
2424
2507
|
const error = this.accumulateResultOrReturnError(result, generatedOperations, operationsWithContext);
|
|
2425
2508
|
if (error !== null) return {
|
|
2426
2509
|
success: false,
|
|
@@ -2445,11 +2528,11 @@ var SimpleJobExecutor = class {
|
|
|
2445
2528
|
operationsWithContext
|
|
2446
2529
|
};
|
|
2447
2530
|
}
|
|
2448
|
-
async executeRegularAction(job, action, startTime, indexTxn, stores, skip = 0, sourceOperation, sourceRemote = "", signal, deniedReason) {
|
|
2531
|
+
async executeRegularAction(job, action, startTime, indexTxn, stores, skip = 0, sourceOperation, sourceRemote = "", signal, deniedReason, replayingAcceptedHistory = false) {
|
|
2449
2532
|
let appendCondition;
|
|
2450
2533
|
let documentVersion;
|
|
2451
|
-
const
|
|
2452
|
-
if (this.featureFlags.documentDecisions && !
|
|
2534
|
+
const alreadyEvaluated = this.featureFlags.documentDecisions && replayingAcceptedHistory;
|
|
2535
|
+
if (this.featureFlags.documentDecisions && !alreadyEvaluated) {
|
|
2453
2536
|
const target = {
|
|
2454
2537
|
documentId: job.documentId,
|
|
2455
2538
|
branch: job.branch
|
|
@@ -2471,7 +2554,7 @@ var SimpleJobExecutor = class {
|
|
|
2471
2554
|
}, { scopeState: void 0 }) === "deny") return buildErrorResult(job, new DocumentDeletedError(job.documentId, built.model.document.deletedAtUtcIso), startTime);
|
|
2472
2555
|
appendCondition = built.appendCondition;
|
|
2473
2556
|
documentVersion = built.model.document.version;
|
|
2474
|
-
} else if (
|
|
2557
|
+
} else if (alreadyEvaluated) documentVersion = (await stores.writeCache.getState(job.documentId, "document", job.branch, void 0, signal)).state.document.version;
|
|
2475
2558
|
else {
|
|
2476
2559
|
let docMeta;
|
|
2477
2560
|
try {
|
|
@@ -2489,7 +2572,7 @@ var SimpleJobExecutor = class {
|
|
|
2489
2572
|
} catch (error) {
|
|
2490
2573
|
return buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
|
|
2491
2574
|
}
|
|
2492
|
-
if (
|
|
2575
|
+
if (!replayingAcceptedHistory) {
|
|
2493
2576
|
const subject = {
|
|
2494
2577
|
address: action.context?.signer?.user.address,
|
|
2495
2578
|
key: action.context?.signer?.app.key
|
|
@@ -2509,18 +2592,25 @@ var SimpleJobExecutor = class {
|
|
|
2509
2592
|
}
|
|
2510
2593
|
let updatedDocument;
|
|
2511
2594
|
if (deniedReason !== void 0) {
|
|
2512
|
-
const
|
|
2595
|
+
const index = getNextIndexForScope(document, job.scope);
|
|
2596
|
+
const denied = createOperation(action, index, skip, {
|
|
2513
2597
|
documentId: job.documentId,
|
|
2514
2598
|
scope: job.scope,
|
|
2515
2599
|
branch: job.branch
|
|
2516
2600
|
});
|
|
2517
2601
|
denied.deniedReason = deniedReason;
|
|
2518
|
-
|
|
2602
|
+
let standing = document;
|
|
2603
|
+
if (skip > 0) try {
|
|
2604
|
+
standing = await stores.writeCache.getState(job.documentId, job.scope, job.branch, index - skip - 1, signal);
|
|
2605
|
+
} catch (error) {
|
|
2606
|
+
return buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
|
|
2607
|
+
}
|
|
2608
|
+
denied.hash = hashDocumentStateForScope(standing, job.scope);
|
|
2519
2609
|
updatedDocument = {
|
|
2520
|
-
...
|
|
2610
|
+
...standing,
|
|
2521
2611
|
operations: {
|
|
2522
|
-
...
|
|
2523
|
-
[job.scope]: [...
|
|
2612
|
+
...standing.operations,
|
|
2613
|
+
[job.scope]: [...standing.operations[job.scope] ?? [], denied]
|
|
2524
2614
|
}
|
|
2525
2615
|
};
|
|
2526
2616
|
} else try {
|
|
@@ -2600,24 +2690,43 @@ var SimpleJobExecutor = class {
|
|
|
2600
2690
|
};
|
|
2601
2691
|
}
|
|
2602
2692
|
/**
|
|
2603
|
-
*
|
|
2604
|
-
*
|
|
2605
|
-
*
|
|
2606
|
-
*
|
|
2693
|
+
* Re-evaluates the document when a write meets both criteria: it was written
|
|
2694
|
+
* to a stream the model reads, and it is timestamped before an operation
|
|
2695
|
+
* already stored. The caller supplies the timestamp and the reactor does not replace
|
|
2696
|
+
* it, so a mutation job can write such an operation just as a load job can,
|
|
2697
|
+
* which is why both executeJob and executeLoadJob call this.
|
|
2607
2698
|
*/
|
|
2608
|
-
async
|
|
2699
|
+
async reevaluateIfCriteriaMet(criteria, executing) {
|
|
2700
|
+
if (!this.featureFlags.documentDecisions) return;
|
|
2701
|
+
const { job, stores, signal } = executing;
|
|
2702
|
+
if (!staticReadSet(documentDecisionModel({
|
|
2703
|
+
documentId: job.documentId,
|
|
2704
|
+
branch: job.branch
|
|
2705
|
+
})).some((stream) => stream.query.documentId === job.documentId && stream.query.scope === criteria.scope && stream.query.branch === job.branch)) return;
|
|
2706
|
+
const revisions = await stores.operationStore.getRevisions(job.documentId, job.branch, signal);
|
|
2707
|
+
const latest = Date.parse(revisions.latestTimestamp);
|
|
2708
|
+
if (!criteria.operations.some((operation) => Date.parse(operation.timestampUtcMs) < latest)) return;
|
|
2709
|
+
return this.reevaluateDocument(executing);
|
|
2710
|
+
}
|
|
2711
|
+
/**
|
|
2712
|
+
* Re-evaluates every scope the model evaluates. Where an operation's
|
|
2713
|
+
* evaluation differs from what is stored, the tail from that operation is
|
|
2714
|
+
* re-appended, carrying a skip that spans the indices it supersedes.
|
|
2715
|
+
*/
|
|
2716
|
+
async reevaluateDocument(executing) {
|
|
2717
|
+
const { job, startTime, indexTxn, stores, signal } = executing;
|
|
2609
2718
|
const definition = documentDecisionModel({
|
|
2610
2719
|
documentId: job.documentId,
|
|
2611
2720
|
branch: job.branch
|
|
2612
2721
|
});
|
|
2613
2722
|
const revisions = await stores.operationStore.getRevisions(job.documentId, job.branch, signal);
|
|
2614
2723
|
for (const scope of Object.keys(revisions.revision)) {
|
|
2615
|
-
if (!definition.
|
|
2724
|
+
if (!definition.evaluatesScope(scope)) continue;
|
|
2616
2725
|
const stored = (await stores.operationStore.getSince(job.documentId, scope, job.branch, -1, void 0, void 0, signal)).results;
|
|
2617
2726
|
const effective = garbageCollect(sortOperations([...stored]));
|
|
2618
2727
|
if (effective.length === 0) continue;
|
|
2619
|
-
const
|
|
2620
|
-
const firstChange = effective.findIndex((operation, i) => operation.deniedReason !==
|
|
2728
|
+
const reevaluated = await evaluateDeletionsByPosition(job.documentId, scope, job.branch, effective, stores.writeCache, stores.operationStore, signal);
|
|
2729
|
+
const firstChange = effective.findIndex((operation, i) => operation.deniedReason !== reevaluated[i]);
|
|
2621
2730
|
if (firstChange === -1) continue;
|
|
2622
2731
|
const tail = effective.slice(firstChange);
|
|
2623
2732
|
const nextIndex = revisions.revision[scope];
|
|
@@ -2625,7 +2734,7 @@ var SimpleJobExecutor = class {
|
|
|
2625
2734
|
const result = await this.processActions({
|
|
2626
2735
|
...job,
|
|
2627
2736
|
scope
|
|
2628
|
-
}, tail.map((operation) => operation.action), startTime, indexTxn, stores, tail.map((_, i) => i === 0 ? retractionSkip(nextIndex, tail[0].index) : 0), void 0, "", signal,
|
|
2737
|
+
}, tail.map((operation) => operation.action), startTime, indexTxn, stores, tail.map((_, i) => i === 0 ? retractionSkip(nextIndex, tail[0].index) : 0), void 0, "", signal, reevaluated.slice(firstChange));
|
|
2629
2738
|
if (!result.success) return result.error ?? /* @__PURE__ */ new Error(`Re-evaluation of ${job.documentId} ${scope} failed`);
|
|
2630
2739
|
}
|
|
2631
2740
|
}
|
|
@@ -2726,7 +2835,7 @@ var SimpleJobExecutor = class {
|
|
|
2726
2835
|
const skipValues = reshuffledOperations.map((operation) => operation.skip);
|
|
2727
2836
|
let deniedReasons;
|
|
2728
2837
|
if (this.featureFlags.documentDecisions) try {
|
|
2729
|
-
deniedReasons = await
|
|
2838
|
+
deniedReasons = await evaluateDeletionsByPosition(job.documentId, scope, job.branch, reshuffledOperations, stores.writeCache, stores.operationStore, signal);
|
|
2730
2839
|
} catch (error) {
|
|
2731
2840
|
return {
|
|
2732
2841
|
job,
|
|
@@ -2745,23 +2854,22 @@ var SimpleJobExecutor = class {
|
|
|
2745
2854
|
};
|
|
2746
2855
|
stores.writeCache.invalidate(job.documentId, scope, job.branch);
|
|
2747
2856
|
if (scope === "document") stores.documentMetaCache.invalidate(job.documentId, job.branch);
|
|
2748
|
-
const
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
}
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
}
|
|
2857
|
+
const reevaluationError = await this.reevaluateIfCriteriaMet({
|
|
2858
|
+
scope,
|
|
2859
|
+
operations: result.generatedOperations
|
|
2860
|
+
}, {
|
|
2861
|
+
job,
|
|
2862
|
+
startTime,
|
|
2863
|
+
indexTxn,
|
|
2864
|
+
stores,
|
|
2865
|
+
signal
|
|
2866
|
+
});
|
|
2867
|
+
if (reevaluationError) return {
|
|
2868
|
+
job,
|
|
2869
|
+
success: false,
|
|
2870
|
+
error: reevaluationError,
|
|
2871
|
+
duration: Date.now() - startTime
|
|
2872
|
+
};
|
|
2765
2873
|
return {
|
|
2766
2874
|
job,
|
|
2767
2875
|
success: true,
|
|
@@ -3622,4 +3730,4 @@ const DEFAULT_DRIVE_CONTAINER_TYPES = new Set(["powerhouse/document-drive", "pow
|
|
|
3622
3730
|
//#endregion
|
|
3623
3731
|
export { AuthorizationDeniedError as A, DuplicateOperationError as C, DuplicateModuleError as D, DuplicateManifestError as E, throwIfAborted as F, __exportAll as I, DocumentNotFoundError as M, matchesScope as N, InvalidModuleError as O, parsePagingOptions as P, AppendConditionFailedError as S, RevisionMismatchError as T, DocumentMetaCache as _, createForwardingPoolInstrumentation as a, CollectionMembershipCache as b, KyselyKeyframeStore as c, DriveCollectionId as d, buildDecisionModel as f, KyselyOperationIndex as g, KyselyWriteCache as h, runMigrations as i, DocumentDeletedError as j, ModuleNotFoundError as k, DocumentModelRegistry as l, EventBus as m, REACTOR_SCHEMA as n, instrumentPgPool as o, KyselyExecutionScope as p, getMigrationStatus as r, KyselyOperationStore as s, DEFAULT_DRIVE_CONTAINER_TYPES as t, SimpleJobExecutor as u, createConsistencyToken as v, OptimisticLockError as w, APPEND_CONDITION_FAILED_PREFIX as x, createEmptyConsistencyToken as y };
|
|
3624
3732
|
|
|
3625
|
-
//# sourceMappingURL=drive-container-types-
|
|
3733
|
+
//# sourceMappingURL=drive-container-types-CPQDOggp.js.map
|