@powerhousedao/reactor 6.2.2-dev.26 → 6.2.2-dev.28
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-BTWIynvM.js → build-worker-executor-CO2_-cnA.js} +2 -2
- package/dist/{build-worker-executor-BTWIynvM.js.map → build-worker-executor-CO2_-cnA.js.map} +1 -1
- package/dist/{drive-container-types-uigTGO3i.js → drive-container-types-BNwEHEXP.js} +475 -67
- package/dist/drive-container-types-BNwEHEXP.js.map +1 -0
- package/dist/entry.js +2 -2
- package/dist/index.d.ts +39 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -66
- package/dist/index.js.map +1 -1
- package/dist/projection-entry.js +2 -2
- package/dist/{worker-DhlYAOhr.js → worker-DNfxv5Ir.js} +2 -2
- package/dist/{worker-DhlYAOhr.js.map → worker-DNfxv5Ir.js.map} +1 -1
- package/package.json +4 -4
- package/dist/drive-container-types-uigTGO3i.js.map +0 -1
|
@@ -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, decide, defaultBaseState, deriveOperationId, isUndoRedo } from "@powerhousedao/shared/document-model";
|
|
2
|
+
import { DowngradeNotSupportedError, applyDeleteDocumentAction, applyDeleteDocumentAction as applyDeleteDocumentAction$1, applyUpgradeDocumentAction, applyUpgradeDocumentAction as applyUpgradeDocumentAction$1, baseReducerVersion, createPresignedHeader, decide, defaultBaseState, deriveOperationId, garbageCollect, hashDocumentStateForScope, isDenied, isUndoRedo, sortOperations } from "@powerhousedao/shared/document-model";
|
|
3
3
|
import { v4 } from "uuid";
|
|
4
4
|
import { Migrator, sql } from "kysely";
|
|
5
5
|
//#region \0rolldown/runtime.js
|
|
@@ -706,6 +706,7 @@ var KyselyOperationIndex = class KyselyOperationIndex {
|
|
|
706
706
|
skip: op.skip,
|
|
707
707
|
hash: op.hash,
|
|
708
708
|
action: op.action,
|
|
709
|
+
deniedReason: op.deniedReason ?? null,
|
|
709
710
|
sourceRemote: op.sourceRemote
|
|
710
711
|
}));
|
|
711
712
|
operationOrdinals = (await trx.insertInto("operation_index_operations").values(operationRows).returning("ordinal").execute()).map((row) => row.ordinal);
|
|
@@ -837,6 +838,7 @@ var KyselyOperationIndex = class KyselyOperationIndex {
|
|
|
837
838
|
hash: row.hash,
|
|
838
839
|
skip: row.skip,
|
|
839
840
|
action: row.action,
|
|
841
|
+
deniedReason: row.deniedReason ?? void 0,
|
|
840
842
|
id: row.opId
|
|
841
843
|
},
|
|
842
844
|
context: {
|
|
@@ -860,6 +862,7 @@ var KyselyOperationIndex = class KyselyOperationIndex {
|
|
|
860
862
|
hash: row.hash,
|
|
861
863
|
skip: row.skip,
|
|
862
864
|
action: row.action,
|
|
865
|
+
deniedReason: row.deniedReason ?? void 0,
|
|
863
866
|
id: row.opId,
|
|
864
867
|
sourceRemote: row.sourceRemote
|
|
865
868
|
};
|
|
@@ -971,6 +974,20 @@ function keyframeRevision(keyframe, documentId, scope) {
|
|
|
971
974
|
if (typeof nextIndex !== "number") throw new Error(`Corrupt keyframe for document ${documentId} at revision ${keyframe.revision}: header carries no ${scope} revision`);
|
|
972
975
|
return nextIndex - 1;
|
|
973
976
|
}
|
|
977
|
+
/**
|
|
978
|
+
* Records a denied operation in the history without applying it. This is used
|
|
979
|
+
* for auth-rejected operations, as these need to be recorded without being
|
|
980
|
+
* applied.
|
|
981
|
+
*/
|
|
982
|
+
function appendWithoutApplying(document, scope, operation) {
|
|
983
|
+
return {
|
|
984
|
+
...document,
|
|
985
|
+
operations: {
|
|
986
|
+
...document.operations,
|
|
987
|
+
[scope]: [...document.operations[scope] ?? [], operation]
|
|
988
|
+
}
|
|
989
|
+
};
|
|
990
|
+
}
|
|
974
991
|
function extractModuleVersion(doc) {
|
|
975
992
|
const v = doc.state.document.version;
|
|
976
993
|
return v === 0 ? void 0 : v;
|
|
@@ -1254,7 +1271,7 @@ var KyselyWriteCache = class KyselyWriteCache {
|
|
|
1254
1271
|
for (const operation of docScopeOpsAfterKeyframe.results) {
|
|
1255
1272
|
if (documentScopeBound !== void 0 && operation.index > documentScopeBound) break;
|
|
1256
1273
|
lastDocumentScopeOperation = operation;
|
|
1257
|
-
if (operation.error) continue;
|
|
1274
|
+
if (operation.error || isDenied(operation)) continue;
|
|
1258
1275
|
if (operation.action.type === "UPGRADE_DOCUMENT") {
|
|
1259
1276
|
const upgradeAction = operation.action;
|
|
1260
1277
|
const fromVersion = upgradeAction.input.fromVersion;
|
|
@@ -1297,7 +1314,7 @@ var KyselyWriteCache = class KyselyWriteCache {
|
|
|
1297
1314
|
if (documentScopeBound !== void 0 && operation.index > documentScopeBound) break;
|
|
1298
1315
|
lastDocumentScopeOperation = operation;
|
|
1299
1316
|
if (operation.index === 0) continue;
|
|
1300
|
-
if (operation.error) continue;
|
|
1317
|
+
if (operation.error || isDenied(operation)) continue;
|
|
1301
1318
|
if (operation.action.type === "UPGRADE_DOCUMENT") {
|
|
1302
1319
|
const upgradeAction = operation.action;
|
|
1303
1320
|
const fromVersion = upgradeAction.input.fromVersion;
|
|
@@ -1321,7 +1338,7 @@ var KyselyWriteCache = class KyselyWriteCache {
|
|
|
1321
1338
|
docModule = this.registry.getModule(documentType, extractModuleVersion(document));
|
|
1322
1339
|
} else if (operation.action.type === "DELETE_DOCUMENT") applyDeleteDocumentAction(document, operation.action);
|
|
1323
1340
|
else {
|
|
1324
|
-
const protocolVersion = document.header
|
|
1341
|
+
const protocolVersion = baseReducerVersion(document.header);
|
|
1325
1342
|
document = docModule.reducer(document, operation.action, void 0, {
|
|
1326
1343
|
skip: operation.skip,
|
|
1327
1344
|
protocolVersion
|
|
@@ -1368,11 +1385,14 @@ var KyselyWriteCache = class KyselyWriteCache {
|
|
|
1368
1385
|
for (const operation of result.results) {
|
|
1369
1386
|
if (targetRevision !== void 0 && operation.index > targetRevision) break;
|
|
1370
1387
|
const moduleVersion = this.resolveModuleVersionForOp(operation.index, operation.timestampUtcMs, scope, validatedUpgrades, extractModuleVersion(document));
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1388
|
+
if (isDenied(operation)) document = appendWithoutApplying(document, scope, operation);
|
|
1389
|
+
else {
|
|
1390
|
+
const protocolVersion = baseReducerVersion(document.header);
|
|
1391
|
+
document = getModuleCached(moduleVersion).reducer(document, operation.action, void 0, {
|
|
1392
|
+
skip: operation.skip,
|
|
1393
|
+
protocolVersion
|
|
1394
|
+
});
|
|
1395
|
+
}
|
|
1376
1396
|
}
|
|
1377
1397
|
const reachedTarget = targetRevision !== void 0 && result.results.some((op) => op.index >= targetRevision);
|
|
1378
1398
|
hasMorePages = Boolean(result.nextCursor) && !reachedTarget;
|
|
@@ -1437,11 +1457,14 @@ var KyselyWriteCache = class KyselyWriteCache {
|
|
|
1437
1457
|
for (const operation of pagedResults.results) {
|
|
1438
1458
|
if (signal?.aborted) throw new Error("Operation aborted");
|
|
1439
1459
|
if (targetRevision !== void 0 && operation.index > targetRevision) break;
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1460
|
+
if (isDenied(operation)) document = appendWithoutApplying(document, scope, operation);
|
|
1461
|
+
else {
|
|
1462
|
+
const protocolVersion = baseReducerVersion(document.header);
|
|
1463
|
+
document = module.reducer(document, operation.action, void 0, {
|
|
1464
|
+
skip: operation.skip,
|
|
1465
|
+
protocolVersion
|
|
1466
|
+
});
|
|
1467
|
+
}
|
|
1445
1468
|
if (targetRevision !== void 0 && operation.index === targetRevision) break;
|
|
1446
1469
|
}
|
|
1447
1470
|
} catch (err) {
|
|
@@ -1619,6 +1642,260 @@ function reshuffleByTimestamp(startIndex, opsA, opsB) {
|
|
|
1619
1642
|
}));
|
|
1620
1643
|
}
|
|
1621
1644
|
//#endregion
|
|
1645
|
+
//#region src/decision/build-decision-model.ts
|
|
1646
|
+
/**
|
|
1647
|
+
* Reads each projection's stream through the write cache, recording the
|
|
1648
|
+
* revision observed. Static projections resolve first; derived projections
|
|
1649
|
+
* see only those and contribute a map from document id to state. Each
|
|
1650
|
+
* distinct stream is read once and yields one append condition entry.
|
|
1651
|
+
*/
|
|
1652
|
+
async function buildDecisionModel(cache, definition, target, signal) {
|
|
1653
|
+
const decisionModel = definition(target);
|
|
1654
|
+
const projections = Object.entries(decisionModel.projections);
|
|
1655
|
+
const reads = /* @__PURE__ */ new Map();
|
|
1656
|
+
const model = {};
|
|
1657
|
+
for (const [key, projection] of projections) {
|
|
1658
|
+
if (typeof projection.query === "function") continue;
|
|
1659
|
+
model[key] = (await readStream(cache, projection.query, reads, signal)).state;
|
|
1660
|
+
}
|
|
1661
|
+
const staticModel = { ...model };
|
|
1662
|
+
for (const [key, projection] of projections) {
|
|
1663
|
+
if (typeof projection.query !== "function") continue;
|
|
1664
|
+
const queries = projection.query(staticModel);
|
|
1665
|
+
const value = {};
|
|
1666
|
+
for (const query of queries) {
|
|
1667
|
+
const read = await readStream(cache, query, reads, signal);
|
|
1668
|
+
value[query.documentId] = read.state;
|
|
1669
|
+
}
|
|
1670
|
+
model[key] = value;
|
|
1671
|
+
}
|
|
1672
|
+
return {
|
|
1673
|
+
model,
|
|
1674
|
+
appendCondition: { streams: [...reads.values()].map((read) => read.stream) }
|
|
1675
|
+
};
|
|
1676
|
+
}
|
|
1677
|
+
async function readStream(cache, query, reads, signal) {
|
|
1678
|
+
const key = `${query.documentId}:${query.scope}:${query.branch}`;
|
|
1679
|
+
const existing = reads.get(key);
|
|
1680
|
+
if (existing) return existing;
|
|
1681
|
+
const document = await cache.getState(query.documentId, query.scope, query.branch, void 0, signal);
|
|
1682
|
+
const read = {
|
|
1683
|
+
state: document.state[query.scope],
|
|
1684
|
+
stream: {
|
|
1685
|
+
documentId: query.documentId,
|
|
1686
|
+
scope: query.scope,
|
|
1687
|
+
branch: query.branch,
|
|
1688
|
+
revision: observedRevision(document, query.scope)
|
|
1689
|
+
}
|
|
1690
|
+
};
|
|
1691
|
+
reads.set(key, read);
|
|
1692
|
+
return read;
|
|
1693
|
+
}
|
|
1694
|
+
/**
|
|
1695
|
+
* The highest operation index the document reflects for the scope, or -1 if
|
|
1696
|
+
* empty. `header.revision` is authoritative, not the rebuilt operation list.
|
|
1697
|
+
*/
|
|
1698
|
+
function observedRevision(document, scope) {
|
|
1699
|
+
if (scope in document.header.revision) return document.header.revision[scope] - 1;
|
|
1700
|
+
if (scope in document.operations) {
|
|
1701
|
+
const operations = document.operations[scope];
|
|
1702
|
+
if (operations.length > 0) return operations[operations.length - 1].index;
|
|
1703
|
+
}
|
|
1704
|
+
if (!(scope in document.header.revision)) return -1;
|
|
1705
|
+
return document.header.revision[scope] - 1;
|
|
1706
|
+
}
|
|
1707
|
+
/**
|
|
1708
|
+
* The streams a model reads whose queries are known before it is built. A
|
|
1709
|
+
* derived query needs the statically-queried projections first, so it is not
|
|
1710
|
+
* included here.
|
|
1711
|
+
*/
|
|
1712
|
+
function staticReadSet(definition) {
|
|
1713
|
+
const streams = [];
|
|
1714
|
+
for (const projection of Object.values(definition.projections)) {
|
|
1715
|
+
if (typeof projection.query === "function") continue;
|
|
1716
|
+
streams.push({
|
|
1717
|
+
query: projection.query,
|
|
1718
|
+
decidingActions: projection.decidingActions,
|
|
1719
|
+
apply: projection.apply
|
|
1720
|
+
});
|
|
1721
|
+
}
|
|
1722
|
+
return streams;
|
|
1723
|
+
}
|
|
1724
|
+
//#endregion
|
|
1725
|
+
//#region src/decision/document-decision-model.ts
|
|
1726
|
+
/** Why the model refused an operation. */
|
|
1727
|
+
const DOCUMENT_DELETED_REASON = "document deleted";
|
|
1728
|
+
/**
|
|
1729
|
+
* The simplest decision model: one projection over the document scope, which
|
|
1730
|
+
* rejects on a deleted document.
|
|
1731
|
+
*/
|
|
1732
|
+
function documentDecisionModel(target) {
|
|
1733
|
+
return {
|
|
1734
|
+
projections: { document: {
|
|
1735
|
+
decidingActions: ["DELETE_DOCUMENT"],
|
|
1736
|
+
apply: (document, operation) => operation.action.type === "DELETE_DOCUMENT" ? applyDeleteDocumentAction({
|
|
1737
|
+
...document,
|
|
1738
|
+
state: { ...document.state }
|
|
1739
|
+
}, operation.action) : document,
|
|
1740
|
+
query: {
|
|
1741
|
+
documentId: target.documentId,
|
|
1742
|
+
branch: target.branch,
|
|
1743
|
+
scope: "document"
|
|
1744
|
+
}
|
|
1745
|
+
} },
|
|
1746
|
+
judgesScope() {
|
|
1747
|
+
return true;
|
|
1748
|
+
},
|
|
1749
|
+
decide(model) {
|
|
1750
|
+
return model.document.isDeleted ? "deny" : "allow";
|
|
1751
|
+
}
|
|
1752
|
+
};
|
|
1753
|
+
}
|
|
1754
|
+
//#endregion
|
|
1755
|
+
//#region src/decision/merged-order.ts
|
|
1756
|
+
/** Identifies a stream within a walk. */
|
|
1757
|
+
function streamKey(query) {
|
|
1758
|
+
return `${query.documentId}:${query.scope}:${query.branch}`;
|
|
1759
|
+
}
|
|
1760
|
+
/**
|
|
1761
|
+
* Orders two operations from different streams by position. Timestamp decides;
|
|
1762
|
+
* an equal timestamp falls to the action id and then the operation id, so that
|
|
1763
|
+
* two replicas holding the same operations agree on the order whatever order
|
|
1764
|
+
* they happen to store them in.
|
|
1765
|
+
*/
|
|
1766
|
+
function comparePositions(a, b) {
|
|
1767
|
+
const aTime = Date.parse(a.operation.timestampUtcMs);
|
|
1768
|
+
const bTime = Date.parse(b.operation.timestampUtcMs);
|
|
1769
|
+
if (aTime !== bTime) return aTime - bTime;
|
|
1770
|
+
const actionIds = (a.operation.action.id ?? "").localeCompare(b.operation.action.id ?? "");
|
|
1771
|
+
if (actionIds !== 0) return actionIds;
|
|
1772
|
+
return (a.operation.id ?? "").localeCompare(b.operation.id ?? "");
|
|
1773
|
+
}
|
|
1774
|
+
/**
|
|
1775
|
+
* Merges the read-set streams into one sequence by position. Each stream keeps
|
|
1776
|
+
* its stored order; only how far to go is decided by the timestamp, so a stream
|
|
1777
|
+
* whose stored order disagrees with its timestamp order still applies in the
|
|
1778
|
+
* order it is stored.
|
|
1779
|
+
*
|
|
1780
|
+
* An operation's position in the result is the bound a decision at that
|
|
1781
|
+
* operation reads to: every operation before it has been applied, and it has
|
|
1782
|
+
* not.
|
|
1783
|
+
*/
|
|
1784
|
+
function mergeByPosition(streams) {
|
|
1785
|
+
const merged = [];
|
|
1786
|
+
for (const stream of streams) for (const operation of stream.operations) merged.push({
|
|
1787
|
+
streamKey: stream.streamKey,
|
|
1788
|
+
operation
|
|
1789
|
+
});
|
|
1790
|
+
return merged.sort(comparePositions);
|
|
1791
|
+
}
|
|
1792
|
+
/**
|
|
1793
|
+
* For auth-related reshuffles, we may need to retract previous operations.
|
|
1794
|
+
* This function calculates the needed skip value.
|
|
1795
|
+
*/
|
|
1796
|
+
function retractionSkip(nextIndex, firstRetractedIndex) {
|
|
1797
|
+
return nextIndex - firstRetractedIndex;
|
|
1798
|
+
}
|
|
1799
|
+
//#endregion
|
|
1800
|
+
//#region src/decision/walk.ts
|
|
1801
|
+
/**
|
|
1802
|
+
* Visits every operation in the read-set once, in the order their positions
|
|
1803
|
+
* fall, and hands back the state each stream held just before it. That state is
|
|
1804
|
+
* what a decision at that operation reads: everything ahead of it has been
|
|
1805
|
+
* applied and it has not.
|
|
1806
|
+
*
|
|
1807
|
+
* Skips are resolved first (i.e. this is performed on a garbage collected
|
|
1808
|
+
* stream), which means we can do a single forward pass.
|
|
1809
|
+
*
|
|
1810
|
+
* A denied operation is visited but not applied.
|
|
1811
|
+
*/
|
|
1812
|
+
function* walkByPosition(streams) {
|
|
1813
|
+
const merged = mergeByPosition(streams.map((stream) => ({
|
|
1814
|
+
streamKey: stream.streamKey,
|
|
1815
|
+
operations: garbageCollect(sortOperations([...stream.operations]))
|
|
1816
|
+
})));
|
|
1817
|
+
const states = new Map(streams.map((stream) => [stream.streamKey, stream.document]));
|
|
1818
|
+
for (const { streamKey, operation } of merged) {
|
|
1819
|
+
yield {
|
|
1820
|
+
streamKey,
|
|
1821
|
+
operation,
|
|
1822
|
+
states: new Map(states)
|
|
1823
|
+
};
|
|
1824
|
+
if (isDenied(operation)) continue;
|
|
1825
|
+
const stream = streams.find((candidate) => candidate.streamKey === streamKey);
|
|
1826
|
+
const before = states.get(streamKey);
|
|
1827
|
+
if (before === void 0 || stream === void 0) throw new Error(`No state for stream ${streamKey}`);
|
|
1828
|
+
states.set(streamKey, stream.apply(before, operation));
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
//#endregion
|
|
1832
|
+
//#region src/decision/deletion-verdicts.ts
|
|
1833
|
+
const WRITTEN = "written";
|
|
1834
|
+
/** Whether a read stream counts this action as one that changes a verdict. */
|
|
1835
|
+
function canRefuseOthers(operation, readSet) {
|
|
1836
|
+
return readSet.some((stream) => stream.decidingActions.includes(operation.action.type));
|
|
1837
|
+
}
|
|
1838
|
+
/** The deleted one of these, or the first if none of them is deleted. */
|
|
1839
|
+
function firstDeleted(candidates) {
|
|
1840
|
+
for (const candidate of candidates) if (candidate?.state.document.isDeleted) return candidate.state.document;
|
|
1841
|
+
return candidates[0].state.document;
|
|
1842
|
+
}
|
|
1843
|
+
/**
|
|
1844
|
+
* This function determines which operations will be refused because of
|
|
1845
|
+
* deletes, returning reasons in a parallel array (undefined means the
|
|
1846
|
+
* operation is not refused).
|
|
1847
|
+
*
|
|
1848
|
+
* A deletion already in the stream refuses the operations timestamped after it
|
|
1849
|
+
* and leaves the earlier ones alone. A deletion among the ones passed in does
|
|
1850
|
+
* the same to those after it.
|
|
1851
|
+
*/
|
|
1852
|
+
async function deletionVerdictsByPosition(documentId, scope, branch, operations, writeCache, operationStore, signal) {
|
|
1853
|
+
const definition = documentDecisionModel({
|
|
1854
|
+
documentId,
|
|
1855
|
+
branch
|
|
1856
|
+
});
|
|
1857
|
+
const readSet = staticReadSet(definition);
|
|
1858
|
+
if (!definition.judgesScope(scope)) return operations.map(() => void 0);
|
|
1859
|
+
const readStreams = await Promise.all(readSet.map(async (stream) => ({
|
|
1860
|
+
stream,
|
|
1861
|
+
operations: (await operationStore.getSince(stream.query.documentId, stream.query.scope, stream.query.branch, 0, { actionTypes: stream.decidingActions }, void 0, signal)).results
|
|
1862
|
+
})));
|
|
1863
|
+
const decidingWritten = operations.filter((operation) => canRefuseOthers(operation, readSet));
|
|
1864
|
+
if (readStreams.every((read) => read.operations.length === 0) && decidingWritten.length === 0) return operations.map(() => void 0);
|
|
1865
|
+
const walked = [];
|
|
1866
|
+
for (const read of readStreams) {
|
|
1867
|
+
const before = await writeCache.getState(read.stream.query.documentId, read.stream.query.scope, read.stream.query.branch, 0, signal);
|
|
1868
|
+
walked.push({
|
|
1869
|
+
streamKey: streamKey(read.stream.query),
|
|
1870
|
+
document: before,
|
|
1871
|
+
operations: read.operations,
|
|
1872
|
+
apply: read.stream.apply
|
|
1873
|
+
});
|
|
1874
|
+
}
|
|
1875
|
+
const writtenStreamIsRead = readSet.find((stream) => stream.query.scope === scope);
|
|
1876
|
+
walked.push({
|
|
1877
|
+
streamKey: WRITTEN,
|
|
1878
|
+
document: walked[0].document,
|
|
1879
|
+
operations,
|
|
1880
|
+
apply: writtenStreamIsRead?.apply ?? ((document) => document)
|
|
1881
|
+
});
|
|
1882
|
+
const reasons = /* @__PURE__ */ new Map();
|
|
1883
|
+
for (const step of walkByPosition(walked)) {
|
|
1884
|
+
if (step.streamKey !== WRITTEN) continue;
|
|
1885
|
+
const document = firstDeleted([...step.states.values()]);
|
|
1886
|
+
const decision = definition.decide({ document }, {
|
|
1887
|
+
address: void 0,
|
|
1888
|
+
key: void 0
|
|
1889
|
+
}, {
|
|
1890
|
+
verb: "execute",
|
|
1891
|
+
scope,
|
|
1892
|
+
operation: step.operation.action.type
|
|
1893
|
+
}, { scopeState: void 0 });
|
|
1894
|
+
reasons.set(step.operation.id, decision === "deny" ? DOCUMENT_DELETED_REASON : void 0);
|
|
1895
|
+
}
|
|
1896
|
+
return operations.map((operation) => reasons.get(operation.id));
|
|
1897
|
+
}
|
|
1898
|
+
//#endregion
|
|
1622
1899
|
//#region src/cache/operation-index-types.ts
|
|
1623
1900
|
const DRIVE_COLLECTION_PREFIX = "drive.";
|
|
1624
1901
|
/**
|
|
@@ -1670,11 +1947,11 @@ var DocumentActionHandler = class {
|
|
|
1670
1947
|
this.logger = logger;
|
|
1671
1948
|
this.driveContainerTypes = driveContainerTypes;
|
|
1672
1949
|
}
|
|
1673
|
-
async execute(job, action, startTime, indexTxn, stores, skip = 0, sourceRemote = "", signal) {
|
|
1950
|
+
async execute(job, action, startTime, indexTxn, stores, skip = 0, sourceRemote = "", signal, verdictAlreadyDecided = false) {
|
|
1674
1951
|
switch (action.type) {
|
|
1675
1952
|
case "CREATE_DOCUMENT": return this.executeCreate(job, action, startTime, indexTxn, stores, skip, sourceRemote, signal);
|
|
1676
|
-
case "DELETE_DOCUMENT": return this.executeDelete(job, action, startTime, indexTxn, stores, sourceRemote, signal);
|
|
1677
|
-
case "UPGRADE_DOCUMENT": return this.executeUpgrade(job, action, startTime, indexTxn, stores, skip, sourceRemote, signal);
|
|
1953
|
+
case "DELETE_DOCUMENT": return this.executeDelete(job, action, startTime, indexTxn, stores, sourceRemote, signal, verdictAlreadyDecided);
|
|
1954
|
+
case "UPGRADE_DOCUMENT": return this.executeUpgrade(job, action, startTime, indexTxn, stores, skip, sourceRemote, signal, verdictAlreadyDecided);
|
|
1678
1955
|
case "ADD_RELATIONSHIP": return this.executeAddRelationship(job, action, startTime, indexTxn, stores, sourceRemote, signal);
|
|
1679
1956
|
case "REMOVE_RELATIONSHIP": return this.executeRemoveRelationship(job, action, startTime, indexTxn, stores, sourceRemote, signal);
|
|
1680
1957
|
case "UPDATE_RELATIONSHIP": return this.executeUpdateRelationship(job, action, startTime, indexTxn, stores, sourceRemote, signal);
|
|
@@ -1728,7 +2005,7 @@ var DocumentActionHandler = class {
|
|
|
1728
2005
|
});
|
|
1729
2006
|
return buildSuccessResult(job, operation, document.header.id, document.header.documentType, resultingState, startTime);
|
|
1730
2007
|
}
|
|
1731
|
-
async executeDelete(job, action, startTime, indexTxn, stores, sourceRemote = "", signal) {
|
|
2008
|
+
async executeDelete(job, action, startTime, indexTxn, stores, sourceRemote = "", signal, verdictAlreadyDecided = false) {
|
|
1732
2009
|
const input = action.input;
|
|
1733
2010
|
if (!input.documentId) return buildErrorResult(job, /* @__PURE__ */ new Error("DELETE_DOCUMENT action requires a documentId in input"), startTime);
|
|
1734
2011
|
const documentId = input.documentId;
|
|
@@ -1739,7 +2016,7 @@ var DocumentActionHandler = class {
|
|
|
1739
2016
|
return buildErrorResult(job, /* @__PURE__ */ new Error(`Failed to fetch document before deletion: ${error instanceof Error ? error.message : String(error)}`), startTime);
|
|
1740
2017
|
}
|
|
1741
2018
|
const documentState = document.state.document;
|
|
1742
|
-
if (documentState.isDeleted) return buildErrorResult(job, new DocumentDeletedError(documentId, documentState.deletedAtUtcIso), startTime);
|
|
2019
|
+
if (documentState.isDeleted && !verdictAlreadyDecided) return buildErrorResult(job, new DocumentDeletedError(documentId, documentState.deletedAtUtcIso), startTime);
|
|
1743
2020
|
let operation = createOperation(action, getNextIndexForScope(document, job.scope), 0, {
|
|
1744
2021
|
documentId,
|
|
1745
2022
|
scope: job.scope,
|
|
@@ -1775,7 +2052,7 @@ var DocumentActionHandler = class {
|
|
|
1775
2052
|
});
|
|
1776
2053
|
return buildSuccessResult(job, operation, documentId, document.header.documentType, resultingState, startTime);
|
|
1777
2054
|
}
|
|
1778
|
-
async executeUpgrade(job, action, startTime, indexTxn, stores, skip = 0, sourceRemote = "", signal) {
|
|
2055
|
+
async executeUpgrade(job, action, startTime, indexTxn, stores, skip = 0, sourceRemote = "", signal, verdictAlreadyDecided = false) {
|
|
1779
2056
|
const input = action.input;
|
|
1780
2057
|
if (!input.documentId) return buildErrorResult(job, /* @__PURE__ */ new Error("UPGRADE_DOCUMENT action requires a documentId in input"), startTime);
|
|
1781
2058
|
const documentId = input.documentId;
|
|
@@ -1788,7 +2065,7 @@ var DocumentActionHandler = class {
|
|
|
1788
2065
|
return buildErrorResult(job, /* @__PURE__ */ new Error(`Failed to fetch document for upgrade: ${error instanceof Error ? error.message : String(error)}`), startTime);
|
|
1789
2066
|
}
|
|
1790
2067
|
const documentState = document.state.document;
|
|
1791
|
-
if (documentState.isDeleted) return buildErrorResult(job, new DocumentDeletedError(documentId, documentState.deletedAtUtcIso), startTime);
|
|
2068
|
+
if (documentState.isDeleted && !verdictAlreadyDecided) return buildErrorResult(job, new DocumentDeletedError(documentId, documentState.deletedAtUtcIso), startTime);
|
|
1792
2069
|
const nextIndex = getNextIndexForScope(document, job.scope);
|
|
1793
2070
|
let upgradePath;
|
|
1794
2071
|
if (fromVersion > 0 && fromVersion < toVersion) try {
|
|
@@ -2011,6 +2288,7 @@ const documentScopeActions = [
|
|
|
2011
2288
|
*/
|
|
2012
2289
|
var SimpleJobExecutor = class {
|
|
2013
2290
|
config;
|
|
2291
|
+
featureFlags;
|
|
2014
2292
|
signatureVerifierModule;
|
|
2015
2293
|
documentActionHandler;
|
|
2016
2294
|
executionScope;
|
|
@@ -2025,6 +2303,7 @@ var SimpleJobExecutor = class {
|
|
|
2025
2303
|
this.collectionMembershipCache = collectionMembershipCache;
|
|
2026
2304
|
this.driveContainerTypes = driveContainerTypes;
|
|
2027
2305
|
this.config = {
|
|
2306
|
+
featureFlags: config.featureFlags ?? {},
|
|
2028
2307
|
maxSkipThreshold: config.maxSkipThreshold ?? MAX_SKIP_THRESHOLD,
|
|
2029
2308
|
maxConcurrency: config.maxConcurrency ?? 1,
|
|
2030
2309
|
jobTimeoutMs: config.jobTimeoutMs ?? 3e4,
|
|
@@ -2032,6 +2311,7 @@ var SimpleJobExecutor = class {
|
|
|
2032
2311
|
retryMaxDelayMs: config.retryMaxDelayMs ?? 5e3,
|
|
2033
2312
|
yieldDeadlineMs: config.yieldDeadlineMs ?? 50
|
|
2034
2313
|
};
|
|
2314
|
+
this.featureFlags = { documentDecisions: config.featureFlags?.documentDecisions ?? false };
|
|
2035
2315
|
this.signatureVerifierModule = new SignatureVerifier(signatureVerifier);
|
|
2036
2316
|
this.documentActionHandler = new DocumentActionHandler(registry, logger, driveContainerTypes);
|
|
2037
2317
|
this.executionScope = executionScope ?? new DefaultExecutionScope(operationStore, operationIndex, writeCache, documentMetaCache, collectionMembershipCache);
|
|
@@ -2115,7 +2395,7 @@ var SimpleJobExecutor = class {
|
|
|
2115
2395
|
const documentIds = [...new Set(operations.map((op) => op.context.documentId))];
|
|
2116
2396
|
return stores.collectionMembershipCache.getCollectionsForDocuments(documentIds);
|
|
2117
2397
|
}
|
|
2118
|
-
async processActions(job, actions, startTime, indexTxn, stores, skipValues, sourceOperations, sourceRemote = "", signal) {
|
|
2398
|
+
async processActions(job, actions, startTime, indexTxn, stores, skipValues, sourceOperations, sourceRemote = "", signal, deniedReasons) {
|
|
2119
2399
|
const generatedOperations = [];
|
|
2120
2400
|
const operationsWithContext = [];
|
|
2121
2401
|
try {
|
|
@@ -2139,7 +2419,8 @@ var SimpleJobExecutor = class {
|
|
|
2139
2419
|
const action = actions[actionIndex];
|
|
2140
2420
|
const skip = skipValues?.[actionIndex] ?? 0;
|
|
2141
2421
|
const sourceOperation = sourceOperations?.[actionIndex];
|
|
2142
|
-
const
|
|
2422
|
+
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 && job.kind === "load") : await this.executeRegularAction(job, action, startTime, indexTxn, stores, skip, sourceOperation, sourceRemote, signal, deniedReason);
|
|
2143
2424
|
const error = this.accumulateResultOrReturnError(result, generatedOperations, operationsWithContext);
|
|
2144
2425
|
if (error !== null) return {
|
|
2145
2426
|
success: false,
|
|
@@ -2164,14 +2445,43 @@ var SimpleJobExecutor = class {
|
|
|
2164
2445
|
operationsWithContext
|
|
2165
2446
|
};
|
|
2166
2447
|
}
|
|
2167
|
-
async executeRegularAction(job, action, startTime, indexTxn, stores, skip = 0, sourceOperation, sourceRemote = "", signal) {
|
|
2168
|
-
let
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2448
|
+
async executeRegularAction(job, action, startTime, indexTxn, stores, skip = 0, sourceOperation, sourceRemote = "", signal, deniedReason) {
|
|
2449
|
+
let appendCondition;
|
|
2450
|
+
let documentVersion;
|
|
2451
|
+
const verdictAlreadyDecided = this.featureFlags.documentDecisions && job.kind === "load";
|
|
2452
|
+
if (this.featureFlags.documentDecisions && !verdictAlreadyDecided) {
|
|
2453
|
+
const target = {
|
|
2454
|
+
documentId: job.documentId,
|
|
2455
|
+
branch: job.branch
|
|
2456
|
+
};
|
|
2457
|
+
const definition = documentDecisionModel(target);
|
|
2458
|
+
let built;
|
|
2459
|
+
try {
|
|
2460
|
+
built = await buildDecisionModel(stores.writeCache, () => definition, target, signal);
|
|
2461
|
+
} catch (error) {
|
|
2462
|
+
return buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
|
|
2463
|
+
}
|
|
2464
|
+
if (definition.decide(built.model, {
|
|
2465
|
+
address: action.context?.signer?.user.address,
|
|
2466
|
+
key: action.context?.signer?.app.key
|
|
2467
|
+
}, {
|
|
2468
|
+
verb: "execute",
|
|
2469
|
+
scope: action.scope,
|
|
2470
|
+
operation: action.type
|
|
2471
|
+
}, { scopeState: void 0 }) === "deny") return buildErrorResult(job, new DocumentDeletedError(job.documentId, built.model.document.deletedAtUtcIso), startTime);
|
|
2472
|
+
appendCondition = built.appendCondition;
|
|
2473
|
+
documentVersion = built.model.document.version;
|
|
2474
|
+
} else if (verdictAlreadyDecided) documentVersion = (await stores.writeCache.getState(job.documentId, "document", job.branch, void 0, signal)).state.document.version;
|
|
2475
|
+
else {
|
|
2476
|
+
let docMeta;
|
|
2477
|
+
try {
|
|
2478
|
+
docMeta = await stores.documentMetaCache.getDocumentMeta(job.documentId, job.branch, signal);
|
|
2479
|
+
} catch (error) {
|
|
2480
|
+
return buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
|
|
2481
|
+
}
|
|
2482
|
+
if (docMeta.state.isDeleted) return buildErrorResult(job, new DocumentDeletedError(job.documentId, docMeta.state.deletedAtUtcIso), startTime);
|
|
2483
|
+
documentVersion = docMeta.state.version;
|
|
2173
2484
|
}
|
|
2174
|
-
if (docMeta.state.isDeleted) return buildErrorResult(job, new DocumentDeletedError(job.documentId, docMeta.state.deletedAtUtcIso), startTime);
|
|
2175
2485
|
if (isUndoRedo(action) || action.type === "PRUNE" || action.type === "NOOP" && skip > 0) stores.writeCache.invalidate(job.documentId, job.scope, job.branch);
|
|
2176
2486
|
let document;
|
|
2177
2487
|
try {
|
|
@@ -2192,14 +2502,29 @@ var SimpleJobExecutor = class {
|
|
|
2192
2502
|
}
|
|
2193
2503
|
let module;
|
|
2194
2504
|
try {
|
|
2195
|
-
const moduleVersion =
|
|
2505
|
+
const moduleVersion = documentVersion === 0 ? void 0 : documentVersion;
|
|
2196
2506
|
module = this.registry.getModule(document.header.documentType, moduleVersion);
|
|
2197
2507
|
} catch (error) {
|
|
2198
2508
|
return buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
|
|
2199
2509
|
}
|
|
2200
2510
|
let updatedDocument;
|
|
2201
|
-
|
|
2202
|
-
const
|
|
2511
|
+
if (deniedReason !== void 0) {
|
|
2512
|
+
const denied = createOperation(action, getNextIndexForScope(document, job.scope), skip, {
|
|
2513
|
+
documentId: job.documentId,
|
|
2514
|
+
scope: job.scope,
|
|
2515
|
+
branch: job.branch
|
|
2516
|
+
});
|
|
2517
|
+
denied.deniedReason = deniedReason;
|
|
2518
|
+
denied.hash = hashDocumentStateForScope(document, job.scope);
|
|
2519
|
+
updatedDocument = {
|
|
2520
|
+
...document,
|
|
2521
|
+
operations: {
|
|
2522
|
+
...document.operations,
|
|
2523
|
+
[job.scope]: [...document.operations[job.scope] ?? [], denied]
|
|
2524
|
+
}
|
|
2525
|
+
};
|
|
2526
|
+
} else try {
|
|
2527
|
+
const protocolVersion = baseReducerVersion(document.header);
|
|
2203
2528
|
const reducerOptions = sourceOperation ? {
|
|
2204
2529
|
skip,
|
|
2205
2530
|
branch: job.branch,
|
|
@@ -2230,7 +2555,7 @@ var SimpleJobExecutor = class {
|
|
|
2230
2555
|
try {
|
|
2231
2556
|
storedOperations = await stores.operationStore.apply(job.documentId, document.header.documentType, scope, job.branch, newOperation.index, (txn) => {
|
|
2232
2557
|
txn.addOperations(newOperation);
|
|
2233
|
-
}, signal);
|
|
2558
|
+
}, signal, appendCondition);
|
|
2234
2559
|
} catch (error) {
|
|
2235
2560
|
this.logger.error("Error writing @Operation to IOperationStore: @Error", newOperation, error);
|
|
2236
2561
|
stores.writeCache.invalidate(job.documentId, scope, job.branch);
|
|
@@ -2274,13 +2599,43 @@ var SimpleJobExecutor = class {
|
|
|
2274
2599
|
duration: Date.now() - startTime
|
|
2275
2600
|
};
|
|
2276
2601
|
}
|
|
2602
|
+
/**
|
|
2603
|
+
* If an operation happened that could change a decision model verdict, we
|
|
2604
|
+
* need to re-evaluate across the streams that could be affected. Previously
|
|
2605
|
+
* approved operations may need to be denied, which will apply new denied
|
|
2606
|
+
* operations with appropriate skip.
|
|
2607
|
+
*/
|
|
2608
|
+
async reevaluateReadingScopes(job, startTime, indexTxn, stores, signal) {
|
|
2609
|
+
const definition = documentDecisionModel({
|
|
2610
|
+
documentId: job.documentId,
|
|
2611
|
+
branch: job.branch
|
|
2612
|
+
});
|
|
2613
|
+
const revisions = await stores.operationStore.getRevisions(job.documentId, job.branch, signal);
|
|
2614
|
+
for (const scope of Object.keys(revisions.revision)) {
|
|
2615
|
+
if (!definition.judgesScope(scope)) continue;
|
|
2616
|
+
const stored = (await stores.operationStore.getSince(job.documentId, scope, job.branch, -1, void 0, void 0, signal)).results;
|
|
2617
|
+
const effective = garbageCollect(sortOperations([...stored]));
|
|
2618
|
+
if (effective.length === 0) continue;
|
|
2619
|
+
const recomputed = await deletionVerdictsByPosition(job.documentId, scope, job.branch, effective, stores.writeCache, stores.operationStore, signal);
|
|
2620
|
+
const firstChange = effective.findIndex((operation, i) => operation.deniedReason !== recomputed[i]);
|
|
2621
|
+
if (firstChange === -1) continue;
|
|
2622
|
+
const tail = effective.slice(firstChange);
|
|
2623
|
+
const nextIndex = revisions.revision[scope];
|
|
2624
|
+
stores.writeCache.invalidate(job.documentId, scope, job.branch);
|
|
2625
|
+
const result = await this.processActions({
|
|
2626
|
+
...job,
|
|
2627
|
+
scope
|
|
2628
|
+
}, tail.map((operation) => operation.action), startTime, indexTxn, stores, tail.map((_, i) => i === 0 ? retractionSkip(nextIndex, tail[0].index) : 0), void 0, "", signal, recomputed.slice(firstChange));
|
|
2629
|
+
if (!result.success) return result.error ?? /* @__PURE__ */ new Error(`Re-evaluation of ${job.documentId} ${scope} failed`);
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2277
2632
|
async executeLoadJob(job, startTime, indexTxn, stores, signal) {
|
|
2278
2633
|
if (job.operations.length === 0) return buildErrorResult(job, /* @__PURE__ */ new Error("Load job must include at least one operation"), startTime);
|
|
2279
2634
|
let docMeta;
|
|
2280
2635
|
try {
|
|
2281
2636
|
docMeta = await stores.documentMetaCache.getDocumentMeta(job.documentId, job.branch, signal);
|
|
2282
2637
|
} catch {}
|
|
2283
|
-
if (docMeta?.state.isDeleted) return buildErrorResult(job, new DocumentDeletedError(job.documentId, docMeta.state.deletedAtUtcIso), startTime);
|
|
2638
|
+
if (docMeta?.state.isDeleted && !this.featureFlags.documentDecisions) return buildErrorResult(job, new DocumentDeletedError(job.documentId, docMeta.state.deletedAtUtcIso), startTime);
|
|
2284
2639
|
const scope = job.scope;
|
|
2285
2640
|
let latestRevision;
|
|
2286
2641
|
try {
|
|
@@ -2369,8 +2724,19 @@ var SimpleJobExecutor = class {
|
|
|
2369
2724
|
for (const operation of reshuffledOperations) if (operation.action.type === "NOOP") operation.skip = 1;
|
|
2370
2725
|
const actions = reshuffledOperations.map((operation) => operation.action);
|
|
2371
2726
|
const skipValues = reshuffledOperations.map((operation) => operation.skip);
|
|
2727
|
+
let deniedReasons;
|
|
2728
|
+
if (this.featureFlags.documentDecisions) try {
|
|
2729
|
+
deniedReasons = await deletionVerdictsByPosition(job.documentId, scope, job.branch, reshuffledOperations, stores.writeCache, stores.operationStore, signal);
|
|
2730
|
+
} catch (error) {
|
|
2731
|
+
return {
|
|
2732
|
+
job,
|
|
2733
|
+
success: false,
|
|
2734
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
2735
|
+
duration: Date.now() - startTime
|
|
2736
|
+
};
|
|
2737
|
+
}
|
|
2372
2738
|
const effectiveSourceRemote = skipCount > 0 ? "" : job.meta.sourceRemote || "";
|
|
2373
|
-
const result = await this.processActions(job, actions, startTime, indexTxn, stores, skipValues, reshuffledOperations, effectiveSourceRemote, signal);
|
|
2739
|
+
const result = await this.processActions(job, actions, startTime, indexTxn, stores, skipValues, reshuffledOperations, effectiveSourceRemote, signal, deniedReasons);
|
|
2374
2740
|
if (!result.success) return {
|
|
2375
2741
|
job,
|
|
2376
2742
|
success: false,
|
|
@@ -2379,6 +2745,23 @@ var SimpleJobExecutor = class {
|
|
|
2379
2745
|
};
|
|
2380
2746
|
stores.writeCache.invalidate(job.documentId, scope, job.branch);
|
|
2381
2747
|
if (scope === "document") stores.documentMetaCache.invalidate(job.documentId, job.branch);
|
|
2748
|
+
const readsThisStream = staticReadSet(documentDecisionModel({
|
|
2749
|
+
documentId: job.documentId,
|
|
2750
|
+
branch: job.branch
|
|
2751
|
+
})).some((stream) => stream.query.documentId === job.documentId && stream.query.scope === scope && stream.query.branch === job.branch);
|
|
2752
|
+
if (this.featureFlags.documentDecisions && readsThisStream) {
|
|
2753
|
+
const revisions = await stores.operationStore.getRevisions(job.documentId, job.branch, signal);
|
|
2754
|
+
const latest = Date.parse(revisions.latestTimestamp);
|
|
2755
|
+
if (result.generatedOperations.some((operation) => Date.parse(operation.timestampUtcMs) < latest)) {
|
|
2756
|
+
const error = await this.reevaluateReadingScopes(job, startTime, indexTxn, stores, signal);
|
|
2757
|
+
if (error) return {
|
|
2758
|
+
job,
|
|
2759
|
+
success: false,
|
|
2760
|
+
error,
|
|
2761
|
+
duration: Date.now() - startTime
|
|
2762
|
+
};
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2382
2765
|
return {
|
|
2383
2766
|
job,
|
|
2384
2767
|
success: true,
|
|
@@ -2639,6 +3022,7 @@ var AtomicTransaction = class {
|
|
|
2639
3022
|
action: JSON.stringify(op.action),
|
|
2640
3023
|
skip: op.skip,
|
|
2641
3024
|
error: op.error || null,
|
|
3025
|
+
deniedReason: op.deniedReason || null,
|
|
2642
3026
|
hash: op.hash
|
|
2643
3027
|
});
|
|
2644
3028
|
}
|
|
@@ -2745,6 +3129,7 @@ var KyselyOperationStore = class KyselyOperationStore {
|
|
|
2745
3129
|
hash: op.hash,
|
|
2746
3130
|
skip: op.skip,
|
|
2747
3131
|
error: op.error || void 0,
|
|
3132
|
+
deniedReason: op.deniedReason || void 0,
|
|
2748
3133
|
id: op.opId,
|
|
2749
3134
|
action: JSON.parse(op.action)
|
|
2750
3135
|
}));
|
|
@@ -2787,6 +3172,7 @@ var KyselyOperationStore = class KyselyOperationStore {
|
|
|
2787
3172
|
sql`${op.action}::jsonb`.as("action"),
|
|
2788
3173
|
sql`${op.skip}::integer`.as("skip"),
|
|
2789
3174
|
sql`${op.error ?? null}::text`.as("error"),
|
|
3175
|
+
sql`${op.deniedReason ?? null}::text`.as("deniedReason"),
|
|
2790
3176
|
sql`${op.hash}::text`.as("hash")
|
|
2791
3177
|
]).where((eb) => eb.not(eb.exists(eb.selectFrom("Operation").select("Operation.id").where((web) => web.or(condition.streams.map((s) => web.and([
|
|
2792
3178
|
web("Operation.documentId", "=", s.documentId),
|
|
@@ -2809,6 +3195,7 @@ var KyselyOperationStore = class KyselyOperationStore {
|
|
|
2809
3195
|
"action",
|
|
2810
3196
|
"skip",
|
|
2811
3197
|
"error",
|
|
3198
|
+
"deniedReason",
|
|
2812
3199
|
"hash"
|
|
2813
3200
|
]).expression(expression).returning("id").execute()).length;
|
|
2814
3201
|
}
|
|
@@ -2898,6 +3285,7 @@ var KyselyOperationStore = class KyselyOperationStore {
|
|
|
2898
3285
|
hash: row.hash,
|
|
2899
3286
|
skip: row.skip,
|
|
2900
3287
|
error: row.error || void 0,
|
|
3288
|
+
deniedReason: row.deniedReason || void 0,
|
|
2901
3289
|
id: row.opId,
|
|
2902
3290
|
action: row.action
|
|
2903
3291
|
};
|
|
@@ -2983,8 +3371,8 @@ function createForwardingPoolInstrumentation(name) {
|
|
|
2983
3371
|
}
|
|
2984
3372
|
//#endregion
|
|
2985
3373
|
//#region src/storage/migrations/001_create_operation_table.ts
|
|
2986
|
-
var _001_create_operation_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
2987
|
-
async function up$
|
|
3374
|
+
var _001_create_operation_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$14 });
|
|
3375
|
+
async function up$14(db) {
|
|
2988
3376
|
await db.schema.createTable("Operation").addColumn("id", "serial", (col) => col.primaryKey()).addColumn("jobId", "text", (col) => col.notNull()).addColumn("opId", "text", (col) => col.notNull()).addColumn("prevOpId", "text", (col) => col.notNull()).addColumn("writeTimestampUtcMs", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addColumn("documentId", "text", (col) => col.notNull()).addColumn("documentType", "text", (col) => col.notNull()).addColumn("scope", "text", (col) => col.notNull()).addColumn("branch", "text", (col) => col.notNull()).addColumn("timestampUtcMs", "timestamptz", (col) => col.notNull()).addColumn("index", "integer", (col) => col.notNull()).addColumn("action", "jsonb", (col) => col.notNull()).addColumn("skip", "integer", (col) => col.notNull()).addColumn("error", "text").addColumn("hash", "text", (col) => col.notNull()).addUniqueConstraint("unique_revision", [
|
|
2989
3377
|
"documentId",
|
|
2990
3378
|
"scope",
|
|
@@ -3009,8 +3397,8 @@ async function up$13(db) {
|
|
|
3009
3397
|
}
|
|
3010
3398
|
//#endregion
|
|
3011
3399
|
//#region src/storage/migrations/002_create_keyframe_table.ts
|
|
3012
|
-
var _002_create_keyframe_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
3013
|
-
async function up$
|
|
3400
|
+
var _002_create_keyframe_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$13 });
|
|
3401
|
+
async function up$13(db) {
|
|
3014
3402
|
await db.schema.createTable("Keyframe").addColumn("id", "serial", (col) => col.primaryKey()).addColumn("documentId", "text", (col) => col.notNull()).addColumn("documentType", "text", (col) => col.notNull()).addColumn("scope", "text", (col) => col.notNull()).addColumn("branch", "text", (col) => col.notNull()).addColumn("revision", "integer", (col) => col.notNull()).addColumn("document", "jsonb", (col) => col.notNull()).addColumn("createdAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addUniqueConstraint("unique_keyframe", [
|
|
3015
3403
|
"documentId",
|
|
3016
3404
|
"scope",
|
|
@@ -3026,14 +3414,14 @@ async function up$12(db) {
|
|
|
3026
3414
|
}
|
|
3027
3415
|
//#endregion
|
|
3028
3416
|
//#region src/storage/migrations/003_create_document_table.ts
|
|
3029
|
-
var _003_create_document_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
3030
|
-
async function up$
|
|
3417
|
+
var _003_create_document_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$12 });
|
|
3418
|
+
async function up$12(db) {
|
|
3031
3419
|
await db.schema.createTable("Document").addColumn("id", "text", (col) => col.primaryKey()).addColumn("createdAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addColumn("updatedAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).execute();
|
|
3032
3420
|
}
|
|
3033
3421
|
//#endregion
|
|
3034
3422
|
//#region src/storage/migrations/004_create_document_relationship_table.ts
|
|
3035
|
-
var _004_create_document_relationship_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
3036
|
-
async function up$
|
|
3423
|
+
var _004_create_document_relationship_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$11 });
|
|
3424
|
+
async function up$11(db) {
|
|
3037
3425
|
await db.schema.createTable("DocumentRelationship").addColumn("id", "text", (col) => col.primaryKey()).addColumn("sourceId", "text", (col) => col.notNull().references("Document.id").onDelete("cascade")).addColumn("targetId", "text", (col) => col.notNull().references("Document.id").onDelete("cascade")).addColumn("relationshipType", "text", (col) => col.notNull()).addColumn("metadata", "jsonb").addColumn("createdAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addColumn("updatedAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addUniqueConstraint("unique_source_target_type", [
|
|
3038
3426
|
"sourceId",
|
|
3039
3427
|
"targetId",
|
|
@@ -3045,14 +3433,14 @@ async function up$10(db) {
|
|
|
3045
3433
|
}
|
|
3046
3434
|
//#endregion
|
|
3047
3435
|
//#region src/storage/migrations/005_create_indexer_state_table.ts
|
|
3048
|
-
var _005_create_indexer_state_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
3049
|
-
async function up$
|
|
3436
|
+
var _005_create_indexer_state_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$10 });
|
|
3437
|
+
async function up$10(db) {
|
|
3050
3438
|
await db.schema.createTable("IndexerState").addColumn("id", "integer", (col) => col.primaryKey().generatedAlwaysAsIdentity()).addColumn("lastOperationId", "integer", (col) => col.notNull()).addColumn("lastOperationTimestamp", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).execute();
|
|
3051
3439
|
}
|
|
3052
3440
|
//#endregion
|
|
3053
3441
|
//#region src/storage/migrations/006_create_document_snapshot_table.ts
|
|
3054
|
-
var _006_create_document_snapshot_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
3055
|
-
async function up$
|
|
3442
|
+
var _006_create_document_snapshot_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$9 });
|
|
3443
|
+
async function up$9(db) {
|
|
3056
3444
|
await db.schema.createTable("DocumentSnapshot").addColumn("id", "text", (col) => col.primaryKey()).addColumn("documentId", "text", (col) => col.notNull()).addColumn("slug", "text").addColumn("name", "text").addColumn("scope", "text", (col) => col.notNull()).addColumn("branch", "text", (col) => col.notNull()).addColumn("content", "jsonb", (col) => col.notNull()).addColumn("documentType", "text", (col) => col.notNull()).addColumn("lastOperationIndex", "integer", (col) => col.notNull()).addColumn("lastOperationHash", "text", (col) => col.notNull()).addColumn("lastUpdatedAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addColumn("snapshotVersion", "integer", (col) => col.notNull().defaultTo(1)).addColumn("identifiers", "jsonb").addColumn("metadata", "jsonb").addColumn("isDeleted", "boolean", (col) => col.notNull().defaultTo(false)).addColumn("deletedAt", "timestamptz").addUniqueConstraint("unique_doc_scope_branch", [
|
|
3057
3445
|
"documentId",
|
|
3058
3446
|
"scope",
|
|
@@ -3073,8 +3461,8 @@ async function up$8(db) {
|
|
|
3073
3461
|
}
|
|
3074
3462
|
//#endregion
|
|
3075
3463
|
//#region src/storage/migrations/007_create_slug_mapping_table.ts
|
|
3076
|
-
var _007_create_slug_mapping_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
3077
|
-
async function up$
|
|
3464
|
+
var _007_create_slug_mapping_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$8 });
|
|
3465
|
+
async function up$8(db) {
|
|
3078
3466
|
await db.schema.createTable("SlugMapping").addColumn("slug", "text", (col) => col.primaryKey()).addColumn("documentId", "text", (col) => col.notNull()).addColumn("scope", "text", (col) => col.notNull()).addColumn("branch", "text", (col) => col.notNull()).addColumn("createdAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addColumn("updatedAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addUniqueConstraint("unique_docid_scope_branch", [
|
|
3079
3467
|
"documentId",
|
|
3080
3468
|
"scope",
|
|
@@ -3084,14 +3472,14 @@ async function up$7(db) {
|
|
|
3084
3472
|
}
|
|
3085
3473
|
//#endregion
|
|
3086
3474
|
//#region src/storage/migrations/008_create_view_state_table.ts
|
|
3087
|
-
var _008_create_view_state_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
3088
|
-
async function up$
|
|
3475
|
+
var _008_create_view_state_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$7 });
|
|
3476
|
+
async function up$7(db) {
|
|
3089
3477
|
await db.schema.createTable("ViewState").addColumn("readModelId", "text", (col) => col.primaryKey()).addColumn("lastOrdinal", "integer", (col) => col.notNull().defaultTo(0)).addColumn("lastOperationTimestamp", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).execute();
|
|
3090
3478
|
}
|
|
3091
3479
|
//#endregion
|
|
3092
3480
|
//#region src/storage/migrations/009_create_operation_index_tables.ts
|
|
3093
|
-
var _009_create_operation_index_tables_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
3094
|
-
async function up$
|
|
3481
|
+
var _009_create_operation_index_tables_exports = /* @__PURE__ */ __exportAll({ up: () => up$6 });
|
|
3482
|
+
async function up$6(db) {
|
|
3095
3483
|
await db.schema.createTable("document_collections").addColumn("documentId", "text", (col) => col.notNull()).addColumn("collectionId", "text", (col) => col.notNull()).addColumn("joinedOrdinal", "bigint", (col) => col.notNull().defaultTo(0)).addColumn("leftOrdinal", "bigint").addPrimaryKeyConstraint("document_collections_pkey", ["documentId", "collectionId"]).execute();
|
|
3096
3484
|
await db.schema.createIndex("idx_document_collections_collectionId").on("document_collections").column("collectionId").execute();
|
|
3097
3485
|
await db.schema.createIndex("idx_doc_collections_collection_range").on("document_collections").columns(["collectionId", "joinedOrdinal"]).execute();
|
|
@@ -3105,8 +3493,8 @@ async function up$5(db) {
|
|
|
3105
3493
|
}
|
|
3106
3494
|
//#endregion
|
|
3107
3495
|
//#region src/storage/migrations/010_create_sync_tables.ts
|
|
3108
|
-
var _010_create_sync_tables_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
3109
|
-
async function up$
|
|
3496
|
+
var _010_create_sync_tables_exports = /* @__PURE__ */ __exportAll({ up: () => up$5 });
|
|
3497
|
+
async function up$5(db) {
|
|
3110
3498
|
await db.schema.createTable("sync_remotes").addColumn("name", "text", (col) => col.primaryKey()).addColumn("collection_id", "text", (col) => col.notNull()).addColumn("channel_type", "text", (col) => col.notNull()).addColumn("channel_id", "text", (col) => col.notNull().defaultTo("")).addColumn("remote_name", "text", (col) => col.notNull().defaultTo("")).addColumn("channel_parameters", "jsonb", (col) => col.notNull().defaultTo(sql`'{}'::jsonb`)).addColumn("filter_document_ids", "jsonb").addColumn("filter_scopes", "jsonb").addColumn("filter_branch", "text", (col) => col.notNull().defaultTo("main")).addColumn("push_state", "text", (col) => col.notNull().defaultTo("idle")).addColumn("push_last_success_utc_ms", "text").addColumn("push_last_failure_utc_ms", "text").addColumn("push_failure_count", "integer", (col) => col.notNull().defaultTo(0)).addColumn("pull_state", "text", (col) => col.notNull().defaultTo("idle")).addColumn("pull_last_success_utc_ms", "text").addColumn("pull_last_failure_utc_ms", "text").addColumn("pull_failure_count", "integer", (col) => col.notNull().defaultTo(0)).addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).execute();
|
|
3111
3499
|
await db.schema.createIndex("idx_sync_remotes_collection").on("sync_remotes").column("collection_id").execute();
|
|
3112
3500
|
await db.schema.createTable("sync_cursors").addColumn("remote_name", "text", (col) => col.primaryKey().references("sync_remotes.name").onDelete("cascade")).addColumn("cursor_ordinal", "bigint", (col) => col.notNull().defaultTo(0)).addColumn("last_synced_at_utc_ms", "text").addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).execute();
|
|
@@ -3114,8 +3502,8 @@ async function up$4(db) {
|
|
|
3114
3502
|
}
|
|
3115
3503
|
//#endregion
|
|
3116
3504
|
//#region src/storage/migrations/011_add_cursor_type_column.ts
|
|
3117
|
-
var _011_add_cursor_type_column_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
3118
|
-
async function up$
|
|
3505
|
+
var _011_add_cursor_type_column_exports = /* @__PURE__ */ __exportAll({ up: () => up$4 });
|
|
3506
|
+
async function up$4(db) {
|
|
3119
3507
|
await db.deleteFrom("sync_cursors").where("remote_name", "like", "outbox::%").execute();
|
|
3120
3508
|
await db.deleteFrom("sync_remotes").where("name", "like", "outbox::%").execute();
|
|
3121
3509
|
await db.schema.dropTable("sync_cursors").execute();
|
|
@@ -3124,24 +3512,43 @@ async function up$3(db) {
|
|
|
3124
3512
|
}
|
|
3125
3513
|
//#endregion
|
|
3126
3514
|
//#region src/storage/migrations/012_add_source_remote_column.ts
|
|
3127
|
-
var _012_add_source_remote_column_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
3128
|
-
async function up$
|
|
3515
|
+
var _012_add_source_remote_column_exports = /* @__PURE__ */ __exportAll({ up: () => up$3 });
|
|
3516
|
+
async function up$3(db) {
|
|
3129
3517
|
await db.schema.alterTable("operation_index_operations").addColumn("sourceRemote", "text", (col) => col.notNull().defaultTo("")).execute();
|
|
3130
3518
|
}
|
|
3131
3519
|
//#endregion
|
|
3132
3520
|
//#region src/storage/migrations/013_create_sync_dead_letters_table.ts
|
|
3133
|
-
var _013_create_sync_dead_letters_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
3134
|
-
async function up$
|
|
3521
|
+
var _013_create_sync_dead_letters_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$2 });
|
|
3522
|
+
async function up$2(db) {
|
|
3135
3523
|
await db.schema.createTable("sync_dead_letters").addColumn("ordinal", "serial", (col) => col.primaryKey()).addColumn("id", "text", (col) => col.unique().notNull()).addColumn("job_id", "text", (col) => col.notNull()).addColumn("job_dependencies", "jsonb", (col) => col.notNull().defaultTo(sql`'[]'::jsonb`)).addColumn("remote_name", "text", (col) => col.notNull().references("sync_remotes.name").onDelete("cascade")).addColumn("document_id", "text", (col) => col.notNull()).addColumn("scopes", "jsonb", (col) => col.notNull().defaultTo(sql`'[]'::jsonb`)).addColumn("branch", "text", (col) => col.notNull()).addColumn("operations", "jsonb", (col) => col.notNull().defaultTo(sql`'[]'::jsonb`)).addColumn("error_source", "text", (col) => col.notNull()).addColumn("error_message", "text", (col) => col.notNull()).addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).execute();
|
|
3136
3524
|
await db.schema.createIndex("idx_sync_dead_letters_remote").on("sync_dead_letters").column("remote_name").execute();
|
|
3137
3525
|
}
|
|
3138
3526
|
//#endregion
|
|
3139
3527
|
//#region src/storage/migrations/014_create_processor_cursor_table.ts
|
|
3140
|
-
var _014_create_processor_cursor_table_exports = /* @__PURE__ */ __exportAll({ up: () => up });
|
|
3141
|
-
async function up(db) {
|
|
3528
|
+
var _014_create_processor_cursor_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$1 });
|
|
3529
|
+
async function up$1(db) {
|
|
3142
3530
|
await db.schema.createTable("ProcessorCursor").addColumn("processorId", "text", (col) => col.primaryKey()).addColumn("factoryId", "text", (col) => col.notNull()).addColumn("driveId", "text", (col) => col.notNull()).addColumn("processorIndex", "integer", (col) => col.notNull()).addColumn("lastOrdinal", "integer", (col) => col.notNull().defaultTo(sql`0`)).addColumn("status", "text", (col) => col.notNull().defaultTo(sql`'active'`)).addColumn("lastError", "text").addColumn("lastErrorTimestamp", "timestamptz").addColumn("createdAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addColumn("updatedAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).execute();
|
|
3143
3531
|
}
|
|
3144
3532
|
//#endregion
|
|
3533
|
+
//#region src/storage/migrations/015_add_operation_denied_reason.ts
|
|
3534
|
+
var _015_add_operation_denied_reason_exports = /* @__PURE__ */ __exportAll({
|
|
3535
|
+
down: () => down,
|
|
3536
|
+
up: () => up
|
|
3537
|
+
});
|
|
3538
|
+
/**
|
|
3539
|
+
* Records why authorization refused an operation. Separate from `error` so a
|
|
3540
|
+
* denial is distinguishable from a reducer failure without matching on a
|
|
3541
|
+
* message. Null for every operation written before decisions were enforced.
|
|
3542
|
+
*/
|
|
3543
|
+
async function up(db) {
|
|
3544
|
+
await db.schema.alterTable("Operation").addColumn("deniedReason", "text").execute();
|
|
3545
|
+
await db.schema.alterTable("operation_index_operations").addColumn("deniedReason", "text").execute();
|
|
3546
|
+
}
|
|
3547
|
+
async function down(db) {
|
|
3548
|
+
await db.schema.alterTable("operation_index_operations").dropColumn("deniedReason").execute();
|
|
3549
|
+
await db.schema.alterTable("Operation").dropColumn("deniedReason").execute();
|
|
3550
|
+
}
|
|
3551
|
+
//#endregion
|
|
3145
3552
|
//#region src/storage/migrations/migrator.ts
|
|
3146
3553
|
const REACTOR_SCHEMA = "reactor";
|
|
3147
3554
|
const migrations = {
|
|
@@ -3158,7 +3565,8 @@ const migrations = {
|
|
|
3158
3565
|
"011_add_cursor_type_column": _011_add_cursor_type_column_exports,
|
|
3159
3566
|
"012_add_source_remote_column": _012_add_source_remote_column_exports,
|
|
3160
3567
|
"013_create_sync_dead_letters_table": _013_create_sync_dead_letters_table_exports,
|
|
3161
|
-
"014_create_processor_cursor_table": _014_create_processor_cursor_table_exports
|
|
3568
|
+
"014_create_processor_cursor_table": _014_create_processor_cursor_table_exports,
|
|
3569
|
+
"015_add_operation_denied_reason": _015_add_operation_denied_reason_exports
|
|
3162
3570
|
};
|
|
3163
3571
|
var ProgrammaticMigrationProvider = class {
|
|
3164
3572
|
getMigrations() {
|
|
@@ -3212,6 +3620,6 @@ async function getMigrationStatus(db, schema = REACTOR_SCHEMA) {
|
|
|
3212
3620
|
//#region src/core/drive-container-types.ts
|
|
3213
3621
|
const DEFAULT_DRIVE_CONTAINER_TYPES = new Set(["powerhouse/document-drive", "powerhouse/reactor-drive"]);
|
|
3214
3622
|
//#endregion
|
|
3215
|
-
export {
|
|
3623
|
+
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 };
|
|
3216
3624
|
|
|
3217
|
-
//# sourceMappingURL=drive-container-types-
|
|
3625
|
+
//# sourceMappingURL=drive-container-types-BNwEHEXP.js.map
|