@powerhousedao/reactor 6.2.2-dev.36 → 6.2.2-dev.38
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-B9gIvuvf.js → build-worker-executor-CAEZD_yJ.js} +2 -2
- package/dist/{build-worker-executor-B9gIvuvf.js.map → build-worker-executor-CAEZD_yJ.js.map} +1 -1
- package/dist/{document-indexer-BY-mPXJF.js → document-indexer-C0GB0b8Q.js} +11 -5
- package/dist/document-indexer-C0GB0b8Q.js.map +1 -0
- package/dist/{drive-container-types-ZSLCC3lC.js → drive-container-types-BJCKXJwH.js} +607 -178
- package/dist/drive-container-types-BJCKXJwH.js.map +1 -0
- package/dist/entry.js +2 -2
- package/dist/index.d.ts +215 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +225 -83
- package/dist/index.js.map +1 -1
- package/dist/projection-entry.js +4 -4
- package/dist/projection-entry.js.map +1 -1
- package/dist/{worker-BMn-2vsH.js → worker-jEJW6_j7.js} +2 -2
- package/dist/{worker-BMn-2vsH.js.map → worker-jEJW6_j7.js.map} +1 -1
- package/package.json +5 -4
- package/dist/document-indexer-BY-mPXJF.js.map +0 -1
- package/dist/drive-container-types-ZSLCC3lC.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, baseReducerVersion, createPresignedHeader, decide, defaultBaseState, deriveOperationId, 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, 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";
|
|
3
3
|
import { v4 } from "uuid";
|
|
4
4
|
import { Migrator, sql } from "kysely";
|
|
5
5
|
//#region \0rolldown/runtime.js
|
|
@@ -93,6 +93,81 @@ var AuthorizationDeniedError = class AuthorizationDeniedError extends Error {
|
|
|
93
93
|
}
|
|
94
94
|
};
|
|
95
95
|
/**
|
|
96
|
+
* An auth operation did not strictly exceed the newest timestamp in its stream.
|
|
97
|
+
*
|
|
98
|
+
* Terminal and asymmetric by design: no ordering rule can reconcile two replicas
|
|
99
|
+
* that each accepted an auth operation offline, because either order hands one
|
|
100
|
+
* authority the other never granted, so the replica ahead holds the arrival.
|
|
101
|
+
*/
|
|
102
|
+
var AuthTimestampNotMonotonicError = class AuthTimestampNotMonotonicError extends Error {
|
|
103
|
+
documentId;
|
|
104
|
+
branch;
|
|
105
|
+
timestampUtcMs;
|
|
106
|
+
newestTimestampUtcMs;
|
|
107
|
+
constructor(documentId, branch, timestampUtcMs, newestTimestampUtcMs) {
|
|
108
|
+
super(`Auth timestamp not monotonic: ${timestampUtcMs} does not exceed ${newestTimestampUtcMs} in the auth stream of document ${documentId} on branch ${branch}`);
|
|
109
|
+
this.name = "AuthTimestampNotMonotonicError";
|
|
110
|
+
this.documentId = documentId;
|
|
111
|
+
this.branch = branch;
|
|
112
|
+
this.timestampUtcMs = timestampUtcMs;
|
|
113
|
+
this.newestTimestampUtcMs = newestTimestampUtcMs;
|
|
114
|
+
Error.captureStackTrace(this, AuthTimestampNotMonotonicError);
|
|
115
|
+
}
|
|
116
|
+
static isError(error) {
|
|
117
|
+
return Error.isError(error) && error.name === "AuthTimestampNotMonotonicError";
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* An operation or action carried a timestamp that is not an ISO-8601 UTC
|
|
122
|
+
* instant.
|
|
123
|
+
*
|
|
124
|
+
* Terminal rather than retryable: the value does not change between attempts,
|
|
125
|
+
* so a retry re-runs the whole job to fail identically. Quarantining, unlike a
|
|
126
|
+
* held auth operation — this is malformed data rather than two replicas
|
|
127
|
+
* disagreeing, and nothing further from that source should be trusted until it
|
|
128
|
+
* is looked at.
|
|
129
|
+
*/
|
|
130
|
+
var InvalidOperationTimestampError = class InvalidOperationTimestampError extends Error {
|
|
131
|
+
documentId;
|
|
132
|
+
scope;
|
|
133
|
+
timestampUtcMs;
|
|
134
|
+
constructor(documentId, scope, timestampUtcMs, context) {
|
|
135
|
+
super(`Invalid timestamp "${timestampUtcMs}" on ${context} in scope "${scope}" of document ${documentId}`);
|
|
136
|
+
this.name = "InvalidOperationTimestampError";
|
|
137
|
+
this.documentId = documentId;
|
|
138
|
+
this.scope = scope;
|
|
139
|
+
this.timestampUtcMs = timestampUtcMs;
|
|
140
|
+
Error.captureStackTrace(this, InvalidOperationTimestampError);
|
|
141
|
+
}
|
|
142
|
+
static isError(error) {
|
|
143
|
+
return Error.isError(error) && error.name === "InvalidOperationTimestampError";
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
/**
|
|
147
|
+
* A load would move more operations than the bound allows, indicating a real
|
|
148
|
+
* divergence between local and incoming history. Counts only first-time moves,
|
|
149
|
+
* so a re-evaluation pass's re-appends do not make busy documents
|
|
150
|
+
* revocation-proof. Terminal: the condition is deterministic.
|
|
151
|
+
*/
|
|
152
|
+
var ExcessiveReshuffleError = class ExcessiveReshuffleError extends Error {
|
|
153
|
+
documentId;
|
|
154
|
+
scope;
|
|
155
|
+
count;
|
|
156
|
+
threshold;
|
|
157
|
+
constructor(documentId, scope, count, threshold) {
|
|
158
|
+
super(`Excessive reshuffle detected: ${count} operations in scope "${scope}" of document ${documentId} exceeds the threshold of ${threshold}. This indicates a significant divergence between local and incoming operations.`);
|
|
159
|
+
this.name = "ExcessiveReshuffleError";
|
|
160
|
+
this.documentId = documentId;
|
|
161
|
+
this.scope = scope;
|
|
162
|
+
this.count = count;
|
|
163
|
+
this.threshold = threshold;
|
|
164
|
+
Error.captureStackTrace(this, ExcessiveReshuffleError);
|
|
165
|
+
}
|
|
166
|
+
static isError(error) {
|
|
167
|
+
return Error.isError(error) && error.name === "ExcessiveReshuffleError";
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
/**
|
|
96
171
|
* Error thrown when an operation has an invalid signature.
|
|
97
172
|
*/
|
|
98
173
|
var InvalidSignatureError = class InvalidSignatureError extends Error {
|
|
@@ -288,6 +363,34 @@ var CollectionMembershipCache = class CollectionMembershipCache {
|
|
|
288
363
|
};
|
|
289
364
|
//#endregion
|
|
290
365
|
//#region src/executor/util.ts
|
|
366
|
+
/** Actions the reactor reduces itself, onto the document scope. */
|
|
367
|
+
const DOCUMENT_SCOPE_ACTIONS = new Set([
|
|
368
|
+
"CREATE_DOCUMENT",
|
|
369
|
+
"DELETE_DOCUMENT",
|
|
370
|
+
"UPGRADE_DOCUMENT",
|
|
371
|
+
"ADD_RELATIONSHIP",
|
|
372
|
+
"REMOVE_RELATIONSHIP",
|
|
373
|
+
"UPDATE_RELATIONSHIP"
|
|
374
|
+
]);
|
|
375
|
+
/**
|
|
376
|
+
* `CREATE_DOCUMENT` is exempt by necessity: it runs before the document exists,
|
|
377
|
+
* so building a decision model would throw and defer the job forever.
|
|
378
|
+
*/
|
|
379
|
+
const GATED_DOCUMENT_ACTIONS = new Set([...DOCUMENT_SCOPE_ACTIONS].filter((type) => type !== "CREATE_DOCUMENT"));
|
|
380
|
+
/**
|
|
381
|
+
* The document a document-scope action writes to, which is not always the job's
|
|
382
|
+
* own document: delete and upgrade name it in `input.documentId`, and the
|
|
383
|
+
* relationship actions in `input.sourceId`. `execute` only checks that a batch
|
|
384
|
+
* shares one scope, so a caller can submit an action whose target is a document
|
|
385
|
+
* other than the one the job is keyed by. The policy gate has to follow the
|
|
386
|
+
* action rather than the job, or it decides against a policy the caller may
|
|
387
|
+
* control instead of the one guarding the write.
|
|
388
|
+
*/
|
|
389
|
+
function targetDocumentId(action, fallback) {
|
|
390
|
+
const input = action.input;
|
|
391
|
+
if (action.type === "ADD_RELATIONSHIP" || action.type === "REMOVE_RELATIONSHIP" || action.type === "UPDATE_RELATIONSHIP") return typeof input?.sourceId === "string" && input.sourceId.length > 0 ? input.sourceId : fallback;
|
|
392
|
+
return typeof input?.documentId === "string" && input.documentId.length > 0 ? input.documentId : fallback;
|
|
393
|
+
}
|
|
291
394
|
/**
|
|
292
395
|
* Creates a PHDocument from a CREATE_DOCUMENT action input.
|
|
293
396
|
* Reconstructs the document header and initializes the base state.
|
|
@@ -426,6 +529,14 @@ function buildErrorResult(job, error, startTime) {
|
|
|
426
529
|
};
|
|
427
530
|
}
|
|
428
531
|
/**
|
|
532
|
+
* The error a refusal surfaces as. Both classes are already terminal in the job
|
|
533
|
+
* result handler, so a refusal never burns a retry.
|
|
534
|
+
*/
|
|
535
|
+
function refusalError(reason, documentId, deletedAtUtcIso, action) {
|
|
536
|
+
if (reason === DOCUMENT_DELETED_REASON) return new DocumentDeletedError(documentId, deletedAtUtcIso);
|
|
537
|
+
return new AuthorizationDeniedError(documentId, action.scope, action.type, action.context?.signer?.user.address);
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
429
540
|
* Whether this operation is part of the document's creation. The create and the
|
|
430
541
|
* upgrade from version zero hold the first two indexes for the life of the
|
|
431
542
|
* document, so a reshuffle has to leave them where they are.
|
|
@@ -984,20 +1095,6 @@ function keyframeRevision(keyframe, documentId, scope) {
|
|
|
984
1095
|
if (typeof nextIndex !== "number") throw new Error(`Corrupt keyframe for document ${documentId} at revision ${keyframe.revision}: header carries no ${scope} revision`);
|
|
985
1096
|
return nextIndex - 1;
|
|
986
1097
|
}
|
|
987
|
-
/**
|
|
988
|
-
* Records a denied operation in the history without applying it. This is used
|
|
989
|
-
* for auth-rejected operations, as these need to be recorded without being
|
|
990
|
-
* applied.
|
|
991
|
-
*/
|
|
992
|
-
function appendWithoutApplying(document, scope, operation) {
|
|
993
|
-
return {
|
|
994
|
-
...document,
|
|
995
|
-
operations: {
|
|
996
|
-
...document.operations,
|
|
997
|
-
[scope]: [...document.operations[scope] ?? [], operation]
|
|
998
|
-
}
|
|
999
|
-
};
|
|
1000
|
-
}
|
|
1001
1098
|
function extractModuleVersion(doc) {
|
|
1002
1099
|
const v = doc.state.document.version;
|
|
1003
1100
|
return v === 0 ? void 0 : v;
|
|
@@ -1395,7 +1492,7 @@ var KyselyWriteCache = class KyselyWriteCache {
|
|
|
1395
1492
|
for (const operation of result.results) {
|
|
1396
1493
|
if (targetRevision !== void 0 && operation.index > targetRevision) break;
|
|
1397
1494
|
const moduleVersion = this.resolveModuleVersionForOp(operation.index, operation.timestampUtcMs, scope, validatedUpgrades, extractModuleVersion(document));
|
|
1398
|
-
if (isDenied(operation)) document = appendWithoutApplying(document,
|
|
1495
|
+
if (isDenied(operation)) document = appendWithoutApplying(document, operation, scope);
|
|
1399
1496
|
else {
|
|
1400
1497
|
const protocolVersion = baseReducerVersion(document.header);
|
|
1401
1498
|
document = getModuleCached(moduleVersion).reducer(document, operation.action, void 0, {
|
|
@@ -1467,7 +1564,7 @@ var KyselyWriteCache = class KyselyWriteCache {
|
|
|
1467
1564
|
for (const operation of pagedResults.results) {
|
|
1468
1565
|
if (signal?.aborted) throw new Error("Operation aborted");
|
|
1469
1566
|
if (targetRevision !== void 0 && operation.index > targetRevision) break;
|
|
1470
|
-
if (isDenied(operation)) document = appendWithoutApplying(document,
|
|
1567
|
+
if (isDenied(operation)) document = appendWithoutApplying(document, operation, scope);
|
|
1471
1568
|
else {
|
|
1472
1569
|
const protocolVersion = baseReducerVersion(document.header);
|
|
1473
1570
|
document = module.reducer(document, operation.action, void 0, {
|
|
@@ -1555,6 +1652,32 @@ var EventBus = class {
|
|
|
1555
1652
|
}
|
|
1556
1653
|
};
|
|
1557
1654
|
//#endregion
|
|
1655
|
+
//#region src/core/feature-flags.ts
|
|
1656
|
+
/**
|
|
1657
|
+
* Every flag this reactor knows, with the flags it requires. A stage adds its
|
|
1658
|
+
* flag here when it ships, so asking an older reactor for a later stage's flag
|
|
1659
|
+
* is an unrecognized name rather than a flag that quietly does nothing.
|
|
1660
|
+
*/
|
|
1661
|
+
const FLAG_PREREQUISITES = {
|
|
1662
|
+
documentDecisions: [],
|
|
1663
|
+
authEnforcement: ["documentDecisions"]
|
|
1664
|
+
};
|
|
1665
|
+
/**
|
|
1666
|
+
* Throws when the flags ask for enforcement the reactor cannot deliver. Either
|
|
1667
|
+
* failure would otherwise read as enforcement being on while the reactor
|
|
1668
|
+
* applies less than the caller asked for.
|
|
1669
|
+
*/
|
|
1670
|
+
function validateFeatureFlags(flags, prerequisites) {
|
|
1671
|
+
const known = Object.keys(prerequisites);
|
|
1672
|
+
const unrecognized = Object.keys(flags).filter((name) => !known.includes(name));
|
|
1673
|
+
if (unrecognized.length > 0) throw new Error(`Unrecognized reactor feature flag: ${unrecognized.join(", ")}. This reactor knows: ${known.join(", ")}.`);
|
|
1674
|
+
for (const name of known) {
|
|
1675
|
+
if (flags[name] !== true) continue;
|
|
1676
|
+
const missing = prerequisites[name].filter((required) => flags[required] !== true);
|
|
1677
|
+
if (missing.length > 0) throw new Error(`Reactor feature flag ${name} requires ${missing.join(", ")}.`);
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
//#endregion
|
|
1558
1681
|
//#region src/executor/execution-scope.ts
|
|
1559
1682
|
var DefaultExecutionScope = class {
|
|
1560
1683
|
constructor(operationStore, operationIndex, writeCache, documentMetaCache, collectionMembershipCache) {
|
|
@@ -1652,6 +1775,58 @@ function reshuffleByTimestamp(startIndex, opsA, opsB) {
|
|
|
1652
1775
|
}));
|
|
1653
1776
|
}
|
|
1654
1777
|
//#endregion
|
|
1778
|
+
//#region src/decision/auth-decision-model.ts
|
|
1779
|
+
function refusalReason(refusal) {
|
|
1780
|
+
switch (refusal) {
|
|
1781
|
+
case "version-unsupported": return AUTH_VERSION_UNSUPPORTED_REASON;
|
|
1782
|
+
case "denied-by-grant": return AUTH_DENIED_BY_GRANT_REASON;
|
|
1783
|
+
case "no-applicable-grant": return AUTH_NO_GRANT_REASON;
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
/** This decision model uses both the document and the auth streams. */
|
|
1787
|
+
function authDecisionModel(target) {
|
|
1788
|
+
return {
|
|
1789
|
+
projections: {
|
|
1790
|
+
document: {
|
|
1791
|
+
decidingActions: ["DELETE_DOCUMENT"],
|
|
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
|
+
}
|
|
1811
|
+
},
|
|
1812
|
+
evaluatesScope() {
|
|
1813
|
+
return true;
|
|
1814
|
+
},
|
|
1815
|
+
decide(model, subject, request) {
|
|
1816
|
+
if (request.verb === "execute" && model.document.isDeleted) return {
|
|
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
|
+
};
|
|
1826
|
+
}
|
|
1827
|
+
};
|
|
1828
|
+
}
|
|
1829
|
+
//#endregion
|
|
1655
1830
|
//#region src/decision/build-decision-model.ts
|
|
1656
1831
|
/**
|
|
1657
1832
|
* Reads each projection's stream through the write cache, recording the
|
|
@@ -1721,9 +1896,10 @@ function observedRevision(document, scope) {
|
|
|
1721
1896
|
*/
|
|
1722
1897
|
function staticReadSet(definition) {
|
|
1723
1898
|
const streams = [];
|
|
1724
|
-
for (const projection of Object.
|
|
1899
|
+
for (const [name, projection] of Object.entries(definition.projections)) {
|
|
1725
1900
|
if (typeof projection.query === "function") continue;
|
|
1726
1901
|
streams.push({
|
|
1902
|
+
name,
|
|
1727
1903
|
query: projection.query,
|
|
1728
1904
|
decidingActions: projection.decidingActions,
|
|
1729
1905
|
apply: projection.apply
|
|
@@ -1733,8 +1909,6 @@ function staticReadSet(definition) {
|
|
|
1733
1909
|
}
|
|
1734
1910
|
//#endregion
|
|
1735
1911
|
//#region src/decision/document-decision-model.ts
|
|
1736
|
-
/** Why the model refused an operation. */
|
|
1737
|
-
const DOCUMENT_DELETED_REASON = "document deleted";
|
|
1738
1912
|
/**
|
|
1739
1913
|
* The simplest decision model: one projection over the document scope, which
|
|
1740
1914
|
* rejects on a deleted document.
|
|
@@ -1757,11 +1931,36 @@ function documentDecisionModel(target) {
|
|
|
1757
1931
|
return true;
|
|
1758
1932
|
},
|
|
1759
1933
|
decide(model) {
|
|
1760
|
-
return model.document.isDeleted ?
|
|
1934
|
+
return model.document.isDeleted ? {
|
|
1935
|
+
decision: "deny",
|
|
1936
|
+
reason: DOCUMENT_DELETED_REASON
|
|
1937
|
+
} : { decision: "allow" };
|
|
1761
1938
|
}
|
|
1762
1939
|
};
|
|
1763
1940
|
}
|
|
1764
1941
|
//#endregion
|
|
1942
|
+
//#region src/decision/registered-model.ts
|
|
1943
|
+
/**
|
|
1944
|
+
* Builds the model at the stream heads and decides one request against it. The
|
|
1945
|
+
* append condition it returns is the read-set the store enforces at write time.
|
|
1946
|
+
*/
|
|
1947
|
+
async function decideAtHead(model, cache, target, subject, request, signal) {
|
|
1948
|
+
const built = await buildDecisionModel(cache, model, target, signal);
|
|
1949
|
+
return {
|
|
1950
|
+
evaluation: model(target).decide(built.model, subject, request, { scopeState: void 0 }),
|
|
1951
|
+
appendCondition: built.appendCondition,
|
|
1952
|
+
documentVersion: built.model.document.version,
|
|
1953
|
+
deletedAtUtcIso: built.model.document.deletedAtUtcIso ?? null
|
|
1954
|
+
};
|
|
1955
|
+
}
|
|
1956
|
+
/**
|
|
1957
|
+
* The model this reactor enforces. With `authEnforcement` off the auth scope is
|
|
1958
|
+
* absent from every append condition and no load walks it.
|
|
1959
|
+
*/
|
|
1960
|
+
function selectDecisionModel(flags) {
|
|
1961
|
+
return flags.authEnforcement ? authDecisionModel : documentDecisionModel;
|
|
1962
|
+
}
|
|
1963
|
+
//#endregion
|
|
1765
1964
|
//#region src/decision/merged-order.ts
|
|
1766
1965
|
/** Identifies a stream within a walk. */
|
|
1767
1966
|
function streamKey(query) {
|
|
@@ -1769,15 +1968,17 @@ function streamKey(query) {
|
|
|
1769
1968
|
}
|
|
1770
1969
|
/**
|
|
1771
1970
|
* Orders two operations from different streams by position. Timestamp decides;
|
|
1772
|
-
* an equal timestamp
|
|
1773
|
-
*
|
|
1774
|
-
* they happen to store them in.
|
|
1971
|
+
* an equal timestamp puts an auth operation first, and otherwise falls to the
|
|
1972
|
+
* action id and then the operation id, so that two replicas holding the same
|
|
1973
|
+
* operations agree on the order whatever order they happen to store them in.
|
|
1775
1974
|
*/
|
|
1776
1975
|
function comparePositions(a, b) {
|
|
1777
1976
|
const aTime = Date.parse(a.operation.timestampUtcMs);
|
|
1778
1977
|
const bTime = Date.parse(b.operation.timestampUtcMs);
|
|
1779
1978
|
if (aTime !== bTime) return aTime - bTime;
|
|
1780
1979
|
if (a.streamKey === b.streamKey) return a.operation.index - b.operation.index;
|
|
1980
|
+
const aAuth = a.scope === "auth";
|
|
1981
|
+
if (aAuth !== (b.scope === "auth")) return aAuth ? -1 : 1;
|
|
1781
1982
|
const actionIds = (a.operation.action.id ?? "").localeCompare(b.operation.action.id ?? "");
|
|
1782
1983
|
if (actionIds !== 0) return actionIds;
|
|
1783
1984
|
return (a.operation.id ?? "").localeCompare(b.operation.id ?? "");
|
|
@@ -1791,6 +1992,7 @@ function mergeByPosition(streams) {
|
|
|
1791
1992
|
const merged = [];
|
|
1792
1993
|
for (const stream of streams) for (const operation of stream.operations) merged.push({
|
|
1793
1994
|
streamKey: stream.streamKey,
|
|
1995
|
+
scope: stream.scope,
|
|
1794
1996
|
operation
|
|
1795
1997
|
});
|
|
1796
1998
|
return merged.sort(comparePositions);
|
|
@@ -1809,15 +2011,17 @@ function retractionSkip(nextIndex, firstRetractedIndex) {
|
|
|
1809
2011
|
* A single forward pass is only correct while a stream's effective operations
|
|
1810
2012
|
* are ordered.
|
|
1811
2013
|
*/
|
|
1812
|
-
function assertPositionOrder(streamKey, operations) {
|
|
2014
|
+
function assertPositionOrder(streamKey, scope, operations) {
|
|
1813
2015
|
for (let i = 1; i < operations.length; i++) {
|
|
1814
2016
|
const previous = operations[i - 1];
|
|
1815
2017
|
const current = operations[i];
|
|
1816
2018
|
if (comparePositions({
|
|
1817
2019
|
streamKey,
|
|
2020
|
+
scope,
|
|
1818
2021
|
operation: previous
|
|
1819
2022
|
}, {
|
|
1820
2023
|
streamKey,
|
|
2024
|
+
scope,
|
|
1821
2025
|
operation: current
|
|
1822
2026
|
}) > 0) throw new Error(`Stream ${streamKey} is out of position order: index ${previous.index} at ${previous.timestampUtcMs} precedes index ${current.index} at ${current.timestampUtcMs}`);
|
|
1823
2027
|
}
|
|
@@ -1825,32 +2029,35 @@ function assertPositionOrder(streamKey, operations) {
|
|
|
1825
2029
|
/**
|
|
1826
2030
|
* Visits every operation in the read-set once, in the order their positions
|
|
1827
2031
|
* fall, and hands back the state each stream held just before it. That state is
|
|
1828
|
-
* what a decision at that operation reads
|
|
1829
|
-
* applied and it has not.
|
|
2032
|
+
* what a decision at that operation reads.
|
|
1830
2033
|
*
|
|
1831
2034
|
* Skips are resolved first (i.e. this is performed on a garbage collected
|
|
1832
2035
|
* stream), which means we can do a single forward pass.
|
|
1833
2036
|
*
|
|
1834
|
-
*
|
|
2037
|
+
* An operation that contributes no state, whether denied or holding a reducer
|
|
2038
|
+
* error, is visited but not applied (this matches the write cache's rebuild).
|
|
2039
|
+
*
|
|
2040
|
+
* The consumer sends back whether it refused the operation it was handed: a
|
|
2041
|
+
* refusal this pass produced must suppress it the same way a stored one does.
|
|
1835
2042
|
*/
|
|
1836
2043
|
function* walkByPosition(streams) {
|
|
1837
2044
|
const merged = mergeByPosition(streams.map((stream) => {
|
|
1838
2045
|
const operations = garbageCollect(sortOperations([...stream.operations]));
|
|
1839
|
-
assertPositionOrder(stream.streamKey, operations);
|
|
2046
|
+
assertPositionOrder(stream.streamKey, stream.scope, operations);
|
|
1840
2047
|
return {
|
|
1841
2048
|
streamKey: stream.streamKey,
|
|
2049
|
+
scope: stream.scope,
|
|
1842
2050
|
operations
|
|
1843
2051
|
};
|
|
1844
2052
|
}));
|
|
1845
2053
|
const byKey = new Map(streams.map((stream) => [stream.streamKey, stream]));
|
|
1846
2054
|
const states = new Map(streams.map((stream) => [stream.streamKey, stream.document]));
|
|
1847
2055
|
for (const { streamKey, operation } of merged) {
|
|
1848
|
-
yield {
|
|
2056
|
+
if ((yield {
|
|
1849
2057
|
streamKey,
|
|
1850
2058
|
operation,
|
|
1851
2059
|
states: new Map(states)
|
|
1852
|
-
};
|
|
1853
|
-
if (isDenied(operation)) continue;
|
|
2060
|
+
}) || operation.error !== void 0 || isDenied(operation)) continue;
|
|
1854
2061
|
const stream = byKey.get(streamKey);
|
|
1855
2062
|
const before = states.get(streamKey);
|
|
1856
2063
|
if (before === void 0 || stream === void 0) throw new Error(`No state for stream ${streamKey}`);
|
|
@@ -1858,70 +2065,99 @@ function* walkByPosition(streams) {
|
|
|
1858
2065
|
}
|
|
1859
2066
|
}
|
|
1860
2067
|
//#endregion
|
|
1861
|
-
//#region src/decision/
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
2068
|
+
//#region src/decision/evaluation.ts
|
|
2069
|
+
/** The stream key for evaluated operations whose scope no projection reads. */
|
|
2070
|
+
const EVALUATED_ONLY = "evaluated";
|
|
2071
|
+
/**
|
|
2072
|
+
* Whether any stream the model reads declares this operation's action type as
|
|
2073
|
+
* one that can change an evaluation.
|
|
2074
|
+
*/
|
|
2075
|
+
function isDecidingAction(operation, readSet) {
|
|
1865
2076
|
return readSet.some((stream) => stream.decidingActions.includes(operation.action.type));
|
|
1866
2077
|
}
|
|
1867
|
-
/**
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
2078
|
+
/**
|
|
2079
|
+
* Who an operation acts as. A replayed operation is evaluated as its own signer,
|
|
2080
|
+
* so an address-scoped policy does not deny its own author's history.
|
|
2081
|
+
*/
|
|
2082
|
+
function subjectOf(operation) {
|
|
2083
|
+
const signer = operation.action.context?.signer;
|
|
2084
|
+
return {
|
|
2085
|
+
address: signer?.user.address,
|
|
2086
|
+
key: signer?.app.key
|
|
2087
|
+
};
|
|
2088
|
+
}
|
|
2089
|
+
/**
|
|
2090
|
+
* The model as the walk reached this operation: each projection's value is its
|
|
2091
|
+
* own scope's state, taken from the stream that projection reads.
|
|
2092
|
+
*/
|
|
2093
|
+
function modelAt(readSet, states) {
|
|
2094
|
+
const model = {};
|
|
2095
|
+
for (const stream of readSet) {
|
|
2096
|
+
const document = states.get(streamKey(stream.query));
|
|
2097
|
+
if (document === void 0) throw new Error(`No state walked for projection ${stream.name}`);
|
|
2098
|
+
model[stream.name] = document.state[stream.query.scope];
|
|
2099
|
+
}
|
|
2100
|
+
return model;
|
|
1871
2101
|
}
|
|
1872
2102
|
/**
|
|
1873
|
-
*
|
|
1874
|
-
*
|
|
1875
|
-
* operation is not refused).
|
|
2103
|
+
* Evaluates each operation at its own position and returns the refusals in an
|
|
2104
|
+
* array parallel to the operations, where undefined means allowed.
|
|
1876
2105
|
*
|
|
1877
|
-
* A
|
|
1878
|
-
*
|
|
1879
|
-
* the
|
|
2106
|
+
* A position is a timestamp, so an operation refused by a delete is one that
|
|
2107
|
+
* sorts after it, and the operations before it are left alone. That holds
|
|
2108
|
+
* whether the delete is already stored or is among the operations passed in.
|
|
1880
2109
|
*/
|
|
1881
|
-
async function
|
|
2110
|
+
async function evaluateByPosition(model, target, subject, stores, signal) {
|
|
1882
2111
|
const { scope, operations } = subject;
|
|
1883
2112
|
const { writeCache, operationStore } = stores;
|
|
1884
|
-
const definition =
|
|
2113
|
+
const definition = model(target);
|
|
1885
2114
|
const readSet = staticReadSet(definition);
|
|
1886
2115
|
if (!definition.evaluatesScope(scope)) return operations.map(() => void 0);
|
|
1887
2116
|
const evaluating = new Set(operations.map((operation) => operation.id));
|
|
1888
2117
|
const readStreams = await Promise.all(readSet.map(async (stream) => ({
|
|
1889
2118
|
stream,
|
|
1890
|
-
operations: (await operationStore.getSince(stream.query.documentId, stream.query.scope, stream.query.branch,
|
|
2119
|
+
operations: (await operationStore.getSince(stream.query.documentId, stream.query.scope, stream.query.branch, -1, { actionTypes: stream.decidingActions }, void 0, signal)).results.filter((operation) => !evaluating.has(operation.id))
|
|
1891
2120
|
})));
|
|
1892
|
-
const
|
|
1893
|
-
if (readStreams.every((read) => read.operations.length === 0) &&
|
|
2121
|
+
const decidingOperations = operations.filter((operation) => isDecidingAction(operation, readSet));
|
|
2122
|
+
if (readStreams.every((read) => read.operations.length === 0) && decidingOperations.length === 0) return operations.map(() => void 0);
|
|
1894
2123
|
if (readStreams.length === 0) throw new Error(`Decision model for ${target.documentId} reads no stream whose query is known before it is built`);
|
|
2124
|
+
const writtenProjection = readSet.find((stream) => stream.query.scope === scope);
|
|
1895
2125
|
const walked = [];
|
|
1896
2126
|
for (const read of readStreams) {
|
|
1897
|
-
const
|
|
2127
|
+
const isWritten = read.stream === writtenProjection;
|
|
2128
|
+
const before = await writeCache.getState(read.stream.query.documentId, read.stream.query.scope, read.stream.query.branch, -1, signal);
|
|
1898
2129
|
walked.push({
|
|
1899
2130
|
streamKey: streamKey(read.stream.query),
|
|
2131
|
+
scope: read.stream.query.scope,
|
|
1900
2132
|
document: before,
|
|
1901
|
-
operations: read.operations,
|
|
2133
|
+
operations: isWritten ? [...read.operations, ...operations] : read.operations,
|
|
1902
2134
|
apply: read.stream.apply
|
|
1903
2135
|
});
|
|
1904
2136
|
}
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
2137
|
+
if (writtenProjection === void 0) walked.push({
|
|
2138
|
+
streamKey: EVALUATED_ONLY,
|
|
2139
|
+
scope,
|
|
1908
2140
|
document: walked[0].document,
|
|
1909
2141
|
operations,
|
|
1910
|
-
apply:
|
|
2142
|
+
apply: (document) => document
|
|
1911
2143
|
});
|
|
1912
2144
|
const reasons = /* @__PURE__ */ new Map();
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
const
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
2145
|
+
const walk = walkByPosition(walked);
|
|
2146
|
+
let step = walk.next(false);
|
|
2147
|
+
while (!step.done) {
|
|
2148
|
+
const position = step.value;
|
|
2149
|
+
if (!evaluating.has(position.operation.id)) {
|
|
2150
|
+
step = walk.next(false);
|
|
2151
|
+
continue;
|
|
2152
|
+
}
|
|
2153
|
+
const evaluation = definition.decide(modelAt(readSet, position.states), subjectOf(position.operation), {
|
|
1920
2154
|
verb: "execute",
|
|
1921
|
-
scope,
|
|
1922
|
-
operation:
|
|
2155
|
+
scope: position.operation.action.scope,
|
|
2156
|
+
operation: position.operation.action.type
|
|
1923
2157
|
}, { scopeState: void 0 });
|
|
1924
|
-
|
|
2158
|
+
const denied = evaluation.decision === "deny";
|
|
2159
|
+
reasons.set(position.operation.id, denied ? evaluation.reason : void 0);
|
|
2160
|
+
step = walk.next(denied);
|
|
1925
2161
|
}
|
|
1926
2162
|
return operations.map((operation) => reasons.get(operation.id));
|
|
1927
2163
|
}
|
|
@@ -1972,19 +2208,22 @@ var DriveCollectionId = class DriveCollectionId {
|
|
|
1972
2208
|
//#endregion
|
|
1973
2209
|
//#region src/executor/document-action-handler.ts
|
|
1974
2210
|
var DocumentActionHandler = class {
|
|
1975
|
-
constructor(registry, logger, driveContainerTypes, featureFlags) {
|
|
2211
|
+
constructor(registry, logger, driveContainerTypes, featureFlags, decisionModel) {
|
|
1976
2212
|
this.registry = registry;
|
|
1977
2213
|
this.logger = logger;
|
|
1978
2214
|
this.driveContainerTypes = driveContainerTypes;
|
|
1979
2215
|
this.featureFlags = featureFlags;
|
|
2216
|
+
this.decisionModel = decisionModel;
|
|
1980
2217
|
}
|
|
1981
2218
|
/** Whether the write arrives with its evaluation already decided. */
|
|
1982
2219
|
alreadyEvaluated(executing) {
|
|
1983
|
-
return this.featureFlags.documentDecisions && executing.replayingAcceptedHistory;
|
|
2220
|
+
return this.featureFlags.documentDecisions && (executing.replayingAcceptedHistory || executing.evaluatedByPosition);
|
|
1984
2221
|
}
|
|
1985
2222
|
async execute(write, executing) {
|
|
1986
2223
|
const { action } = write;
|
|
1987
2224
|
if (write.deniedReason !== void 0) return this.writeDenied(write, executing);
|
|
2225
|
+
const refusal = await this.refuseIfPolicyDenies(write, executing);
|
|
2226
|
+
if (refusal) return refusal;
|
|
1988
2227
|
switch (action.type) {
|
|
1989
2228
|
case "CREATE_DOCUMENT": return this.executeCreate(write, executing);
|
|
1990
2229
|
case "DELETE_DOCUMENT": return this.executeDelete(write, executing);
|
|
@@ -1995,6 +2234,34 @@ var DocumentActionHandler = class {
|
|
|
1995
2234
|
default: return buildErrorResult(executing.job, /* @__PURE__ */ new Error(`Unknown document action type: ${action.type}`), executing.startTime);
|
|
1996
2235
|
}
|
|
1997
2236
|
}
|
|
2237
|
+
/**
|
|
2238
|
+
* Refuses a document-scope write the policy denies, or undefined to proceed.
|
|
2239
|
+
* Without this an `execute`-on-`document` grant is unenforceable.
|
|
2240
|
+
*/
|
|
2241
|
+
async refuseIfPolicyDenies(write, executing) {
|
|
2242
|
+
const { action } = write;
|
|
2243
|
+
const { job, startTime, stores, signal } = executing;
|
|
2244
|
+
if (!this.featureFlags.documentDecisions || !this.featureFlags.authEnforcement || this.alreadyEvaluated(executing) || !GATED_DOCUMENT_ACTIONS.has(action.type)) return;
|
|
2245
|
+
const documentId = targetDocumentId(action, job.documentId);
|
|
2246
|
+
let admission;
|
|
2247
|
+
try {
|
|
2248
|
+
admission = await decideAtHead(this.decisionModel, stores.writeCache, {
|
|
2249
|
+
documentId,
|
|
2250
|
+
branch: job.branch
|
|
2251
|
+
}, {
|
|
2252
|
+
address: action.context?.signer?.user.address,
|
|
2253
|
+
key: action.context?.signer?.app.key
|
|
2254
|
+
}, {
|
|
2255
|
+
verb: "execute",
|
|
2256
|
+
scope: action.scope,
|
|
2257
|
+
operation: action.type
|
|
2258
|
+
}, signal);
|
|
2259
|
+
} catch (error) {
|
|
2260
|
+
return buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
|
|
2261
|
+
}
|
|
2262
|
+
if (admission.evaluation.decision === "allow") return;
|
|
2263
|
+
return buildErrorResult(job, refusalError(admission.evaluation.reason, documentId, admission.deletedAtUtcIso, action), startTime);
|
|
2264
|
+
}
|
|
1998
2265
|
/** A refused operation holds a position in the stream but changes nothing. */
|
|
1999
2266
|
async writeDenied(write, executing) {
|
|
2000
2267
|
const { action, skip, sourceRemote, deniedReason } = write;
|
|
@@ -2005,13 +2272,20 @@ var DocumentActionHandler = class {
|
|
|
2005
2272
|
} catch (error) {
|
|
2006
2273
|
return buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
|
|
2007
2274
|
}
|
|
2008
|
-
|
|
2275
|
+
const index = getNextIndexForScope(document, job.scope);
|
|
2276
|
+
let standing = document;
|
|
2277
|
+
if (skip > 0) try {
|
|
2278
|
+
standing = await stores.writeCache.getState(job.documentId, job.scope, job.branch, index - skip - 1, signal);
|
|
2279
|
+
} catch (error) {
|
|
2280
|
+
return buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
|
|
2281
|
+
}
|
|
2282
|
+
let operation = createOperation(action, index, skip, {
|
|
2009
2283
|
documentId: job.documentId,
|
|
2010
2284
|
scope: job.scope,
|
|
2011
2285
|
branch: job.branch
|
|
2012
2286
|
});
|
|
2013
2287
|
operation.deniedReason = deniedReason;
|
|
2014
|
-
operation.hash = hashDocumentStateForScope(
|
|
2288
|
+
operation.hash = hashDocumentStateForScope(standing, job.scope);
|
|
2015
2289
|
const writeResult = await this.writeOperationToStore({
|
|
2016
2290
|
documentId: job.documentId,
|
|
2017
2291
|
documentType: document.header.documentType,
|
|
@@ -2020,12 +2294,12 @@ var DocumentActionHandler = class {
|
|
|
2020
2294
|
}, operation, executing);
|
|
2021
2295
|
if (!Array.isArray(writeResult)) return writeResult;
|
|
2022
2296
|
operation = writeResult[0];
|
|
2023
|
-
updateDocumentRevision(
|
|
2024
|
-
|
|
2025
|
-
...
|
|
2026
|
-
[job.scope]: [...
|
|
2297
|
+
updateDocumentRevision(standing, job.scope, operation.index);
|
|
2298
|
+
standing.operations = {
|
|
2299
|
+
...standing.operations,
|
|
2300
|
+
[job.scope]: [...standing.operations[job.scope] ?? [], operation]
|
|
2027
2301
|
};
|
|
2028
|
-
stores.writeCache.putState(job.documentId, job.scope, job.branch, operation.index,
|
|
2302
|
+
stores.writeCache.putState(job.documentId, job.scope, job.branch, operation.index, standing, SnapshotPosition.Head);
|
|
2029
2303
|
indexTxn.write([{
|
|
2030
2304
|
...operation,
|
|
2031
2305
|
documentId: job.documentId,
|
|
@@ -2035,13 +2309,13 @@ var DocumentActionHandler = class {
|
|
|
2035
2309
|
sourceRemote
|
|
2036
2310
|
}]);
|
|
2037
2311
|
stores.documentMetaCache.putDocumentMeta(job.documentId, job.branch, {
|
|
2038
|
-
state:
|
|
2039
|
-
documentType:
|
|
2312
|
+
state: standing.state.document,
|
|
2313
|
+
documentType: standing.header.documentType,
|
|
2040
2314
|
documentScopeRevision: operation.index + 1
|
|
2041
2315
|
});
|
|
2042
|
-
return buildSuccessResult(job, operation, job.documentId,
|
|
2043
|
-
header:
|
|
2044
|
-
document:
|
|
2316
|
+
return buildSuccessResult(job, operation, job.documentId, standing.header.documentType, JSON.stringify({
|
|
2317
|
+
header: standing.header,
|
|
2318
|
+
document: standing.state.document
|
|
2045
2319
|
}), startTime);
|
|
2046
2320
|
}
|
|
2047
2321
|
async executeCreate(write, executing) {
|
|
@@ -2391,20 +2665,13 @@ function isValidISOTimestamp(value) {
|
|
|
2391
2665
|
if (!ISO_TIMESTAMP_REGEX.test(value)) return false;
|
|
2392
2666
|
return !isNaN(new Date(value).getTime());
|
|
2393
2667
|
}
|
|
2394
|
-
const documentScopeActions = [
|
|
2395
|
-
"CREATE_DOCUMENT",
|
|
2396
|
-
"DELETE_DOCUMENT",
|
|
2397
|
-
"UPGRADE_DOCUMENT",
|
|
2398
|
-
"ADD_RELATIONSHIP",
|
|
2399
|
-
"REMOVE_RELATIONSHIP",
|
|
2400
|
-
"UPDATE_RELATIONSHIP"
|
|
2401
|
-
];
|
|
2402
2668
|
/**
|
|
2403
2669
|
* Simple job executor that processes a job by applying actions through document model reducers.
|
|
2404
2670
|
*/
|
|
2405
2671
|
var SimpleJobExecutor = class {
|
|
2406
2672
|
config;
|
|
2407
2673
|
featureFlags;
|
|
2674
|
+
decisionModel;
|
|
2408
2675
|
signatureVerifierModule;
|
|
2409
2676
|
documentActionHandler;
|
|
2410
2677
|
executionScope;
|
|
@@ -2427,9 +2694,14 @@ var SimpleJobExecutor = class {
|
|
|
2427
2694
|
retryMaxDelayMs: config.retryMaxDelayMs ?? 5e3,
|
|
2428
2695
|
yieldDeadlineMs: config.yieldDeadlineMs ?? 50
|
|
2429
2696
|
};
|
|
2430
|
-
this.featureFlags = {
|
|
2697
|
+
this.featureFlags = {
|
|
2698
|
+
documentDecisions: config.featureFlags?.documentDecisions ?? false,
|
|
2699
|
+
authEnforcement: config.featureFlags?.authEnforcement ?? false
|
|
2700
|
+
};
|
|
2701
|
+
validateFeatureFlags(this.featureFlags, FLAG_PREREQUISITES);
|
|
2702
|
+
this.decisionModel = selectDecisionModel(this.featureFlags);
|
|
2431
2703
|
this.signatureVerifierModule = new SignatureVerifier(signatureVerifier);
|
|
2432
|
-
this.documentActionHandler = new DocumentActionHandler(registry, logger, driveContainerTypes, this.featureFlags);
|
|
2704
|
+
this.documentActionHandler = new DocumentActionHandler(registry, logger, driveContainerTypes, this.featureFlags, this.decisionModel);
|
|
2433
2705
|
this.executionScope = executionScope ?? new DefaultExecutionScope(operationStore, operationIndex, writeCache, documentMetaCache, collectionMembershipCache);
|
|
2434
2706
|
}
|
|
2435
2707
|
/**
|
|
@@ -2451,7 +2723,8 @@ var SimpleJobExecutor = class {
|
|
|
2451
2723
|
indexTxn,
|
|
2452
2724
|
stores,
|
|
2453
2725
|
signal,
|
|
2454
|
-
replayingAcceptedHistory: true
|
|
2726
|
+
replayingAcceptedHistory: true,
|
|
2727
|
+
evaluatedByPosition: false
|
|
2455
2728
|
});
|
|
2456
2729
|
if (loadResult.success && loadResult.operationsWithContext) {
|
|
2457
2730
|
for (const owc of loadResult.operationsWithContext) touchedCacheEntries.push({
|
|
@@ -2471,20 +2744,18 @@ var SimpleJobExecutor = class {
|
|
|
2471
2744
|
}
|
|
2472
2745
|
return loadResult;
|
|
2473
2746
|
}
|
|
2747
|
+
const positioned = await this.positionByTimestamp(job, stores, signal);
|
|
2748
|
+
if (positioned.error) return buildErrorResult(job, positioned.error, startTime);
|
|
2474
2749
|
const executing = {
|
|
2475
2750
|
job,
|
|
2476
2751
|
startTime,
|
|
2477
2752
|
indexTxn,
|
|
2478
2753
|
stores,
|
|
2479
2754
|
signal,
|
|
2480
|
-
replayingAcceptedHistory: false
|
|
2755
|
+
replayingAcceptedHistory: false,
|
|
2756
|
+
evaluatedByPosition: positioned.evaluatedByPosition
|
|
2481
2757
|
};
|
|
2482
|
-
const
|
|
2483
|
-
const actionResult = await this.processActions(positioned.actions.map((action, i) => ({
|
|
2484
|
-
action,
|
|
2485
|
-
skip: positioned.skipValues?.[i] ?? 0,
|
|
2486
|
-
sourceRemote: ""
|
|
2487
|
-
})), executing);
|
|
2758
|
+
const actionResult = await this.processActions(positioned.writes, executing);
|
|
2488
2759
|
if (!actionResult.success) return {
|
|
2489
2760
|
job,
|
|
2490
2761
|
success: false,
|
|
@@ -2560,11 +2831,11 @@ var SimpleJobExecutor = class {
|
|
|
2560
2831
|
success: false,
|
|
2561
2832
|
generatedOperations,
|
|
2562
2833
|
operationsWithContext,
|
|
2563
|
-
error:
|
|
2834
|
+
error: new InvalidOperationTimestampError(job.documentId, action.scope, action.timestampUtcMs, `action ${action.type} (id: ${action.id})`)
|
|
2564
2835
|
};
|
|
2565
2836
|
let lastYield = performance.now();
|
|
2566
2837
|
for (const write of writes) {
|
|
2567
|
-
const result =
|
|
2838
|
+
const result = DOCUMENT_SCOPE_ACTIONS.has(write.action.type) ? await this.documentActionHandler.execute(write, executing) : await this.executeRegularAction(write, executing);
|
|
2568
2839
|
const error = this.accumulateResultOrReturnError(result, generatedOperations, operationsWithContext);
|
|
2569
2840
|
if (error !== null) return {
|
|
2570
2841
|
success: false,
|
|
@@ -2594,29 +2865,28 @@ var SimpleJobExecutor = class {
|
|
|
2594
2865
|
const { job, startTime, indexTxn, stores, signal } = executing;
|
|
2595
2866
|
let appendCondition;
|
|
2596
2867
|
let documentVersion;
|
|
2597
|
-
const alreadyEvaluated = this.featureFlags.documentDecisions && executing.replayingAcceptedHistory;
|
|
2868
|
+
const alreadyEvaluated = this.featureFlags.documentDecisions && (executing.replayingAcceptedHistory || executing.evaluatedByPosition);
|
|
2598
2869
|
if (this.featureFlags.documentDecisions && !alreadyEvaluated) {
|
|
2599
2870
|
const target = {
|
|
2600
2871
|
documentId: job.documentId,
|
|
2601
2872
|
branch: job.branch
|
|
2602
2873
|
};
|
|
2603
|
-
|
|
2604
|
-
let built;
|
|
2874
|
+
let admission;
|
|
2605
2875
|
try {
|
|
2606
|
-
|
|
2876
|
+
admission = await decideAtHead(this.decisionModel, stores.writeCache, target, {
|
|
2877
|
+
address: action.context?.signer?.user.address,
|
|
2878
|
+
key: action.context?.signer?.app.key
|
|
2879
|
+
}, {
|
|
2880
|
+
verb: "execute",
|
|
2881
|
+
scope: action.scope,
|
|
2882
|
+
operation: action.type
|
|
2883
|
+
}, signal);
|
|
2607
2884
|
} catch (error) {
|
|
2608
2885
|
return buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
|
|
2609
2886
|
}
|
|
2610
|
-
if (
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
}, {
|
|
2614
|
-
verb: "execute",
|
|
2615
|
-
scope: action.scope,
|
|
2616
|
-
operation: action.type
|
|
2617
|
-
}, { scopeState: void 0 }) === "deny") return buildErrorResult(job, new DocumentDeletedError(job.documentId, built.model.document.deletedAtUtcIso), startTime);
|
|
2618
|
-
appendCondition = built.appendCondition;
|
|
2619
|
-
documentVersion = built.model.document.version;
|
|
2887
|
+
if (admission.evaluation.decision === "deny") return buildErrorResult(job, refusalError(admission.evaluation.reason, job.documentId, admission.deletedAtUtcIso, action), startTime);
|
|
2888
|
+
appendCondition = admission.appendCondition;
|
|
2889
|
+
documentVersion = admission.documentVersion;
|
|
2620
2890
|
} else if (alreadyEvaluated) documentVersion = (await stores.writeCache.getState(job.documentId, "document", job.branch, void 0, signal)).state.document.version;
|
|
2621
2891
|
else {
|
|
2622
2892
|
let docMeta;
|
|
@@ -2635,7 +2905,7 @@ var SimpleJobExecutor = class {
|
|
|
2635
2905
|
} catch (error) {
|
|
2636
2906
|
return buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
|
|
2637
2907
|
}
|
|
2638
|
-
if (!executing.replayingAcceptedHistory) {
|
|
2908
|
+
if (!this.featureFlags.authEnforcement && !executing.replayingAcceptedHistory) {
|
|
2639
2909
|
const subject = {
|
|
2640
2910
|
address: write.action.context?.signer?.user.address,
|
|
2641
2911
|
key: write.action.context?.signer?.app.key
|
|
@@ -2753,19 +3023,51 @@ var SimpleJobExecutor = class {
|
|
|
2753
3023
|
};
|
|
2754
3024
|
}
|
|
2755
3025
|
/**
|
|
2756
|
-
* Orders a write by timestamp
|
|
2757
|
-
*
|
|
2758
|
-
*
|
|
2759
|
-
*
|
|
3026
|
+
* Orders a write by timestamp and decides it where it lands. The caller
|
|
3027
|
+
* supplies the timestamp, so a write can belong before operations already
|
|
3028
|
+
* stored; those are re-appended alongside it, the way a load reshuffles.
|
|
3029
|
+
*
|
|
3030
|
+
* Deciding a backdated write at the stream heads instead of at its position
|
|
3031
|
+
* would overwrite the verdict every other replica computes for it.
|
|
2760
3032
|
*/
|
|
2761
3033
|
async positionByTimestamp(job, stores, signal) {
|
|
2762
|
-
|
|
3034
|
+
const plain = () => ({
|
|
3035
|
+
writes: job.actions.map((action) => ({
|
|
3036
|
+
action,
|
|
3037
|
+
skip: 0,
|
|
3038
|
+
sourceRemote: ""
|
|
3039
|
+
})),
|
|
3040
|
+
evaluatedByPosition: false
|
|
3041
|
+
});
|
|
3042
|
+
if (!this.featureFlags.documentDecisions || job.actions.length === 0) return plain();
|
|
2763
3043
|
let earliest = job.actions[0].timestampUtcMs;
|
|
2764
|
-
|
|
3044
|
+
let earliestAt = Date.parse(earliest);
|
|
3045
|
+
for (const action of job.actions) {
|
|
3046
|
+
const at = Date.parse(action.timestampUtcMs);
|
|
3047
|
+
if (at < earliestAt) {
|
|
3048
|
+
earliest = action.timestampUtcMs;
|
|
3049
|
+
earliestAt = at;
|
|
3050
|
+
}
|
|
3051
|
+
}
|
|
2765
3052
|
const revisions = await stores.operationStore.getRevisions(job.documentId, job.branch, signal);
|
|
2766
|
-
|
|
3053
|
+
const backdated = earliestAt < Date.parse(revisions.latestTimestamp);
|
|
3054
|
+
if (this.featureFlags.authEnforcement && job.scope === "auth") {
|
|
3055
|
+
const newest = await stores.operationStore.getStreamLatestTimestamp(job.documentId, "auth", job.branch, signal);
|
|
3056
|
+
const violation = this.firstNonMonotonicTimestamp(job.actions, newest, job.documentId, job.branch);
|
|
3057
|
+
if (violation) return {
|
|
3058
|
+
writes: [],
|
|
3059
|
+
evaluatedByPosition: false,
|
|
3060
|
+
error: violation
|
|
3061
|
+
};
|
|
3062
|
+
if (!backdated) return plain();
|
|
3063
|
+
return this.evaluatePositioned(job, stores, this.appendedOperations(job, revisions.revision[job.scope] ?? 0), signal);
|
|
3064
|
+
}
|
|
3065
|
+
if (!backdated) return plain();
|
|
2767
3066
|
const conflicting = (await stores.operationStore.getConflicting(job.documentId, job.scope, job.branch, earliest, void 0, signal)).results.filter((operation) => !isGenesisOperation(operation));
|
|
2768
|
-
if (conflicting.length === 0)
|
|
3067
|
+
if (conflicting.length === 0) {
|
|
3068
|
+
if (!this.featureFlags.authEnforcement) return plain();
|
|
3069
|
+
return this.evaluatePositioned(job, stores, this.appendedOperations(job, revisions.revision[job.scope] ?? 0), signal);
|
|
3070
|
+
}
|
|
2769
3071
|
const nextIndex = revisions.revision[job.scope] ?? 0;
|
|
2770
3072
|
let firstConflicting = conflicting[0].index;
|
|
2771
3073
|
for (const operation of conflicting) if (operation.index < firstConflicting) firstConflicting = operation.index;
|
|
@@ -2782,12 +3084,105 @@ var SimpleJobExecutor = class {
|
|
|
2782
3084
|
skip: retractionSkip(nextIndex, firstConflicting)
|
|
2783
3085
|
}, conflicting, incoming);
|
|
2784
3086
|
stores.writeCache.invalidate(job.documentId, job.scope, job.branch);
|
|
3087
|
+
if (!this.featureFlags.authEnforcement) return {
|
|
3088
|
+
writes: merged.map((operation) => ({
|
|
3089
|
+
action: operation.action,
|
|
3090
|
+
skip: operation.skip,
|
|
3091
|
+
sourceRemote: ""
|
|
3092
|
+
})),
|
|
3093
|
+
evaluatedByPosition: false
|
|
3094
|
+
};
|
|
3095
|
+
return this.evaluatePositioned(job, stores, merged, signal);
|
|
3096
|
+
}
|
|
3097
|
+
/**
|
|
3098
|
+
* Decides each operation where it lands and carries the verdict on it. A
|
|
3099
|
+
* refused submitted action is reported to the caller and nothing is stored; a
|
|
3100
|
+
* refused operation the reshuffle merely moved keeps its verdict, because it
|
|
3101
|
+
* already holds a position.
|
|
3102
|
+
*
|
|
3103
|
+
* The operations carry the indexes and skips they will be stored at, because
|
|
3104
|
+
* the walk resolves skips before it orders them.
|
|
3105
|
+
*/
|
|
3106
|
+
async evaluatePositioned(job, stores, operations, signal) {
|
|
3107
|
+
const reasons = await evaluateByPosition(this.decisionModel, {
|
|
3108
|
+
documentId: job.documentId,
|
|
3109
|
+
branch: job.branch
|
|
3110
|
+
}, {
|
|
3111
|
+
scope: job.scope,
|
|
3112
|
+
operations
|
|
3113
|
+
}, stores, signal);
|
|
3114
|
+
const submitted = new Set(job.actions.map((action) => action.id));
|
|
3115
|
+
for (let i = 0; i < operations.length; i++) {
|
|
3116
|
+
const reason = reasons[i];
|
|
3117
|
+
if (reason !== void 0 && submitted.has(operations[i].action.id)) return {
|
|
3118
|
+
writes: [],
|
|
3119
|
+
evaluatedByPosition: false,
|
|
3120
|
+
error: refusalError(reason, job.documentId, null, operations[i].action)
|
|
3121
|
+
};
|
|
3122
|
+
}
|
|
2785
3123
|
return {
|
|
2786
|
-
|
|
2787
|
-
|
|
3124
|
+
writes: operations.map((operation, i) => ({
|
|
3125
|
+
action: operation.action,
|
|
3126
|
+
skip: operation.skip,
|
|
3127
|
+
sourceRemote: "",
|
|
3128
|
+
deniedReason: reasons[i]
|
|
3129
|
+
})),
|
|
3130
|
+
evaluatedByPosition: true
|
|
2788
3131
|
};
|
|
2789
3132
|
}
|
|
2790
3133
|
/**
|
|
3134
|
+
* The scopes a re-evaluation pass visits, in a fixed order.
|
|
3135
|
+
*
|
|
3136
|
+
* The revisions map comes from a query with no ORDER BY, and the order is
|
|
3137
|
+
* load-bearing: each scope's pass re-reads the auth stream, and the walk skips
|
|
3138
|
+
* an operation by its stored denial, so a denial this pass just wrote is
|
|
3139
|
+
* visible to a later-visited scope and invisible to an earlier one. The model's
|
|
3140
|
+
* own projection order leads, then the rest sorted, so the pass is reproducible
|
|
3141
|
+
* across replicas and across runs.
|
|
3142
|
+
*/
|
|
3143
|
+
evaluationOrder(target, revision) {
|
|
3144
|
+
const definition = this.decisionModel(target);
|
|
3145
|
+
const evaluated = Object.keys(revision).filter((scope) => definition.evaluatesScope(scope));
|
|
3146
|
+
const leading = [];
|
|
3147
|
+
for (const stream of staticReadSet(definition)) {
|
|
3148
|
+
const scope = stream.query.scope;
|
|
3149
|
+
if (evaluated.includes(scope) && !leading.includes(scope)) leading.push(scope);
|
|
3150
|
+
}
|
|
3151
|
+
const rest = evaluated.filter((scope) => !leading.includes(scope)).sort((a, b) => a.localeCompare(b));
|
|
3152
|
+
return [...leading, ...rest];
|
|
3153
|
+
}
|
|
3154
|
+
/**
|
|
3155
|
+
* The first timestamp in the batch that does not strictly exceed everything
|
|
3156
|
+
* ahead of it, or undefined when the whole batch is monotonic.
|
|
3157
|
+
*
|
|
3158
|
+
* The bound is carried forward rather than compared against one stored maximum,
|
|
3159
|
+
* because a single execute can carry several auth actions stamped in the same
|
|
3160
|
+
* millisecond. Letting a tie through would store a stream the position walk
|
|
3161
|
+
* then refuses to read, with no repair path.
|
|
3162
|
+
*/
|
|
3163
|
+
firstNonMonotonicTimestamp(entries, newest, documentId, branch) {
|
|
3164
|
+
let boundIso = newest;
|
|
3165
|
+
let bound = newest === void 0 ? Number.NEGATIVE_INFINITY : Date.parse(newest);
|
|
3166
|
+
for (const entry of entries) {
|
|
3167
|
+
if (!isValidISOTimestamp(entry.timestampUtcMs)) return new InvalidOperationTimestampError(documentId, "auth", entry.timestampUtcMs, "auth operation");
|
|
3168
|
+
const at = Date.parse(entry.timestampUtcMs);
|
|
3169
|
+
if (boundIso !== void 0 && at <= bound) return new AuthTimestampNotMonotonicError(documentId, branch, entry.timestampUtcMs, boundIso);
|
|
3170
|
+
bound = at;
|
|
3171
|
+
boundIso = entry.timestampUtcMs;
|
|
3172
|
+
}
|
|
3173
|
+
}
|
|
3174
|
+
/** The operations a batch of submitted actions appends at the scope's tail. */
|
|
3175
|
+
appendedOperations(job, nextIndex) {
|
|
3176
|
+
return job.actions.map((action, i) => ({
|
|
3177
|
+
id: action.id,
|
|
3178
|
+
index: nextIndex + i,
|
|
3179
|
+
skip: 0,
|
|
3180
|
+
hash: "",
|
|
3181
|
+
timestampUtcMs: action.timestampUtcMs,
|
|
3182
|
+
action
|
|
3183
|
+
}));
|
|
3184
|
+
}
|
|
3185
|
+
/**
|
|
2791
3186
|
* Re-evaluates the document when a write meets both criteria: it was written
|
|
2792
3187
|
* to a stream the model reads, and it is timestamped before an operation
|
|
2793
3188
|
* already stored. The caller supplies the timestamp and the reactor does not replace
|
|
@@ -2797,10 +3192,11 @@ var SimpleJobExecutor = class {
|
|
|
2797
3192
|
async reevaluateIfCriteriaMet(criteria, executing) {
|
|
2798
3193
|
if (!this.featureFlags.documentDecisions) return;
|
|
2799
3194
|
const { job, stores, signal } = executing;
|
|
2800
|
-
|
|
3195
|
+
const target = {
|
|
2801
3196
|
documentId: job.documentId,
|
|
2802
3197
|
branch: job.branch
|
|
2803
|
-
}
|
|
3198
|
+
};
|
|
3199
|
+
if (!staticReadSet(this.decisionModel(target)).some((stream) => stream.query.documentId === job.documentId && stream.query.scope === criteria.scope && stream.query.branch === job.branch)) return;
|
|
2804
3200
|
const revisions = await stores.operationStore.getRevisions(job.documentId, job.branch, signal);
|
|
2805
3201
|
const latest = Date.parse(revisions.latestTimestamp);
|
|
2806
3202
|
if (!criteria.operations.some((operation) => Date.parse(operation.timestampUtcMs) < latest)) return;
|
|
@@ -2813,20 +3209,16 @@ var SimpleJobExecutor = class {
|
|
|
2813
3209
|
*/
|
|
2814
3210
|
async reevaluateDocument(executing) {
|
|
2815
3211
|
const { job, stores, signal } = executing;
|
|
2816
|
-
const
|
|
3212
|
+
const target = {
|
|
2817
3213
|
documentId: job.documentId,
|
|
2818
3214
|
branch: job.branch
|
|
2819
|
-
}
|
|
3215
|
+
};
|
|
2820
3216
|
const revisions = await stores.operationStore.getRevisions(job.documentId, job.branch, signal);
|
|
2821
|
-
for (const scope of
|
|
2822
|
-
if (!definition.evaluatesScope(scope)) continue;
|
|
3217
|
+
for (const scope of this.evaluationOrder(target, revisions.revision)) {
|
|
2823
3218
|
const stored = (await stores.operationStore.getSince(job.documentId, scope, job.branch, -1, void 0, void 0, signal)).results;
|
|
2824
3219
|
const effective = garbageCollect(sortOperations([...stored]));
|
|
2825
3220
|
if (effective.length === 0) continue;
|
|
2826
|
-
const reevaluated = await
|
|
2827
|
-
documentId: job.documentId,
|
|
2828
|
-
branch: job.branch
|
|
2829
|
-
}, {
|
|
3221
|
+
const reevaluated = await evaluateByPosition(this.decisionModel, target, {
|
|
2830
3222
|
scope,
|
|
2831
3223
|
operations: effective
|
|
2832
3224
|
}, stores, signal);
|
|
@@ -2846,7 +3238,8 @@ var SimpleJobExecutor = class {
|
|
|
2846
3238
|
...job,
|
|
2847
3239
|
scope
|
|
2848
3240
|
},
|
|
2849
|
-
replayingAcceptedHistory: true
|
|
3241
|
+
replayingAcceptedHistory: true,
|
|
3242
|
+
evaluatedByPosition: true
|
|
2850
3243
|
});
|
|
2851
3244
|
if (!result.success) return result.error ?? /* @__PURE__ */ new Error(`Re-evaluation of ${job.documentId} ${scope} failed`);
|
|
2852
3245
|
}
|
|
@@ -2860,6 +3253,7 @@ var SimpleJobExecutor = class {
|
|
|
2860
3253
|
} catch {}
|
|
2861
3254
|
if (docMeta?.state.isDeleted && !this.featureFlags.documentDecisions) return buildErrorResult(job, new DocumentDeletedError(job.documentId, docMeta.state.deletedAtUtcIso), startTime);
|
|
2862
3255
|
const scope = job.scope;
|
|
3256
|
+
const monotonicAuthStream = this.featureFlags.authEnforcement && scope === "auth";
|
|
2863
3257
|
let latestRevision;
|
|
2864
3258
|
try {
|
|
2865
3259
|
latestRevision = (await stores.operationStore.getRevisions(job.documentId, job.branch, signal)).revision[scope] ?? 0;
|
|
@@ -2869,7 +3263,7 @@ var SimpleJobExecutor = class {
|
|
|
2869
3263
|
for (const operation of job.operations) if (operation.timestampUtcMs && !isValidISOTimestamp(operation.timestampUtcMs)) return {
|
|
2870
3264
|
job,
|
|
2871
3265
|
success: false,
|
|
2872
|
-
error:
|
|
3266
|
+
error: new InvalidOperationTimestampError(job.documentId, scope, operation.timestampUtcMs, `operation (index: ${operation.index})`),
|
|
2873
3267
|
duration: Date.now() - startTime
|
|
2874
3268
|
};
|
|
2875
3269
|
let minIncomingIndex = Number.POSITIVE_INFINITY;
|
|
@@ -2877,7 +3271,7 @@ var SimpleJobExecutor = class {
|
|
|
2877
3271
|
for (const operation of job.operations) {
|
|
2878
3272
|
minIncomingIndex = Math.min(minIncomingIndex, operation.index);
|
|
2879
3273
|
const ts = operation.timestampUtcMs || "";
|
|
2880
|
-
if (ts < minIncomingTimestamp) minIncomingTimestamp = ts;
|
|
3274
|
+
if (Date.parse(ts) < Date.parse(minIncomingTimestamp)) minIncomingTimestamp = ts;
|
|
2881
3275
|
}
|
|
2882
3276
|
let conflictingOps;
|
|
2883
3277
|
try {
|
|
@@ -2902,11 +3296,14 @@ var SimpleJobExecutor = class {
|
|
|
2902
3296
|
}
|
|
2903
3297
|
return true;
|
|
2904
3298
|
});
|
|
2905
|
-
const existingOpsToReshuffle = nonSupersededOps.filter((operation) => !isGenesisOperation(operation));
|
|
2906
|
-
|
|
3299
|
+
const existingOpsToReshuffle = monotonicAuthStream ? [] : nonSupersededOps.filter((operation) => !isGenesisOperation(operation));
|
|
3300
|
+
const actionIdCounts = /* @__PURE__ */ new Map();
|
|
3301
|
+
for (const operation of allOpsFromMinConflictingIndex) actionIdCounts.set(operation.action.id, (actionIdCounts.get(operation.action.id) ?? 0) + 1);
|
|
3302
|
+
const reshuffleCost = existingOpsToReshuffle.filter((operation) => (actionIdCounts.get(operation.action.id) ?? 0) < 2).length;
|
|
3303
|
+
if (reshuffleCost > this.config.maxSkipThreshold) return {
|
|
2907
3304
|
job,
|
|
2908
3305
|
success: false,
|
|
2909
|
-
error:
|
|
3306
|
+
error: new ExcessiveReshuffleError(job.documentId, scope, reshuffleCost, this.config.maxSkipThreshold),
|
|
2910
3307
|
duration: Date.now() - startTime
|
|
2911
3308
|
};
|
|
2912
3309
|
let skipCount = existingOpsToReshuffle.length;
|
|
@@ -2934,6 +3331,16 @@ var SimpleJobExecutor = class {
|
|
|
2934
3331
|
operationsWithContext: [],
|
|
2935
3332
|
duration: Date.now() - startTime
|
|
2936
3333
|
};
|
|
3334
|
+
if (monotonicAuthStream) {
|
|
3335
|
+
const newest = await stores.operationStore.getStreamLatestTimestamp(job.documentId, "auth", job.branch, signal);
|
|
3336
|
+
const violation = this.firstNonMonotonicTimestamp([...incomingOpsToApply].sort((a, b) => a.index - b.index), newest, job.documentId, job.branch);
|
|
3337
|
+
if (violation) return {
|
|
3338
|
+
job,
|
|
3339
|
+
success: false,
|
|
3340
|
+
error: violation,
|
|
3341
|
+
duration: Date.now() - startTime
|
|
3342
|
+
};
|
|
3343
|
+
}
|
|
2937
3344
|
const reshuffledOperations = existingOpsToReshuffle.length === 0 && skipCount === 0 ? incomingOpsToApply.slice().sort((a, b) => a.index - b.index).map((operation, i) => ({
|
|
2938
3345
|
...operation,
|
|
2939
3346
|
index: latestRevision + i
|
|
@@ -2947,7 +3354,7 @@ var SimpleJobExecutor = class {
|
|
|
2947
3354
|
for (const operation of reshuffledOperations) if (operation.action.type === "NOOP") operation.skip = 1;
|
|
2948
3355
|
let deniedReasons;
|
|
2949
3356
|
if (this.featureFlags.documentDecisions) try {
|
|
2950
|
-
deniedReasons = await
|
|
3357
|
+
deniedReasons = await evaluateByPosition(this.decisionModel, {
|
|
2951
3358
|
documentId: job.documentId,
|
|
2952
3359
|
branch: job.branch
|
|
2953
3360
|
}, {
|
|
@@ -3500,6 +3907,10 @@ var KyselyOperationStore = class KyselyOperationStore {
|
|
|
3500
3907
|
latestTimestamp: latest?.latestTimestamp ? new Date(latest.latestTimestamp).toISOString() : (/* @__PURE__ */ new Date(0)).toISOString()
|
|
3501
3908
|
};
|
|
3502
3909
|
}
|
|
3910
|
+
async getStreamLatestTimestamp(documentId, scope, branch, signal) {
|
|
3911
|
+
const latest = await this.queryExecutor.selectFrom("Operation").select((eb) => eb.fn.max("timestampUtcMs").as("latestTimestamp")).where("documentId", "=", documentId).where("scope", "=", scope).where("branch", "=", branch).executeTakeFirst();
|
|
3912
|
+
return latest?.latestTimestamp ? new Date(latest.latestTimestamp).toISOString() : void 0;
|
|
3913
|
+
}
|
|
3503
3914
|
rowToOperation(row) {
|
|
3504
3915
|
return {
|
|
3505
3916
|
index: row.index,
|
|
@@ -3593,8 +4004,8 @@ function createForwardingPoolInstrumentation(name) {
|
|
|
3593
4004
|
}
|
|
3594
4005
|
//#endregion
|
|
3595
4006
|
//#region src/storage/migrations/001_create_operation_table.ts
|
|
3596
|
-
var _001_create_operation_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
3597
|
-
async function up$
|
|
4007
|
+
var _001_create_operation_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$15 });
|
|
4008
|
+
async function up$15(db) {
|
|
3598
4009
|
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", [
|
|
3599
4010
|
"documentId",
|
|
3600
4011
|
"scope",
|
|
@@ -3619,8 +4030,8 @@ async function up$14(db) {
|
|
|
3619
4030
|
}
|
|
3620
4031
|
//#endregion
|
|
3621
4032
|
//#region src/storage/migrations/002_create_keyframe_table.ts
|
|
3622
|
-
var _002_create_keyframe_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
3623
|
-
async function up$
|
|
4033
|
+
var _002_create_keyframe_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$14 });
|
|
4034
|
+
async function up$14(db) {
|
|
3624
4035
|
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", [
|
|
3625
4036
|
"documentId",
|
|
3626
4037
|
"scope",
|
|
@@ -3636,14 +4047,14 @@ async function up$13(db) {
|
|
|
3636
4047
|
}
|
|
3637
4048
|
//#endregion
|
|
3638
4049
|
//#region src/storage/migrations/003_create_document_table.ts
|
|
3639
|
-
var _003_create_document_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
3640
|
-
async function up$
|
|
4050
|
+
var _003_create_document_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$13 });
|
|
4051
|
+
async function up$13(db) {
|
|
3641
4052
|
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();
|
|
3642
4053
|
}
|
|
3643
4054
|
//#endregion
|
|
3644
4055
|
//#region src/storage/migrations/004_create_document_relationship_table.ts
|
|
3645
|
-
var _004_create_document_relationship_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
3646
|
-
async function up$
|
|
4056
|
+
var _004_create_document_relationship_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$12 });
|
|
4057
|
+
async function up$12(db) {
|
|
3647
4058
|
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", [
|
|
3648
4059
|
"sourceId",
|
|
3649
4060
|
"targetId",
|
|
@@ -3655,14 +4066,14 @@ async function up$11(db) {
|
|
|
3655
4066
|
}
|
|
3656
4067
|
//#endregion
|
|
3657
4068
|
//#region src/storage/migrations/005_create_indexer_state_table.ts
|
|
3658
|
-
var _005_create_indexer_state_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
3659
|
-
async function up$
|
|
4069
|
+
var _005_create_indexer_state_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$11 });
|
|
4070
|
+
async function up$11(db) {
|
|
3660
4071
|
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();
|
|
3661
4072
|
}
|
|
3662
4073
|
//#endregion
|
|
3663
4074
|
//#region src/storage/migrations/006_create_document_snapshot_table.ts
|
|
3664
|
-
var _006_create_document_snapshot_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
3665
|
-
async function up$
|
|
4075
|
+
var _006_create_document_snapshot_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$10 });
|
|
4076
|
+
async function up$10(db) {
|
|
3666
4077
|
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", [
|
|
3667
4078
|
"documentId",
|
|
3668
4079
|
"scope",
|
|
@@ -3683,8 +4094,8 @@ async function up$9(db) {
|
|
|
3683
4094
|
}
|
|
3684
4095
|
//#endregion
|
|
3685
4096
|
//#region src/storage/migrations/007_create_slug_mapping_table.ts
|
|
3686
|
-
var _007_create_slug_mapping_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
3687
|
-
async function up$
|
|
4097
|
+
var _007_create_slug_mapping_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$9 });
|
|
4098
|
+
async function up$9(db) {
|
|
3688
4099
|
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", [
|
|
3689
4100
|
"documentId",
|
|
3690
4101
|
"scope",
|
|
@@ -3694,14 +4105,14 @@ async function up$8(db) {
|
|
|
3694
4105
|
}
|
|
3695
4106
|
//#endregion
|
|
3696
4107
|
//#region src/storage/migrations/008_create_view_state_table.ts
|
|
3697
|
-
var _008_create_view_state_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
3698
|
-
async function up$
|
|
4108
|
+
var _008_create_view_state_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$8 });
|
|
4109
|
+
async function up$8(db) {
|
|
3699
4110
|
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();
|
|
3700
4111
|
}
|
|
3701
4112
|
//#endregion
|
|
3702
4113
|
//#region src/storage/migrations/009_create_operation_index_tables.ts
|
|
3703
|
-
var _009_create_operation_index_tables_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
3704
|
-
async function up$
|
|
4114
|
+
var _009_create_operation_index_tables_exports = /* @__PURE__ */ __exportAll({ up: () => up$7 });
|
|
4115
|
+
async function up$7(db) {
|
|
3705
4116
|
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();
|
|
3706
4117
|
await db.schema.createIndex("idx_document_collections_collectionId").on("document_collections").column("collectionId").execute();
|
|
3707
4118
|
await db.schema.createIndex("idx_doc_collections_collection_range").on("document_collections").columns(["collectionId", "joinedOrdinal"]).execute();
|
|
@@ -3715,8 +4126,8 @@ async function up$6(db) {
|
|
|
3715
4126
|
}
|
|
3716
4127
|
//#endregion
|
|
3717
4128
|
//#region src/storage/migrations/010_create_sync_tables.ts
|
|
3718
|
-
var _010_create_sync_tables_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
3719
|
-
async function up$
|
|
4129
|
+
var _010_create_sync_tables_exports = /* @__PURE__ */ __exportAll({ up: () => up$6 });
|
|
4130
|
+
async function up$6(db) {
|
|
3720
4131
|
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();
|
|
3721
4132
|
await db.schema.createIndex("idx_sync_remotes_collection").on("sync_remotes").column("collection_id").execute();
|
|
3722
4133
|
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();
|
|
@@ -3724,8 +4135,8 @@ async function up$5(db) {
|
|
|
3724
4135
|
}
|
|
3725
4136
|
//#endregion
|
|
3726
4137
|
//#region src/storage/migrations/011_add_cursor_type_column.ts
|
|
3727
|
-
var _011_add_cursor_type_column_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
3728
|
-
async function up$
|
|
4138
|
+
var _011_add_cursor_type_column_exports = /* @__PURE__ */ __exportAll({ up: () => up$5 });
|
|
4139
|
+
async function up$5(db) {
|
|
3729
4140
|
await db.deleteFrom("sync_cursors").where("remote_name", "like", "outbox::%").execute();
|
|
3730
4141
|
await db.deleteFrom("sync_remotes").where("name", "like", "outbox::%").execute();
|
|
3731
4142
|
await db.schema.dropTable("sync_cursors").execute();
|
|
@@ -3734,43 +4145,60 @@ async function up$4(db) {
|
|
|
3734
4145
|
}
|
|
3735
4146
|
//#endregion
|
|
3736
4147
|
//#region src/storage/migrations/012_add_source_remote_column.ts
|
|
3737
|
-
var _012_add_source_remote_column_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
3738
|
-
async function up$
|
|
4148
|
+
var _012_add_source_remote_column_exports = /* @__PURE__ */ __exportAll({ up: () => up$4 });
|
|
4149
|
+
async function up$4(db) {
|
|
3739
4150
|
await db.schema.alterTable("operation_index_operations").addColumn("sourceRemote", "text", (col) => col.notNull().defaultTo("")).execute();
|
|
3740
4151
|
}
|
|
3741
4152
|
//#endregion
|
|
3742
4153
|
//#region src/storage/migrations/013_create_sync_dead_letters_table.ts
|
|
3743
|
-
var _013_create_sync_dead_letters_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
3744
|
-
async function up$
|
|
4154
|
+
var _013_create_sync_dead_letters_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$3 });
|
|
4155
|
+
async function up$3(db) {
|
|
3745
4156
|
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();
|
|
3746
4157
|
await db.schema.createIndex("idx_sync_dead_letters_remote").on("sync_dead_letters").column("remote_name").execute();
|
|
3747
4158
|
}
|
|
3748
4159
|
//#endregion
|
|
3749
4160
|
//#region src/storage/migrations/014_create_processor_cursor_table.ts
|
|
3750
|
-
var _014_create_processor_cursor_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$
|
|
3751
|
-
async function up$
|
|
4161
|
+
var _014_create_processor_cursor_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$2 });
|
|
4162
|
+
async function up$2(db) {
|
|
3752
4163
|
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();
|
|
3753
4164
|
}
|
|
3754
4165
|
//#endregion
|
|
3755
4166
|
//#region src/storage/migrations/015_add_operation_denied_reason.ts
|
|
3756
4167
|
var _015_add_operation_denied_reason_exports = /* @__PURE__ */ __exportAll({
|
|
3757
|
-
down: () => down,
|
|
3758
|
-
up: () => up
|
|
4168
|
+
down: () => down$1,
|
|
4169
|
+
up: () => up$1
|
|
3759
4170
|
});
|
|
3760
4171
|
/**
|
|
3761
4172
|
* Records why authorization refused an operation. Separate from `error` so a
|
|
3762
4173
|
* denial is distinguishable from a reducer failure without matching on a
|
|
3763
4174
|
* message. Null for every operation written before decisions were enforced.
|
|
3764
4175
|
*/
|
|
3765
|
-
async function up(db) {
|
|
4176
|
+
async function up$1(db) {
|
|
3766
4177
|
await db.schema.alterTable("Operation").addColumn("deniedReason", "text").execute();
|
|
3767
4178
|
await db.schema.alterTable("operation_index_operations").addColumn("deniedReason", "text").execute();
|
|
3768
4179
|
}
|
|
3769
|
-
async function down(db) {
|
|
4180
|
+
async function down$1(db) {
|
|
3770
4181
|
await db.schema.alterTable("operation_index_operations").dropColumn("deniedReason").execute();
|
|
3771
4182
|
await db.schema.alterTable("Operation").dropColumn("deniedReason").execute();
|
|
3772
4183
|
}
|
|
3773
4184
|
//#endregion
|
|
4185
|
+
//#region src/storage/migrations/016_add_dead_letter_error_type.ts
|
|
4186
|
+
var _016_add_dead_letter_error_type_exports = /* @__PURE__ */ __exportAll({
|
|
4187
|
+
down: () => down,
|
|
4188
|
+
up: () => up
|
|
4189
|
+
});
|
|
4190
|
+
/**
|
|
4191
|
+
* The classification a dead letter falls into, stored because it decides whether
|
|
4192
|
+
* the document stays quarantined and the in-memory error is gone after a restart.
|
|
4193
|
+
* Defaulted rather than nullable, so a pre-existing row rehydrates.
|
|
4194
|
+
*/
|
|
4195
|
+
async function up(db) {
|
|
4196
|
+
await db.schema.alterTable("sync_dead_letters").addColumn("error_type", "text", (col) => col.notNull().defaultTo("UNCLASSIFIED")).execute();
|
|
4197
|
+
}
|
|
4198
|
+
async function down(db) {
|
|
4199
|
+
await db.schema.alterTable("sync_dead_letters").dropColumn("error_type").execute();
|
|
4200
|
+
}
|
|
4201
|
+
//#endregion
|
|
3774
4202
|
//#region src/storage/migrations/migrator.ts
|
|
3775
4203
|
const REACTOR_SCHEMA = "reactor";
|
|
3776
4204
|
const migrations = {
|
|
@@ -3788,7 +4216,8 @@ const migrations = {
|
|
|
3788
4216
|
"012_add_source_remote_column": _012_add_source_remote_column_exports,
|
|
3789
4217
|
"013_create_sync_dead_letters_table": _013_create_sync_dead_letters_table_exports,
|
|
3790
4218
|
"014_create_processor_cursor_table": _014_create_processor_cursor_table_exports,
|
|
3791
|
-
"015_add_operation_denied_reason": _015_add_operation_denied_reason_exports
|
|
4219
|
+
"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
|
|
3792
4221
|
};
|
|
3793
4222
|
var ProgrammaticMigrationProvider = class {
|
|
3794
4223
|
getMigrations() {
|
|
@@ -3842,6 +4271,6 @@ async function getMigrationStatus(db, schema = REACTOR_SCHEMA) {
|
|
|
3842
4271
|
//#region src/core/drive-container-types.ts
|
|
3843
4272
|
const DEFAULT_DRIVE_CONTAINER_TYPES = new Set(["powerhouse/document-drive", "powerhouse/reactor-drive"]);
|
|
3844
4273
|
//#endregion
|
|
3845
|
-
export {
|
|
4274
|
+
export { OptimisticLockError as A, ExcessiveReshuffleError as B, DocumentMetaCache as C, APPEND_CONDITION_FAILED_PREFIX as D, CollectionMembershipCache as E, ModuleNotFoundError as F, __exportAll as G, matchesScope as H, AuthTimestampNotMonotonicError as I, AuthorizationDeniedError as L, DuplicateManifestError as M, DuplicateModuleError as N, AppendConditionFailedError as O, InvalidModuleError as P, DocumentDeletedError as R, KyselyOperationIndex as S, createEmptyConsistencyToken as T, parsePagingOptions as U, InvalidOperationTimestampError as V, throwIfAborted 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 };
|
|
3846
4275
|
|
|
3847
|
-
//# sourceMappingURL=drive-container-types-
|
|
4276
|
+
//# sourceMappingURL=drive-container-types-BJCKXJwH.js.map
|