@voidhash/mimic 0.0.2 → 0.0.4
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/.turbo/turbo-build.log +257 -42
- package/dist/Document.cjs +152 -0
- package/dist/Document.d.cts +67 -0
- package/dist/Document.d.cts.map +1 -0
- package/dist/Document.d.mts +68 -0
- package/dist/Document.d.mts.map +1 -0
- package/dist/Document.mjs +147 -0
- package/dist/Document.mjs.map +1 -0
- package/dist/EffectSchema.cjs +180 -0
- package/dist/EffectSchema.d.cts +84 -0
- package/dist/EffectSchema.d.cts.map +1 -0
- package/dist/EffectSchema.d.mts +84 -0
- package/dist/EffectSchema.d.mts.map +1 -0
- package/dist/EffectSchema.mjs +176 -0
- package/dist/EffectSchema.mjs.map +1 -0
- package/dist/FractionalIndex.cjs +365 -0
- package/dist/FractionalIndex.mjs +364 -0
- package/dist/FractionalIndex.mjs.map +1 -0
- package/dist/Operation.cjs +53 -0
- package/dist/Operation.d.cts +39 -0
- package/dist/Operation.d.cts.map +1 -0
- package/dist/Operation.d.mts +39 -0
- package/dist/Operation.d.mts.map +1 -0
- package/dist/Operation.mjs +46 -0
- package/dist/Operation.mjs.map +1 -0
- package/dist/OperationDefinition.cjs +13 -0
- package/dist/OperationDefinition.d.cts +12 -0
- package/dist/OperationDefinition.d.cts.map +1 -0
- package/dist/OperationDefinition.d.mts +12 -0
- package/dist/OperationDefinition.d.mts.map +1 -0
- package/dist/OperationDefinition.mjs +13 -0
- package/dist/OperationDefinition.mjs.map +1 -0
- package/dist/OperationPath.cjs +148 -0
- package/dist/OperationPath.d.cts +60 -0
- package/dist/OperationPath.d.cts.map +1 -0
- package/dist/OperationPath.d.mts +60 -0
- package/dist/OperationPath.d.mts.map +1 -0
- package/dist/OperationPath.mjs +138 -0
- package/dist/OperationPath.mjs.map +1 -0
- package/dist/{Presence-gWrmGBeu.cjs → Presence.cjs} +4 -39
- package/dist/{Presence-N8u7Eppr.d.mts → Presence.d.cts} +2 -2
- package/dist/Presence.d.cts.map +1 -0
- package/dist/{Presence-DKKP4v5X.d.cts → Presence.d.mts} +2 -2
- package/dist/Presence.d.mts.map +1 -0
- package/dist/{Presence-DdMVKcOv.mjs → Presence.mjs} +3 -28
- package/dist/Presence.mjs.map +1 -0
- package/dist/Primitive.cjs +52 -0
- package/dist/Primitive.d.cts +20 -0
- package/dist/Primitive.d.cts.map +1 -0
- package/dist/Primitive.d.mts +20 -0
- package/dist/Primitive.d.mts.map +1 -0
- package/dist/Primitive.mjs +48 -0
- package/dist/Primitive.mjs.map +1 -0
- package/dist/ProxyEnvironment.cjs +34 -0
- package/dist/ProxyEnvironment.d.cts +31 -0
- package/dist/ProxyEnvironment.d.cts.map +1 -0
- package/dist/ProxyEnvironment.d.mts +31 -0
- package/dist/ProxyEnvironment.d.mts.map +1 -0
- package/dist/ProxyEnvironment.mjs +29 -0
- package/dist/ProxyEnvironment.mjs.map +1 -0
- package/dist/Transaction.cjs +66 -0
- package/dist/Transaction.d.cts +56 -0
- package/dist/Transaction.d.cts.map +1 -0
- package/dist/Transaction.d.mts +56 -0
- package/dist/Transaction.d.mts.map +1 -0
- package/dist/Transaction.mjs +58 -0
- package/dist/Transaction.mjs.map +1 -0
- package/dist/Transform.cjs +11 -0
- package/dist/Transform.d.cts +21 -0
- package/dist/Transform.d.cts.map +1 -0
- package/dist/Transform.d.mts +21 -0
- package/dist/Transform.d.mts.map +1 -0
- package/dist/Transform.mjs +6 -0
- package/dist/Transform.mjs.map +1 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/defineProperty.cjs +14 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/defineProperty.mjs +14 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/objectSpread2.cjs +27 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/objectSpread2.mjs +27 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/toPrimitive.cjs +16 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/toPrimitive.mjs +16 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/toPropertyKey.cjs +11 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/toPropertyKey.mjs +11 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/typeof.cjs +18 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/typeof.mjs +12 -0
- package/dist/_virtual/rolldown_runtime.cjs +43 -0
- package/dist/{chunk-CLMFDpHK.mjs → _virtual/rolldown_runtime.mjs} +1 -1
- package/dist/client/ClientDocument.cjs +590 -0
- package/dist/client/ClientDocument.d.cts +161 -0
- package/dist/client/ClientDocument.d.cts.map +1 -0
- package/dist/client/ClientDocument.d.mts +162 -0
- package/dist/client/ClientDocument.d.mts.map +1 -0
- package/dist/client/ClientDocument.mjs +586 -0
- package/dist/client/ClientDocument.mjs.map +1 -0
- package/dist/client/Rebase.cjs +204 -0
- package/dist/client/Rebase.d.cts +87 -0
- package/dist/client/Rebase.d.cts.map +1 -0
- package/dist/client/Rebase.d.mts +88 -0
- package/dist/client/Rebase.d.mts.map +1 -0
- package/dist/client/Rebase.mjs +198 -0
- package/dist/client/Rebase.mjs.map +1 -0
- package/dist/client/StateMonitor.cjs +133 -0
- package/dist/client/StateMonitor.d.cts +122 -0
- package/dist/client/StateMonitor.d.cts.map +1 -0
- package/dist/client/StateMonitor.d.mts +122 -0
- package/dist/client/StateMonitor.d.mts.map +1 -0
- package/dist/client/StateMonitor.mjs +129 -0
- package/dist/client/StateMonitor.mjs.map +1 -0
- package/dist/client/Transport.cjs +11 -0
- package/dist/client/Transport.d.cts +237 -0
- package/dist/client/Transport.d.cts.map +1 -0
- package/dist/client/Transport.d.mts +237 -0
- package/dist/client/Transport.d.mts.map +1 -0
- package/dist/client/Transport.mjs +6 -0
- package/dist/client/Transport.mjs.map +1 -0
- package/dist/client/WebSocketTransport.cjs +396 -0
- package/dist/client/WebSocketTransport.d.cts +29 -0
- package/dist/client/WebSocketTransport.d.cts.map +1 -0
- package/dist/client/WebSocketTransport.d.mts +29 -0
- package/dist/client/WebSocketTransport.d.mts.map +1 -0
- package/dist/client/WebSocketTransport.mjs +392 -0
- package/dist/client/WebSocketTransport.mjs.map +1 -0
- package/dist/client/errors.cjs +135 -0
- package/dist/client/errors.d.cts +87 -0
- package/dist/client/errors.d.cts.map +1 -0
- package/dist/client/errors.d.mts +87 -0
- package/dist/client/errors.d.mts.map +1 -0
- package/dist/client/errors.mjs +127 -0
- package/dist/client/errors.mjs.map +1 -0
- package/dist/client/index.cjs +22 -1424
- package/dist/client/index.d.cts +8 -692
- package/dist/client/index.d.mts +8 -692
- package/dist/client/index.mjs +9 -1413
- package/dist/index.cjs +20 -2973
- package/dist/index.d.cts +12 -419
- package/dist/index.d.mts +12 -419
- package/dist/index.mjs +13 -2968
- package/dist/primitives/Array.cjs +302 -0
- package/dist/primitives/Array.d.cts +95 -0
- package/dist/primitives/Array.d.cts.map +1 -0
- package/dist/primitives/Array.d.mts +95 -0
- package/dist/primitives/Array.d.mts.map +1 -0
- package/dist/primitives/Array.mjs +301 -0
- package/dist/primitives/Array.mjs.map +1 -0
- package/dist/primitives/Boolean.cjs +95 -0
- package/dist/primitives/Boolean.d.cts +44 -0
- package/dist/primitives/Boolean.d.cts.map +1 -0
- package/dist/primitives/Boolean.d.mts +44 -0
- package/dist/primitives/Boolean.d.mts.map +1 -0
- package/dist/primitives/Boolean.mjs +94 -0
- package/dist/primitives/Boolean.mjs.map +1 -0
- package/dist/primitives/Either.cjs +200 -0
- package/dist/primitives/Either.d.cts +113 -0
- package/dist/primitives/Either.d.cts.map +1 -0
- package/dist/primitives/Either.d.mts +113 -0
- package/dist/primitives/Either.d.mts.map +1 -0
- package/dist/primitives/Either.mjs +199 -0
- package/dist/primitives/Either.mjs.map +1 -0
- package/dist/primitives/Lazy.cjs +46 -0
- package/dist/primitives/Lazy.d.cts +46 -0
- package/dist/primitives/Lazy.d.cts.map +1 -0
- package/dist/primitives/Lazy.d.mts +46 -0
- package/dist/primitives/Lazy.d.mts.map +1 -0
- package/dist/primitives/Lazy.mjs +46 -0
- package/dist/primitives/Lazy.mjs.map +1 -0
- package/dist/primitives/Literal.cjs +91 -0
- package/dist/primitives/Literal.d.cts +46 -0
- package/dist/primitives/Literal.d.cts.map +1 -0
- package/dist/primitives/Literal.d.mts +46 -0
- package/dist/primitives/Literal.d.mts.map +1 -0
- package/dist/primitives/Literal.mjs +90 -0
- package/dist/primitives/Literal.mjs.map +1 -0
- package/dist/primitives/Number.cjs +115 -0
- package/dist/primitives/Number.d.cts +54 -0
- package/dist/primitives/Number.d.cts.map +1 -0
- package/dist/primitives/Number.d.mts +54 -0
- package/dist/primitives/Number.d.mts.map +1 -0
- package/dist/primitives/Number.mjs +114 -0
- package/dist/primitives/Number.mjs.map +1 -0
- package/dist/primitives/String.cjs +127 -0
- package/dist/primitives/String.d.cts +56 -0
- package/dist/primitives/String.d.cts.map +1 -0
- package/dist/primitives/String.d.mts +56 -0
- package/dist/primitives/String.d.mts.map +1 -0
- package/dist/primitives/String.mjs +126 -0
- package/dist/primitives/String.mjs.map +1 -0
- package/dist/primitives/Struct.cjs +207 -0
- package/dist/primitives/Struct.d.cts +96 -0
- package/dist/primitives/Struct.d.cts.map +1 -0
- package/dist/primitives/Struct.d.mts +97 -0
- package/dist/primitives/Struct.d.mts.map +1 -0
- package/dist/primitives/Struct.mjs +206 -0
- package/dist/primitives/Struct.mjs.map +1 -0
- package/dist/primitives/Tree.cjs +575 -0
- package/dist/primitives/Tree.d.cts +185 -0
- package/dist/primitives/Tree.d.cts.map +1 -0
- package/dist/primitives/Tree.d.mts +185 -0
- package/dist/primitives/Tree.d.mts.map +1 -0
- package/dist/primitives/Tree.mjs +574 -0
- package/dist/primitives/Tree.mjs.map +1 -0
- package/dist/primitives/TreeNode.cjs +73 -0
- package/dist/primitives/TreeNode.d.cts +92 -0
- package/dist/primitives/TreeNode.d.cts.map +1 -0
- package/dist/primitives/TreeNode.d.mts +93 -0
- package/dist/primitives/TreeNode.d.mts.map +1 -0
- package/dist/primitives/TreeNode.mjs +72 -0
- package/dist/primitives/TreeNode.mjs.map +1 -0
- package/dist/primitives/Union.cjs +170 -0
- package/dist/primitives/Union.d.cts +81 -0
- package/dist/primitives/Union.d.cts.map +1 -0
- package/dist/primitives/Union.d.mts +81 -0
- package/dist/primitives/Union.d.mts.map +1 -0
- package/dist/primitives/Union.mjs +169 -0
- package/dist/primitives/Union.mjs.map +1 -0
- package/dist/primitives/shared.cjs +60 -0
- package/dist/primitives/shared.d.cts +147 -0
- package/dist/primitives/shared.d.cts.map +1 -0
- package/dist/primitives/shared.d.mts +147 -0
- package/dist/primitives/shared.d.mts.map +1 -0
- package/dist/primitives/shared.mjs +58 -0
- package/dist/primitives/shared.mjs.map +1 -0
- package/dist/server/ServerDocument.cjs +110 -0
- package/dist/server/ServerDocument.d.cts +98 -0
- package/dist/server/ServerDocument.d.cts.map +1 -0
- package/dist/server/ServerDocument.d.mts +99 -0
- package/dist/server/ServerDocument.d.mts.map +1 -0
- package/dist/server/ServerDocument.mjs +106 -0
- package/dist/server/ServerDocument.mjs.map +1 -0
- package/dist/server/errors.cjs +85 -0
- package/dist/server/errors.d.cts +53 -0
- package/dist/server/errors.d.cts.map +1 -0
- package/dist/server/errors.d.mts +53 -0
- package/dist/server/errors.d.mts.map +1 -0
- package/dist/server/errors.mjs +81 -0
- package/dist/server/errors.mjs.map +1 -0
- package/dist/server/index.cjs +9 -185
- package/dist/server/index.d.cts +3 -148
- package/dist/server/index.d.mts +3 -148
- package/dist/server/index.mjs +3 -181
- package/dist/types/index.cjs +16 -0
- package/dist/types/index.d.cts +16 -0
- package/dist/types/index.d.cts.map +1 -0
- package/dist/types/index.d.mts +16 -0
- package/dist/types/index.d.mts.map +1 -0
- package/dist/types/index.mjs +12 -0
- package/dist/types/index.mjs.map +1 -0
- package/dist/utils/tree-helpers.cjs +443 -0
- package/dist/utils/tree-helpers.d.cts +280 -0
- package/dist/utils/tree-helpers.d.cts.map +1 -0
- package/dist/utils/tree-helpers.d.mts +280 -0
- package/dist/utils/tree-helpers.d.mts.map +1 -0
- package/dist/utils/tree-helpers.mjs +439 -0
- package/dist/utils/tree-helpers.mjs.map +1 -0
- package/package.json +2 -2
- package/tsdown.config.ts +1 -1
- package/dist/Document-ChuFrTk1.cjs +0 -571
- package/dist/Document-CwiAFTIq.mjs +0 -438
- package/dist/Document-CwiAFTIq.mjs.map +0 -1
- package/dist/Presence-DKKP4v5X.d.cts.map +0 -1
- package/dist/Presence-DdMVKcOv.mjs.map +0 -1
- package/dist/Presence-N8u7Eppr.d.mts.map +0 -1
- package/dist/Primitive-DqQFc3Gu.d.mts +0 -1180
- package/dist/Primitive-DqQFc3Gu.d.mts.map +0 -1
- package/dist/Primitive-awpEjnKL.d.cts +0 -1180
- package/dist/Primitive-awpEjnKL.d.cts.map +0 -1
- package/dist/client/index.d.cts.map +0 -1
- package/dist/client/index.d.mts.map +0 -1
- package/dist/client/index.mjs.map +0 -1
- package/dist/index.d.cts.map +0 -1
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/dist/server/index.d.cts.map +0 -1
- package/dist/server/index.d.mts.map +0 -1
- package/dist/server/index.mjs.map +0 -1
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { __export } from "../_virtual/rolldown_runtime.mjs";
|
|
2
|
+
import { isPrefix, pathsEqual, pathsOverlap } from "../OperationPath.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/client/Rebase.ts
|
|
5
|
+
var Rebase_exports = /* @__PURE__ */ __export({
|
|
6
|
+
rebaseAfterRejection: () => rebaseAfterRejection,
|
|
7
|
+
rebaseAfterRejectionWithPrimitive: () => rebaseAfterRejectionWithPrimitive,
|
|
8
|
+
rebasePendingTransactions: () => rebasePendingTransactions,
|
|
9
|
+
rebasePendingTransactionsWithPrimitive: () => rebasePendingTransactionsWithPrimitive,
|
|
10
|
+
transformOperation: () => transformOperation,
|
|
11
|
+
transformOperationWithPrimitive: () => transformOperationWithPrimitive,
|
|
12
|
+
transformTransaction: () => transformTransaction,
|
|
13
|
+
transformTransactionWithPrimitive: () => transformTransactionWithPrimitive
|
|
14
|
+
});
|
|
15
|
+
/**
|
|
16
|
+
* Transforms a client operation against a server operation using a primitive.
|
|
17
|
+
*
|
|
18
|
+
* This delegates to the primitive's transformOperation method, which handles
|
|
19
|
+
* type-specific conflict resolution.
|
|
20
|
+
*
|
|
21
|
+
* @param clientOp - The client's operation to transform
|
|
22
|
+
* @param serverOp - The server's operation that has already been applied
|
|
23
|
+
* @param primitive - The root primitive to use for transformation
|
|
24
|
+
* @returns TransformResult indicating how the client operation should be handled
|
|
25
|
+
*/
|
|
26
|
+
const transformOperationWithPrimitive = (clientOp, serverOp, primitive) => {
|
|
27
|
+
return primitive._internal.transformOperation(clientOp, serverOp);
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Transforms a client operation against a server operation.
|
|
31
|
+
*
|
|
32
|
+
* This is a standalone implementation for cases where the primitive is not available.
|
|
33
|
+
* For schema-aware transformation, use transformOperationWithPrimitive instead.
|
|
34
|
+
*
|
|
35
|
+
* The key principle: client ops "shadow" server ops for the same path,
|
|
36
|
+
* meaning if both touch the same field, the client's intention wins
|
|
37
|
+
* (since it was made with knowledge of the server state at that time).
|
|
38
|
+
*/
|
|
39
|
+
const transformOperation = (clientOp, serverOp) => {
|
|
40
|
+
const clientPath = clientOp.path;
|
|
41
|
+
const serverPath = serverOp.path;
|
|
42
|
+
if (!pathsOverlap(clientPath, serverPath)) return {
|
|
43
|
+
type: "transformed",
|
|
44
|
+
operation: clientOp
|
|
45
|
+
};
|
|
46
|
+
if (serverOp.kind === "array.remove") {
|
|
47
|
+
const removedId = serverOp.payload.id;
|
|
48
|
+
const clientTokens = clientPath.toTokens().filter((t) => t !== "");
|
|
49
|
+
const serverTokens = serverPath.toTokens().filter((t) => t !== "");
|
|
50
|
+
if (clientTokens.length > serverTokens.length) {
|
|
51
|
+
if (clientTokens[serverTokens.length] === removedId) return { type: "noop" };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (serverOp.kind === "array.insert" && clientOp.kind === "array.insert") return {
|
|
55
|
+
type: "transformed",
|
|
56
|
+
operation: clientOp
|
|
57
|
+
};
|
|
58
|
+
if (serverOp.kind === "array.move" && clientOp.kind === "array.move") {
|
|
59
|
+
if (serverOp.payload.id === clientOp.payload.id) return {
|
|
60
|
+
type: "transformed",
|
|
61
|
+
operation: clientOp
|
|
62
|
+
};
|
|
63
|
+
return {
|
|
64
|
+
type: "transformed",
|
|
65
|
+
operation: clientOp
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
if (pathsEqual(clientPath, serverPath)) return {
|
|
69
|
+
type: "transformed",
|
|
70
|
+
operation: clientOp
|
|
71
|
+
};
|
|
72
|
+
if (isPrefix(serverPath, clientPath)) {
|
|
73
|
+
const serverKind = serverOp.kind;
|
|
74
|
+
if (serverKind === "struct.set" || serverKind === "array.set" || serverKind === "union.set") return {
|
|
75
|
+
type: "transformed",
|
|
76
|
+
operation: clientOp
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
type: "transformed",
|
|
81
|
+
operation: clientOp
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Transforms all operations in a client transaction against a server transaction.
|
|
86
|
+
* Uses the primitive's transformOperation for schema-aware transformation.
|
|
87
|
+
*/
|
|
88
|
+
const transformTransactionWithPrimitive = (clientTx, serverTx, primitive) => {
|
|
89
|
+
const transformedOps = [];
|
|
90
|
+
for (const clientOp of clientTx.ops) {
|
|
91
|
+
let currentOp = clientOp;
|
|
92
|
+
for (const serverOp of serverTx.ops) {
|
|
93
|
+
if (currentOp === null) break;
|
|
94
|
+
const result = transformOperationWithPrimitive(currentOp, serverOp, primitive);
|
|
95
|
+
switch (result.type) {
|
|
96
|
+
case "transformed":
|
|
97
|
+
currentOp = result.operation;
|
|
98
|
+
break;
|
|
99
|
+
case "noop":
|
|
100
|
+
currentOp = null;
|
|
101
|
+
break;
|
|
102
|
+
case "conflict": break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (currentOp !== null) transformedOps.push(currentOp);
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
id: clientTx.id,
|
|
109
|
+
ops: transformedOps,
|
|
110
|
+
timestamp: clientTx.timestamp
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Transforms all operations in a client transaction against a server transaction.
|
|
115
|
+
* This is a standalone version that doesn't require a primitive.
|
|
116
|
+
*/
|
|
117
|
+
const transformTransaction = (clientTx, serverTx) => {
|
|
118
|
+
const transformedOps = [];
|
|
119
|
+
for (const clientOp of clientTx.ops) {
|
|
120
|
+
let currentOp = clientOp;
|
|
121
|
+
for (const serverOp of serverTx.ops) {
|
|
122
|
+
if (currentOp === null) break;
|
|
123
|
+
const result = transformOperation(currentOp, serverOp);
|
|
124
|
+
switch (result.type) {
|
|
125
|
+
case "transformed":
|
|
126
|
+
currentOp = result.operation;
|
|
127
|
+
break;
|
|
128
|
+
case "noop":
|
|
129
|
+
currentOp = null;
|
|
130
|
+
break;
|
|
131
|
+
case "conflict": break;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (currentOp !== null) transformedOps.push(currentOp);
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
id: clientTx.id,
|
|
138
|
+
ops: transformedOps,
|
|
139
|
+
timestamp: clientTx.timestamp
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
/**
|
|
143
|
+
* Rebases a list of pending transactions against a server transaction using a primitive.
|
|
144
|
+
*
|
|
145
|
+
* This is called when a server transaction arrives that is NOT one of our pending
|
|
146
|
+
* transactions. We need to transform all pending transactions to work correctly
|
|
147
|
+
* on top of the new server state.
|
|
148
|
+
*/
|
|
149
|
+
const rebasePendingTransactionsWithPrimitive = (pendingTxs, serverTx, primitive) => {
|
|
150
|
+
return pendingTxs.map((pendingTx) => transformTransactionWithPrimitive(pendingTx, serverTx, primitive));
|
|
151
|
+
};
|
|
152
|
+
/**
|
|
153
|
+
* Rebases a list of pending transactions against a server transaction.
|
|
154
|
+
*
|
|
155
|
+
* This is called when a server transaction arrives that is NOT one of our pending
|
|
156
|
+
* transactions. We need to transform all pending transactions to work correctly
|
|
157
|
+
* on top of the new server state.
|
|
158
|
+
*/
|
|
159
|
+
const rebasePendingTransactions = (pendingTxs, serverTx) => {
|
|
160
|
+
return pendingTxs.map((pendingTx) => transformTransaction(pendingTx, serverTx));
|
|
161
|
+
};
|
|
162
|
+
/**
|
|
163
|
+
* Rebases pending transactions after a rejection using a primitive.
|
|
164
|
+
*
|
|
165
|
+
* When a transaction is rejected, we need to re-transform remaining pending
|
|
166
|
+
* transactions as if the rejected transaction never happened. This is done by
|
|
167
|
+
* rebuilding from the original operations against the current server state.
|
|
168
|
+
*
|
|
169
|
+
* @param originalPendingTxs - The original pending transactions before any rebasing
|
|
170
|
+
* @param rejectedTxId - ID of the rejected transaction
|
|
171
|
+
* @param serverTxsSinceOriginal - Server transactions that have arrived since original
|
|
172
|
+
* @param primitive - The root primitive to use for transformation
|
|
173
|
+
*/
|
|
174
|
+
const rebaseAfterRejectionWithPrimitive = (originalPendingTxs, rejectedTxId, serverTxsSinceOriginal, primitive) => {
|
|
175
|
+
let result = [...originalPendingTxs.filter((tx) => tx.id !== rejectedTxId)];
|
|
176
|
+
for (const serverTx of serverTxsSinceOriginal) result = rebasePendingTransactionsWithPrimitive(result, serverTx, primitive);
|
|
177
|
+
return result;
|
|
178
|
+
};
|
|
179
|
+
/**
|
|
180
|
+
* Rebases pending transactions after a rejection.
|
|
181
|
+
*
|
|
182
|
+
* When a transaction is rejected, we need to re-transform remaining pending
|
|
183
|
+
* transactions as if the rejected transaction never happened. This is done by
|
|
184
|
+
* rebuilding from the original operations against the current server state.
|
|
185
|
+
*
|
|
186
|
+
* @param originalPendingTxs - The original pending transactions before any rebasing
|
|
187
|
+
* @param rejectedTxId - ID of the rejected transaction
|
|
188
|
+
* @param serverTxsSinceOriginal - Server transactions that have arrived since original
|
|
189
|
+
*/
|
|
190
|
+
const rebaseAfterRejection = (originalPendingTxs, rejectedTxId, serverTxsSinceOriginal) => {
|
|
191
|
+
let result = [...originalPendingTxs.filter((tx) => tx.id !== rejectedTxId)];
|
|
192
|
+
for (const serverTx of serverTxsSinceOriginal) result = rebasePendingTransactions(result, serverTx);
|
|
193
|
+
return result;
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
//#endregion
|
|
197
|
+
export { Rebase_exports, rebaseAfterRejectionWithPrimitive, transformTransactionWithPrimitive };
|
|
198
|
+
//# sourceMappingURL=Rebase.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Rebase.mjs","names":["OperationPath.pathsOverlap","OperationPath.pathsEqual","OperationPath.isPrefix","transformedOps: Operation.Operation<any, any, any>[]","currentOp: Operation.Operation<any, any, any> | null"],"sources":["../../src/client/Rebase.ts"],"sourcesContent":["import type * as Operation from \"../Operation\";\nimport type * as Transaction from \"../Transaction\";\nimport type * as Primitive from \"../Primitive\";\nimport * as OperationPath from \"../OperationPath\";\nimport * as Transform from \"../Transform\";\n\n// =============================================================================\n// Re-export Transform types from mimic for backwards compatibility\n// =============================================================================\n\nexport type TransformResult = Transform.TransformResult;\n\n// =============================================================================\n// Operation Transformation\n// =============================================================================\n\n/**\n * Transforms a client operation against a server operation using a primitive.\n * \n * This delegates to the primitive's transformOperation method, which handles\n * type-specific conflict resolution.\n * \n * @param clientOp - The client's operation to transform\n * @param serverOp - The server's operation that has already been applied\n * @param primitive - The root primitive to use for transformation\n * @returns TransformResult indicating how the client operation should be handled\n */\nexport const transformOperationWithPrimitive = (\n clientOp: Operation.Operation<any, any, any>,\n serverOp: Operation.Operation<any, any, any>,\n primitive: Primitive.AnyPrimitive\n): TransformResult => {\n return primitive._internal.transformOperation(clientOp, serverOp);\n};\n\n/**\n * Transforms a client operation against a server operation.\n *\n * This is a standalone implementation for cases where the primitive is not available.\n * For schema-aware transformation, use transformOperationWithPrimitive instead.\n *\n * The key principle: client ops \"shadow\" server ops for the same path,\n * meaning if both touch the same field, the client's intention wins\n * (since it was made with knowledge of the server state at that time).\n */\nexport const transformOperation = (\n clientOp: Operation.Operation<any, any, any>,\n serverOp: Operation.Operation<any, any, any>\n): TransformResult => {\n const clientPath = clientOp.path;\n const serverPath = serverOp.path;\n\n // If paths don't overlap at all, no transformation needed\n if (!OperationPath.pathsOverlap(clientPath, serverPath)) {\n return { type: \"transformed\", operation: clientOp };\n }\n\n // Handle array operations specially\n if (serverOp.kind === \"array.remove\") {\n // If server removed an array element that client is operating on\n const removedId = (serverOp.payload as { id: string }).id;\n const clientTokens = clientPath.toTokens().filter((t: string) => t !== \"\");\n const serverTokens = serverPath.toTokens().filter((t: string) => t !== \"\");\n\n // Check if client is operating on the removed element or its children\n if (clientTokens.length > serverTokens.length) {\n const elementId = clientTokens[serverTokens.length];\n if (elementId === removedId) {\n // Client operation targets a removed element - becomes noop\n return { type: \"noop\" };\n }\n }\n }\n\n if (serverOp.kind === \"array.insert\" && clientOp.kind === \"array.insert\") {\n // Both inserting into same array - positions are independent (fractional indexing)\n // No transformation needed as fractional indices handle ordering\n return { type: \"transformed\", operation: clientOp };\n }\n\n if (serverOp.kind === \"array.move\" && clientOp.kind === \"array.move\") {\n // Both moving elements - if same element, client wins (last-write-wins for position)\n const serverMoveId = (serverOp.payload as { id: string }).id;\n const clientMoveId = (clientOp.payload as { id: string }).id;\n\n if (serverMoveId === clientMoveId) {\n // Client's move supersedes server's move\n return { type: \"transformed\", operation: clientOp };\n }\n // Different elements - no conflict\n return { type: \"transformed\", operation: clientOp };\n }\n\n // For set operations on the same exact path: client wins (last-write-wins)\n if (OperationPath.pathsEqual(clientPath, serverPath)) {\n // Both operations target the same path\n // Client operation was made with intent to set this value,\n // so it should override the server's change\n return { type: \"transformed\", operation: clientOp };\n }\n\n // If server set a parent path, client's child operation might be invalid\n if (OperationPath.isPrefix(serverPath, clientPath)) {\n const serverKind = serverOp.kind;\n if (\n serverKind === \"struct.set\" ||\n serverKind === \"array.set\" ||\n serverKind === \"union.set\"\n ) {\n // Server replaced the entire parent - client's child op may be invalid\n // However, for optimistic updates, we let the client op proceed\n // and the server will validate/reject if needed\n return { type: \"transformed\", operation: clientOp };\n }\n }\n\n // Default: no transformation needed, client op proceeds as-is\n return { type: \"transformed\", operation: clientOp };\n};\n\n/**\n * Transforms all operations in a client transaction against a server transaction.\n * Uses the primitive's transformOperation for schema-aware transformation.\n */\nexport const transformTransactionWithPrimitive = (\n clientTx: Transaction.Transaction,\n serverTx: Transaction.Transaction,\n primitive: Primitive.AnyPrimitive\n): Transaction.Transaction => {\n const transformedOps: Operation.Operation<any, any, any>[] = [];\n\n for (const clientOp of clientTx.ops) {\n let currentOp: Operation.Operation<any, any, any> | null = clientOp;\n\n // Transform against each server operation\n for (const serverOp of serverTx.ops) {\n if (currentOp === null) break;\n\n const result = transformOperationWithPrimitive(currentOp, serverOp, primitive);\n\n switch (result.type) {\n case \"transformed\":\n currentOp = result.operation;\n break;\n case \"noop\":\n currentOp = null;\n break;\n case \"conflict\":\n // For now, treat conflicts as the client op proceeding\n // Server will ultimately validate\n break;\n }\n }\n\n if (currentOp !== null) {\n transformedOps.push(currentOp);\n }\n }\n\n // Return a new transaction with the same ID but transformed ops\n return {\n id: clientTx.id,\n ops: transformedOps,\n timestamp: clientTx.timestamp,\n };\n};\n\n/**\n * Transforms all operations in a client transaction against a server transaction.\n * This is a standalone version that doesn't require a primitive.\n */\nexport const transformTransaction = (\n clientTx: Transaction.Transaction,\n serverTx: Transaction.Transaction\n): Transaction.Transaction => {\n const transformedOps: Operation.Operation<any, any, any>[] = [];\n\n for (const clientOp of clientTx.ops) {\n let currentOp: Operation.Operation<any, any, any> | null = clientOp;\n\n // Transform against each server operation\n for (const serverOp of serverTx.ops) {\n if (currentOp === null) break;\n\n const result = transformOperation(currentOp, serverOp);\n\n switch (result.type) {\n case \"transformed\":\n currentOp = result.operation;\n break;\n case \"noop\":\n currentOp = null;\n break;\n case \"conflict\":\n // For now, treat conflicts as the client op proceeding\n // Server will ultimately validate\n break;\n }\n }\n\n if (currentOp !== null) {\n transformedOps.push(currentOp);\n }\n }\n\n // Return a new transaction with the same ID but transformed ops\n return {\n id: clientTx.id,\n ops: transformedOps,\n timestamp: clientTx.timestamp,\n };\n};\n\n/**\n * Rebases a list of pending transactions against a server transaction using a primitive.\n *\n * This is called when a server transaction arrives that is NOT one of our pending\n * transactions. We need to transform all pending transactions to work correctly\n * on top of the new server state.\n */\nexport const rebasePendingTransactionsWithPrimitive = (\n pendingTxs: ReadonlyArray<Transaction.Transaction>,\n serverTx: Transaction.Transaction,\n primitive: Primitive.AnyPrimitive\n): Transaction.Transaction[] => {\n return pendingTxs.map((pendingTx) =>\n transformTransactionWithPrimitive(pendingTx, serverTx, primitive)\n );\n};\n\n/**\n * Rebases a list of pending transactions against a server transaction.\n *\n * This is called when a server transaction arrives that is NOT one of our pending\n * transactions. We need to transform all pending transactions to work correctly\n * on top of the new server state.\n */\nexport const rebasePendingTransactions = (\n pendingTxs: ReadonlyArray<Transaction.Transaction>,\n serverTx: Transaction.Transaction\n): Transaction.Transaction[] => {\n return pendingTxs.map((pendingTx) =>\n transformTransaction(pendingTx, serverTx)\n );\n};\n\n/**\n * Rebases pending transactions after a rejection using a primitive.\n *\n * When a transaction is rejected, we need to re-transform remaining pending\n * transactions as if the rejected transaction never happened. This is done by\n * rebuilding from the original operations against the current server state.\n *\n * @param originalPendingTxs - The original pending transactions before any rebasing\n * @param rejectedTxId - ID of the rejected transaction\n * @param serverTxsSinceOriginal - Server transactions that have arrived since original\n * @param primitive - The root primitive to use for transformation\n */\nexport const rebaseAfterRejectionWithPrimitive = (\n originalPendingTxs: ReadonlyArray<Transaction.Transaction>,\n rejectedTxId: string,\n serverTxsSinceOriginal: ReadonlyArray<Transaction.Transaction>,\n primitive: Primitive.AnyPrimitive\n): Transaction.Transaction[] => {\n // Filter out the rejected transaction\n const remainingOriginals = originalPendingTxs.filter(\n (tx) => tx.id !== rejectedTxId\n );\n\n // Re-transform each remaining transaction against all server transactions\n let result = [...remainingOriginals];\n\n for (const serverTx of serverTxsSinceOriginal) {\n result = rebasePendingTransactionsWithPrimitive(result, serverTx, primitive);\n }\n\n return result;\n};\n\n/**\n * Rebases pending transactions after a rejection.\n *\n * When a transaction is rejected, we need to re-transform remaining pending\n * transactions as if the rejected transaction never happened. This is done by\n * rebuilding from the original operations against the current server state.\n *\n * @param originalPendingTxs - The original pending transactions before any rebasing\n * @param rejectedTxId - ID of the rejected transaction\n * @param serverTxsSinceOriginal - Server transactions that have arrived since original\n */\nexport const rebaseAfterRejection = (\n originalPendingTxs: ReadonlyArray<Transaction.Transaction>,\n rejectedTxId: string,\n serverTxsSinceOriginal: ReadonlyArray<Transaction.Transaction>\n): Transaction.Transaction[] => {\n // Filter out the rejected transaction\n const remainingOriginals = originalPendingTxs.filter(\n (tx) => tx.id !== rejectedTxId\n );\n\n // Re-transform each remaining transaction against all server transactions\n let result = [...remainingOriginals];\n\n for (const serverTx of serverTxsSinceOriginal) {\n result = rebasePendingTransactions(result, serverTx);\n }\n\n return result;\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
|
|
2
|
+
|
|
3
|
+
//#region src/client/StateMonitor.ts
|
|
4
|
+
var StateMonitor_exports = /* @__PURE__ */ require_rolldown_runtime.__export({
|
|
5
|
+
determineRecoveryAction: () => determineRecoveryAction,
|
|
6
|
+
make: () => make
|
|
7
|
+
});
|
|
8
|
+
/**
|
|
9
|
+
* Creates a new StateMonitor.
|
|
10
|
+
*/
|
|
11
|
+
const make = (options = {}) => {
|
|
12
|
+
const { onEvent, healthCheckInterval = 5e3, stalePendingThreshold = 1e4, maxVersionGap = 10 } = options;
|
|
13
|
+
let _expectedVersion = 0;
|
|
14
|
+
let _pendingMap = /* @__PURE__ */ new Map();
|
|
15
|
+
let _isRecovering = false;
|
|
16
|
+
let _healthCheckHandle = null;
|
|
17
|
+
/**
|
|
18
|
+
* Emits an event if handler is provided.
|
|
19
|
+
*/
|
|
20
|
+
const emit = (event) => {
|
|
21
|
+
onEvent === null || onEvent === void 0 || onEvent(event);
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Checks if there's a version gap indicating drift.
|
|
25
|
+
*/
|
|
26
|
+
const checkVersionGap = (receivedVersion) => {
|
|
27
|
+
const expectedNext = _expectedVersion + 1;
|
|
28
|
+
if (receivedVersion < expectedNext) return true;
|
|
29
|
+
if (receivedVersion > expectedNext + maxVersionGap) {
|
|
30
|
+
emit({
|
|
31
|
+
type: "drift_detected",
|
|
32
|
+
expectedVersion: expectedNext,
|
|
33
|
+
receivedVersion
|
|
34
|
+
});
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
return true;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Runs a health check.
|
|
41
|
+
*/
|
|
42
|
+
const runHealthCheck = () => {
|
|
43
|
+
const now = Date.now();
|
|
44
|
+
let oldestPendingMs = null;
|
|
45
|
+
for (const [id, info] of _pendingMap) {
|
|
46
|
+
const elapsed = now - info.sentAt;
|
|
47
|
+
if (oldestPendingMs === null || elapsed > oldestPendingMs) oldestPendingMs = elapsed;
|
|
48
|
+
if (elapsed > stalePendingThreshold) emit({
|
|
49
|
+
type: "pending_timeout",
|
|
50
|
+
transactionId: id,
|
|
51
|
+
elapsedMs: elapsed
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
emit({
|
|
55
|
+
type: "health_check",
|
|
56
|
+
pendingCount: _pendingMap.size,
|
|
57
|
+
oldestPendingMs
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
return {
|
|
61
|
+
onServerVersion: (version) => {
|
|
62
|
+
const isValid = checkVersionGap(version);
|
|
63
|
+
if (isValid) _expectedVersion = Math.max(_expectedVersion, version);
|
|
64
|
+
return isValid;
|
|
65
|
+
},
|
|
66
|
+
trackPending: (info) => {
|
|
67
|
+
_pendingMap.set(info.id, info);
|
|
68
|
+
},
|
|
69
|
+
untrackPending: (id) => {
|
|
70
|
+
_pendingMap.delete(id);
|
|
71
|
+
},
|
|
72
|
+
getStalePending: () => {
|
|
73
|
+
const now = Date.now();
|
|
74
|
+
const stale = [];
|
|
75
|
+
for (const info of _pendingMap.values()) if (now - info.sentAt > stalePendingThreshold) stale.push(info);
|
|
76
|
+
return stale;
|
|
77
|
+
},
|
|
78
|
+
getStatus: () => {
|
|
79
|
+
const now = Date.now();
|
|
80
|
+
let oldestPendingMs = null;
|
|
81
|
+
for (const info of _pendingMap.values()) {
|
|
82
|
+
const elapsed = now - info.sentAt;
|
|
83
|
+
if (oldestPendingMs === null || elapsed > oldestPendingMs) oldestPendingMs = elapsed;
|
|
84
|
+
}
|
|
85
|
+
const isHealthy = !_isRecovering && (oldestPendingMs === null || oldestPendingMs < stalePendingThreshold * 2);
|
|
86
|
+
return {
|
|
87
|
+
expectedVersion: _expectedVersion,
|
|
88
|
+
pendingCount: _pendingMap.size,
|
|
89
|
+
oldestPendingMs,
|
|
90
|
+
isHealthy,
|
|
91
|
+
isRecovering: _isRecovering
|
|
92
|
+
};
|
|
93
|
+
},
|
|
94
|
+
start: () => {
|
|
95
|
+
if (_healthCheckHandle !== null) return;
|
|
96
|
+
_healthCheckHandle = setInterval(runHealthCheck, healthCheckInterval);
|
|
97
|
+
},
|
|
98
|
+
stop: () => {
|
|
99
|
+
if (_healthCheckHandle !== null) {
|
|
100
|
+
clearInterval(_healthCheckHandle);
|
|
101
|
+
_healthCheckHandle = null;
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
reset: (newVersion) => {
|
|
105
|
+
_expectedVersion = newVersion;
|
|
106
|
+
_pendingMap.clear();
|
|
107
|
+
_isRecovering = false;
|
|
108
|
+
emit({
|
|
109
|
+
type: "recovery_completed",
|
|
110
|
+
version: newVersion
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* Determines the appropriate recovery action based on current state.
|
|
117
|
+
*/
|
|
118
|
+
const determineRecoveryAction = (status, stalePending) => {
|
|
119
|
+
if (!status.isHealthy || stalePending.length > 3) return { type: "request_snapshot" };
|
|
120
|
+
if (stalePending.length > 0) return {
|
|
121
|
+
type: "drop_pending",
|
|
122
|
+
transactionIds: stalePending.map((p) => p.id)
|
|
123
|
+
};
|
|
124
|
+
return { type: "request_snapshot" };
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
//#endregion
|
|
128
|
+
Object.defineProperty(exports, 'StateMonitor_exports', {
|
|
129
|
+
enumerable: true,
|
|
130
|
+
get: function () {
|
|
131
|
+
return StateMonitor_exports;
|
|
132
|
+
}
|
|
133
|
+
});
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
declare namespace StateMonitor_d_exports {
|
|
2
|
+
export { PendingInfo, RecoveryAction, StateMonitor, StateMonitorEvent, StateMonitorEventHandler, StateMonitorOptions, StateMonitorStatus, determineRecoveryAction, make };
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Events emitted by the state monitor.
|
|
6
|
+
*/
|
|
7
|
+
type StateMonitorEvent = {
|
|
8
|
+
type: "drift_detected";
|
|
9
|
+
expectedVersion: number;
|
|
10
|
+
receivedVersion: number;
|
|
11
|
+
} | {
|
|
12
|
+
type: "recovery_started";
|
|
13
|
+
} | {
|
|
14
|
+
type: "recovery_completed";
|
|
15
|
+
version: number;
|
|
16
|
+
} | {
|
|
17
|
+
type: "recovery_failed";
|
|
18
|
+
error: Error;
|
|
19
|
+
} | {
|
|
20
|
+
type: "pending_timeout";
|
|
21
|
+
transactionId: string;
|
|
22
|
+
elapsedMs: number;
|
|
23
|
+
} | {
|
|
24
|
+
type: "health_check";
|
|
25
|
+
pendingCount: number;
|
|
26
|
+
oldestPendingMs: number | null;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Handler for state monitor events.
|
|
30
|
+
*/
|
|
31
|
+
type StateMonitorEventHandler = (event: StateMonitorEvent) => void;
|
|
32
|
+
/**
|
|
33
|
+
* Options for creating a StateMonitor.
|
|
34
|
+
*/
|
|
35
|
+
interface StateMonitorOptions {
|
|
36
|
+
/** Handler for monitor events */
|
|
37
|
+
readonly onEvent?: StateMonitorEventHandler;
|
|
38
|
+
/** Interval for health checks in ms (default: 5000) */
|
|
39
|
+
readonly healthCheckInterval?: number;
|
|
40
|
+
/** Threshold for considering a pending transaction "stale" in ms (default: 10000) */
|
|
41
|
+
readonly stalePendingThreshold?: number;
|
|
42
|
+
/** Maximum allowed version gap before triggering recovery (default: 10) */
|
|
43
|
+
readonly maxVersionGap?: number;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Pending transaction info for monitoring.
|
|
47
|
+
*/
|
|
48
|
+
interface PendingInfo {
|
|
49
|
+
readonly id: string;
|
|
50
|
+
readonly sentAt: number;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* A StateMonitor watches for state drift and triggers recovery.
|
|
54
|
+
*/
|
|
55
|
+
interface StateMonitor {
|
|
56
|
+
/**
|
|
57
|
+
* Called when a server transaction is received.
|
|
58
|
+
* Returns true if the version is valid, false if drift is detected.
|
|
59
|
+
*/
|
|
60
|
+
readonly onServerVersion: (version: number) => boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Called when a pending transaction is added.
|
|
63
|
+
*/
|
|
64
|
+
readonly trackPending: (info: PendingInfo) => void;
|
|
65
|
+
/**
|
|
66
|
+
* Called when a pending transaction is confirmed or rejected.
|
|
67
|
+
*/
|
|
68
|
+
readonly untrackPending: (id: string) => void;
|
|
69
|
+
/**
|
|
70
|
+
* Returns pending transactions that have exceeded the stale threshold.
|
|
71
|
+
*/
|
|
72
|
+
readonly getStalePending: () => PendingInfo[];
|
|
73
|
+
/**
|
|
74
|
+
* Returns current monitoring status.
|
|
75
|
+
*/
|
|
76
|
+
readonly getStatus: () => StateMonitorStatus;
|
|
77
|
+
/**
|
|
78
|
+
* Starts the health check loop.
|
|
79
|
+
*/
|
|
80
|
+
readonly start: () => void;
|
|
81
|
+
/**
|
|
82
|
+
* Stops the health check loop.
|
|
83
|
+
*/
|
|
84
|
+
readonly stop: () => void;
|
|
85
|
+
/**
|
|
86
|
+
* Resets the monitor state (called after recovery).
|
|
87
|
+
*/
|
|
88
|
+
readonly reset: (newVersion: number) => void;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Current monitoring status.
|
|
92
|
+
*/
|
|
93
|
+
interface StateMonitorStatus {
|
|
94
|
+
readonly expectedVersion: number;
|
|
95
|
+
readonly pendingCount: number;
|
|
96
|
+
readonly oldestPendingMs: number | null;
|
|
97
|
+
readonly isHealthy: boolean;
|
|
98
|
+
readonly isRecovering: boolean;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Creates a new StateMonitor.
|
|
102
|
+
*/
|
|
103
|
+
declare const make: (options?: StateMonitorOptions) => StateMonitor;
|
|
104
|
+
/**
|
|
105
|
+
* Recovery actions that can be taken.
|
|
106
|
+
*/
|
|
107
|
+
type RecoveryAction = {
|
|
108
|
+
type: "request_snapshot";
|
|
109
|
+
} | {
|
|
110
|
+
type: "retry_pending";
|
|
111
|
+
transactionIds: string[];
|
|
112
|
+
} | {
|
|
113
|
+
type: "drop_pending";
|
|
114
|
+
transactionIds: string[];
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* Determines the appropriate recovery action based on current state.
|
|
118
|
+
*/
|
|
119
|
+
declare const determineRecoveryAction: (status: StateMonitorStatus, stalePending: PendingInfo[]) => RecoveryAction;
|
|
120
|
+
//#endregion
|
|
121
|
+
export { StateMonitor_d_exports };
|
|
122
|
+
//# sourceMappingURL=StateMonitor.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StateMonitor.d.cts","names":[],"sources":["../../src/client/StateMonitor.ts"],"sourcesContent":[],"mappings":";;;;;;KASY,iBAAA;;;;;;;;;;;EAAA,KAAA,EAI0B,KAJ1B;AAWZ,CAAA,GAAY;EAKK,IAAA,EAAA,iBAAmB;EAcnB,aAAA,EAAW,MAAA;EAQX,SAAA,EAAA,MAAY;CAUG,GAAA;EAUE,IAAA,EAAA,cAAA;EAKN,YAAA,EAAA,MAAA;EAAkB,eAAA,EAAA,MAAA,GAAA,IAAA;AAqB9C,CAAA;AAeA;AA2KA;AAQA;AACU,KA5QE,wBAAA,GA4QF,CAAA,KAAA,EA5QqC,iBA4QrC,EAAA,GAAA,IAAA;;;;UAvQO,mBAAA;;qBAEI;;;;;;;;;;;UAYJ,WAAA;;;;;;;UAQA,YAAA;;;;;;;;;gCAUe;;;;;;;;kCAUE;;;;4BAKN;;;;;;;;;;;;;;;;;UAqBX,kBAAA;;;;;;;;;;cAeJ,iBAAiB,wBAA2B;;;;KA2K7C,cAAA;;;;;;;;;;;;cAQC,kCACH,kCACM,kBACb"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
declare namespace StateMonitor_d_exports {
|
|
2
|
+
export { PendingInfo, RecoveryAction, StateMonitor, StateMonitorEvent, StateMonitorEventHandler, StateMonitorOptions, StateMonitorStatus, determineRecoveryAction, make };
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Events emitted by the state monitor.
|
|
6
|
+
*/
|
|
7
|
+
type StateMonitorEvent = {
|
|
8
|
+
type: "drift_detected";
|
|
9
|
+
expectedVersion: number;
|
|
10
|
+
receivedVersion: number;
|
|
11
|
+
} | {
|
|
12
|
+
type: "recovery_started";
|
|
13
|
+
} | {
|
|
14
|
+
type: "recovery_completed";
|
|
15
|
+
version: number;
|
|
16
|
+
} | {
|
|
17
|
+
type: "recovery_failed";
|
|
18
|
+
error: Error;
|
|
19
|
+
} | {
|
|
20
|
+
type: "pending_timeout";
|
|
21
|
+
transactionId: string;
|
|
22
|
+
elapsedMs: number;
|
|
23
|
+
} | {
|
|
24
|
+
type: "health_check";
|
|
25
|
+
pendingCount: number;
|
|
26
|
+
oldestPendingMs: number | null;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Handler for state monitor events.
|
|
30
|
+
*/
|
|
31
|
+
type StateMonitorEventHandler = (event: StateMonitorEvent) => void;
|
|
32
|
+
/**
|
|
33
|
+
* Options for creating a StateMonitor.
|
|
34
|
+
*/
|
|
35
|
+
interface StateMonitorOptions {
|
|
36
|
+
/** Handler for monitor events */
|
|
37
|
+
readonly onEvent?: StateMonitorEventHandler;
|
|
38
|
+
/** Interval for health checks in ms (default: 5000) */
|
|
39
|
+
readonly healthCheckInterval?: number;
|
|
40
|
+
/** Threshold for considering a pending transaction "stale" in ms (default: 10000) */
|
|
41
|
+
readonly stalePendingThreshold?: number;
|
|
42
|
+
/** Maximum allowed version gap before triggering recovery (default: 10) */
|
|
43
|
+
readonly maxVersionGap?: number;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Pending transaction info for monitoring.
|
|
47
|
+
*/
|
|
48
|
+
interface PendingInfo {
|
|
49
|
+
readonly id: string;
|
|
50
|
+
readonly sentAt: number;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* A StateMonitor watches for state drift and triggers recovery.
|
|
54
|
+
*/
|
|
55
|
+
interface StateMonitor {
|
|
56
|
+
/**
|
|
57
|
+
* Called when a server transaction is received.
|
|
58
|
+
* Returns true if the version is valid, false if drift is detected.
|
|
59
|
+
*/
|
|
60
|
+
readonly onServerVersion: (version: number) => boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Called when a pending transaction is added.
|
|
63
|
+
*/
|
|
64
|
+
readonly trackPending: (info: PendingInfo) => void;
|
|
65
|
+
/**
|
|
66
|
+
* Called when a pending transaction is confirmed or rejected.
|
|
67
|
+
*/
|
|
68
|
+
readonly untrackPending: (id: string) => void;
|
|
69
|
+
/**
|
|
70
|
+
* Returns pending transactions that have exceeded the stale threshold.
|
|
71
|
+
*/
|
|
72
|
+
readonly getStalePending: () => PendingInfo[];
|
|
73
|
+
/**
|
|
74
|
+
* Returns current monitoring status.
|
|
75
|
+
*/
|
|
76
|
+
readonly getStatus: () => StateMonitorStatus;
|
|
77
|
+
/**
|
|
78
|
+
* Starts the health check loop.
|
|
79
|
+
*/
|
|
80
|
+
readonly start: () => void;
|
|
81
|
+
/**
|
|
82
|
+
* Stops the health check loop.
|
|
83
|
+
*/
|
|
84
|
+
readonly stop: () => void;
|
|
85
|
+
/**
|
|
86
|
+
* Resets the monitor state (called after recovery).
|
|
87
|
+
*/
|
|
88
|
+
readonly reset: (newVersion: number) => void;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Current monitoring status.
|
|
92
|
+
*/
|
|
93
|
+
interface StateMonitorStatus {
|
|
94
|
+
readonly expectedVersion: number;
|
|
95
|
+
readonly pendingCount: number;
|
|
96
|
+
readonly oldestPendingMs: number | null;
|
|
97
|
+
readonly isHealthy: boolean;
|
|
98
|
+
readonly isRecovering: boolean;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Creates a new StateMonitor.
|
|
102
|
+
*/
|
|
103
|
+
declare const make: (options?: StateMonitorOptions) => StateMonitor;
|
|
104
|
+
/**
|
|
105
|
+
* Recovery actions that can be taken.
|
|
106
|
+
*/
|
|
107
|
+
type RecoveryAction = {
|
|
108
|
+
type: "request_snapshot";
|
|
109
|
+
} | {
|
|
110
|
+
type: "retry_pending";
|
|
111
|
+
transactionIds: string[];
|
|
112
|
+
} | {
|
|
113
|
+
type: "drop_pending";
|
|
114
|
+
transactionIds: string[];
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* Determines the appropriate recovery action based on current state.
|
|
118
|
+
*/
|
|
119
|
+
declare const determineRecoveryAction: (status: StateMonitorStatus, stalePending: PendingInfo[]) => RecoveryAction;
|
|
120
|
+
//#endregion
|
|
121
|
+
export { StateMonitor_d_exports };
|
|
122
|
+
//# sourceMappingURL=StateMonitor.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StateMonitor.d.mts","names":[],"sources":["../../src/client/StateMonitor.ts"],"sourcesContent":[],"mappings":";;;;;;KASY,iBAAA;;;;;;;;;;;EAAA,KAAA,EAI0B,KAJ1B;AAWZ,CAAA,GAAY;EAKK,IAAA,EAAA,iBAAmB;EAcnB,aAAA,EAAW,MAAA;EAQX,SAAA,EAAA,MAAY;CAUG,GAAA;EAUE,IAAA,EAAA,cAAA;EAKN,YAAA,EAAA,MAAA;EAAkB,eAAA,EAAA,MAAA,GAAA,IAAA;AAqB9C,CAAA;AAeA;AA2KA;AAQA;AACU,KA5QE,wBAAA,GA4QF,CAAA,KAAA,EA5QqC,iBA4QrC,EAAA,GAAA,IAAA;;;;UAvQO,mBAAA;;qBAEI;;;;;;;;;;;UAYJ,WAAA;;;;;;;UAQA,YAAA;;;;;;;;;gCAUe;;;;;;;;kCAUE;;;;4BAKN;;;;;;;;;;;;;;;;;UAqBX,kBAAA;;;;;;;;;;cAeJ,iBAAiB,wBAA2B;;;;KA2K7C,cAAA;;;;;;;;;;;;cAQC,kCACH,kCACM,kBACb"}
|