@powerhousedao/reactor 6.2.2-dev.42 → 6.2.2-dev.44
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-CAEZD_yJ.js → build-worker-executor-CHkkh55C.js} +2 -2
- package/dist/{build-worker-executor-CAEZD_yJ.js.map → build-worker-executor-CHkkh55C.js.map} +1 -1
- package/dist/{document-indexer-C0GB0b8Q.js → document-indexer-BfdCgjG-.js} +15 -14
- package/dist/document-indexer-BfdCgjG-.js.map +1 -0
- package/dist/{drive-container-types-BJCKXJwH.js → drive-container-types-h3M1AK3K.js} +655 -114
- package/dist/drive-container-types-h3M1AK3K.js.map +1 -0
- package/dist/entry.js +2 -2
- package/dist/index.d.ts +232 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +176 -18
- package/dist/index.js.map +1 -1
- package/dist/projection-entry.js +3 -3
- package/dist/{worker-jEJW6_j7.js → worker-CbtYMIju.js} +2 -2
- package/dist/{worker-jEJW6_j7.js.map → worker-CbtYMIju.js.map} +1 -1
- package/package.json +4 -4
- package/dist/document-indexer-C0GB0b8Q.js.map +0 -1
- package/dist/drive-container-types-BJCKXJwH.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as ReactorEventTypes, t as EventBusAggregateError } from "./types-DMKLa0Ok.js";
|
|
2
|
-
import { AUTH_ACTION_TYPES, AUTH_DENIED_BY_GRANT_REASON, AUTH_NO_GRANT_REASON, AUTH_VERSION_UNSUPPORTED_REASON, DOCUMENT_DELETED_REASON, DowngradeNotSupportedError, appendWithoutApplying, applyAuthAction, applyDeleteDocumentAction, applyDeleteDocumentAction as applyDeleteDocumentAction$1, applyUpgradeDocumentAction, applyUpgradeDocumentAction as applyUpgradeDocumentAction$1, baseReducerVersion, createPresignedHeader, decide, defaultBaseState, deriveOperationId, evaluate, garbageCollect, hashDocumentStateForScope, isDenied, isUndoRedo, sortOperations } from "@powerhousedao/shared/document-model";
|
|
2
|
+
import { AUTH_ACTION_TYPES, AUTH_DENIED_BY_GRANT_REASON, AUTH_NO_GRANT_REASON, AUTH_VERSION_UNSUPPORTED_REASON, DOCUMENT_DELETED_REASON, DowngradeNotSupportedError as DowngradeNotSupportedError$1, appendWithoutApplying, applyAuthAction, applyDeleteDocumentAction, applyDeleteDocumentAction as applyDeleteDocumentAction$1, applyUpgradeDocumentAction, applyUpgradeDocumentAction as applyUpgradeDocumentAction$1, baseReducerVersion, createPresignedHeader, decide, defaultBaseState, deriveOperationId, evaluate, garbageCollect, groupDocumentType, groupMembershipActionTypes, hashDocumentStateForScope, isDenied, isUndoRedo, mentionedGroupIds, normalizeDocumentModelVersion, referencedGroupIds, 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
|
|
@@ -182,6 +182,28 @@ var InvalidSignatureError = class InvalidSignatureError extends Error {
|
|
|
182
182
|
}
|
|
183
183
|
};
|
|
184
184
|
/**
|
|
185
|
+
* An UPGRADE_DOCUMENT action's preconditions (fromVersion and the per-scope
|
|
186
|
+
* revision snapshot) did not match the document state the executor loaded.
|
|
187
|
+
*
|
|
188
|
+
* Terminal rather than retryable: the action carries the client's snapshot,
|
|
189
|
+
* which stays stale no matter how often the job re-runs. The client is
|
|
190
|
+
* expected to re-read the document and submit a fresh action instead.
|
|
191
|
+
*/
|
|
192
|
+
var UpgradePreconditionFailedError = class UpgradePreconditionFailedError extends Error {
|
|
193
|
+
documentId;
|
|
194
|
+
detail;
|
|
195
|
+
constructor(documentId, detail) {
|
|
196
|
+
super(`Upgrade precondition failed for document ${documentId}: ${detail}`);
|
|
197
|
+
this.name = "UpgradePreconditionFailedError";
|
|
198
|
+
this.documentId = documentId;
|
|
199
|
+
this.detail = detail;
|
|
200
|
+
Error.captureStackTrace(this, UpgradePreconditionFailedError);
|
|
201
|
+
}
|
|
202
|
+
static isError(error) {
|
|
203
|
+
return Error.isError(error) && error.name === "UpgradePreconditionFailedError";
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
/**
|
|
185
207
|
* Error thrown when a document is not found (no operations exist for the document ID).
|
|
186
208
|
*/
|
|
187
209
|
var DocumentNotFoundError = class DocumentNotFoundError extends Error {
|
|
@@ -736,6 +758,7 @@ var KyselyOperationIndexTxn = class {
|
|
|
736
758
|
collections = [];
|
|
737
759
|
collectionMemberships = [];
|
|
738
760
|
collectionRemovals = [];
|
|
761
|
+
groupReferences = [];
|
|
739
762
|
operations = [];
|
|
740
763
|
createCollection(collectionId) {
|
|
741
764
|
this.collections.push(collectionId);
|
|
@@ -758,12 +781,25 @@ var KyselyOperationIndexTxn = class {
|
|
|
758
781
|
operationIndex: lastOpIndex
|
|
759
782
|
});
|
|
760
783
|
}
|
|
784
|
+
recordGroupReferences(documentId, groupIds) {
|
|
785
|
+
const lastOpIndex = this.operations.length - 1;
|
|
786
|
+
if (lastOpIndex < 0) throw new Error("recordGroupReferences must be called after write() - no operations in transaction");
|
|
787
|
+
if (groupIds.length === 0) return;
|
|
788
|
+
this.groupReferences.push({
|
|
789
|
+
documentId,
|
|
790
|
+
groupIds,
|
|
791
|
+
operationIndex: lastOpIndex
|
|
792
|
+
});
|
|
793
|
+
}
|
|
761
794
|
write(operations) {
|
|
762
795
|
this.operations.push(...operations);
|
|
763
796
|
}
|
|
764
797
|
getCollections() {
|
|
765
798
|
return this.collections;
|
|
766
799
|
}
|
|
800
|
+
getGroupReferenceRecords() {
|
|
801
|
+
return this.groupReferences;
|
|
802
|
+
}
|
|
767
803
|
getCollectionMembershipRecords() {
|
|
768
804
|
return this.collectionMemberships;
|
|
769
805
|
}
|
|
@@ -800,10 +836,27 @@ var KyselyOperationIndex = class KyselyOperationIndex {
|
|
|
800
836
|
});
|
|
801
837
|
return resultOrdinals;
|
|
802
838
|
}
|
|
839
|
+
/**
|
|
840
|
+
* A policy-driven join: keeps the earliest join so a rediscovered reference
|
|
841
|
+
* never shrinks a backfill window remotes already rely on, and reopens a
|
|
842
|
+
* closed membership because a policy reference is not a removable one.
|
|
843
|
+
*/
|
|
844
|
+
async joinKeepingEarliest(trx, documentId, collectionId, ordinal) {
|
|
845
|
+
await trx.insertInto("document_collections").values({
|
|
846
|
+
documentId,
|
|
847
|
+
collectionId,
|
|
848
|
+
joinedOrdinal: ordinal,
|
|
849
|
+
leftOrdinal: null
|
|
850
|
+
}).onConflict((oc) => oc.columns(["documentId", "collectionId"]).doUpdateSet({
|
|
851
|
+
joinedOrdinal: sql`LEAST("document_collections"."joinedOrdinal", EXCLUDED."joinedOrdinal")`,
|
|
852
|
+
leftOrdinal: null
|
|
853
|
+
})).execute();
|
|
854
|
+
}
|
|
803
855
|
async executeCommit(trx, kyselyTxn) {
|
|
804
856
|
const collections = kyselyTxn.getCollections();
|
|
805
857
|
const memberships = kyselyTxn.getCollectionMembershipRecords();
|
|
806
858
|
const removals = kyselyTxn.getCollectionRemovals();
|
|
859
|
+
const groupReferences = kyselyTxn.getGroupReferenceRecords();
|
|
807
860
|
const operations = kyselyTxn.getOperations();
|
|
808
861
|
if (collections.length > 0) {
|
|
809
862
|
const collectionRows = collections.map((collectionId) => ({
|
|
@@ -843,13 +896,28 @@ var KyselyOperationIndex = class KyselyOperationIndex {
|
|
|
843
896
|
joinedOrdinal: BigInt(ordinal),
|
|
844
897
|
leftOrdinal: null
|
|
845
898
|
})).execute();
|
|
899
|
+
const references = await trx.selectFrom("group_references").select("groupId").where("documentId", "=", m.documentId).execute();
|
|
900
|
+
for (const { groupId } of references) await this.joinKeepingEarliest(trx, groupId, m.collectionId, BigInt(ordinal));
|
|
846
901
|
}
|
|
847
902
|
if (removals.length > 0) for (const r of removals) {
|
|
848
903
|
const ordinal = operationOrdinals[r.operationIndex];
|
|
849
904
|
await trx.updateTable("document_collections").set({ leftOrdinal: BigInt(ordinal) }).where("collectionId", "=", r.collectionId).where("documentId", "=", r.documentId).where("leftOrdinal", "is", null).execute();
|
|
850
905
|
}
|
|
906
|
+
if (groupReferences.length > 0) for (const record of groupReferences) {
|
|
907
|
+
const ordinal = operationOrdinals[record.operationIndex];
|
|
908
|
+
await trx.insertInto("group_references").values(record.groupIds.map((groupId) => ({
|
|
909
|
+
documentId: record.documentId,
|
|
910
|
+
groupId
|
|
911
|
+
}))).onConflict((oc) => oc.doNothing()).execute();
|
|
912
|
+
const rows = await trx.selectFrom("document_collections").select("collectionId").where("documentId", "=", record.documentId).execute();
|
|
913
|
+
for (const groupId of record.groupIds) for (const { collectionId } of rows) await this.joinKeepingEarliest(trx, groupId, collectionId, BigInt(ordinal));
|
|
914
|
+
}
|
|
851
915
|
return operationOrdinals;
|
|
852
916
|
}
|
|
917
|
+
async getGroupReferencers(groupId, signal) {
|
|
918
|
+
if (signal?.aborted) throw new Error("Operation aborted");
|
|
919
|
+
return (await this.queryExecutor.selectFrom("group_references").select("documentId").where("groupId", "=", groupId).orderBy("documentId").execute()).map((row) => row.documentId);
|
|
920
|
+
}
|
|
853
921
|
async find(collectionId, cursor, view, paging, signal) {
|
|
854
922
|
if (signal?.aborted) throw new Error("Operation aborted");
|
|
855
923
|
const outerCursor = cursor ?? -1;
|
|
@@ -1097,7 +1165,7 @@ function keyframeRevision(keyframe, documentId, scope) {
|
|
|
1097
1165
|
}
|
|
1098
1166
|
function extractModuleVersion(doc) {
|
|
1099
1167
|
const v = doc.state.document.version;
|
|
1100
|
-
return v
|
|
1168
|
+
return normalizeDocumentModelVersion(v);
|
|
1101
1169
|
}
|
|
1102
1170
|
/** The highest revision held, latest push winning a tie. */
|
|
1103
1171
|
function highestRevision(snapshots) {
|
|
@@ -1360,6 +1428,18 @@ var KyselyWriteCache = class KyselyWriteCache {
|
|
|
1360
1428
|
document: keyframe.document
|
|
1361
1429
|
};
|
|
1362
1430
|
}
|
|
1431
|
+
/**
|
|
1432
|
+
* Rebuilds a scope from a keyframe or from the whole operation history.
|
|
1433
|
+
*
|
|
1434
|
+
* The document scope is always rebuilt first, because it carries the type,
|
|
1435
|
+
* the upgrades and the deletion marker. Its version-changing upgrades are not
|
|
1436
|
+
* applied there though: an upgrade reducer must see the state the requested
|
|
1437
|
+
* scope has reached at that upgrade's boundary, so each one is held back and
|
|
1438
|
+
* applied when the replay below crosses the boundary that
|
|
1439
|
+
* resolveModuleVersionForOp derives from it. Upgrades whose boundary lies past
|
|
1440
|
+
* the last replayed operation are applied at the end. Creation-time 0->N seed
|
|
1441
|
+
* upgrades carry the initial state, so they still apply immediately.
|
|
1442
|
+
*/
|
|
1363
1443
|
async coldMissRebuild(documentId, scope, branch, targetRevision, signal) {
|
|
1364
1444
|
const effectiveTargetRevision = targetRevision || Number.MAX_SAFE_INTEGER;
|
|
1365
1445
|
const keyframe = await this.findNearestKeyframe(documentId, scope, branch, effectiveTargetRevision, signal);
|
|
@@ -1368,6 +1448,7 @@ var KyselyWriteCache = class KyselyWriteCache {
|
|
|
1368
1448
|
let startRevision;
|
|
1369
1449
|
let documentType;
|
|
1370
1450
|
const validatedUpgrades = [];
|
|
1451
|
+
const pendingUpgrades = [];
|
|
1371
1452
|
let lastDocumentScopeOperation;
|
|
1372
1453
|
if (keyframe) {
|
|
1373
1454
|
document = keyframe.document;
|
|
@@ -1397,9 +1478,17 @@ var KyselyWriteCache = class KyselyWriteCache {
|
|
|
1397
1478
|
revision: upgradeAction.input.revision,
|
|
1398
1479
|
timestampUtcMs: operation.timestampUtcMs
|
|
1399
1480
|
});
|
|
1400
|
-
|
|
1481
|
+
pendingUpgrades.push({
|
|
1482
|
+
action: upgradeAction,
|
|
1483
|
+
upgradePath,
|
|
1484
|
+
index: operation.index,
|
|
1485
|
+
subsequentDeletes: []
|
|
1486
|
+
});
|
|
1401
1487
|
}
|
|
1402
|
-
} else if (operation.action.type === "DELETE_DOCUMENT")
|
|
1488
|
+
} else if (operation.action.type === "DELETE_DOCUMENT") {
|
|
1489
|
+
applyDeleteDocumentAction(document, operation.action);
|
|
1490
|
+
for (const pending of pendingUpgrades) pending.subsequentDeletes.push(operation.action);
|
|
1491
|
+
}
|
|
1403
1492
|
}
|
|
1404
1493
|
} else {
|
|
1405
1494
|
startRevision = -1;
|
|
@@ -1426,8 +1515,8 @@ var KyselyWriteCache = class KyselyWriteCache {
|
|
|
1426
1515
|
const upgradeAction = operation.action;
|
|
1427
1516
|
const fromVersion = upgradeAction.input.fromVersion;
|
|
1428
1517
|
const toVersion = upgradeAction.input.toVersion;
|
|
1429
|
-
let upgradePath;
|
|
1430
1518
|
if (fromVersion > 0 && fromVersion < toVersion) {
|
|
1519
|
+
let upgradePath;
|
|
1431
1520
|
try {
|
|
1432
1521
|
upgradePath = this.registry.computeUpgradePath(documentType, fromVersion, toVersion);
|
|
1433
1522
|
} catch (err) {
|
|
@@ -1440,11 +1529,18 @@ var KyselyWriteCache = class KyselyWriteCache {
|
|
|
1440
1529
|
revision: upgradeAction.input.revision,
|
|
1441
1530
|
timestampUtcMs: operation.timestampUtcMs
|
|
1442
1531
|
});
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1532
|
+
pendingUpgrades.push({
|
|
1533
|
+
action: upgradeAction,
|
|
1534
|
+
upgradePath,
|
|
1535
|
+
index: operation.index,
|
|
1536
|
+
subsequentDeletes: []
|
|
1537
|
+
});
|
|
1538
|
+
} else document = applyUpgradeDocumentAction(document, upgradeAction, void 0);
|
|
1539
|
+
docModule = this.registry.getModule(documentType, normalizeDocumentModelVersion(toVersion));
|
|
1540
|
+
} else if (operation.action.type === "DELETE_DOCUMENT") {
|
|
1541
|
+
applyDeleteDocumentAction(document, operation.action);
|
|
1542
|
+
for (const pending of pendingUpgrades) pending.subsequentDeletes.push(operation.action);
|
|
1543
|
+
} else {
|
|
1448
1544
|
const protocolVersion = baseReducerVersion(document.header);
|
|
1449
1545
|
document = docModule.reducer(document, operation.action, void 0, {
|
|
1450
1546
|
skip: operation.skip,
|
|
@@ -1454,6 +1550,7 @@ var KyselyWriteCache = class KyselyWriteCache {
|
|
|
1454
1550
|
}
|
|
1455
1551
|
}
|
|
1456
1552
|
if (scope === "document") {
|
|
1553
|
+
document = this.applyPendingUpgrades(document, pendingUpgrades, Number.MAX_SAFE_INTEGER);
|
|
1457
1554
|
const last = lastDocumentScopeOperation ?? await this.operationAt(documentId, "document", branch, startRevision, signal);
|
|
1458
1555
|
document.operations = {
|
|
1459
1556
|
...document.operations,
|
|
@@ -1478,6 +1575,7 @@ var KyselyWriteCache = class KyselyWriteCache {
|
|
|
1478
1575
|
}
|
|
1479
1576
|
return mod;
|
|
1480
1577
|
};
|
|
1578
|
+
const finalVersion = validatedUpgrades.at(-1)?.toVersion ?? extractModuleVersion(document);
|
|
1481
1579
|
let cursor = void 0;
|
|
1482
1580
|
const pageSize = 100;
|
|
1483
1581
|
let hasMorePages;
|
|
@@ -1491,7 +1589,8 @@ var KyselyWriteCache = class KyselyWriteCache {
|
|
|
1491
1589
|
const result = await this.operationStore.getSince(documentId, scope, branch, startRevision, void 0, paging, signal);
|
|
1492
1590
|
for (const operation of result.results) {
|
|
1493
1591
|
if (targetRevision !== void 0 && operation.index > targetRevision) break;
|
|
1494
|
-
const moduleVersion = this.resolveModuleVersionForOp(operation.index, operation.timestampUtcMs, scope, validatedUpgrades,
|
|
1592
|
+
const moduleVersion = this.resolveModuleVersionForOp(operation.index, operation.timestampUtcMs, scope, validatedUpgrades, finalVersion);
|
|
1593
|
+
document = this.applyPendingUpgrades(document, pendingUpgrades, moduleVersion ?? Number.MAX_SAFE_INTEGER);
|
|
1495
1594
|
if (isDenied(operation)) document = appendWithoutApplying(document, operation, scope);
|
|
1496
1595
|
else {
|
|
1497
1596
|
const protocolVersion = baseReducerVersion(document.header);
|
|
@@ -1508,7 +1607,62 @@ var KyselyWriteCache = class KyselyWriteCache {
|
|
|
1508
1607
|
throw new Error(`Failed to rebuild document ${documentId}: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
1509
1608
|
}
|
|
1510
1609
|
} while (hasMorePages);
|
|
1511
|
-
|
|
1610
|
+
document = this.applyTailPendingUpgrades(document, pendingUpgrades, scope, targetRevision);
|
|
1611
|
+
document = await this.stampRevisions(document, documentId, scope, branch, targetRevision, signal);
|
|
1612
|
+
if (pendingUpgrades.length > 0) {
|
|
1613
|
+
const firstHeldBack = pendingUpgrades[0];
|
|
1614
|
+
const stamped = document.header.revision["document"] ?? 0;
|
|
1615
|
+
document.header.revision = {
|
|
1616
|
+
...document.header.revision,
|
|
1617
|
+
document: Math.min(stamped, firstHeldBack.index)
|
|
1618
|
+
};
|
|
1619
|
+
}
|
|
1620
|
+
return document;
|
|
1621
|
+
}
|
|
1622
|
+
/**
|
|
1623
|
+
* Applies and removes every held-back upgrade whose target version is at or
|
|
1624
|
+
* below `throughVersion`, in the order the document scope recorded them.
|
|
1625
|
+
*/
|
|
1626
|
+
applyPendingUpgrades(document, pendingUpgrades, throughVersion) {
|
|
1627
|
+
while (pendingUpgrades.length > 0) {
|
|
1628
|
+
const pending = pendingUpgrades[0];
|
|
1629
|
+
if (throughVersion < pending.action.input.toVersion) break;
|
|
1630
|
+
pendingUpgrades.shift();
|
|
1631
|
+
document = this.applyPendingUpgrade(document, pending);
|
|
1632
|
+
}
|
|
1633
|
+
return document;
|
|
1634
|
+
}
|
|
1635
|
+
/**
|
|
1636
|
+
* Applies the remaining held-back upgrades after the requested scope's
|
|
1637
|
+
* replay has finished. A head read applies them all. A positional read
|
|
1638
|
+
* applies only those whose boundary for this scope lies at or before the
|
|
1639
|
+
* target position: applying a later one would label migrated state with a
|
|
1640
|
+
* pre-upgrade revision, and a keyframe stored from that poisons every
|
|
1641
|
+
* rebuild that resumes from it. Boundaries come from the upgrade's revision
|
|
1642
|
+
* snapshot; an upgrade without one records no position for this scope, and
|
|
1643
|
+
* the replay loop not having crossed it already places it past the target.
|
|
1644
|
+
*/
|
|
1645
|
+
applyTailPendingUpgrades(document, pendingUpgrades, scope, targetRevision) {
|
|
1646
|
+
while (pendingUpgrades.length > 0) {
|
|
1647
|
+
const pending = pendingUpgrades[0];
|
|
1648
|
+
if (targetRevision !== void 0) {
|
|
1649
|
+
const snapshot = pending.action.input.revision;
|
|
1650
|
+
if (snapshot === void 0) break;
|
|
1651
|
+
if ((snapshot[scope] ?? 0) > targetRevision) break;
|
|
1652
|
+
}
|
|
1653
|
+
pendingUpgrades.shift();
|
|
1654
|
+
document = this.applyPendingUpgrade(document, pending);
|
|
1655
|
+
}
|
|
1656
|
+
return document;
|
|
1657
|
+
}
|
|
1658
|
+
/**
|
|
1659
|
+
* Applies one held-back upgrade, then re-applies the deletes the document
|
|
1660
|
+
* scope recorded after it so the hold-back cannot invert their order.
|
|
1661
|
+
*/
|
|
1662
|
+
applyPendingUpgrade(document, pending) {
|
|
1663
|
+
document = applyUpgradeDocumentAction(document, pending.action, pending.upgradePath);
|
|
1664
|
+
for (const deleteAction of pending.subsequentDeletes) document = applyDeleteDocumentAction(document, deleteAction);
|
|
1665
|
+
return document;
|
|
1512
1666
|
}
|
|
1513
1667
|
/**
|
|
1514
1668
|
* Copies the current document revisions onto the document. Overwrites the
|
|
@@ -1660,7 +1814,9 @@ var EventBus = class {
|
|
|
1660
1814
|
*/
|
|
1661
1815
|
const FLAG_PREREQUISITES = {
|
|
1662
1816
|
documentDecisions: [],
|
|
1663
|
-
authEnforcement: ["documentDecisions"]
|
|
1817
|
+
authEnforcement: ["documentDecisions"],
|
|
1818
|
+
authGroups: ["authEnforcement"],
|
|
1819
|
+
authConditions: ["authGroups"]
|
|
1664
1820
|
};
|
|
1665
1821
|
/**
|
|
1666
1822
|
* Throws when the flags ask for enforcement the reactor cannot deliver. Either
|
|
@@ -1783,49 +1939,159 @@ function refusalReason(refusal) {
|
|
|
1783
1939
|
case "no-applicable-grant": return AUTH_NO_GRANT_REASON;
|
|
1784
1940
|
}
|
|
1785
1941
|
}
|
|
1942
|
+
function decideAuthModel(model, subject, request, groups, conditions) {
|
|
1943
|
+
if (request.verb === "execute" && model.document.isDeleted) return {
|
|
1944
|
+
decision: "deny",
|
|
1945
|
+
reason: DOCUMENT_DELETED_REASON
|
|
1946
|
+
};
|
|
1947
|
+
const evaluation = evaluate(model.auth, subject, request, groups, conditions);
|
|
1948
|
+
if (evaluation.decision === "allow") return { decision: "allow" };
|
|
1949
|
+
return {
|
|
1950
|
+
decision: "deny",
|
|
1951
|
+
reason: refusalReason(evaluation.refusal)
|
|
1952
|
+
};
|
|
1953
|
+
}
|
|
1954
|
+
function documentProjection(target) {
|
|
1955
|
+
return {
|
|
1956
|
+
decidingActions: ["DELETE_DOCUMENT"],
|
|
1957
|
+
apply: (document, operation) => operation.action.type === "DELETE_DOCUMENT" ? applyDeleteDocumentAction({
|
|
1958
|
+
...document,
|
|
1959
|
+
state: { ...document.state }
|
|
1960
|
+
}, operation.action) : document,
|
|
1961
|
+
query: {
|
|
1962
|
+
documentId: target.documentId,
|
|
1963
|
+
branch: target.branch,
|
|
1964
|
+
scope: "document"
|
|
1965
|
+
}
|
|
1966
|
+
};
|
|
1967
|
+
}
|
|
1968
|
+
function authProjection(target) {
|
|
1969
|
+
return {
|
|
1970
|
+
decidingActions: [...AUTH_ACTION_TYPES],
|
|
1971
|
+
apply: (document, operation) => applyAuthAction(document, operation.action),
|
|
1972
|
+
query: {
|
|
1973
|
+
documentId: target.documentId,
|
|
1974
|
+
branch: target.branch,
|
|
1975
|
+
scope: "auth"
|
|
1976
|
+
}
|
|
1977
|
+
};
|
|
1978
|
+
}
|
|
1786
1979
|
/** This decision model uses both the document and the auth streams. */
|
|
1787
1980
|
function authDecisionModel(target) {
|
|
1788
1981
|
return {
|
|
1789
1982
|
projections: {
|
|
1790
|
-
document:
|
|
1791
|
-
|
|
1792
|
-
apply: (document, operation) => operation.action.type === "DELETE_DOCUMENT" ? applyDeleteDocumentAction({
|
|
1793
|
-
...document,
|
|
1794
|
-
state: { ...document.state }
|
|
1795
|
-
}, operation.action) : document,
|
|
1796
|
-
query: {
|
|
1797
|
-
documentId: target.documentId,
|
|
1798
|
-
branch: target.branch,
|
|
1799
|
-
scope: "document"
|
|
1800
|
-
}
|
|
1801
|
-
},
|
|
1802
|
-
auth: {
|
|
1803
|
-
decidingActions: [...AUTH_ACTION_TYPES],
|
|
1804
|
-
apply: (document, operation) => applyAuthAction(document, operation.action),
|
|
1805
|
-
query: {
|
|
1806
|
-
documentId: target.documentId,
|
|
1807
|
-
branch: target.branch,
|
|
1808
|
-
scope: "auth"
|
|
1809
|
-
}
|
|
1810
|
-
}
|
|
1983
|
+
document: documentProjection(target),
|
|
1984
|
+
auth: authProjection(target)
|
|
1811
1985
|
},
|
|
1812
1986
|
evaluatesScope() {
|
|
1813
1987
|
return true;
|
|
1814
1988
|
},
|
|
1815
1989
|
decide(model, subject, request) {
|
|
1816
|
-
|
|
1817
|
-
decision: "deny",
|
|
1818
|
-
reason: DOCUMENT_DELETED_REASON
|
|
1819
|
-
};
|
|
1820
|
-
const evaluation = evaluate(model.auth, subject, request);
|
|
1821
|
-
if (evaluation.decision === "allow") return { decision: "allow" };
|
|
1822
|
-
return {
|
|
1823
|
-
decision: "deny",
|
|
1824
|
-
reason: refusalReason(evaluation.refusal)
|
|
1825
|
-
};
|
|
1990
|
+
return decideAuthModel(model, subject, request);
|
|
1826
1991
|
}
|
|
1827
1992
|
};
|
|
1828
1993
|
}
|
|
1994
|
+
/**
|
|
1995
|
+
* Folds one group-stream operation with the registered group model's reducer.
|
|
1996
|
+
* A reactor without the module registered folds nothing, so the member list
|
|
1997
|
+
* stays as read and a missing reducer never widens access.
|
|
1998
|
+
*/
|
|
1999
|
+
function applyGroupOperation(registry, document, operation) {
|
|
2000
|
+
let reducer;
|
|
2001
|
+
try {
|
|
2002
|
+
reducer = registry.getModule(groupDocumentType).reducer;
|
|
2003
|
+
} catch {
|
|
2004
|
+
return document;
|
|
2005
|
+
}
|
|
2006
|
+
return reducer(document, operation.action);
|
|
2007
|
+
}
|
|
2008
|
+
/**
|
|
2009
|
+
* Folds one evaluated-scope operation with the reducer registered for the
|
|
2010
|
+
* document's own type, at the document's stamped version. A reactor without
|
|
2011
|
+
* that module folds nothing, so conditions read the base state and an
|
|
2012
|
+
* unresolvable reducer never widens access.
|
|
2013
|
+
*/
|
|
2014
|
+
function applyModelOperation(registry, document, operation) {
|
|
2015
|
+
let reducer;
|
|
2016
|
+
try {
|
|
2017
|
+
const version = normalizeDocumentModelVersion(document.state.document?.version);
|
|
2018
|
+
reducer = registry.getModule(document.header.documentType, version).reducer;
|
|
2019
|
+
} catch {
|
|
2020
|
+
return document;
|
|
2021
|
+
}
|
|
2022
|
+
return reducer(document, operation.action);
|
|
2023
|
+
}
|
|
2024
|
+
/**
|
|
2025
|
+
* The auth model extended with a derived groups projection: the streams it
|
|
2026
|
+
* reads are the group documents the folded grant list names, so adding a
|
|
2027
|
+
* grant that names a new group pulls that group's stream into the read-set.
|
|
2028
|
+
* Group queries pin the main branch, because a group's member list lives on
|
|
2029
|
+
* its main branch no matter which branch the referencing document is on.
|
|
2030
|
+
*/
|
|
2031
|
+
function groupsProjection(registry) {
|
|
2032
|
+
return {
|
|
2033
|
+
decidingActions: [...groupMembershipActionTypes],
|
|
2034
|
+
apply: (document, operation) => applyGroupOperation(registry, document, operation),
|
|
2035
|
+
query: (model) => referencedGroupIds(model.auth?.grants ?? []).map((id) => ({
|
|
2036
|
+
documentId: id,
|
|
2037
|
+
branch: "main",
|
|
2038
|
+
scope: "global"
|
|
2039
|
+
})),
|
|
2040
|
+
queryOverHistory: (reads) => {
|
|
2041
|
+
const ids = [];
|
|
2042
|
+
for (const read of reads) {
|
|
2043
|
+
if (read.name !== "auth") continue;
|
|
2044
|
+
for (const operation of read.operations) for (const id of mentionedGroupIds(operation.action)) if (!ids.includes(id)) ids.push(id);
|
|
2045
|
+
}
|
|
2046
|
+
return ids.map((id) => ({
|
|
2047
|
+
documentId: id,
|
|
2048
|
+
branch: "main",
|
|
2049
|
+
scope: "global"
|
|
2050
|
+
}));
|
|
2051
|
+
}
|
|
2052
|
+
};
|
|
2053
|
+
}
|
|
2054
|
+
function authGroupsDecisionModel(registry) {
|
|
2055
|
+
return (target) => ({
|
|
2056
|
+
projections: {
|
|
2057
|
+
document: documentProjection(target),
|
|
2058
|
+
auth: authProjection(target),
|
|
2059
|
+
groups: groupsProjection(registry)
|
|
2060
|
+
},
|
|
2061
|
+
evaluatesScope() {
|
|
2062
|
+
return true;
|
|
2063
|
+
},
|
|
2064
|
+
decide(model, subject, request) {
|
|
2065
|
+
return decideAuthModel(model, subject, request, model.groups);
|
|
2066
|
+
}
|
|
2067
|
+
});
|
|
2068
|
+
}
|
|
2069
|
+
/**
|
|
2070
|
+
* The groups model with conditions live: decide hands the executing scope's
|
|
2071
|
+
* state and the action input through to the evaluator, so `where` clauses
|
|
2072
|
+
* and { match } principals apply. The model folds the evaluated scope during
|
|
2073
|
+
* a positional walk, so a condition reads the state as it stood at each
|
|
2074
|
+
* operation's position.
|
|
2075
|
+
*/
|
|
2076
|
+
function authConditionsDecisionModel(registry) {
|
|
2077
|
+
return (target) => ({
|
|
2078
|
+
projections: {
|
|
2079
|
+
document: documentProjection(target),
|
|
2080
|
+
auth: authProjection(target),
|
|
2081
|
+
groups: groupsProjection(registry)
|
|
2082
|
+
},
|
|
2083
|
+
foldEvaluatedScope: (document, operation) => applyModelOperation(registry, document, operation),
|
|
2084
|
+
evaluatesScope() {
|
|
2085
|
+
return true;
|
|
2086
|
+
},
|
|
2087
|
+
decide(model, subject, request, ctx) {
|
|
2088
|
+
return decideAuthModel(model, subject, request, model.groups, {
|
|
2089
|
+
scopeState: ctx.scopeState,
|
|
2090
|
+
actionInput: ctx.actionInput
|
|
2091
|
+
});
|
|
2092
|
+
}
|
|
2093
|
+
});
|
|
2094
|
+
}
|
|
1829
2095
|
//#endregion
|
|
1830
2096
|
//#region src/decision/build-decision-model.ts
|
|
1831
2097
|
/**
|
|
@@ -1849,7 +2115,16 @@ async function buildDecisionModel(cache, definition, target, signal) {
|
|
|
1849
2115
|
const queries = projection.query(staticModel);
|
|
1850
2116
|
const value = {};
|
|
1851
2117
|
for (const query of queries) {
|
|
1852
|
-
|
|
2118
|
+
let read;
|
|
2119
|
+
try {
|
|
2120
|
+
read = await readStream(cache, query, reads, signal);
|
|
2121
|
+
} catch (error) {
|
|
2122
|
+
if (error instanceof DocumentNotFoundError) {
|
|
2123
|
+
recordEmptyStream(query, reads);
|
|
2124
|
+
continue;
|
|
2125
|
+
}
|
|
2126
|
+
throw error;
|
|
2127
|
+
}
|
|
1853
2128
|
value[query.documentId] = read.state;
|
|
1854
2129
|
}
|
|
1855
2130
|
model[key] = value;
|
|
@@ -1859,6 +2134,20 @@ async function buildDecisionModel(cache, definition, target, signal) {
|
|
|
1859
2134
|
appendCondition: { streams: [...reads.values()].map((read) => read.stream) }
|
|
1860
2135
|
};
|
|
1861
2136
|
}
|
|
2137
|
+
/** Guards a stream that holds nothing yet: any operation appearing is growth. */
|
|
2138
|
+
function recordEmptyStream(query, reads) {
|
|
2139
|
+
const key = `${query.documentId}:${query.scope}:${query.branch}`;
|
|
2140
|
+
if (reads.has(key)) return;
|
|
2141
|
+
reads.set(key, {
|
|
2142
|
+
state: void 0,
|
|
2143
|
+
stream: {
|
|
2144
|
+
documentId: query.documentId,
|
|
2145
|
+
scope: query.scope,
|
|
2146
|
+
branch: query.branch,
|
|
2147
|
+
revision: -1
|
|
2148
|
+
}
|
|
2149
|
+
});
|
|
2150
|
+
}
|
|
1862
2151
|
async function readStream(cache, query, reads, signal) {
|
|
1863
2152
|
const key = `${query.documentId}:${query.scope}:${query.branch}`;
|
|
1864
2153
|
const existing = reads.get(key);
|
|
@@ -1890,6 +2179,24 @@ function observedRevision(document, scope) {
|
|
|
1890
2179
|
return document.header.revision[scope] - 1;
|
|
1891
2180
|
}
|
|
1892
2181
|
/**
|
|
2182
|
+
* The projections whose queries depend on folded state. A positional walk
|
|
2183
|
+
* resolves their streams through `queryOverHistory`; a projection without one
|
|
2184
|
+
* contributes no streams to a walk.
|
|
2185
|
+
*/
|
|
2186
|
+
function derivedReadSet(definition) {
|
|
2187
|
+
const projections = [];
|
|
2188
|
+
for (const [name, projection] of Object.entries(definition.projections)) {
|
|
2189
|
+
if (typeof projection.query !== "function") continue;
|
|
2190
|
+
projections.push({
|
|
2191
|
+
name,
|
|
2192
|
+
decidingActions: projection.decidingActions,
|
|
2193
|
+
apply: projection.apply,
|
|
2194
|
+
queryOverHistory: projection.queryOverHistory
|
|
2195
|
+
});
|
|
2196
|
+
}
|
|
2197
|
+
return projections;
|
|
2198
|
+
}
|
|
2199
|
+
/**
|
|
1893
2200
|
* The streams a model reads whose queries are known before it is built. A
|
|
1894
2201
|
* derived query needs the statically-queried projections first, so it is not
|
|
1895
2202
|
* included here.
|
|
@@ -1943,11 +2250,21 @@ function documentDecisionModel(target) {
|
|
|
1943
2250
|
/**
|
|
1944
2251
|
* Builds the model at the stream heads and decides one request against it. The
|
|
1945
2252
|
* append condition it returns is the read-set the store enforces at write time.
|
|
2253
|
+
*
|
|
2254
|
+
* With `conditions` supplied, the executing scope's state is read at the head
|
|
2255
|
+
* for `doc.<scope>.*` paths. That read carries no append-condition entry of
|
|
2256
|
+
* its own: the written stream's expected-revision check already refuses a
|
|
2257
|
+
* write whose scope grew between the read and the append.
|
|
1946
2258
|
*/
|
|
1947
|
-
async function decideAtHead(model, cache, target, subject, request, signal) {
|
|
2259
|
+
async function decideAtHead(model, cache, target, subject, request, signal, conditions) {
|
|
1948
2260
|
const built = await buildDecisionModel(cache, model, target, signal);
|
|
2261
|
+
let scopeState;
|
|
2262
|
+
if (conditions !== void 0) scopeState = (await cache.getState(target.documentId, request.scope, target.branch, void 0, signal)).state[request.scope];
|
|
1949
2263
|
return {
|
|
1950
|
-
evaluation: model(target).decide(built.model, subject, request, {
|
|
2264
|
+
evaluation: model(target).decide(built.model, subject, request, {
|
|
2265
|
+
scopeState,
|
|
2266
|
+
actionInput: conditions?.actionInput
|
|
2267
|
+
}),
|
|
1951
2268
|
appendCondition: built.appendCondition,
|
|
1952
2269
|
documentVersion: built.model.document.version,
|
|
1953
2270
|
deletedAtUtcIso: built.model.document.deletedAtUtcIso ?? null
|
|
@@ -1955,9 +2272,13 @@ async function decideAtHead(model, cache, target, subject, request, signal) {
|
|
|
1955
2272
|
}
|
|
1956
2273
|
/**
|
|
1957
2274
|
* The model this reactor enforces. With `authEnforcement` off the auth scope is
|
|
1958
|
-
* absent from every append condition and no load walks it
|
|
2275
|
+
* absent from every append condition and no load walks it; with `authGroups`
|
|
2276
|
+
* on, the group documents the grant list names join the read-set and the
|
|
2277
|
+
* registry supplies the reducer that folds them.
|
|
1959
2278
|
*/
|
|
1960
|
-
function selectDecisionModel(flags) {
|
|
2279
|
+
function selectDecisionModel(flags, registry) {
|
|
2280
|
+
if (flags.authConditions) return authConditionsDecisionModel(registry);
|
|
2281
|
+
if (flags.authGroups) return authGroupsDecisionModel(registry);
|
|
1961
2282
|
return flags.authEnforcement ? authDecisionModel : documentDecisionModel;
|
|
1962
2283
|
}
|
|
1963
2284
|
//#endregion
|
|
@@ -2087,16 +2408,24 @@ function subjectOf(operation) {
|
|
|
2087
2408
|
};
|
|
2088
2409
|
}
|
|
2089
2410
|
/**
|
|
2090
|
-
* The model as the walk reached this operation: each projection's value
|
|
2091
|
-
* own scope's state,
|
|
2411
|
+
* The model as the walk reached this operation: each static projection's value
|
|
2412
|
+
* is its own scope's state, and each derived projection's value maps document
|
|
2413
|
+
* id to that document's state, holding only the streams this replica walked. A
|
|
2414
|
+
* derived stream it does not hold stays out of the map, which fails closed.
|
|
2092
2415
|
*/
|
|
2093
|
-
function modelAt(readSet, states) {
|
|
2416
|
+
function modelAt(readSet, derivedNames, derived, states) {
|
|
2094
2417
|
const model = {};
|
|
2095
2418
|
for (const stream of readSet) {
|
|
2096
2419
|
const document = states.get(streamKey(stream.query));
|
|
2097
2420
|
if (document === void 0) throw new Error(`No state walked for projection ${stream.name}`);
|
|
2098
2421
|
model[stream.name] = document.state[stream.query.scope];
|
|
2099
2422
|
}
|
|
2423
|
+
for (const name of derivedNames) model[name] = {};
|
|
2424
|
+
for (const entry of derived) {
|
|
2425
|
+
const map = model[entry.name];
|
|
2426
|
+
const document = states.get(streamKey(entry.query));
|
|
2427
|
+
if (document !== void 0) map[entry.query.documentId] = document.state[entry.query.scope];
|
|
2428
|
+
}
|
|
2100
2429
|
return model;
|
|
2101
2430
|
}
|
|
2102
2431
|
/**
|
|
@@ -2112,6 +2441,7 @@ async function evaluateByPosition(model, target, subject, stores, signal) {
|
|
|
2112
2441
|
const { writeCache, operationStore } = stores;
|
|
2113
2442
|
const definition = model(target);
|
|
2114
2443
|
const readSet = staticReadSet(definition);
|
|
2444
|
+
const derivedSet = derivedReadSet(definition);
|
|
2115
2445
|
if (!definition.evaluatesScope(scope)) return operations.map(() => void 0);
|
|
2116
2446
|
const evaluating = new Set(operations.map((operation) => operation.id));
|
|
2117
2447
|
const readStreams = await Promise.all(readSet.map(async (stream) => ({
|
|
@@ -2123,24 +2453,76 @@ async function evaluateByPosition(model, target, subject, stores, signal) {
|
|
|
2123
2453
|
if (readStreams.length === 0) throw new Error(`Decision model for ${target.documentId} reads no stream whose query is known before it is built`);
|
|
2124
2454
|
const writtenProjection = readSet.find((stream) => stream.query.scope === scope);
|
|
2125
2455
|
const walked = [];
|
|
2456
|
+
const histories = [];
|
|
2126
2457
|
for (const read of readStreams) {
|
|
2127
|
-
const
|
|
2458
|
+
const streamOperations = read.stream === writtenProjection ? [...read.operations, ...operations] : read.operations;
|
|
2128
2459
|
const before = await writeCache.getState(read.stream.query.documentId, read.stream.query.scope, read.stream.query.branch, -1, signal);
|
|
2129
2460
|
walked.push({
|
|
2130
2461
|
streamKey: streamKey(read.stream.query),
|
|
2131
2462
|
scope: read.stream.query.scope,
|
|
2132
2463
|
document: before,
|
|
2133
|
-
operations:
|
|
2464
|
+
operations: streamOperations,
|
|
2134
2465
|
apply: read.stream.apply
|
|
2135
2466
|
});
|
|
2467
|
+
histories.push({
|
|
2468
|
+
name: read.stream.name,
|
|
2469
|
+
operations: streamOperations
|
|
2470
|
+
});
|
|
2136
2471
|
}
|
|
2137
|
-
|
|
2472
|
+
let evaluatedStateKey;
|
|
2473
|
+
if (writtenProjection !== void 0) evaluatedStateKey = streamKey(writtenProjection.query);
|
|
2474
|
+
else if (definition.foldEvaluatedScope !== void 0) {
|
|
2475
|
+
const query = {
|
|
2476
|
+
documentId: target.documentId,
|
|
2477
|
+
scope,
|
|
2478
|
+
branch: target.branch
|
|
2479
|
+
};
|
|
2480
|
+
const storedOperations = (await operationStore.getSince(query.documentId, query.scope, query.branch, -1, void 0, void 0, signal)).results.filter((operation) => !evaluating.has(operation.id));
|
|
2481
|
+
const before = await writeCache.getState(query.documentId, query.scope, query.branch, -1, signal);
|
|
2482
|
+
evaluatedStateKey = streamKey(query);
|
|
2483
|
+
walked.push({
|
|
2484
|
+
streamKey: evaluatedStateKey,
|
|
2485
|
+
scope,
|
|
2486
|
+
document: before,
|
|
2487
|
+
operations: [...storedOperations, ...operations],
|
|
2488
|
+
apply: definition.foldEvaluatedScope
|
|
2489
|
+
});
|
|
2490
|
+
} else walked.push({
|
|
2138
2491
|
streamKey: EVALUATED_ONLY,
|
|
2139
2492
|
scope,
|
|
2140
2493
|
document: walked[0].document,
|
|
2141
2494
|
operations,
|
|
2142
2495
|
apply: (document) => document
|
|
2143
2496
|
});
|
|
2497
|
+
const derivedEntries = [];
|
|
2498
|
+
const walkedKeys = new Set(walked.map((stream) => stream.streamKey));
|
|
2499
|
+
for (const projection of derivedSet) {
|
|
2500
|
+
const queries = projection.queryOverHistory?.(histories) ?? [];
|
|
2501
|
+
for (const query of queries) {
|
|
2502
|
+
const key = streamKey(query);
|
|
2503
|
+
if (walkedKeys.has(key)) continue;
|
|
2504
|
+
let before;
|
|
2505
|
+
try {
|
|
2506
|
+
before = await writeCache.getState(query.documentId, query.scope, query.branch, -1, signal);
|
|
2507
|
+
} catch (error) {
|
|
2508
|
+
if (error instanceof DocumentNotFoundError) continue;
|
|
2509
|
+
throw error;
|
|
2510
|
+
}
|
|
2511
|
+
const streamOperations = (await operationStore.getSince(query.documentId, query.scope, query.branch, -1, { actionTypes: projection.decidingActions }, void 0, signal)).results.filter((operation) => !evaluating.has(operation.id));
|
|
2512
|
+
walkedKeys.add(key);
|
|
2513
|
+
walked.push({
|
|
2514
|
+
streamKey: key,
|
|
2515
|
+
scope: query.scope,
|
|
2516
|
+
document: before,
|
|
2517
|
+
operations: streamOperations,
|
|
2518
|
+
apply: projection.apply
|
|
2519
|
+
});
|
|
2520
|
+
derivedEntries.push({
|
|
2521
|
+
name: projection.name,
|
|
2522
|
+
query
|
|
2523
|
+
});
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2144
2526
|
const reasons = /* @__PURE__ */ new Map();
|
|
2145
2527
|
const walk = walkByPosition(walked);
|
|
2146
2528
|
let step = walk.next(false);
|
|
@@ -2150,11 +2532,16 @@ async function evaluateByPosition(model, target, subject, stores, signal) {
|
|
|
2150
2532
|
step = walk.next(false);
|
|
2151
2533
|
continue;
|
|
2152
2534
|
}
|
|
2153
|
-
const
|
|
2535
|
+
const evaluatedDocument = evaluatedStateKey === void 0 ? void 0 : position.states.get(evaluatedStateKey);
|
|
2536
|
+
const scopeState = evaluatedDocument === void 0 ? void 0 : evaluatedDocument.state[scope];
|
|
2537
|
+
const evaluation = definition.decide(modelAt(readSet, derivedSet.map((projection) => projection.name), derivedEntries, position.states), subjectOf(position.operation), {
|
|
2154
2538
|
verb: "execute",
|
|
2155
2539
|
scope: position.operation.action.scope,
|
|
2156
2540
|
operation: position.operation.action.type
|
|
2157
|
-
}, {
|
|
2541
|
+
}, {
|
|
2542
|
+
scopeState,
|
|
2543
|
+
actionInput: position.operation.action.input
|
|
2544
|
+
});
|
|
2158
2545
|
const denied = evaluation.decision === "deny";
|
|
2159
2546
|
reasons.set(position.operation.id, denied ? evaluation.reason : void 0);
|
|
2160
2547
|
step = walk.next(denied);
|
|
@@ -2255,7 +2642,7 @@ var DocumentActionHandler = class {
|
|
|
2255
2642
|
verb: "execute",
|
|
2256
2643
|
scope: action.scope,
|
|
2257
2644
|
operation: action.type
|
|
2258
|
-
}, signal);
|
|
2645
|
+
}, signal, this.featureFlags.authConditions ? { actionInput: action.input } : void 0);
|
|
2259
2646
|
} catch (error) {
|
|
2260
2647
|
return buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
|
|
2261
2648
|
}
|
|
@@ -2442,13 +2829,6 @@ var DocumentActionHandler = class {
|
|
|
2442
2829
|
}
|
|
2443
2830
|
const documentState = document.state.document;
|
|
2444
2831
|
if (documentState.isDeleted && !this.alreadyEvaluated(executing)) return buildErrorResult(job, new DocumentDeletedError(documentId, documentState.deletedAtUtcIso), startTime);
|
|
2445
|
-
const nextIndex = getNextIndexForScope(document, job.scope);
|
|
2446
|
-
let upgradePath;
|
|
2447
|
-
if (fromVersion > 0 && fromVersion < toVersion) try {
|
|
2448
|
-
upgradePath = this.registry.computeUpgradePath(document.header.documentType, fromVersion, toVersion);
|
|
2449
|
-
} catch (error) {
|
|
2450
|
-
return buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
|
|
2451
|
-
}
|
|
2452
2832
|
if (fromVersion === toVersion && fromVersion > 0) return {
|
|
2453
2833
|
job,
|
|
2454
2834
|
success: true,
|
|
@@ -2456,6 +2836,48 @@ var DocumentActionHandler = class {
|
|
|
2456
2836
|
operationsWithContext: [],
|
|
2457
2837
|
duration: Date.now() - startTime
|
|
2458
2838
|
};
|
|
2839
|
+
const arrivesDecided = executing.replayingAcceptedHistory || executing.evaluatedByPosition;
|
|
2840
|
+
if (fromVersion > 0 && !arrivesDecided) {
|
|
2841
|
+
const stampedVersion = normalizeDocumentModelVersion(documentState.version);
|
|
2842
|
+
if (fromVersion !== stampedVersion) return buildErrorResult(job, new UpgradePreconditionFailedError(documentId, `fromVersion ${fromVersion} does not match the document's version ${stampedVersion}`), startTime);
|
|
2843
|
+
if (input.revision !== void 0) {
|
|
2844
|
+
let actualRevisions;
|
|
2845
|
+
try {
|
|
2846
|
+
actualRevisions = (await stores.operationStore.getRevisions(documentId, job.branch, signal)).revision;
|
|
2847
|
+
} catch (error) {
|
|
2848
|
+
return buildErrorResult(job, /* @__PURE__ */ new Error(`Failed to fetch revisions for upgrade: ${error instanceof Error ? error.message : String(error)}`), startTime);
|
|
2849
|
+
}
|
|
2850
|
+
const revisionScopes = new Set([...Object.keys(input.revision), ...Object.keys(actualRevisions)]);
|
|
2851
|
+
for (const revisionScope of revisionScopes) {
|
|
2852
|
+
const snapshot = input.revision[revisionScope] ?? 0;
|
|
2853
|
+
const actual = actualRevisions[revisionScope] ?? 0;
|
|
2854
|
+
if (snapshot !== actual) return buildErrorResult(job, new UpgradePreconditionFailedError(documentId, `revision snapshot for scope "${revisionScope}" is ${snapshot} but the document is at ${actual}`), startTime);
|
|
2855
|
+
}
|
|
2856
|
+
}
|
|
2857
|
+
}
|
|
2858
|
+
let upgradePath;
|
|
2859
|
+
if (fromVersion > 0 && fromVersion < toVersion) try {
|
|
2860
|
+
upgradePath = this.registry.computeUpgradePath(document.header.documentType, fromVersion, toVersion);
|
|
2861
|
+
} catch (error) {
|
|
2862
|
+
return buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
|
|
2863
|
+
}
|
|
2864
|
+
const otherScopes = Object.keys(document.state).filter((scope) => scope !== job.scope);
|
|
2865
|
+
if (fromVersion > 0) for (const scope of otherScopes) {
|
|
2866
|
+
let scopedDocument;
|
|
2867
|
+
try {
|
|
2868
|
+
scopedDocument = await stores.writeCache.getState(documentId, scope, job.branch, void 0, signal);
|
|
2869
|
+
} catch (error) {
|
|
2870
|
+
return buildErrorResult(job, /* @__PURE__ */ new Error(`Failed to fetch ${scope} scope for upgrade: ${error instanceof Error ? error.message : String(error)}`), startTime);
|
|
2871
|
+
}
|
|
2872
|
+
document = {
|
|
2873
|
+
...document,
|
|
2874
|
+
state: {
|
|
2875
|
+
...document.state,
|
|
2876
|
+
[scope]: scopedDocument.state[scope]
|
|
2877
|
+
}
|
|
2878
|
+
};
|
|
2879
|
+
}
|
|
2880
|
+
const nextIndex = getNextIndexForScope(document, job.scope);
|
|
2459
2881
|
try {
|
|
2460
2882
|
document = applyUpgradeDocumentAction$1(document, action, upgradePath);
|
|
2461
2883
|
} catch (error) {
|
|
@@ -2470,6 +2892,7 @@ var DocumentActionHandler = class {
|
|
|
2470
2892
|
header: document.header,
|
|
2471
2893
|
...document.state
|
|
2472
2894
|
};
|
|
2895
|
+
if (fromVersion > 0) resultingStateObj.__migrated = true;
|
|
2473
2896
|
const resultingState = JSON.stringify(resultingStateObj);
|
|
2474
2897
|
const writeResult = await this.writeOperationToStore({
|
|
2475
2898
|
documentId,
|
|
@@ -2485,6 +2908,11 @@ var DocumentActionHandler = class {
|
|
|
2485
2908
|
[job.scope]: [...document.operations[job.scope] ?? [], operation]
|
|
2486
2909
|
};
|
|
2487
2910
|
stores.writeCache.putState(documentId, job.scope, job.branch, operation.index, document, SnapshotPosition.Head);
|
|
2911
|
+
for (const scope of otherScopes) executing.postCommitInvalidations.push({
|
|
2912
|
+
documentId,
|
|
2913
|
+
scope,
|
|
2914
|
+
branch: job.branch
|
|
2915
|
+
});
|
|
2488
2916
|
indexTxn.write([{
|
|
2489
2917
|
...operation,
|
|
2490
2918
|
documentId,
|
|
@@ -2696,10 +3124,12 @@ var SimpleJobExecutor = class {
|
|
|
2696
3124
|
};
|
|
2697
3125
|
this.featureFlags = {
|
|
2698
3126
|
documentDecisions: config.featureFlags?.documentDecisions ?? false,
|
|
2699
|
-
authEnforcement: config.featureFlags?.authEnforcement ?? false
|
|
3127
|
+
authEnforcement: config.featureFlags?.authEnforcement ?? false,
|
|
3128
|
+
authGroups: config.featureFlags?.authGroups ?? false,
|
|
3129
|
+
authConditions: config.featureFlags?.authConditions ?? false
|
|
2700
3130
|
};
|
|
2701
3131
|
validateFeatureFlags(this.featureFlags, FLAG_PREREQUISITES);
|
|
2702
|
-
this.decisionModel = selectDecisionModel(this.featureFlags);
|
|
3132
|
+
this.decisionModel = selectDecisionModel(this.featureFlags, registry);
|
|
2703
3133
|
this.signatureVerifierModule = new SignatureVerifier(signatureVerifier);
|
|
2704
3134
|
this.documentActionHandler = new DocumentActionHandler(registry, logger, driveContainerTypes, this.featureFlags, this.decisionModel);
|
|
2705
3135
|
this.executionScope = executionScope ?? new DefaultExecutionScope(operationStore, operationIndex, writeCache, documentMetaCache, collectionMembershipCache);
|
|
@@ -2711,6 +3141,7 @@ var SimpleJobExecutor = class {
|
|
|
2711
3141
|
async executeJob(job, signal) {
|
|
2712
3142
|
const startTime = Date.now();
|
|
2713
3143
|
const touchedCacheEntries = [];
|
|
3144
|
+
const postCommitInvalidations = [];
|
|
2714
3145
|
let pendingEvent;
|
|
2715
3146
|
let result;
|
|
2716
3147
|
try {
|
|
@@ -2724,7 +3155,8 @@ var SimpleJobExecutor = class {
|
|
|
2724
3155
|
stores,
|
|
2725
3156
|
signal,
|
|
2726
3157
|
replayingAcceptedHistory: true,
|
|
2727
|
-
evaluatedByPosition: false
|
|
3158
|
+
evaluatedByPosition: false,
|
|
3159
|
+
postCommitInvalidations
|
|
2728
3160
|
});
|
|
2729
3161
|
if (loadResult.success && loadResult.operationsWithContext) {
|
|
2730
3162
|
for (const owc of loadResult.operationsWithContext) touchedCacheEntries.push({
|
|
@@ -2744,6 +3176,37 @@ var SimpleJobExecutor = class {
|
|
|
2744
3176
|
}
|
|
2745
3177
|
return loadResult;
|
|
2746
3178
|
}
|
|
3179
|
+
if (job.kind === "reevaluation") {
|
|
3180
|
+
const reevalResult = await this.executeReevaluationJob({
|
|
3181
|
+
job,
|
|
3182
|
+
startTime,
|
|
3183
|
+
indexTxn,
|
|
3184
|
+
stores,
|
|
3185
|
+
signal,
|
|
3186
|
+
replayingAcceptedHistory: false,
|
|
3187
|
+
evaluatedByPosition: false,
|
|
3188
|
+
postCommitInvalidations
|
|
3189
|
+
});
|
|
3190
|
+
if (reevalResult.success && reevalResult.operationsWithContext) {
|
|
3191
|
+
for (const owc of reevalResult.operationsWithContext) touchedCacheEntries.push({
|
|
3192
|
+
documentId: owc.context.documentId,
|
|
3193
|
+
scope: owc.context.scope,
|
|
3194
|
+
branch: owc.context.branch
|
|
3195
|
+
});
|
|
3196
|
+
const ordinals = await stores.operationIndex.commit(indexTxn, signal);
|
|
3197
|
+
for (let i = 0; i < reevalResult.operationsWithContext.length; i++) reevalResult.operationsWithContext[i].context.ordinal = ordinals[i];
|
|
3198
|
+
if (reevalResult.operationsWithContext.length > 0) {
|
|
3199
|
+
const collectionMemberships = await this.getCollectionMembershipsForOperations(reevalResult.operationsWithContext, stores);
|
|
3200
|
+
pendingEvent = {
|
|
3201
|
+
jobId: job.id,
|
|
3202
|
+
operations: reevalResult.operationsWithContext,
|
|
3203
|
+
jobMeta: job.meta,
|
|
3204
|
+
collectionMemberships
|
|
3205
|
+
};
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3208
|
+
return reevalResult;
|
|
3209
|
+
}
|
|
2747
3210
|
const positioned = await this.positionByTimestamp(job, stores, signal);
|
|
2748
3211
|
if (positioned.error) return buildErrorResult(job, positioned.error, startTime);
|
|
2749
3212
|
const executing = {
|
|
@@ -2753,7 +3216,8 @@ var SimpleJobExecutor = class {
|
|
|
2753
3216
|
stores,
|
|
2754
3217
|
signal,
|
|
2755
3218
|
replayingAcceptedHistory: false,
|
|
2756
|
-
evaluatedByPosition: positioned.evaluatedByPosition
|
|
3219
|
+
evaluatedByPosition: positioned.evaluatedByPosition,
|
|
3220
|
+
postCommitInvalidations
|
|
2757
3221
|
};
|
|
2758
3222
|
const actionResult = await this.processActions(positioned.writes, executing);
|
|
2759
3223
|
if (!actionResult.success) return {
|
|
@@ -2803,6 +3267,7 @@ var SimpleJobExecutor = class {
|
|
|
2803
3267
|
}
|
|
2804
3268
|
throw error;
|
|
2805
3269
|
}
|
|
3270
|
+
if (result.success) for (const entry of postCommitInvalidations) this.writeCache.invalidate(entry.documentId, entry.scope, entry.branch);
|
|
2806
3271
|
if (pendingEvent) this.eventBus.emit(ReactorEventTypes.JOB_WRITE_READY, pendingEvent).catch((error) => {
|
|
2807
3272
|
this.logger.error("Failed to emit JOB_WRITE_READY event: @Event : @Error", pendingEvent, error);
|
|
2808
3273
|
});
|
|
@@ -2880,7 +3345,7 @@ var SimpleJobExecutor = class {
|
|
|
2880
3345
|
verb: "execute",
|
|
2881
3346
|
scope: action.scope,
|
|
2882
3347
|
operation: action.type
|
|
2883
|
-
}, signal);
|
|
3348
|
+
}, signal, this.featureFlags.authConditions ? { actionInput: action.input } : void 0);
|
|
2884
3349
|
} catch (error) {
|
|
2885
3350
|
return buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
|
|
2886
3351
|
}
|
|
@@ -2918,8 +3383,7 @@ var SimpleJobExecutor = class {
|
|
|
2918
3383
|
}
|
|
2919
3384
|
let module;
|
|
2920
3385
|
try {
|
|
2921
|
-
|
|
2922
|
-
module = this.registry.getModule(document.header.documentType, moduleVersion);
|
|
3386
|
+
module = this.registry.getModule(document.header.documentType, normalizeDocumentModelVersion(documentVersion));
|
|
2923
3387
|
} catch (error) {
|
|
2924
3388
|
return buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
|
|
2925
3389
|
}
|
|
@@ -3004,6 +3468,7 @@ var SimpleJobExecutor = class {
|
|
|
3004
3468
|
scope,
|
|
3005
3469
|
sourceRemote
|
|
3006
3470
|
}]);
|
|
3471
|
+
if (scope === "auth") indexTxn.recordGroupReferences(job.documentId, mentionedGroupIds(action));
|
|
3007
3472
|
return {
|
|
3008
3473
|
job,
|
|
3009
3474
|
success: true,
|
|
@@ -3200,7 +3665,7 @@ var SimpleJobExecutor = class {
|
|
|
3200
3665
|
const revisions = await stores.operationStore.getRevisions(job.documentId, job.branch, signal);
|
|
3201
3666
|
const latest = Date.parse(revisions.latestTimestamp);
|
|
3202
3667
|
if (!criteria.operations.some((operation) => Date.parse(operation.timestampUtcMs) < latest)) return;
|
|
3203
|
-
return this.reevaluateDocument(executing);
|
|
3668
|
+
return (await this.reevaluateDocument(executing)).error;
|
|
3204
3669
|
}
|
|
3205
3670
|
/**
|
|
3206
3671
|
* Re-evaluates every scope the model evaluates. Where an operation's
|
|
@@ -3213,6 +3678,7 @@ var SimpleJobExecutor = class {
|
|
|
3213
3678
|
documentId: job.documentId,
|
|
3214
3679
|
branch: job.branch
|
|
3215
3680
|
};
|
|
3681
|
+
const reappended = [];
|
|
3216
3682
|
const revisions = await stores.operationStore.getRevisions(job.documentId, job.branch, signal);
|
|
3217
3683
|
for (const scope of this.evaluationOrder(target, revisions.revision)) {
|
|
3218
3684
|
const stored = (await stores.operationStore.getSince(job.documentId, scope, job.branch, -1, void 0, void 0, signal)).results;
|
|
@@ -3241,8 +3707,60 @@ var SimpleJobExecutor = class {
|
|
|
3241
3707
|
replayingAcceptedHistory: true,
|
|
3242
3708
|
evaluatedByPosition: true
|
|
3243
3709
|
});
|
|
3244
|
-
if (!result.success) return
|
|
3710
|
+
if (!result.success) return {
|
|
3711
|
+
error: result.error ?? /* @__PURE__ */ new Error(`Re-evaluation of ${job.documentId} ${scope} failed`),
|
|
3712
|
+
operationsWithContext: reappended
|
|
3713
|
+
};
|
|
3714
|
+
reappended.push(...result.operationsWithContext);
|
|
3715
|
+
}
|
|
3716
|
+
return { operationsWithContext: reappended };
|
|
3717
|
+
}
|
|
3718
|
+
/**
|
|
3719
|
+
* Re-judges a document's stored operations because a read-set stream in
|
|
3720
|
+
* another document (a group) gained an operation. The trigger timestamp
|
|
3721
|
+
* bounds the work: an operation later than everything this document holds
|
|
3722
|
+
* cannot change any evaluation, so the pass is skipped.
|
|
3723
|
+
*/
|
|
3724
|
+
async executeReevaluationJob(executing) {
|
|
3725
|
+
const { job, startTime, stores, signal } = executing;
|
|
3726
|
+
if (!this.featureFlags.documentDecisions) return {
|
|
3727
|
+
job,
|
|
3728
|
+
success: true,
|
|
3729
|
+
operations: [],
|
|
3730
|
+
operationsWithContext: [],
|
|
3731
|
+
duration: Date.now() - startTime
|
|
3732
|
+
};
|
|
3733
|
+
const trigger = job.meta.triggerTimestampUtcMs;
|
|
3734
|
+
if (typeof trigger === "string") {
|
|
3735
|
+
let latestTimestamp;
|
|
3736
|
+
try {
|
|
3737
|
+
latestTimestamp = (await stores.operationStore.getRevisions(job.documentId, job.branch, signal)).latestTimestamp;
|
|
3738
|
+
} catch {
|
|
3739
|
+
return {
|
|
3740
|
+
job,
|
|
3741
|
+
success: true,
|
|
3742
|
+
operations: [],
|
|
3743
|
+
operationsWithContext: [],
|
|
3744
|
+
duration: Date.now() - startTime
|
|
3745
|
+
};
|
|
3746
|
+
}
|
|
3747
|
+
if (Date.parse(trigger) > Date.parse(latestTimestamp)) return {
|
|
3748
|
+
job,
|
|
3749
|
+
success: true,
|
|
3750
|
+
operations: [],
|
|
3751
|
+
operationsWithContext: [],
|
|
3752
|
+
duration: Date.now() - startTime
|
|
3753
|
+
};
|
|
3245
3754
|
}
|
|
3755
|
+
const outcome = await this.reevaluateDocument(executing);
|
|
3756
|
+
if (outcome.error) return buildErrorResult(job, outcome.error, startTime);
|
|
3757
|
+
return {
|
|
3758
|
+
job,
|
|
3759
|
+
success: true,
|
|
3760
|
+
operations: outcome.operationsWithContext.map((owc) => owc.operation),
|
|
3761
|
+
operationsWithContext: outcome.operationsWithContext,
|
|
3762
|
+
duration: Date.now() - startTime
|
|
3763
|
+
};
|
|
3246
3764
|
}
|
|
3247
3765
|
async executeLoadJob(executing) {
|
|
3248
3766
|
const { job, startTime, indexTxn, stores, signal } = executing;
|
|
@@ -3527,7 +4045,7 @@ var DocumentModelRegistry = class {
|
|
|
3527
4045
|
}
|
|
3528
4046
|
computeUpgradePath(documentType, fromVersion, toVersion) {
|
|
3529
4047
|
if (fromVersion === toVersion) return [];
|
|
3530
|
-
if (toVersion < fromVersion) throw new DowngradeNotSupportedError(documentType, fromVersion, toVersion);
|
|
4048
|
+
if (toVersion < fromVersion) throw new DowngradeNotSupportedError$1(documentType, fromVersion, toVersion);
|
|
3531
4049
|
const manifest = this.getUpgradeManifest(documentType);
|
|
3532
4050
|
const path = [];
|
|
3533
4051
|
for (let v = fromVersion + 1; v <= toVersion; v++) {
|
|
@@ -4004,8 +4522,8 @@ function createForwardingPoolInstrumentation(name) {
|
|
|
4004
4522
|
}
|
|
4005
4523
|
//#endregion
|
|
4006
4524
|
//#region src/storage/migrations/001_create_operation_table.ts
|
|
4007
|
-
var _001_create_operation_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
4008
|
-
async function up$
|
|
4525
|
+
var _001_create_operation_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$16 });
|
|
4526
|
+
async function up$16(db) {
|
|
4009
4527
|
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", [
|
|
4010
4528
|
"documentId",
|
|
4011
4529
|
"scope",
|
|
@@ -4030,8 +4548,8 @@ async function up$15(db) {
|
|
|
4030
4548
|
}
|
|
4031
4549
|
//#endregion
|
|
4032
4550
|
//#region src/storage/migrations/002_create_keyframe_table.ts
|
|
4033
|
-
var _002_create_keyframe_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
4034
|
-
async function up$
|
|
4551
|
+
var _002_create_keyframe_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$15 });
|
|
4552
|
+
async function up$15(db) {
|
|
4035
4553
|
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", [
|
|
4036
4554
|
"documentId",
|
|
4037
4555
|
"scope",
|
|
@@ -4047,14 +4565,14 @@ async function up$14(db) {
|
|
|
4047
4565
|
}
|
|
4048
4566
|
//#endregion
|
|
4049
4567
|
//#region src/storage/migrations/003_create_document_table.ts
|
|
4050
|
-
var _003_create_document_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
4051
|
-
async function up$
|
|
4568
|
+
var _003_create_document_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$14 });
|
|
4569
|
+
async function up$14(db) {
|
|
4052
4570
|
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();
|
|
4053
4571
|
}
|
|
4054
4572
|
//#endregion
|
|
4055
4573
|
//#region src/storage/migrations/004_create_document_relationship_table.ts
|
|
4056
|
-
var _004_create_document_relationship_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
4057
|
-
async function up$
|
|
4574
|
+
var _004_create_document_relationship_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$13 });
|
|
4575
|
+
async function up$13(db) {
|
|
4058
4576
|
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", [
|
|
4059
4577
|
"sourceId",
|
|
4060
4578
|
"targetId",
|
|
@@ -4066,14 +4584,14 @@ async function up$12(db) {
|
|
|
4066
4584
|
}
|
|
4067
4585
|
//#endregion
|
|
4068
4586
|
//#region src/storage/migrations/005_create_indexer_state_table.ts
|
|
4069
|
-
var _005_create_indexer_state_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
4070
|
-
async function up$
|
|
4587
|
+
var _005_create_indexer_state_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$12 });
|
|
4588
|
+
async function up$12(db) {
|
|
4071
4589
|
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();
|
|
4072
4590
|
}
|
|
4073
4591
|
//#endregion
|
|
4074
4592
|
//#region src/storage/migrations/006_create_document_snapshot_table.ts
|
|
4075
|
-
var _006_create_document_snapshot_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
4076
|
-
async function up$
|
|
4593
|
+
var _006_create_document_snapshot_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$11 });
|
|
4594
|
+
async function up$11(db) {
|
|
4077
4595
|
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", [
|
|
4078
4596
|
"documentId",
|
|
4079
4597
|
"scope",
|
|
@@ -4094,8 +4612,8 @@ async function up$10(db) {
|
|
|
4094
4612
|
}
|
|
4095
4613
|
//#endregion
|
|
4096
4614
|
//#region src/storage/migrations/007_create_slug_mapping_table.ts
|
|
4097
|
-
var _007_create_slug_mapping_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
4098
|
-
async function up$
|
|
4615
|
+
var _007_create_slug_mapping_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$10 });
|
|
4616
|
+
async function up$10(db) {
|
|
4099
4617
|
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", [
|
|
4100
4618
|
"documentId",
|
|
4101
4619
|
"scope",
|
|
@@ -4105,14 +4623,14 @@ async function up$9(db) {
|
|
|
4105
4623
|
}
|
|
4106
4624
|
//#endregion
|
|
4107
4625
|
//#region src/storage/migrations/008_create_view_state_table.ts
|
|
4108
|
-
var _008_create_view_state_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
4109
|
-
async function up$
|
|
4626
|
+
var _008_create_view_state_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$9 });
|
|
4627
|
+
async function up$9(db) {
|
|
4110
4628
|
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();
|
|
4111
4629
|
}
|
|
4112
4630
|
//#endregion
|
|
4113
4631
|
//#region src/storage/migrations/009_create_operation_index_tables.ts
|
|
4114
|
-
var _009_create_operation_index_tables_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
4115
|
-
async function up$
|
|
4632
|
+
var _009_create_operation_index_tables_exports = /* @__PURE__ */ __exportAll({ up: () => up$8 });
|
|
4633
|
+
async function up$8(db) {
|
|
4116
4634
|
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();
|
|
4117
4635
|
await db.schema.createIndex("idx_document_collections_collectionId").on("document_collections").column("collectionId").execute();
|
|
4118
4636
|
await db.schema.createIndex("idx_doc_collections_collection_range").on("document_collections").columns(["collectionId", "joinedOrdinal"]).execute();
|
|
@@ -4126,8 +4644,8 @@ async function up$7(db) {
|
|
|
4126
4644
|
}
|
|
4127
4645
|
//#endregion
|
|
4128
4646
|
//#region src/storage/migrations/010_create_sync_tables.ts
|
|
4129
|
-
var _010_create_sync_tables_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
4130
|
-
async function up$
|
|
4647
|
+
var _010_create_sync_tables_exports = /* @__PURE__ */ __exportAll({ up: () => up$7 });
|
|
4648
|
+
async function up$7(db) {
|
|
4131
4649
|
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();
|
|
4132
4650
|
await db.schema.createIndex("idx_sync_remotes_collection").on("sync_remotes").column("collection_id").execute();
|
|
4133
4651
|
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();
|
|
@@ -4135,8 +4653,8 @@ async function up$6(db) {
|
|
|
4135
4653
|
}
|
|
4136
4654
|
//#endregion
|
|
4137
4655
|
//#region src/storage/migrations/011_add_cursor_type_column.ts
|
|
4138
|
-
var _011_add_cursor_type_column_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
4139
|
-
async function up$
|
|
4656
|
+
var _011_add_cursor_type_column_exports = /* @__PURE__ */ __exportAll({ up: () => up$6 });
|
|
4657
|
+
async function up$6(db) {
|
|
4140
4658
|
await db.deleteFrom("sync_cursors").where("remote_name", "like", "outbox::%").execute();
|
|
4141
4659
|
await db.deleteFrom("sync_remotes").where("name", "like", "outbox::%").execute();
|
|
4142
4660
|
await db.schema.dropTable("sync_cursors").execute();
|
|
@@ -4145,60 +4663,82 @@ async function up$5(db) {
|
|
|
4145
4663
|
}
|
|
4146
4664
|
//#endregion
|
|
4147
4665
|
//#region src/storage/migrations/012_add_source_remote_column.ts
|
|
4148
|
-
var _012_add_source_remote_column_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
4149
|
-
async function up$
|
|
4666
|
+
var _012_add_source_remote_column_exports = /* @__PURE__ */ __exportAll({ up: () => up$5 });
|
|
4667
|
+
async function up$5(db) {
|
|
4150
4668
|
await db.schema.alterTable("operation_index_operations").addColumn("sourceRemote", "text", (col) => col.notNull().defaultTo("")).execute();
|
|
4151
4669
|
}
|
|
4152
4670
|
//#endregion
|
|
4153
4671
|
//#region src/storage/migrations/013_create_sync_dead_letters_table.ts
|
|
4154
|
-
var _013_create_sync_dead_letters_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
4155
|
-
async function up$
|
|
4672
|
+
var _013_create_sync_dead_letters_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$4 });
|
|
4673
|
+
async function up$4(db) {
|
|
4156
4674
|
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();
|
|
4157
4675
|
await db.schema.createIndex("idx_sync_dead_letters_remote").on("sync_dead_letters").column("remote_name").execute();
|
|
4158
4676
|
}
|
|
4159
4677
|
//#endregion
|
|
4160
4678
|
//#region src/storage/migrations/014_create_processor_cursor_table.ts
|
|
4161
|
-
var _014_create_processor_cursor_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
4162
|
-
async function up$
|
|
4679
|
+
var _014_create_processor_cursor_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$3 });
|
|
4680
|
+
async function up$3(db) {
|
|
4163
4681
|
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();
|
|
4164
4682
|
}
|
|
4165
4683
|
//#endregion
|
|
4166
4684
|
//#region src/storage/migrations/015_add_operation_denied_reason.ts
|
|
4167
4685
|
var _015_add_operation_denied_reason_exports = /* @__PURE__ */ __exportAll({
|
|
4168
|
-
down: () => down$
|
|
4169
|
-
up: () => up$
|
|
4686
|
+
down: () => down$2,
|
|
4687
|
+
up: () => up$2
|
|
4170
4688
|
});
|
|
4171
4689
|
/**
|
|
4172
4690
|
* Records why authorization refused an operation. Separate from `error` so a
|
|
4173
4691
|
* denial is distinguishable from a reducer failure without matching on a
|
|
4174
4692
|
* message. Null for every operation written before decisions were enforced.
|
|
4175
4693
|
*/
|
|
4176
|
-
async function up$
|
|
4694
|
+
async function up$2(db) {
|
|
4177
4695
|
await db.schema.alterTable("Operation").addColumn("deniedReason", "text").execute();
|
|
4178
4696
|
await db.schema.alterTable("operation_index_operations").addColumn("deniedReason", "text").execute();
|
|
4179
4697
|
}
|
|
4180
|
-
async function down$
|
|
4698
|
+
async function down$2(db) {
|
|
4181
4699
|
await db.schema.alterTable("operation_index_operations").dropColumn("deniedReason").execute();
|
|
4182
4700
|
await db.schema.alterTable("Operation").dropColumn("deniedReason").execute();
|
|
4183
4701
|
}
|
|
4184
4702
|
//#endregion
|
|
4185
4703
|
//#region src/storage/migrations/016_add_dead_letter_error_type.ts
|
|
4186
4704
|
var _016_add_dead_letter_error_type_exports = /* @__PURE__ */ __exportAll({
|
|
4187
|
-
down: () => down,
|
|
4188
|
-
up: () => up
|
|
4705
|
+
down: () => down$1,
|
|
4706
|
+
up: () => up$1
|
|
4189
4707
|
});
|
|
4190
4708
|
/**
|
|
4191
4709
|
* The classification a dead letter falls into, stored because it decides whether
|
|
4192
4710
|
* the document stays quarantined and the in-memory error is gone after a restart.
|
|
4193
4711
|
* Defaulted rather than nullable, so a pre-existing row rehydrates.
|
|
4194
4712
|
*/
|
|
4195
|
-
async function up(db) {
|
|
4713
|
+
async function up$1(db) {
|
|
4196
4714
|
await db.schema.alterTable("sync_dead_letters").addColumn("error_type", "text", (col) => col.notNull().defaultTo("UNCLASSIFIED")).execute();
|
|
4197
4715
|
}
|
|
4198
|
-
async function down(db) {
|
|
4716
|
+
async function down$1(db) {
|
|
4199
4717
|
await db.schema.alterTable("sync_dead_letters").dropColumn("error_type").execute();
|
|
4200
4718
|
}
|
|
4201
4719
|
//#endregion
|
|
4720
|
+
//#region src/storage/migrations/017_create_group_references.ts
|
|
4721
|
+
var _017_create_group_references_exports = /* @__PURE__ */ __exportAll({
|
|
4722
|
+
down: () => down,
|
|
4723
|
+
up: () => up
|
|
4724
|
+
});
|
|
4725
|
+
/**
|
|
4726
|
+
* One row per (document, group) reference ever discovered from an auth
|
|
4727
|
+
* operation's input. Rows are never updated or deleted: auth evaluation is
|
|
4728
|
+
* positional, so a grant that named a group at any position keeps that
|
|
4729
|
+
* group's stream in the document's read-set even after a later operation
|
|
4730
|
+
* removes the reference. Read by documentId for the groups a document
|
|
4731
|
+
* requires (sync), and by groupId for the documents a group change affects
|
|
4732
|
+
* (re-evaluation).
|
|
4733
|
+
*/
|
|
4734
|
+
async function up(db) {
|
|
4735
|
+
await db.schema.createTable("group_references").addColumn("documentId", "text", (col) => col.notNull()).addColumn("groupId", "text", (col) => col.notNull()).addPrimaryKeyConstraint("group_references_pkey", ["documentId", "groupId"]).execute();
|
|
4736
|
+
await db.schema.createIndex("idx_group_references_groupId").on("group_references").column("groupId").execute();
|
|
4737
|
+
}
|
|
4738
|
+
async function down(db) {
|
|
4739
|
+
await db.schema.dropTable("group_references").execute();
|
|
4740
|
+
}
|
|
4741
|
+
//#endregion
|
|
4202
4742
|
//#region src/storage/migrations/migrator.ts
|
|
4203
4743
|
const REACTOR_SCHEMA = "reactor";
|
|
4204
4744
|
const migrations = {
|
|
@@ -4217,7 +4757,8 @@ const migrations = {
|
|
|
4217
4757
|
"013_create_sync_dead_letters_table": _013_create_sync_dead_letters_table_exports,
|
|
4218
4758
|
"014_create_processor_cursor_table": _014_create_processor_cursor_table_exports,
|
|
4219
4759
|
"015_add_operation_denied_reason": _015_add_operation_denied_reason_exports,
|
|
4220
|
-
"016_add_dead_letter_error_type": _016_add_dead_letter_error_type_exports
|
|
4760
|
+
"016_add_dead_letter_error_type": _016_add_dead_letter_error_type_exports,
|
|
4761
|
+
"017_create_group_references": _017_create_group_references_exports
|
|
4221
4762
|
};
|
|
4222
4763
|
var ProgrammaticMigrationProvider = class {
|
|
4223
4764
|
getMigrations() {
|
|
@@ -4271,6 +4812,6 @@ async function getMigrationStatus(db, schema = REACTOR_SCHEMA) {
|
|
|
4271
4812
|
//#region src/core/drive-container-types.ts
|
|
4272
4813
|
const DEFAULT_DRIVE_CONTAINER_TYPES = new Set(["powerhouse/document-drive", "powerhouse/reactor-drive"]);
|
|
4273
4814
|
//#endregion
|
|
4274
|
-
export { OptimisticLockError as A, ExcessiveReshuffleError as B, DocumentMetaCache as C, APPEND_CONDITION_FAILED_PREFIX as D, CollectionMembershipCache as E, ModuleNotFoundError as F,
|
|
4815
|
+
export { OptimisticLockError as A, ExcessiveReshuffleError as B, DocumentMetaCache as C, APPEND_CONDITION_FAILED_PREFIX as D, CollectionMembershipCache as E, ModuleNotFoundError as F, throwIfAborted as G, UpgradePreconditionFailedError as H, AuthTimestampNotMonotonicError as I, __exportAll as K, AuthorizationDeniedError as L, DuplicateManifestError as M, DuplicateModuleError as N, AppendConditionFailedError as O, InvalidModuleError as P, DocumentDeletedError as R, KyselyOperationIndex as S, createEmptyConsistencyToken as T, matchesScope as U, InvalidOperationTimestampError as V, parsePagingOptions as W, KyselyExecutionScope as _, createForwardingPoolInstrumentation as a, EventBus as b, KyselyKeyframeStore as c, DriveCollectionId as d, decideAtHead as f, authDecisionModel as g, buildDecisionModel as h, runMigrations as i, RevisionMismatchError as j, DuplicateOperationError as k, DocumentModelRegistry as l, documentDecisionModel as m, REACTOR_SCHEMA as n, instrumentPgPool as o, selectDecisionModel as p, getMigrationStatus as r, KyselyOperationStore as s, DEFAULT_DRIVE_CONTAINER_TYPES as t, SimpleJobExecutor as u, FLAG_PREREQUISITES as v, createConsistencyToken as w, KyselyWriteCache as x, validateFeatureFlags as y, DocumentNotFoundError as z };
|
|
4275
4816
|
|
|
4276
|
-
//# sourceMappingURL=drive-container-types-
|
|
4817
|
+
//# sourceMappingURL=drive-container-types-h3M1AK3K.js.map
|