@git-stunts/git-warp 11.5.1 → 12.1.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 +137 -10
- 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 +52 -15
- package/package.json +3 -2
- package/src/domain/WarpGraph.js +40 -0
- 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/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 +233 -5
- 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 +132 -69
- 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/QueryBuilder.js +15 -44
- package/src/domain/services/TemporalQuery.js +128 -14
- package/src/domain/services/TranslationCost.js +8 -24
- 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/matchGlob.js +51 -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/warp/_wiredMethods.d.ts +7 -1
- 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 +83 -15
- 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
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads a serialized logical bitmap index from a tree (in-memory buffers)
|
|
3
|
+
* or lazily from OID→blob storage, and produces a LogicalIndex interface.
|
|
4
|
+
*
|
|
5
|
+
* Extracted from test/helpers/fixtureDsl.js so that production code can
|
|
6
|
+
* hydrate indexes stored inside checkpoints (Phase 3).
|
|
7
|
+
*
|
|
8
|
+
* @module domain/services/LogicalIndexReader
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import defaultCodec from '../utils/defaultCodec.js';
|
|
12
|
+
import computeShardKey from '../utils/shardKey.js';
|
|
13
|
+
import toBytes from '../utils/toBytes.js';
|
|
14
|
+
import { getRoaringBitmap32 } from '../utils/roaring.js';
|
|
15
|
+
|
|
16
|
+
/** @typedef {import('./BitmapNeighborProvider.js').LogicalIndex} LogicalIndex */
|
|
17
|
+
/** @typedef {import('../utils/roaring.js').RoaringBitmapSubset} Bitmap */
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Expands a bitmap into neighbor entries, pushing into `out`.
|
|
21
|
+
*
|
|
22
|
+
* @param {Bitmap} bitmap
|
|
23
|
+
* @param {string} label
|
|
24
|
+
* @param {{ g2n: Map<number, string>, out: Array<{neighborId: string, label: string}> }} ctx
|
|
25
|
+
*/
|
|
26
|
+
function expandBitmap(bitmap, label, ctx) {
|
|
27
|
+
for (const neighborGid of bitmap.toArray()) {
|
|
28
|
+
const neighborId = ctx.g2n.get(neighborGid);
|
|
29
|
+
if (neighborId) {
|
|
30
|
+
ctx.out.push({ neighborId, label });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Resolves edges from a byOwner map for a given node (all labels).
|
|
37
|
+
*
|
|
38
|
+
* @param {Map<number, Array<{labelId: number, bitmap: Bitmap}>>} byOwner
|
|
39
|
+
* @param {{ gid: number, i2l: Map<number, string>, g2n: Map<number, string> }} ctx
|
|
40
|
+
* @returns {Array<{neighborId: string, label: string}>}
|
|
41
|
+
*/
|
|
42
|
+
function resolveAllLabels(byOwner, ctx) {
|
|
43
|
+
const { gid, i2l, g2n } = ctx;
|
|
44
|
+
const entries = byOwner.get(gid);
|
|
45
|
+
if (!entries) {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
/** @type {import('../../ports/NeighborProviderPort.js').NeighborEdge[]} */
|
|
49
|
+
const out = [];
|
|
50
|
+
for (const { labelId, bitmap } of entries) {
|
|
51
|
+
expandBitmap(bitmap, i2l.get(labelId) ?? '', { g2n, out });
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @typedef {{ path: string, buf: Uint8Array }} ShardItem
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Classifies loaded path/buf pairs into meta, labels, and edge buckets.
|
|
62
|
+
*
|
|
63
|
+
* @param {ShardItem[]} items
|
|
64
|
+
* @returns {{ meta: ShardItem[], labels: Uint8Array|null, edges: ShardItem[] }}
|
|
65
|
+
*/
|
|
66
|
+
function classifyShards(items) {
|
|
67
|
+
/** @type {ShardItem[]} */
|
|
68
|
+
const meta = [];
|
|
69
|
+
/** @type {Uint8Array|null} */
|
|
70
|
+
let labels = null;
|
|
71
|
+
/** @type {ShardItem[]} */
|
|
72
|
+
const edges = [];
|
|
73
|
+
|
|
74
|
+
for (const item of items) {
|
|
75
|
+
const { path } = item;
|
|
76
|
+
if (path.startsWith('meta_') && path.endsWith('.cbor')) {
|
|
77
|
+
meta.push(item);
|
|
78
|
+
} else if (path === 'labels.cbor') {
|
|
79
|
+
labels = item.buf;
|
|
80
|
+
} else if (path.endsWith('.cbor') && (path.startsWith('fwd_') || path.startsWith('rev_'))) {
|
|
81
|
+
edges.push(item);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return { meta, labels, edges };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** @typedef {typeof import('roaring').RoaringBitmap32} RoaringCtor */
|
|
89
|
+
|
|
90
|
+
export default class LogicalIndexReader {
|
|
91
|
+
/**
|
|
92
|
+
* @param {Object} [options]
|
|
93
|
+
* @param {import('../../ports/CodecPort.js').default} [options.codec]
|
|
94
|
+
*/
|
|
95
|
+
constructor({ codec } = {}) {
|
|
96
|
+
this._codec = codec || defaultCodec;
|
|
97
|
+
|
|
98
|
+
/** @type {Map<string, number>} */
|
|
99
|
+
this._nodeToGlobal = new Map();
|
|
100
|
+
/** @type {Map<number, string>} */
|
|
101
|
+
this._globalToNode = new Map();
|
|
102
|
+
/** @type {Map<string, Bitmap>} */
|
|
103
|
+
this._aliveBitmaps = new Map();
|
|
104
|
+
/** @type {Map<string, number>} */
|
|
105
|
+
this._labelRegistry = new Map();
|
|
106
|
+
/** @type {Map<number, string>} */
|
|
107
|
+
this._idToLabel = new Map();
|
|
108
|
+
/** @type {Map<string, Bitmap>} */
|
|
109
|
+
this._edgeFwd = new Map();
|
|
110
|
+
/** @type {Map<string, Bitmap>} */
|
|
111
|
+
this._edgeRev = new Map();
|
|
112
|
+
|
|
113
|
+
/** @type {Map<number, Array<{labelId: number, bitmap: Bitmap}>>} */
|
|
114
|
+
this._edgeByOwnerFwd = new Map();
|
|
115
|
+
/** @type {Map<number, Array<{labelId: number, bitmap: Bitmap}>>} */
|
|
116
|
+
this._edgeByOwnerRev = new Map();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Eagerly decodes all shards from an in-memory tree (Record<path, Uint8Array>).
|
|
121
|
+
*
|
|
122
|
+
* @param {Record<string, Uint8Array>} tree
|
|
123
|
+
* @returns {this}
|
|
124
|
+
*/
|
|
125
|
+
loadFromTree(tree) {
|
|
126
|
+
this._resetState();
|
|
127
|
+
const items = Object.entries(tree).map(([path, buf]) => ({ path, buf }));
|
|
128
|
+
this._processShards(items);
|
|
129
|
+
return this;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Loads all shards from OID→blob storage (async).
|
|
134
|
+
*
|
|
135
|
+
* @param {Record<string, string>} shardOids - path → blob OID
|
|
136
|
+
* @param {{ readBlob(oid: string): Promise<Uint8Array> }} storage
|
|
137
|
+
* @returns {Promise<this>}
|
|
138
|
+
*/
|
|
139
|
+
async loadFromOids(shardOids, storage) {
|
|
140
|
+
this._resetState();
|
|
141
|
+
const entries = Object.entries(shardOids);
|
|
142
|
+
const items = await Promise.all(
|
|
143
|
+
entries.map(async ([path, oid]) => ({ path, buf: await storage.readBlob(oid) }))
|
|
144
|
+
);
|
|
145
|
+
this._processShards(items);
|
|
146
|
+
return this;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Returns a LogicalIndex interface object.
|
|
151
|
+
*
|
|
152
|
+
* @returns {LogicalIndex}
|
|
153
|
+
*/
|
|
154
|
+
toLogicalIndex() {
|
|
155
|
+
const { _nodeToGlobal: n2g, _globalToNode: g2n, _aliveBitmaps: alive,
|
|
156
|
+
_labelRegistry: lr, _idToLabel: i2l, _edgeFwd: fwd, _edgeRev: rev,
|
|
157
|
+
_edgeByOwnerFwd: byOwnerFwd, _edgeByOwnerRev: byOwnerRev } = this;
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
getGlobalId: (nodeId) => n2g.get(nodeId),
|
|
161
|
+
getNodeId: (globalId) => g2n.get(globalId),
|
|
162
|
+
getLabelRegistry: () => lr,
|
|
163
|
+
|
|
164
|
+
isAlive(nodeId) {
|
|
165
|
+
const gid = n2g.get(nodeId);
|
|
166
|
+
if (gid === undefined) {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
const bitmap = alive.get(computeShardKey(nodeId));
|
|
170
|
+
return bitmap ? bitmap.has(gid) : false;
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
getEdges(nodeId, direction, filterLabelIds) {
|
|
174
|
+
const gid = n2g.get(nodeId);
|
|
175
|
+
if (gid === undefined) {
|
|
176
|
+
return [];
|
|
177
|
+
}
|
|
178
|
+
const dir = direction === 'out' ? 'fwd' : 'rev';
|
|
179
|
+
|
|
180
|
+
if (!filterLabelIds) {
|
|
181
|
+
const byOwner = dir === 'fwd' ? byOwnerFwd : byOwnerRev;
|
|
182
|
+
return resolveAllLabels(byOwner, { gid, i2l, g2n });
|
|
183
|
+
}
|
|
184
|
+
const store = dir === 'fwd' ? fwd : rev;
|
|
185
|
+
/** @type {import('../../ports/NeighborProviderPort.js').NeighborEdge[]} */
|
|
186
|
+
const out = [];
|
|
187
|
+
for (const labelId of filterLabelIds) {
|
|
188
|
+
const bitmap = store.get(`${dir}:${labelId}:${gid}`);
|
|
189
|
+
if (bitmap) {
|
|
190
|
+
expandBitmap(bitmap, i2l.get(labelId) ?? '', { g2n, out });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return out;
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ── Private ─────────────────────────────────────────────────────────────────
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Processes classified shards in deterministic order.
|
|
202
|
+
*
|
|
203
|
+
* @param {ShardItem[]} items
|
|
204
|
+
* @private
|
|
205
|
+
*/
|
|
206
|
+
_processShards(items) {
|
|
207
|
+
const Ctor = getRoaringBitmap32();
|
|
208
|
+
const { meta, labels, edges } = classifyShards(items);
|
|
209
|
+
|
|
210
|
+
for (const { path, buf } of meta) {
|
|
211
|
+
this._decodeMeta(path, buf, Ctor);
|
|
212
|
+
}
|
|
213
|
+
if (labels) {
|
|
214
|
+
this._decodeLabels(labels);
|
|
215
|
+
}
|
|
216
|
+
for (const { path, buf } of edges) {
|
|
217
|
+
this._decodeEdgeShard(path.startsWith('fwd_') ? 'fwd' : 'rev', buf, Ctor);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* @param {string} path
|
|
223
|
+
* @param {Uint8Array} buf
|
|
224
|
+
* @param {RoaringCtor} Ctor
|
|
225
|
+
* @private
|
|
226
|
+
*/
|
|
227
|
+
_decodeMeta(path, buf, Ctor) {
|
|
228
|
+
const shardKey = path.slice(5, 7);
|
|
229
|
+
const meta = /** @type {{ nodeToGlobal: Array<[string, number]>|Record<string, number>, alive: Uint8Array|ArrayLike<number> }} */ (this._codec.decode(buf));
|
|
230
|
+
|
|
231
|
+
const entries = Array.isArray(meta.nodeToGlobal)
|
|
232
|
+
? meta.nodeToGlobal
|
|
233
|
+
: Object.entries(meta.nodeToGlobal);
|
|
234
|
+
for (const [nodeId, globalId] of entries) {
|
|
235
|
+
this._nodeToGlobal.set(nodeId, /** @type {number} */ (globalId));
|
|
236
|
+
this._globalToNode.set(/** @type {number} */ (globalId), nodeId);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (meta.alive && meta.alive.length > 0) {
|
|
240
|
+
this._aliveBitmaps.set(
|
|
241
|
+
shardKey,
|
|
242
|
+
Ctor.deserialize(toBytes(meta.alive), true)
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* @param {Uint8Array} buf
|
|
249
|
+
* @private
|
|
250
|
+
*/
|
|
251
|
+
_decodeLabels(buf) {
|
|
252
|
+
const decoded = /** @type {Record<string, number>|Array<[string, number]>} */ (this._codec.decode(buf));
|
|
253
|
+
const entries = Array.isArray(decoded) ? decoded : Object.entries(decoded);
|
|
254
|
+
for (const [label, id] of entries) {
|
|
255
|
+
this._labelRegistry.set(label, id);
|
|
256
|
+
this._idToLabel.set(id, label);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Clears all decoded state so the reader can be reused safely.
|
|
262
|
+
*
|
|
263
|
+
* @private
|
|
264
|
+
*/
|
|
265
|
+
_resetState() {
|
|
266
|
+
this._nodeToGlobal.clear();
|
|
267
|
+
this._globalToNode.clear();
|
|
268
|
+
this._aliveBitmaps.clear();
|
|
269
|
+
this._labelRegistry.clear();
|
|
270
|
+
this._idToLabel.clear();
|
|
271
|
+
this._edgeFwd.clear();
|
|
272
|
+
this._edgeRev.clear();
|
|
273
|
+
this._edgeByOwnerFwd.clear();
|
|
274
|
+
this._edgeByOwnerRev.clear();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* @param {string} dir - 'fwd' or 'rev'
|
|
279
|
+
* @param {Uint8Array} buf
|
|
280
|
+
* @param {RoaringCtor} Ctor
|
|
281
|
+
* @private
|
|
282
|
+
*/
|
|
283
|
+
_decodeEdgeShard(dir, buf, Ctor) {
|
|
284
|
+
const store = dir === 'fwd' ? this._edgeFwd : this._edgeRev;
|
|
285
|
+
const byOwner = dir === 'fwd' ? this._edgeByOwnerFwd : this._edgeByOwnerRev;
|
|
286
|
+
const decoded = /** @type {Record<string, Record<string, Uint8Array|ArrayLike<number>>>} */ (this._codec.decode(buf));
|
|
287
|
+
for (const [bucket, entries] of Object.entries(decoded)) {
|
|
288
|
+
for (const [gidStr, bitmapBytes] of Object.entries(entries)) {
|
|
289
|
+
const bitmap = Ctor.deserialize(toBytes(bitmapBytes), true);
|
|
290
|
+
store.set(`${dir}:${bucket}:${gidStr}`, bitmap);
|
|
291
|
+
this._indexByOwner(byOwner, { bucket, gidStr, bitmap });
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Adds a bitmap entry to the per-owner secondary index (non-'all' buckets only).
|
|
298
|
+
*
|
|
299
|
+
* @param {Map<number, Array<{labelId: number, bitmap: Bitmap}>>} byOwner
|
|
300
|
+
* @param {{ bucket: string, gidStr: string, bitmap: Bitmap }} entry
|
|
301
|
+
* @private
|
|
302
|
+
*/
|
|
303
|
+
_indexByOwner(byOwner, { bucket, gidStr, bitmap }) {
|
|
304
|
+
if (bucket === 'all') {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
const gid = parseInt(gidStr, 10);
|
|
308
|
+
let list = byOwner.get(gid);
|
|
309
|
+
if (!list) {
|
|
310
|
+
list = [];
|
|
311
|
+
byOwner.set(gid, list);
|
|
312
|
+
}
|
|
313
|
+
list.push({ labelId: parseInt(bucket, 10), bitmap });
|
|
314
|
+
}
|
|
315
|
+
}
|