@git-stunts/git-warp 10.1.1
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/LICENSE +201 -0
- package/NOTICE +16 -0
- package/README.md +480 -0
- package/SECURITY.md +30 -0
- package/bin/git-warp +24 -0
- package/bin/warp-graph.js +1574 -0
- package/index.d.ts +2366 -0
- package/index.js +180 -0
- package/package.json +129 -0
- package/scripts/install-git-warp.sh +258 -0
- package/scripts/uninstall-git-warp.sh +139 -0
- package/src/domain/WarpGraph.js +3157 -0
- package/src/domain/crdt/Dot.js +160 -0
- package/src/domain/crdt/LWW.js +154 -0
- package/src/domain/crdt/ORSet.js +371 -0
- package/src/domain/crdt/VersionVector.js +222 -0
- package/src/domain/entities/GraphNode.js +60 -0
- package/src/domain/errors/EmptyMessageError.js +47 -0
- package/src/domain/errors/ForkError.js +30 -0
- package/src/domain/errors/IndexError.js +23 -0
- package/src/domain/errors/OperationAbortedError.js +22 -0
- package/src/domain/errors/QueryError.js +39 -0
- package/src/domain/errors/SchemaUnsupportedError.js +17 -0
- package/src/domain/errors/ShardCorruptionError.js +56 -0
- package/src/domain/errors/ShardLoadError.js +57 -0
- package/src/domain/errors/ShardValidationError.js +61 -0
- package/src/domain/errors/StorageError.js +57 -0
- package/src/domain/errors/SyncError.js +30 -0
- package/src/domain/errors/TraversalError.js +23 -0
- package/src/domain/errors/WarpError.js +31 -0
- package/src/domain/errors/WormholeError.js +28 -0
- package/src/domain/errors/WriterError.js +39 -0
- package/src/domain/errors/index.js +21 -0
- package/src/domain/services/AnchorMessageCodec.js +99 -0
- package/src/domain/services/BitmapIndexBuilder.js +225 -0
- package/src/domain/services/BitmapIndexReader.js +435 -0
- package/src/domain/services/BoundaryTransitionRecord.js +463 -0
- package/src/domain/services/CheckpointMessageCodec.js +147 -0
- package/src/domain/services/CheckpointSerializerV5.js +281 -0
- package/src/domain/services/CheckpointService.js +384 -0
- package/src/domain/services/CommitDagTraversalService.js +156 -0
- package/src/domain/services/DagPathFinding.js +712 -0
- package/src/domain/services/DagTopology.js +239 -0
- package/src/domain/services/DagTraversal.js +245 -0
- package/src/domain/services/Frontier.js +108 -0
- package/src/domain/services/GCMetrics.js +101 -0
- package/src/domain/services/GCPolicy.js +122 -0
- package/src/domain/services/GitLogParser.js +205 -0
- package/src/domain/services/HealthCheckService.js +246 -0
- package/src/domain/services/HookInstaller.js +326 -0
- package/src/domain/services/HttpSyncServer.js +262 -0
- package/src/domain/services/IndexRebuildService.js +426 -0
- package/src/domain/services/IndexStalenessChecker.js +103 -0
- package/src/domain/services/JoinReducer.js +582 -0
- package/src/domain/services/KeyCodec.js +113 -0
- package/src/domain/services/LegacyAnchorDetector.js +67 -0
- package/src/domain/services/LogicalTraversal.js +351 -0
- package/src/domain/services/MessageCodecInternal.js +132 -0
- package/src/domain/services/MessageSchemaDetector.js +145 -0
- package/src/domain/services/MigrationService.js +55 -0
- package/src/domain/services/ObserverView.js +265 -0
- package/src/domain/services/PatchBuilderV2.js +669 -0
- package/src/domain/services/PatchMessageCodec.js +140 -0
- package/src/domain/services/ProvenanceIndex.js +337 -0
- package/src/domain/services/ProvenancePayload.js +242 -0
- package/src/domain/services/QueryBuilder.js +835 -0
- package/src/domain/services/StateDiff.js +300 -0
- package/src/domain/services/StateSerializerV5.js +156 -0
- package/src/domain/services/StreamingBitmapIndexBuilder.js +709 -0
- package/src/domain/services/SyncProtocol.js +593 -0
- package/src/domain/services/TemporalQuery.js +201 -0
- package/src/domain/services/TranslationCost.js +221 -0
- package/src/domain/services/TraversalService.js +8 -0
- package/src/domain/services/WarpMessageCodec.js +29 -0
- package/src/domain/services/WarpStateIndexBuilder.js +127 -0
- package/src/domain/services/WormholeService.js +353 -0
- package/src/domain/types/TickReceipt.js +285 -0
- package/src/domain/types/WarpTypes.js +209 -0
- package/src/domain/types/WarpTypesV2.js +200 -0
- package/src/domain/utils/CachedValue.js +140 -0
- package/src/domain/utils/EventId.js +89 -0
- package/src/domain/utils/LRUCache.js +112 -0
- package/src/domain/utils/MinHeap.js +114 -0
- package/src/domain/utils/RefLayout.js +280 -0
- package/src/domain/utils/WriterId.js +205 -0
- package/src/domain/utils/cancellation.js +33 -0
- package/src/domain/utils/canonicalStringify.js +42 -0
- package/src/domain/utils/defaultClock.js +20 -0
- package/src/domain/utils/defaultCodec.js +51 -0
- package/src/domain/utils/nullLogger.js +21 -0
- package/src/domain/utils/roaring.js +181 -0
- package/src/domain/utils/shardVersion.js +9 -0
- package/src/domain/warp/PatchSession.js +217 -0
- package/src/domain/warp/Writer.js +181 -0
- package/src/hooks/post-merge.sh +60 -0
- package/src/infrastructure/adapters/BunHttpAdapter.js +225 -0
- package/src/infrastructure/adapters/ClockAdapter.js +57 -0
- package/src/infrastructure/adapters/ConsoleLogger.js +150 -0
- package/src/infrastructure/adapters/DenoHttpAdapter.js +230 -0
- package/src/infrastructure/adapters/GitGraphAdapter.js +787 -0
- package/src/infrastructure/adapters/GlobalClockAdapter.js +5 -0
- package/src/infrastructure/adapters/NoOpLogger.js +62 -0
- package/src/infrastructure/adapters/NodeCryptoAdapter.js +32 -0
- package/src/infrastructure/adapters/NodeHttpAdapter.js +98 -0
- package/src/infrastructure/adapters/PerformanceClockAdapter.js +5 -0
- package/src/infrastructure/adapters/WebCryptoAdapter.js +121 -0
- package/src/infrastructure/codecs/CborCodec.js +384 -0
- package/src/ports/BlobPort.js +30 -0
- package/src/ports/ClockPort.js +25 -0
- package/src/ports/CodecPort.js +25 -0
- package/src/ports/CommitPort.js +114 -0
- package/src/ports/ConfigPort.js +31 -0
- package/src/ports/CryptoPort.js +38 -0
- package/src/ports/GraphPersistencePort.js +57 -0
- package/src/ports/HttpServerPort.js +25 -0
- package/src/ports/IndexStoragePort.js +39 -0
- package/src/ports/LoggerPort.js +68 -0
- package/src/ports/RefPort.js +51 -0
- package/src/ports/TreePort.js +51 -0
- package/src/visualization/index.js +26 -0
- package/src/visualization/layouts/converters.js +75 -0
- package/src/visualization/layouts/elkAdapter.js +86 -0
- package/src/visualization/layouts/elkLayout.js +95 -0
- package/src/visualization/layouts/index.js +29 -0
- package/src/visualization/renderers/ascii/box.js +16 -0
- package/src/visualization/renderers/ascii/check.js +271 -0
- package/src/visualization/renderers/ascii/colors.js +13 -0
- package/src/visualization/renderers/ascii/formatters.js +73 -0
- package/src/visualization/renderers/ascii/graph.js +344 -0
- package/src/visualization/renderers/ascii/history.js +335 -0
- package/src/visualization/renderers/ascii/index.js +14 -0
- package/src/visualization/renderers/ascii/info.js +245 -0
- package/src/visualization/renderers/ascii/materialize.js +255 -0
- package/src/visualization/renderers/ascii/path.js +240 -0
- package/src/visualization/renderers/ascii/progress.js +32 -0
- package/src/visualization/renderers/ascii/symbols.js +33 -0
- package/src/visualization/renderers/ascii/table.js +19 -0
- package/src/visualization/renderers/browser/index.js +1 -0
- package/src/visualization/renderers/svg/index.js +159 -0
- package/src/visualization/utils/ansi.js +14 -0
- package/src/visualization/utils/time.js +40 -0
- package/src/visualization/utils/truncate.js +40 -0
- package/src/visualization/utils/unicode.js +52 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StateDiff - Deterministic state diff engine for PULSE subscriptions.
|
|
3
|
+
*
|
|
4
|
+
* Computes what changed between two materialized WarpStateV5 states.
|
|
5
|
+
* Used by the subscription system to notify handlers of graph changes.
|
|
6
|
+
*
|
|
7
|
+
* @module domain/services/StateDiff
|
|
8
|
+
* @see ROADMAP.md PL/DIFF/1
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { orsetElements } from '../crdt/ORSet.js';
|
|
12
|
+
import { lwwValue } from '../crdt/LWW.js';
|
|
13
|
+
import { decodeEdgeKey, decodePropKey, isEdgePropKey } from './KeyCodec.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {Object} EdgeChange
|
|
17
|
+
* @property {string} from - Source node ID
|
|
18
|
+
* @property {string} to - Target node ID
|
|
19
|
+
* @property {string} label - Edge label
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {Object} PropSet
|
|
24
|
+
* @property {string} key - Encoded property key
|
|
25
|
+
* @property {string} nodeId - Node ID (for node props)
|
|
26
|
+
* @property {string} propKey - Property name
|
|
27
|
+
* @property {*} oldValue - Previous value (undefined if new)
|
|
28
|
+
* @property {*} newValue - New value
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @typedef {Object} PropRemoved
|
|
33
|
+
* @property {string} key - Encoded property key
|
|
34
|
+
* @property {string} nodeId - Node ID (for node props)
|
|
35
|
+
* @property {string} propKey - Property name
|
|
36
|
+
* @property {*} oldValue - Previous value
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @typedef {Object} StateDiffResult
|
|
41
|
+
* @property {Object} nodes - Node changes
|
|
42
|
+
* @property {string[]} nodes.added - Added node IDs (sorted)
|
|
43
|
+
* @property {string[]} nodes.removed - Removed node IDs (sorted)
|
|
44
|
+
* @property {Object} edges - Edge changes
|
|
45
|
+
* @property {EdgeChange[]} edges.added - Added edges (sorted)
|
|
46
|
+
* @property {EdgeChange[]} edges.removed - Removed edges (sorted)
|
|
47
|
+
* @property {Object} props - Property changes
|
|
48
|
+
* @property {PropSet[]} props.set - Set/changed properties (sorted)
|
|
49
|
+
* @property {PropRemoved[]} props.removed - Removed properties (sorted)
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Compares two edge changes for deterministic ordering.
|
|
54
|
+
* @param {EdgeChange} a
|
|
55
|
+
* @param {EdgeChange} b
|
|
56
|
+
* @returns {number}
|
|
57
|
+
*/
|
|
58
|
+
function compareEdges(a, b) {
|
|
59
|
+
if (a.from !== b.from) {
|
|
60
|
+
return a.from < b.from ? -1 : 1;
|
|
61
|
+
}
|
|
62
|
+
if (a.to !== b.to) {
|
|
63
|
+
return a.to < b.to ? -1 : 1;
|
|
64
|
+
}
|
|
65
|
+
if (a.label !== b.label) {
|
|
66
|
+
return a.label < b.label ? -1 : 1;
|
|
67
|
+
}
|
|
68
|
+
return 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Compares two property changes for deterministic ordering.
|
|
73
|
+
* @param {{key: string}} a
|
|
74
|
+
* @param {{key: string}} b
|
|
75
|
+
* @returns {number}
|
|
76
|
+
*/
|
|
77
|
+
function compareProps(a, b) {
|
|
78
|
+
if (a.key < b.key) {
|
|
79
|
+
return -1;
|
|
80
|
+
}
|
|
81
|
+
if (a.key > b.key) {
|
|
82
|
+
return 1;
|
|
83
|
+
}
|
|
84
|
+
return 0;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Checks if two arrays are deeply equal.
|
|
89
|
+
* @param {Array} a
|
|
90
|
+
* @param {Array} b
|
|
91
|
+
* @returns {boolean}
|
|
92
|
+
*/
|
|
93
|
+
function arraysEqual(a, b) {
|
|
94
|
+
if (a.length !== b.length) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
for (let i = 0; i < a.length; i++) {
|
|
98
|
+
if (!deepEqual(a[i], b[i])) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Checks if two objects are deeply equal.
|
|
107
|
+
* @param {Object} a
|
|
108
|
+
* @param {Object} b
|
|
109
|
+
* @returns {boolean}
|
|
110
|
+
*/
|
|
111
|
+
function objectsEqual(a, b) {
|
|
112
|
+
const keysA = Object.keys(a);
|
|
113
|
+
const keysB = Object.keys(b);
|
|
114
|
+
if (keysA.length !== keysB.length) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
for (const key of keysA) {
|
|
118
|
+
if (!Object.prototype.hasOwnProperty.call(b, key)) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
if (!deepEqual(a[key], b[key])) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Checks if two values are deeply equal (for property comparison).
|
|
130
|
+
* @param {*} a
|
|
131
|
+
* @param {*} b
|
|
132
|
+
* @returns {boolean}
|
|
133
|
+
*/
|
|
134
|
+
function deepEqual(a, b) {
|
|
135
|
+
if (a === b) {
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
if (a === null || b === null) {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
if (typeof a !== typeof b) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
if (typeof a !== 'object') {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
if (Array.isArray(a) !== Array.isArray(b)) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
if (Array.isArray(a)) {
|
|
151
|
+
return arraysEqual(a, b);
|
|
152
|
+
}
|
|
153
|
+
return objectsEqual(a, b);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Computes set difference: elements in `after` not in `before`.
|
|
158
|
+
* @param {Set} before
|
|
159
|
+
* @param {Set} after
|
|
160
|
+
* @returns {Array}
|
|
161
|
+
*/
|
|
162
|
+
function setAdded(before, after) {
|
|
163
|
+
const result = [];
|
|
164
|
+
for (const item of after) {
|
|
165
|
+
if (!before.has(item)) {
|
|
166
|
+
result.push(item);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Computes node and edge diffs between two states.
|
|
174
|
+
* @param {import('./JoinReducer.js').WarpStateV5 | null} before
|
|
175
|
+
* @param {import('./JoinReducer.js').WarpStateV5} after
|
|
176
|
+
* @returns {{nodesAdded: string[], nodesRemoved: string[], edgesAdded: EdgeChange[], edgesRemoved: EdgeChange[]}}
|
|
177
|
+
*/
|
|
178
|
+
function diffNodesAndEdges(before, after) {
|
|
179
|
+
const beforeNodes = before ? new Set(orsetElements(before.nodeAlive)) : new Set();
|
|
180
|
+
const afterNodes = new Set(orsetElements(after.nodeAlive));
|
|
181
|
+
|
|
182
|
+
// Filter edges to only include those with visible endpoints (both nodes must be alive).
|
|
183
|
+
// This ensures diffs respect node visibility rules - edges with tombstoned endpoints
|
|
184
|
+
// are treated as invisible.
|
|
185
|
+
const beforeEdges = before
|
|
186
|
+
? new Set(
|
|
187
|
+
orsetElements(before.edgeAlive).filter((edgeKey) => {
|
|
188
|
+
const { from, to } = decodeEdgeKey(edgeKey);
|
|
189
|
+
return beforeNodes.has(from) && beforeNodes.has(to);
|
|
190
|
+
})
|
|
191
|
+
)
|
|
192
|
+
: new Set();
|
|
193
|
+
|
|
194
|
+
const afterEdges = new Set(
|
|
195
|
+
orsetElements(after.edgeAlive).filter((edgeKey) => {
|
|
196
|
+
const { from, to } = decodeEdgeKey(edgeKey);
|
|
197
|
+
return afterNodes.has(from) && afterNodes.has(to);
|
|
198
|
+
})
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
const nodesAdded = setAdded(beforeNodes, afterNodes);
|
|
202
|
+
const nodesRemoved = setAdded(afterNodes, beforeNodes);
|
|
203
|
+
const edgesAdded = setAdded(beforeEdges, afterEdges).map(decodeEdgeKey);
|
|
204
|
+
const edgesRemoved = setAdded(afterEdges, beforeEdges).map(decodeEdgeKey);
|
|
205
|
+
|
|
206
|
+
return { nodesAdded, nodesRemoved, edgesAdded, edgesRemoved };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Computes property diffs between two states.
|
|
211
|
+
* @param {import('./JoinReducer.js').WarpStateV5 | null} before
|
|
212
|
+
* @param {import('./JoinReducer.js').WarpStateV5} after
|
|
213
|
+
* @returns {{propsSet: PropSet[], propsRemoved: PropRemoved[]}}
|
|
214
|
+
*/
|
|
215
|
+
function diffProps(before, after) {
|
|
216
|
+
const propsSet = [];
|
|
217
|
+
const propsRemoved = [];
|
|
218
|
+
const beforeProps = before ? before.prop : new Map();
|
|
219
|
+
const afterProps = after.prop;
|
|
220
|
+
const allPropKeys = new Set([...beforeProps.keys(), ...afterProps.keys()]);
|
|
221
|
+
|
|
222
|
+
for (const key of allPropKeys) {
|
|
223
|
+
// Skip edge properties (out of scope per spec)
|
|
224
|
+
if (isEdgePropKey(key)) {
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const beforeReg = beforeProps.get(key);
|
|
229
|
+
const afterReg = afterProps.get(key);
|
|
230
|
+
const beforeValue = lwwValue(beforeReg);
|
|
231
|
+
const afterValue = lwwValue(afterReg);
|
|
232
|
+
const { nodeId, propKey } = decodePropKey(key);
|
|
233
|
+
|
|
234
|
+
if (afterReg !== undefined && beforeReg === undefined) {
|
|
235
|
+
propsSet.push({ key, nodeId, propKey, oldValue: undefined, newValue: afterValue });
|
|
236
|
+
} else if (afterReg === undefined && beforeReg !== undefined) {
|
|
237
|
+
propsRemoved.push({ key, nodeId, propKey, oldValue: beforeValue });
|
|
238
|
+
} else if (afterReg !== undefined && !deepEqual(beforeValue, afterValue)) {
|
|
239
|
+
propsSet.push({ key, nodeId, propKey, oldValue: beforeValue, newValue: afterValue });
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return { propsSet, propsRemoved };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Computes a deterministic diff between two materialized states.
|
|
248
|
+
*
|
|
249
|
+
* @param {import('./JoinReducer.js').WarpStateV5 | null} before - Previous state (null for initial)
|
|
250
|
+
* @param {import('./JoinReducer.js').WarpStateV5} after - Current state
|
|
251
|
+
* @returns {StateDiffResult} The diff between states
|
|
252
|
+
*/
|
|
253
|
+
export function diffStates(before, after) {
|
|
254
|
+
const { nodesAdded, nodesRemoved, edgesAdded, edgesRemoved } = diffNodesAndEdges(before, after);
|
|
255
|
+
const { propsSet, propsRemoved } = diffProps(before, after);
|
|
256
|
+
|
|
257
|
+
// Sort for deterministic output
|
|
258
|
+
nodesAdded.sort();
|
|
259
|
+
nodesRemoved.sort();
|
|
260
|
+
edgesAdded.sort(compareEdges);
|
|
261
|
+
edgesRemoved.sort(compareEdges);
|
|
262
|
+
propsSet.sort(compareProps);
|
|
263
|
+
propsRemoved.sort(compareProps);
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
nodes: { added: nodesAdded, removed: nodesRemoved },
|
|
267
|
+
edges: { added: edgesAdded, removed: edgesRemoved },
|
|
268
|
+
props: { set: propsSet, removed: propsRemoved },
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Returns true if the diff represents no changes.
|
|
274
|
+
*
|
|
275
|
+
* @param {StateDiffResult} diff
|
|
276
|
+
* @returns {boolean}
|
|
277
|
+
*/
|
|
278
|
+
export function isEmptyDiff(diff) {
|
|
279
|
+
return (
|
|
280
|
+
diff.nodes.added.length === 0 &&
|
|
281
|
+
diff.nodes.removed.length === 0 &&
|
|
282
|
+
diff.edges.added.length === 0 &&
|
|
283
|
+
diff.edges.removed.length === 0 &&
|
|
284
|
+
diff.props.set.length === 0 &&
|
|
285
|
+
diff.props.removed.length === 0
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Creates an empty diff result.
|
|
291
|
+
*
|
|
292
|
+
* @returns {StateDiffResult}
|
|
293
|
+
*/
|
|
294
|
+
export function createEmptyDiff() {
|
|
295
|
+
return {
|
|
296
|
+
nodes: { added: [], removed: [] },
|
|
297
|
+
edges: { added: [], removed: [] },
|
|
298
|
+
props: { set: [], removed: [] },
|
|
299
|
+
};
|
|
300
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import defaultCodec from '../utils/defaultCodec.js';
|
|
2
|
+
import { orsetContains, orsetElements } from '../crdt/ORSet.js';
|
|
3
|
+
import { decodeEdgeKey, decodePropKey } from './KeyCodec.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* State Serialization and Hashing for WARP v5
|
|
7
|
+
*
|
|
8
|
+
* Provides visibility predicates for determining what is visible in the graph,
|
|
9
|
+
* canonical state serialization for deterministic hashing, and state hash computation.
|
|
10
|
+
*
|
|
11
|
+
* V5 uses ORSet-based state (rather than LWW registers for nodeAlive/edgeAlive).
|
|
12
|
+
*
|
|
13
|
+
* @module StateSerializerV5
|
|
14
|
+
* @see WARP Spec Section 8.3 (Visibility)
|
|
15
|
+
* @see WARP Spec Section 10.3 (Canonical Serialization)
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// Visibility Predicates (WARP spec Section 8.3)
|
|
20
|
+
// ============================================================================
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Checks if a node is visible (present in the ORSet).
|
|
24
|
+
* @param {import('./JoinReducer.js').WarpStateV5} state
|
|
25
|
+
* @param {string} nodeId
|
|
26
|
+
* @returns {boolean}
|
|
27
|
+
*/
|
|
28
|
+
export function nodeVisibleV5(state, nodeId) {
|
|
29
|
+
return orsetContains(state.nodeAlive, nodeId);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Checks if an edge is visible.
|
|
34
|
+
* Edge is visible if: edge is in ORSet AND both endpoints are visible.
|
|
35
|
+
* @param {import('./JoinReducer.js').WarpStateV5} state
|
|
36
|
+
* @param {string} edgeKey - Encoded edge key
|
|
37
|
+
* @returns {boolean}
|
|
38
|
+
*/
|
|
39
|
+
export function edgeVisibleV5(state, edgeKey) {
|
|
40
|
+
if (!orsetContains(state.edgeAlive, edgeKey)) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
const { from, to } = decodeEdgeKey(edgeKey);
|
|
44
|
+
return nodeVisibleV5(state, from) && nodeVisibleV5(state, to);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Checks if a property is visible.
|
|
49
|
+
* Property is visible if: node is visible AND prop exists.
|
|
50
|
+
* @param {import('./JoinReducer.js').WarpStateV5} state
|
|
51
|
+
* @param {string} propKey - Encoded prop key
|
|
52
|
+
* @returns {boolean}
|
|
53
|
+
*/
|
|
54
|
+
export function propVisibleV5(state, propKey) {
|
|
55
|
+
const { nodeId } = decodePropKey(propKey);
|
|
56
|
+
if (!nodeVisibleV5(state, nodeId)) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
return state.prop.has(propKey);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ============================================================================
|
|
63
|
+
// Canonical State Serialization (WARP spec Section 10.3)
|
|
64
|
+
// ============================================================================
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Serializes state to canonical CBOR bytes.
|
|
68
|
+
* Only includes VISIBLE projection with stable ordering:
|
|
69
|
+
* 1. Nodes sorted by NodeId
|
|
70
|
+
* 2. Edges sorted by (from, to, label)
|
|
71
|
+
* 3. Props sorted by (node, key)
|
|
72
|
+
*
|
|
73
|
+
* Same canonical ordering as v4 for visible projection.
|
|
74
|
+
*
|
|
75
|
+
* @param {import('./JoinReducer.js').WarpStateV5} state
|
|
76
|
+
* @param {Object} [options]
|
|
77
|
+
* @param {import('../../ports/CodecPort.js').default} [options.codec] - Codec for serialization
|
|
78
|
+
* @returns {Buffer}
|
|
79
|
+
*/
|
|
80
|
+
export function serializeStateV5(state, { codec } = {}) {
|
|
81
|
+
const c = codec || defaultCodec;
|
|
82
|
+
// 1. Collect visible nodes, sorted
|
|
83
|
+
const nodes = [...orsetElements(state.nodeAlive)].sort();
|
|
84
|
+
|
|
85
|
+
// 2. Collect visible edges (both endpoints visible), sorted by (from, to, label)
|
|
86
|
+
const visibleEdges = [];
|
|
87
|
+
for (const edgeKey of orsetElements(state.edgeAlive)) {
|
|
88
|
+
if (edgeVisibleV5(state, edgeKey)) {
|
|
89
|
+
visibleEdges.push(decodeEdgeKey(edgeKey));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
visibleEdges.sort((a, b) => {
|
|
93
|
+
if (a.from !== b.from) {
|
|
94
|
+
return a.from < b.from ? -1 : 1;
|
|
95
|
+
}
|
|
96
|
+
if (a.to !== b.to) {
|
|
97
|
+
return a.to < b.to ? -1 : 1;
|
|
98
|
+
}
|
|
99
|
+
return a.label < b.label ? -1 : a.label > b.label ? 1 : 0;
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// 3. Collect visible props (node visible), sorted by (node, key)
|
|
103
|
+
const visibleProps = [];
|
|
104
|
+
for (const [propKey, register] of state.prop) {
|
|
105
|
+
const { nodeId, propKey: key } = decodePropKey(propKey);
|
|
106
|
+
if (nodeVisibleV5(state, nodeId)) {
|
|
107
|
+
visibleProps.push({ node: nodeId, key, value: register.value });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
visibleProps.sort((a, b) => {
|
|
111
|
+
if (a.node !== b.node) {
|
|
112
|
+
return a.node < b.node ? -1 : 1;
|
|
113
|
+
}
|
|
114
|
+
return a.key < b.key ? -1 : a.key > b.key ? 1 : 0;
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Encode as canonical CBOR
|
|
118
|
+
return c.encode({ nodes, edges: visibleEdges, props: visibleProps });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Computes SHA-256 hash of canonical state bytes.
|
|
123
|
+
* @param {import('./JoinReducer.js').WarpStateV5} state
|
|
124
|
+
* @param {Object} [options] - Options
|
|
125
|
+
* @param {import('../../ports/CryptoPort.js').default} options.crypto - CryptoPort instance
|
|
126
|
+
* @param {import('../../ports/CodecPort.js').default} [options.codec] - Codec for serialization
|
|
127
|
+
* @returns {Promise<string|null>} Hex-encoded SHA-256 hash, or null if no crypto
|
|
128
|
+
*/
|
|
129
|
+
export async function computeStateHashV5(state, { crypto, codec } = {}) {
|
|
130
|
+
const serialized = serializeStateV5(state, { codec });
|
|
131
|
+
return crypto ? await crypto.hash('sha256', serialized) : null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Deserializes state from CBOR bytes.
|
|
136
|
+
* Note: This reconstructs the visible projection only.
|
|
137
|
+
* @param {Buffer} buffer
|
|
138
|
+
* @param {Object} [options]
|
|
139
|
+
* @param {import('../../ports/CodecPort.js').default} [options.codec] - Codec for deserialization
|
|
140
|
+
* @returns {{nodes: string[], edges: Array<{from: string, to: string, label: string}>, props: Array<{node: string, key: string, value: *}>}}
|
|
141
|
+
*/
|
|
142
|
+
export function deserializeStateV5(buffer, { codec } = {}) {
|
|
143
|
+
const c = codec || defaultCodec;
|
|
144
|
+
return c.decode(buffer);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ============================================================================
|
|
148
|
+
// Full State Serialization (for BTR replay)
|
|
149
|
+
// ============================================================================
|
|
150
|
+
|
|
151
|
+
// Re-export from CheckpointSerializerV5 for compatibility.
|
|
152
|
+
// Both BTR and Checkpoint use the same canonical full-state format.
|
|
153
|
+
export {
|
|
154
|
+
serializeFullStateV5,
|
|
155
|
+
deserializeFullStateV5,
|
|
156
|
+
} from './CheckpointSerializerV5.js';
|