@git-stunts/git-warp 11.5.0 → 12.0.0
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/README.md +145 -1
- package/bin/cli/commands/registry.js +4 -0
- package/bin/cli/commands/reindex.js +41 -0
- package/bin/cli/commands/verify-index.js +59 -0
- package/bin/cli/infrastructure.js +7 -2
- package/bin/cli/schemas.js +19 -0
- package/bin/cli/types.js +2 -0
- package/index.d.ts +49 -12
- package/package.json +2 -2
- package/src/domain/WarpGraph.js +62 -2
- package/src/domain/errors/ShardIdOverflowError.js +28 -0
- package/src/domain/errors/index.js +1 -0
- package/src/domain/services/AdjacencyNeighborProvider.js +140 -0
- package/src/domain/services/BitmapIndexReader.js +32 -10
- package/src/domain/services/BitmapNeighborProvider.js +178 -0
- package/src/domain/services/CheckpointMessageCodec.js +3 -3
- package/src/domain/services/CheckpointService.js +77 -12
- package/src/domain/services/GraphTraversal.js +1239 -0
- package/src/domain/services/IncrementalIndexUpdater.js +765 -0
- package/src/domain/services/JoinReducer.js +310 -46
- package/src/domain/services/LogicalBitmapIndexBuilder.js +323 -0
- package/src/domain/services/LogicalIndexBuildService.js +108 -0
- package/src/domain/services/LogicalIndexReader.js +315 -0
- package/src/domain/services/LogicalTraversal.js +321 -202
- package/src/domain/services/MaterializedViewService.js +379 -0
- package/src/domain/services/ObserverView.js +138 -47
- package/src/domain/services/PatchBuilderV2.js +3 -3
- package/src/domain/services/PropertyIndexBuilder.js +64 -0
- package/src/domain/services/PropertyIndexReader.js +111 -0
- package/src/domain/services/SyncController.js +576 -0
- package/src/domain/services/TemporalQuery.js +128 -14
- package/src/domain/types/PatchDiff.js +90 -0
- package/src/domain/types/WarpTypesV2.js +4 -4
- package/src/domain/utils/MinHeap.js +45 -17
- package/src/domain/utils/canonicalCbor.js +36 -0
- package/src/domain/utils/fnv1a.js +20 -0
- package/src/domain/utils/roaring.js +14 -3
- package/src/domain/utils/shardKey.js +40 -0
- package/src/domain/utils/toBytes.js +17 -0
- package/src/domain/utils/validateShardOid.js +13 -0
- package/src/domain/warp/_internal.js +0 -9
- package/src/domain/warp/_wiredMethods.d.ts +8 -2
- package/src/domain/warp/checkpoint.methods.js +21 -5
- package/src/domain/warp/materialize.methods.js +17 -5
- package/src/domain/warp/materializeAdvanced.methods.js +142 -3
- package/src/domain/warp/query.methods.js +78 -12
- package/src/infrastructure/adapters/CasSeekCacheAdapter.js +26 -5
- package/src/ports/BlobPort.js +1 -1
- package/src/ports/NeighborProviderPort.js +59 -0
- package/src/ports/SeekCachePort.js +4 -3
- package/src/domain/warp/sync.methods.js +0 -554
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builder for constructing CBOR-based bitmap indexes over the logical graph.
|
|
3
|
+
*
|
|
4
|
+
* Produces sharded index with stable numeric IDs, append-only label registry,
|
|
5
|
+
* per-label forward/reverse bitmaps, and alive bitmaps per shard.
|
|
6
|
+
*
|
|
7
|
+
* Shard output:
|
|
8
|
+
* meta_XX.cbor — nodeId → globalId mappings, nextLocalId, alive bitmap
|
|
9
|
+
* labels.cbor — label registry (append-only)
|
|
10
|
+
* fwd_XX.cbor — forward edge bitmaps (all + byLabel)
|
|
11
|
+
* rev_XX.cbor — reverse edge bitmaps (all + byLabel)
|
|
12
|
+
* receipt.cbor — build metadata
|
|
13
|
+
*
|
|
14
|
+
* @module domain/services/LogicalBitmapIndexBuilder
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import defaultCodec from '../utils/defaultCodec.js';
|
|
18
|
+
import computeShardKey from '../utils/shardKey.js';
|
|
19
|
+
import { getRoaringBitmap32 } from '../utils/roaring.js';
|
|
20
|
+
import { ShardIdOverflowError } from '../errors/index.js';
|
|
21
|
+
|
|
22
|
+
/** Maximum local IDs per shard (2^24). */
|
|
23
|
+
const MAX_LOCAL_ID = 1 << 24;
|
|
24
|
+
|
|
25
|
+
export default class LogicalBitmapIndexBuilder {
|
|
26
|
+
/**
|
|
27
|
+
* @param {Object} [options]
|
|
28
|
+
* @param {import('../../ports/CodecPort.js').default} [options.codec]
|
|
29
|
+
*/
|
|
30
|
+
constructor({ codec } = {}) {
|
|
31
|
+
this._codec = codec || defaultCodec;
|
|
32
|
+
|
|
33
|
+
/** @type {Map<string, number>} nodeId → globalId */
|
|
34
|
+
this._nodeToGlobal = new Map();
|
|
35
|
+
|
|
36
|
+
/** @type {Map<string, string>} globalId(string) → nodeId */
|
|
37
|
+
this._globalToNode = new Map();
|
|
38
|
+
|
|
39
|
+
/** Per-shard next local ID counters. @type {Map<string, number>} */
|
|
40
|
+
this._shardNextLocal = new Map();
|
|
41
|
+
|
|
42
|
+
/** Alive bitmap per shard. @type {Map<string, import('../utils/roaring.js').RoaringBitmapSubset>} */
|
|
43
|
+
this._aliveBitmaps = new Map();
|
|
44
|
+
|
|
45
|
+
/** Label → labelId (append-only). @type {Map<string, number>} */
|
|
46
|
+
this._labelToId = new Map();
|
|
47
|
+
|
|
48
|
+
/** @type {number} */
|
|
49
|
+
this._nextLabelId = 0;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Forward edge bitmaps.
|
|
53
|
+
* Key: `${shardKey}:all:${globalId}` or `${shardKey}:${labelId}:${globalId}`
|
|
54
|
+
* @type {Map<string, import('../utils/roaring.js').RoaringBitmapSubset>}
|
|
55
|
+
*/
|
|
56
|
+
this._fwdBitmaps = new Map();
|
|
57
|
+
|
|
58
|
+
/** Reverse edge bitmaps. Same key scheme as _fwdBitmaps. @type {Map<string, import('../utils/roaring.js').RoaringBitmapSubset>} */
|
|
59
|
+
this._revBitmaps = new Map();
|
|
60
|
+
|
|
61
|
+
/** Per-shard node list for O(shard) serialize. @type {Map<string, Array<[string, number]>>} */
|
|
62
|
+
this._shardNodes = new Map();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Registers a node and returns its stable global ID.
|
|
67
|
+
* GlobalId = (shardByte << 24) | localId.
|
|
68
|
+
*
|
|
69
|
+
* @param {string} nodeId
|
|
70
|
+
* @returns {number} globalId
|
|
71
|
+
* @throws {ShardIdOverflowError} If the shard is full
|
|
72
|
+
*/
|
|
73
|
+
registerNode(nodeId) {
|
|
74
|
+
const existing = this._nodeToGlobal.get(nodeId);
|
|
75
|
+
if (existing !== undefined) {
|
|
76
|
+
return existing;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const shardKey = computeShardKey(nodeId);
|
|
80
|
+
const shardByte = parseInt(shardKey, 16);
|
|
81
|
+
const nextLocal = this._shardNextLocal.get(shardKey) ?? 0;
|
|
82
|
+
|
|
83
|
+
if (nextLocal >= MAX_LOCAL_ID) {
|
|
84
|
+
throw new ShardIdOverflowError(
|
|
85
|
+
`Shard '${shardKey}' exceeded max local ID (${MAX_LOCAL_ID})`,
|
|
86
|
+
{ shardKey, nextLocalId: nextLocal },
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const globalId = ((shardByte << 24) | nextLocal) >>> 0;
|
|
91
|
+
this._nodeToGlobal.set(nodeId, globalId);
|
|
92
|
+
this._globalToNode.set(String(globalId), nodeId);
|
|
93
|
+
this._shardNextLocal.set(shardKey, nextLocal + 1);
|
|
94
|
+
|
|
95
|
+
let shardList = this._shardNodes.get(shardKey);
|
|
96
|
+
if (!shardList) {
|
|
97
|
+
shardList = [];
|
|
98
|
+
this._shardNodes.set(shardKey, shardList);
|
|
99
|
+
}
|
|
100
|
+
shardList.push([nodeId, globalId]);
|
|
101
|
+
|
|
102
|
+
return globalId;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Marks a node as alive in its shard's alive bitmap.
|
|
107
|
+
*
|
|
108
|
+
* @param {string} nodeId
|
|
109
|
+
*/
|
|
110
|
+
markAlive(nodeId) {
|
|
111
|
+
const globalId = this._nodeToGlobal.get(nodeId);
|
|
112
|
+
if (globalId === undefined) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const shardKey = computeShardKey(nodeId);
|
|
116
|
+
let bitmap = this._aliveBitmaps.get(shardKey);
|
|
117
|
+
if (!bitmap) {
|
|
118
|
+
const RoaringBitmap32 = getRoaringBitmap32();
|
|
119
|
+
bitmap = new RoaringBitmap32();
|
|
120
|
+
this._aliveBitmaps.set(shardKey, bitmap);
|
|
121
|
+
}
|
|
122
|
+
bitmap.add(globalId);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Registers a label and returns its append-only labelId.
|
|
127
|
+
*
|
|
128
|
+
* @param {string} label
|
|
129
|
+
* @returns {number}
|
|
130
|
+
*/
|
|
131
|
+
registerLabel(label) {
|
|
132
|
+
const existing = this._labelToId.get(label);
|
|
133
|
+
if (existing !== undefined) {
|
|
134
|
+
return existing;
|
|
135
|
+
}
|
|
136
|
+
const id = this._nextLabelId++;
|
|
137
|
+
this._labelToId.set(label, id);
|
|
138
|
+
return id;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Adds a directed edge, populating forward/reverse bitmaps
|
|
143
|
+
* for both the 'all' bucket and the per-label bucket.
|
|
144
|
+
*
|
|
145
|
+
* @param {string} fromId - Source node ID (must be registered)
|
|
146
|
+
* @param {string} toId - Target node ID (must be registered)
|
|
147
|
+
* @param {string} label - Edge label (must be registered)
|
|
148
|
+
*/
|
|
149
|
+
addEdge(fromId, toId, label) {
|
|
150
|
+
const fromGlobal = this._nodeToGlobal.get(fromId);
|
|
151
|
+
const toGlobal = this._nodeToGlobal.get(toId);
|
|
152
|
+
if (fromGlobal === undefined || toGlobal === undefined) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const labelId = this._labelToId.get(label);
|
|
157
|
+
if (labelId === undefined) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const fromShard = computeShardKey(fromId);
|
|
162
|
+
const toShard = computeShardKey(toId);
|
|
163
|
+
|
|
164
|
+
// Forward: from's shard, keyed by fromGlobal, value contains toGlobal
|
|
165
|
+
this._addToBitmap(this._fwdBitmaps, { shardKey: fromShard, bucket: 'all', owner: fromGlobal, target: toGlobal });
|
|
166
|
+
this._addToBitmap(this._fwdBitmaps, { shardKey: fromShard, bucket: String(labelId), owner: fromGlobal, target: toGlobal });
|
|
167
|
+
|
|
168
|
+
// Reverse: to's shard, keyed by toGlobal, value contains fromGlobal
|
|
169
|
+
this._addToBitmap(this._revBitmaps, { shardKey: toShard, bucket: 'all', owner: toGlobal, target: fromGlobal });
|
|
170
|
+
this._addToBitmap(this._revBitmaps, { shardKey: toShard, bucket: String(labelId), owner: toGlobal, target: fromGlobal });
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Seeds ID mappings from a previously built meta shard for ID stability.
|
|
175
|
+
*
|
|
176
|
+
* @param {string} shardKey
|
|
177
|
+
* @param {{ nodeToGlobal: Array<[string, number]>|Record<string, number>, nextLocalId: number }} metaShard
|
|
178
|
+
*/
|
|
179
|
+
loadExistingMeta(shardKey, metaShard) {
|
|
180
|
+
const entries = Array.isArray(metaShard.nodeToGlobal)
|
|
181
|
+
? metaShard.nodeToGlobal
|
|
182
|
+
: Object.entries(metaShard.nodeToGlobal);
|
|
183
|
+
let shardList = this._shardNodes.get(shardKey);
|
|
184
|
+
if (!shardList) {
|
|
185
|
+
shardList = [];
|
|
186
|
+
this._shardNodes.set(shardKey, shardList);
|
|
187
|
+
}
|
|
188
|
+
for (const [nodeId, globalId] of entries) {
|
|
189
|
+
this._nodeToGlobal.set(nodeId, /** @type {number} */ (globalId));
|
|
190
|
+
this._globalToNode.set(String(globalId), /** @type {string} */ (nodeId));
|
|
191
|
+
shardList.push([/** @type {string} */ (nodeId), /** @type {number} */ (globalId)]);
|
|
192
|
+
}
|
|
193
|
+
const current = this._shardNextLocal.get(shardKey) ?? 0;
|
|
194
|
+
if (metaShard.nextLocalId > current) {
|
|
195
|
+
this._shardNextLocal.set(shardKey, metaShard.nextLocalId);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Seeds the label registry from a previous build for append-only stability.
|
|
201
|
+
*
|
|
202
|
+
* @param {Record<string, number>|Array<[string, number]>} registry - label → labelId
|
|
203
|
+
*/
|
|
204
|
+
loadExistingLabels(registry) {
|
|
205
|
+
const entries = Array.isArray(registry) ? registry : Object.entries(registry);
|
|
206
|
+
let maxId = this._nextLabelId;
|
|
207
|
+
for (const [label, id] of entries) {
|
|
208
|
+
this._labelToId.set(label, id);
|
|
209
|
+
if (id >= maxId) {
|
|
210
|
+
maxId = id + 1;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
this._nextLabelId = maxId;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Serializes the full index to a Record<string, Uint8Array>.
|
|
218
|
+
*
|
|
219
|
+
* @returns {Record<string, Uint8Array>}
|
|
220
|
+
*/
|
|
221
|
+
serialize() {
|
|
222
|
+
/** @type {Record<string, Uint8Array>} */
|
|
223
|
+
const tree = {};
|
|
224
|
+
|
|
225
|
+
// Collect all shard keys that have any data
|
|
226
|
+
const allShardKeys = new Set([
|
|
227
|
+
...this._shardNextLocal.keys(),
|
|
228
|
+
]);
|
|
229
|
+
|
|
230
|
+
// Meta shards
|
|
231
|
+
for (const shardKey of allShardKeys) {
|
|
232
|
+
// Use array of [nodeId, globalId] pairs to avoid __proto__ key issues
|
|
233
|
+
// Sort by nodeId for deterministic output
|
|
234
|
+
const nodeToGlobal = (this._shardNodes.get(shardKey) ?? [])
|
|
235
|
+
.slice()
|
|
236
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
237
|
+
|
|
238
|
+
const aliveBitmap = this._aliveBitmaps.get(shardKey);
|
|
239
|
+
const aliveBytes = aliveBitmap ? aliveBitmap.serialize(true) : new Uint8Array(0);
|
|
240
|
+
|
|
241
|
+
const shard = {
|
|
242
|
+
nodeToGlobal,
|
|
243
|
+
nextLocalId: this._shardNextLocal.get(shardKey) ?? 0,
|
|
244
|
+
alive: aliveBytes,
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
tree[`meta_${shardKey}.cbor`] = this._codec.encode(shard).slice();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Labels registry
|
|
251
|
+
/** @type {Array<[string, number]>} */
|
|
252
|
+
const labelRegistry = [];
|
|
253
|
+
for (const [label, id] of this._labelToId) {
|
|
254
|
+
labelRegistry.push([label, id]);
|
|
255
|
+
}
|
|
256
|
+
tree['labels.cbor'] = this._codec.encode(labelRegistry).slice();
|
|
257
|
+
|
|
258
|
+
// Forward/reverse edge shards
|
|
259
|
+
this._serializeEdgeShards(tree, 'fwd', this._fwdBitmaps);
|
|
260
|
+
this._serializeEdgeShards(tree, 'rev', this._revBitmaps);
|
|
261
|
+
|
|
262
|
+
// Receipt
|
|
263
|
+
const receipt = {
|
|
264
|
+
version: 1,
|
|
265
|
+
nodeCount: this._nodeToGlobal.size,
|
|
266
|
+
labelCount: this._labelToId.size,
|
|
267
|
+
shardCount: allShardKeys.size,
|
|
268
|
+
};
|
|
269
|
+
tree['receipt.cbor'] = this._codec.encode(receipt).slice();
|
|
270
|
+
|
|
271
|
+
return tree;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* @param {Record<string, Uint8Array>} tree
|
|
276
|
+
* @param {string} direction - 'fwd' or 'rev'
|
|
277
|
+
* @param {Map<string, import('../utils/roaring.js').RoaringBitmapSubset>} bitmaps
|
|
278
|
+
* @private
|
|
279
|
+
*/
|
|
280
|
+
_serializeEdgeShards(tree, direction, bitmaps) {
|
|
281
|
+
// Group by shardKey
|
|
282
|
+
/** @type {Map<string, Record<string, Record<string, Uint8Array>>>} */
|
|
283
|
+
const byShardKey = new Map();
|
|
284
|
+
|
|
285
|
+
for (const [key, bitmap] of bitmaps) {
|
|
286
|
+
// key: `${shardKey}:${bucketName}:${globalId}`
|
|
287
|
+
const firstColon = key.indexOf(':');
|
|
288
|
+
const secondColon = key.indexOf(':', firstColon + 1);
|
|
289
|
+
const shardKey = key.substring(0, firstColon);
|
|
290
|
+
const bucketName = key.substring(firstColon + 1, secondColon);
|
|
291
|
+
const globalIdStr = key.substring(secondColon + 1);
|
|
292
|
+
|
|
293
|
+
if (!byShardKey.has(shardKey)) {
|
|
294
|
+
byShardKey.set(shardKey, {});
|
|
295
|
+
}
|
|
296
|
+
const shardData = /** @type {Record<string, Record<string, Uint8Array>>} */ (byShardKey.get(shardKey));
|
|
297
|
+
if (!shardData[bucketName]) {
|
|
298
|
+
shardData[bucketName] = {};
|
|
299
|
+
}
|
|
300
|
+
shardData[bucketName][globalIdStr] = bitmap.serialize(true);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
for (const [shardKey, shardData] of byShardKey) {
|
|
304
|
+
tree[`${direction}_${shardKey}.cbor`] = this._codec.encode(shardData).slice();
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* @param {Map<string, import('../utils/roaring.js').RoaringBitmapSubset>} store
|
|
310
|
+
* @param {{ shardKey: string, bucket: string, owner: number, target: number }} opts
|
|
311
|
+
* @private
|
|
312
|
+
*/
|
|
313
|
+
_addToBitmap(store, { shardKey, bucket, owner, target }) {
|
|
314
|
+
const key = `${shardKey}:${bucket}:${owner}`;
|
|
315
|
+
let bitmap = store.get(key);
|
|
316
|
+
if (!bitmap) {
|
|
317
|
+
const RoaringBitmap32 = getRoaringBitmap32();
|
|
318
|
+
bitmap = new RoaringBitmap32();
|
|
319
|
+
store.set(key, bitmap);
|
|
320
|
+
}
|
|
321
|
+
bitmap.add(target);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orchestrates a full logical bitmap index build from WarpStateV5.
|
|
3
|
+
*
|
|
4
|
+
* Extracts the visible projection (nodes, edges, properties) from materialized
|
|
5
|
+
* state and delegates to LogicalBitmapIndexBuilder + PropertyIndexBuilder.
|
|
6
|
+
*
|
|
7
|
+
* @module domain/services/LogicalIndexBuildService
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import defaultCodec from '../utils/defaultCodec.js';
|
|
11
|
+
import nullLogger from '../utils/nullLogger.js';
|
|
12
|
+
import LogicalBitmapIndexBuilder from './LogicalBitmapIndexBuilder.js';
|
|
13
|
+
import PropertyIndexBuilder from './PropertyIndexBuilder.js';
|
|
14
|
+
import { orsetElements } from '../crdt/ORSet.js';
|
|
15
|
+
import { decodeEdgeKey, decodePropKey, isEdgePropKey } from './KeyCodec.js';
|
|
16
|
+
import { nodeVisibleV5, edgeVisibleV5 } from './StateSerializerV5.js';
|
|
17
|
+
|
|
18
|
+
export default class LogicalIndexBuildService {
|
|
19
|
+
/**
|
|
20
|
+
* @param {Object} [options]
|
|
21
|
+
* @param {import('../../ports/CodecPort.js').default} [options.codec]
|
|
22
|
+
* @param {import('../../ports/LoggerPort.js').default} [options.logger]
|
|
23
|
+
*/
|
|
24
|
+
constructor({ codec, logger } = {}) {
|
|
25
|
+
this._codec = codec || defaultCodec;
|
|
26
|
+
this._logger = logger || nullLogger;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Builds a complete logical index from materialized state.
|
|
31
|
+
*
|
|
32
|
+
* @param {import('./JoinReducer.js').WarpStateV5} state
|
|
33
|
+
* @param {Object} [options]
|
|
34
|
+
* @param {Record<string, { nodeToGlobal: Record<string, number>, nextLocalId: number }>} [options.existingMeta] - Prior meta shards for ID stability
|
|
35
|
+
* @param {Record<string, number>|Array<[string, number]>} [options.existingLabels] - Prior label registry for append-only stability
|
|
36
|
+
* @returns {{ tree: Record<string, Uint8Array>, receipt: Record<string, unknown> }}
|
|
37
|
+
*/
|
|
38
|
+
build(state, options = {}) {
|
|
39
|
+
const indexBuilder = new LogicalBitmapIndexBuilder({ codec: this._codec });
|
|
40
|
+
const propBuilder = new PropertyIndexBuilder({ codec: this._codec });
|
|
41
|
+
|
|
42
|
+
// Seed existing data for stability
|
|
43
|
+
if (options.existingMeta) {
|
|
44
|
+
for (const [shardKey, meta] of Object.entries(options.existingMeta)) {
|
|
45
|
+
indexBuilder.loadExistingMeta(shardKey, meta);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (options.existingLabels) {
|
|
49
|
+
indexBuilder.loadExistingLabels(options.existingLabels);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 1. Register and mark alive all visible nodes (sorted for deterministic ID assignment)
|
|
53
|
+
const aliveNodes = [...orsetElements(state.nodeAlive)].sort();
|
|
54
|
+
for (const nodeId of aliveNodes) {
|
|
55
|
+
indexBuilder.registerNode(nodeId);
|
|
56
|
+
indexBuilder.markAlive(nodeId);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 2. Collect visible edges and register labels (sorted for deterministic ID assignment)
|
|
60
|
+
const visibleEdges = [];
|
|
61
|
+
for (const edgeKey of orsetElements(state.edgeAlive)) {
|
|
62
|
+
if (edgeVisibleV5(state, edgeKey)) {
|
|
63
|
+
visibleEdges.push(decodeEdgeKey(edgeKey));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
visibleEdges.sort((a, b) => {
|
|
67
|
+
if (a.from !== b.from) {
|
|
68
|
+
return a.from < b.from ? -1 : 1;
|
|
69
|
+
}
|
|
70
|
+
if (a.to !== b.to) {
|
|
71
|
+
return a.to < b.to ? -1 : 1;
|
|
72
|
+
}
|
|
73
|
+
if (a.label !== b.label) {
|
|
74
|
+
return a.label < b.label ? -1 : 1;
|
|
75
|
+
}
|
|
76
|
+
return 0;
|
|
77
|
+
});
|
|
78
|
+
const uniqueLabels = [...new Set(visibleEdges.map(e => e.label))].sort();
|
|
79
|
+
for (const label of uniqueLabels) {
|
|
80
|
+
indexBuilder.registerLabel(label);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 3. Add edges
|
|
84
|
+
for (const { from, to, label } of visibleEdges) {
|
|
85
|
+
indexBuilder.addEdge(from, to, label);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 4. Build property index from visible props
|
|
89
|
+
for (const [propKey, register] of state.prop) {
|
|
90
|
+
if (isEdgePropKey(propKey)) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const { nodeId, propKey: key } = decodePropKey(propKey);
|
|
94
|
+
if (nodeVisibleV5(state, nodeId)) {
|
|
95
|
+
propBuilder.addProperty(nodeId, key, register.value);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// 5. Serialize
|
|
100
|
+
const indexTree = indexBuilder.serialize();
|
|
101
|
+
const propTree = propBuilder.serialize();
|
|
102
|
+
const tree = { ...indexTree, ...propTree };
|
|
103
|
+
|
|
104
|
+
const receipt = /** @type {Record<string, unknown>} */ (this._codec.decode(indexTree['receipt.cbor']));
|
|
105
|
+
|
|
106
|
+
return { tree, receipt };
|
|
107
|
+
}
|
|
108
|
+
}
|