@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.
Files changed (49) hide show
  1. package/README.md +137 -10
  2. package/bin/cli/commands/registry.js +4 -0
  3. package/bin/cli/commands/reindex.js +41 -0
  4. package/bin/cli/commands/verify-index.js +59 -0
  5. package/bin/cli/infrastructure.js +7 -2
  6. package/bin/cli/schemas.js +19 -0
  7. package/bin/cli/types.js +2 -0
  8. package/index.d.ts +52 -15
  9. package/package.json +3 -2
  10. package/src/domain/WarpGraph.js +40 -0
  11. package/src/domain/errors/ShardIdOverflowError.js +28 -0
  12. package/src/domain/errors/index.js +1 -0
  13. package/src/domain/services/AdjacencyNeighborProvider.js +140 -0
  14. package/src/domain/services/BitmapNeighborProvider.js +178 -0
  15. package/src/domain/services/CheckpointMessageCodec.js +3 -3
  16. package/src/domain/services/CheckpointService.js +77 -12
  17. package/src/domain/services/GraphTraversal.js +1239 -0
  18. package/src/domain/services/IncrementalIndexUpdater.js +765 -0
  19. package/src/domain/services/JoinReducer.js +233 -5
  20. package/src/domain/services/LogicalBitmapIndexBuilder.js +323 -0
  21. package/src/domain/services/LogicalIndexBuildService.js +108 -0
  22. package/src/domain/services/LogicalIndexReader.js +315 -0
  23. package/src/domain/services/LogicalTraversal.js +321 -202
  24. package/src/domain/services/MaterializedViewService.js +379 -0
  25. package/src/domain/services/ObserverView.js +132 -69
  26. package/src/domain/services/PatchBuilderV2.js +3 -3
  27. package/src/domain/services/PropertyIndexBuilder.js +64 -0
  28. package/src/domain/services/PropertyIndexReader.js +111 -0
  29. package/src/domain/services/QueryBuilder.js +15 -44
  30. package/src/domain/services/TemporalQuery.js +128 -14
  31. package/src/domain/services/TranslationCost.js +8 -24
  32. package/src/domain/types/PatchDiff.js +90 -0
  33. package/src/domain/types/WarpTypesV2.js +4 -4
  34. package/src/domain/utils/MinHeap.js +45 -17
  35. package/src/domain/utils/canonicalCbor.js +36 -0
  36. package/src/domain/utils/fnv1a.js +20 -0
  37. package/src/domain/utils/matchGlob.js +51 -0
  38. package/src/domain/utils/roaring.js +14 -3
  39. package/src/domain/utils/shardKey.js +40 -0
  40. package/src/domain/utils/toBytes.js +17 -0
  41. package/src/domain/warp/_wiredMethods.d.ts +7 -1
  42. package/src/domain/warp/checkpoint.methods.js +21 -5
  43. package/src/domain/warp/materialize.methods.js +17 -5
  44. package/src/domain/warp/materializeAdvanced.methods.js +142 -3
  45. package/src/domain/warp/query.methods.js +83 -15
  46. package/src/infrastructure/adapters/CasSeekCacheAdapter.js +26 -5
  47. package/src/ports/BlobPort.js +1 -1
  48. package/src/ports/NeighborProviderPort.js +59 -0
  49. 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
+ }