@git-stunts/git-warp 11.2.1 → 11.3.3
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/bin/cli/commands/check.js +2 -2
- package/bin/cli/commands/doctor/checks.js +12 -12
- package/bin/cli/commands/doctor/index.js +2 -2
- package/bin/cli/commands/doctor/types.js +1 -1
- package/bin/cli/commands/history.js +12 -5
- package/bin/cli/commands/install-hooks.js +5 -5
- package/bin/cli/commands/materialize.js +2 -2
- package/bin/cli/commands/patch.js +142 -0
- package/bin/cli/commands/path.js +4 -4
- package/bin/cli/commands/query.js +54 -13
- package/bin/cli/commands/registry.js +4 -0
- package/bin/cli/commands/seek.js +17 -11
- package/bin/cli/commands/tree.js +230 -0
- package/bin/cli/commands/trust.js +3 -3
- package/bin/cli/commands/verify-audit.js +8 -7
- package/bin/cli/commands/view.js +6 -5
- package/bin/cli/infrastructure.js +26 -12
- package/bin/cli/shared.js +2 -2
- package/bin/cli/types.js +19 -8
- package/bin/presenters/index.js +35 -9
- package/bin/presenters/json.js +14 -12
- package/bin/presenters/text.js +155 -33
- package/index.d.ts +82 -22
- package/package.json +3 -2
- package/src/domain/WarpGraph.js +4 -1
- package/src/domain/crdt/ORSet.js +8 -8
- package/src/domain/errors/EmptyMessageError.js +2 -2
- package/src/domain/errors/ForkError.js +1 -1
- package/src/domain/errors/IndexError.js +1 -1
- package/src/domain/errors/OperationAbortedError.js +1 -1
- package/src/domain/errors/QueryError.js +1 -1
- package/src/domain/errors/SchemaUnsupportedError.js +1 -1
- package/src/domain/errors/ShardCorruptionError.js +2 -2
- package/src/domain/errors/ShardLoadError.js +2 -2
- package/src/domain/errors/ShardValidationError.js +4 -4
- package/src/domain/errors/StorageError.js +2 -2
- package/src/domain/errors/SyncError.js +1 -1
- package/src/domain/errors/TraversalError.js +1 -1
- package/src/domain/errors/TrustError.js +1 -1
- package/src/domain/errors/WarpError.js +2 -2
- package/src/domain/errors/WormholeError.js +1 -1
- package/src/domain/services/AuditReceiptService.js +6 -6
- package/src/domain/services/AuditVerifierService.js +52 -38
- package/src/domain/services/BitmapIndexBuilder.js +3 -3
- package/src/domain/services/BitmapIndexReader.js +28 -19
- package/src/domain/services/BoundaryTransitionRecord.js +18 -17
- package/src/domain/services/CheckpointSerializerV5.js +17 -16
- package/src/domain/services/CheckpointService.js +2 -2
- package/src/domain/services/CommitDagTraversalService.js +13 -13
- package/src/domain/services/DagPathFinding.js +7 -7
- package/src/domain/services/DagTopology.js +1 -1
- package/src/domain/services/DagTraversal.js +1 -1
- package/src/domain/services/HealthCheckService.js +1 -1
- package/src/domain/services/HookInstaller.js +1 -1
- package/src/domain/services/HttpSyncServer.js +92 -41
- package/src/domain/services/IndexRebuildService.js +7 -7
- package/src/domain/services/IndexStalenessChecker.js +4 -3
- package/src/domain/services/JoinReducer.js +11 -11
- package/src/domain/services/LogicalTraversal.js +1 -1
- package/src/domain/services/MessageCodecInternal.js +1 -1
- package/src/domain/services/MigrationService.js +1 -1
- package/src/domain/services/ObserverView.js +8 -8
- package/src/domain/services/PatchBuilderV2.js +42 -26
- package/src/domain/services/ProvenanceIndex.js +1 -1
- package/src/domain/services/ProvenancePayload.js +1 -1
- package/src/domain/services/QueryBuilder.js +3 -3
- package/src/domain/services/StateDiff.js +14 -11
- package/src/domain/services/StateSerializerV5.js +2 -2
- package/src/domain/services/StreamingBitmapIndexBuilder.js +26 -24
- package/src/domain/services/SyncAuthService.js +3 -2
- package/src/domain/services/SyncProtocol.js +25 -11
- package/src/domain/services/TemporalQuery.js +9 -6
- package/src/domain/services/TranslationCost.js +7 -5
- package/src/domain/services/WormholeService.js +16 -7
- package/src/domain/trust/TrustCanonical.js +3 -3
- package/src/domain/trust/TrustEvaluator.js +18 -3
- package/src/domain/trust/TrustRecordService.js +30 -23
- package/src/domain/trust/TrustStateBuilder.js +21 -8
- package/src/domain/trust/canonical.js +6 -6
- package/src/domain/types/TickReceipt.js +1 -1
- package/src/domain/types/WarpErrors.js +45 -0
- package/src/domain/types/WarpOptions.js +29 -0
- package/src/domain/types/WarpPersistence.js +41 -0
- package/src/domain/types/WarpTypes.js +2 -2
- package/src/domain/types/WarpTypesV2.js +2 -2
- package/src/domain/utils/MinHeap.js +6 -5
- package/src/domain/utils/canonicalStringify.js +5 -4
- package/src/domain/utils/roaring.js +31 -5
- package/src/domain/warp/PatchSession.js +9 -18
- package/src/domain/warp/_wiredMethods.d.ts +199 -45
- package/src/domain/warp/checkpoint.methods.js +5 -1
- package/src/domain/warp/fork.methods.js +2 -2
- package/src/domain/warp/materialize.methods.js +55 -5
- package/src/domain/warp/materializeAdvanced.methods.js +15 -4
- package/src/domain/warp/patch.methods.js +54 -29
- package/src/domain/warp/provenance.methods.js +5 -3
- package/src/domain/warp/query.methods.js +6 -5
- package/src/domain/warp/sync.methods.js +16 -11
- package/src/globals.d.ts +64 -0
- package/src/infrastructure/adapters/BunHttpAdapter.js +14 -9
- package/src/infrastructure/adapters/CasSeekCacheAdapter.js +9 -4
- package/src/infrastructure/adapters/DenoHttpAdapter.js +5 -6
- package/src/infrastructure/adapters/GitGraphAdapter.js +14 -12
- package/src/infrastructure/adapters/NodeHttpAdapter.js +2 -2
- package/src/infrastructure/adapters/WebCryptoAdapter.js +2 -2
- package/src/visualization/layouts/converters.js +2 -2
- package/src/visualization/layouts/elkAdapter.js +1 -1
- package/src/visualization/layouts/elkLayout.js +10 -7
- package/src/visualization/layouts/index.js +1 -1
- package/src/visualization/renderers/ascii/seek.js +16 -6
- package/src/visualization/renderers/svg/index.js +1 -1
|
@@ -8,15 +8,56 @@
|
|
|
8
8
|
* @module domain/services/HttpSyncServer
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import { z } from 'zod';
|
|
11
12
|
import SyncAuthService from './SyncAuthService.js';
|
|
12
13
|
|
|
13
14
|
const DEFAULT_MAX_REQUEST_BYTES = 4 * 1024 * 1024;
|
|
15
|
+
const MAX_REQUEST_BYTES_CEILING = 128 * 1024 * 1024; // 134217728
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Zod schema for HttpSyncServer constructor options.
|
|
19
|
+
* @private
|
|
20
|
+
*/
|
|
21
|
+
const authSchema = z.object({
|
|
22
|
+
mode: z.enum(['enforce', 'log-only']).default('enforce'),
|
|
23
|
+
keys: z.record(z.string()).refine(
|
|
24
|
+
(obj) => Object.keys(obj).length > 0,
|
|
25
|
+
'auth.keys must not be empty',
|
|
26
|
+
),
|
|
27
|
+
crypto: /** @type {z.ZodType<import('../../ports/CryptoPort.js').default>} */ (z.custom((v) => v === undefined || (typeof v === 'object' && v !== null))).optional(),
|
|
28
|
+
logger: /** @type {z.ZodType<import('../../ports/LoggerPort.js').default>} */ (z.custom((v) => v === undefined || (typeof v === 'object' && v !== null))).optional(),
|
|
29
|
+
wallClockMs: /** @type {z.ZodType<() => number>} */ (z.custom((v) => v === undefined || typeof v === 'function')).optional(),
|
|
30
|
+
}).strict();
|
|
31
|
+
|
|
32
|
+
const optionsSchema = z.object({
|
|
33
|
+
httpPort: /** @type {z.ZodType<import('../../ports/HttpServerPort.js').default>} */ (z.custom(
|
|
34
|
+
(v) => v !== null && v !== undefined && typeof v === 'object',
|
|
35
|
+
'httpPort must be a non-null object',
|
|
36
|
+
)),
|
|
37
|
+
graph: /** @type {z.ZodType<import('../WarpGraph.js').default>} */ (z.custom(
|
|
38
|
+
(v) => v !== null && v !== undefined && typeof v === 'object',
|
|
39
|
+
'graph must be a non-null object',
|
|
40
|
+
)),
|
|
41
|
+
maxRequestBytes: z.number().int().positive().max(MAX_REQUEST_BYTES_CEILING).default(DEFAULT_MAX_REQUEST_BYTES),
|
|
42
|
+
path: z.string().startsWith('/').default('/sync'),
|
|
43
|
+
host: z.string().min(1).default('127.0.0.1'),
|
|
44
|
+
auth: authSchema.optional(),
|
|
45
|
+
allowedWriters: z.array(z.string()).optional(),
|
|
46
|
+
}).strict().superRefine((data, ctx) => {
|
|
47
|
+
if (data.allowedWriters && data.allowedWriters.length > 0 && !data.auth) {
|
|
48
|
+
ctx.addIssue({
|
|
49
|
+
code: z.ZodIssueCode.custom,
|
|
50
|
+
message: 'allowedWriters requires auth.keys to be configured',
|
|
51
|
+
path: ['allowedWriters'],
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
});
|
|
14
55
|
|
|
15
56
|
/**
|
|
16
57
|
* Recursively sorts object keys for deterministic JSON output.
|
|
17
58
|
*
|
|
18
|
-
* @param {
|
|
19
|
-
* @returns {
|
|
59
|
+
* @param {unknown} value - Any JSON-serializable value
|
|
60
|
+
* @returns {unknown} The canonicalized value with sorted object keys
|
|
20
61
|
* @private
|
|
21
62
|
*/
|
|
22
63
|
function canonicalizeJson(value) {
|
|
@@ -24,10 +65,10 @@ function canonicalizeJson(value) {
|
|
|
24
65
|
return value.map(canonicalizeJson);
|
|
25
66
|
}
|
|
26
67
|
if (value && typeof value === 'object') {
|
|
27
|
-
/** @type {{ [x: string]:
|
|
68
|
+
/** @type {{ [x: string]: unknown }} */
|
|
28
69
|
const sorted = {};
|
|
29
70
|
for (const key of Object.keys(value).sort()) {
|
|
30
|
-
sorted[key] = canonicalizeJson(/** @type {{ [x: string]:
|
|
71
|
+
sorted[key] = canonicalizeJson(/** @type {{ [x: string]: unknown }} */ (value)[key]);
|
|
31
72
|
}
|
|
32
73
|
return sorted;
|
|
33
74
|
}
|
|
@@ -37,7 +78,7 @@ function canonicalizeJson(value) {
|
|
|
37
78
|
/**
|
|
38
79
|
* Produces a canonical JSON string with sorted keys.
|
|
39
80
|
*
|
|
40
|
-
* @param {
|
|
81
|
+
* @param {unknown} value - Any JSON-serializable value
|
|
41
82
|
* @returns {string} Canonical JSON string
|
|
42
83
|
* @private
|
|
43
84
|
*/
|
|
@@ -64,7 +105,7 @@ function errorResponse(status, message) {
|
|
|
64
105
|
/**
|
|
65
106
|
* Builds a JSON success response with canonical key ordering.
|
|
66
107
|
*
|
|
67
|
-
* @param {
|
|
108
|
+
* @param {unknown} data - Response payload
|
|
68
109
|
* @returns {{ status: number, headers: Object, body: string }}
|
|
69
110
|
* @private
|
|
70
111
|
*/
|
|
@@ -79,7 +120,7 @@ function jsonResponse(data) {
|
|
|
79
120
|
/**
|
|
80
121
|
* Validates that a sync request object has the expected shape.
|
|
81
122
|
*
|
|
82
|
-
* @param {
|
|
123
|
+
* @param {unknown} parsed - Parsed JSON body
|
|
83
124
|
* @returns {boolean} True if valid
|
|
84
125
|
* @private
|
|
85
126
|
*/
|
|
@@ -87,10 +128,11 @@ function isValidSyncRequest(parsed) {
|
|
|
87
128
|
if (!parsed || typeof parsed !== 'object') {
|
|
88
129
|
return false;
|
|
89
130
|
}
|
|
90
|
-
|
|
131
|
+
const rec = /** @type {Record<string, unknown>} */ (parsed);
|
|
132
|
+
if (rec.type !== 'sync-request') {
|
|
91
133
|
return false;
|
|
92
134
|
}
|
|
93
|
-
if (!
|
|
135
|
+
if (!rec.frontier || typeof rec.frontier !== 'object' || Array.isArray(rec.frontier)) {
|
|
94
136
|
return false;
|
|
95
137
|
}
|
|
96
138
|
return true;
|
|
@@ -160,7 +202,7 @@ function checkBodySize(body, maxBytes) {
|
|
|
160
202
|
* Parses and validates the request body as a sync request.
|
|
161
203
|
*
|
|
162
204
|
* @param {Buffer|undefined} body
|
|
163
|
-
* @returns {{ error: { status: number, headers: Object, body: string }, parsed: null } | { error: null, parsed:
|
|
205
|
+
* @returns {{ error: { status: number, headers: Object, body: string }, parsed: null } | { error: null, parsed: import('./SyncProtocol.js').SyncRequest }}
|
|
164
206
|
* @private
|
|
165
207
|
*/
|
|
166
208
|
function parseBody(body) {
|
|
@@ -183,19 +225,14 @@ function parseBody(body) {
|
|
|
183
225
|
/**
|
|
184
226
|
* Initializes auth service from config if present.
|
|
185
227
|
*
|
|
186
|
-
* @param {
|
|
228
|
+
* @param {z.infer<typeof authSchema>} [auth]
|
|
187
229
|
* @param {string[]} [allowedWriters]
|
|
188
230
|
* @returns {{ auth: SyncAuthService|null, authMode: string|null }}
|
|
189
231
|
* @private
|
|
190
232
|
*/
|
|
191
233
|
function initAuth(auth, allowedWriters) {
|
|
192
|
-
if (auth
|
|
193
|
-
|
|
194
|
-
const mode = auth.mode || 'enforce';
|
|
195
|
-
if (!VALID_MODES.has(mode)) {
|
|
196
|
-
throw new Error(`Invalid auth.mode: '${mode}'. Must be 'enforce' or 'log-only'.`);
|
|
197
|
-
}
|
|
198
|
-
return { auth: new SyncAuthService({ ...auth, allowedWriters }), authMode: mode };
|
|
234
|
+
if (auth) {
|
|
235
|
+
return { auth: new SyncAuthService({ ...auth, allowedWriters }), authMode: auth.mode };
|
|
199
236
|
}
|
|
200
237
|
return { auth: null, authMode: null };
|
|
201
238
|
}
|
|
@@ -204,26 +241,35 @@ export default class HttpSyncServer {
|
|
|
204
241
|
/**
|
|
205
242
|
* @param {Object} options
|
|
206
243
|
* @param {import('../../ports/HttpServerPort.js').default} options.httpPort - HTTP server port abstraction
|
|
207
|
-
* @param {{ processSyncRequest:
|
|
244
|
+
* @param {{ processSyncRequest: Function }} options.graph - WarpGraph instance (must expose processSyncRequest)
|
|
208
245
|
* @param {string} [options.path='/sync'] - URL path to handle sync requests on
|
|
209
246
|
* @param {string} [options.host='127.0.0.1'] - Host to bind
|
|
210
247
|
* @param {number} [options.maxRequestBytes=4194304] - Maximum request body size in bytes
|
|
211
248
|
* @param {{ keys: Record<string, string>, mode?: 'enforce'|'log-only', crypto?: import('../../ports/CryptoPort.js').default, logger?: import('../../ports/LoggerPort.js').default, wallClockMs?: () => number }} [options.auth] - Auth configuration
|
|
212
249
|
* @param {string[]} [options.allowedWriters] - Optional whitelist of allowed writer IDs
|
|
213
250
|
*/
|
|
214
|
-
constructor(
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
251
|
+
constructor(options) {
|
|
252
|
+
/** @type {z.infer<typeof optionsSchema>} */
|
|
253
|
+
let parsed;
|
|
254
|
+
try {
|
|
255
|
+
parsed = optionsSchema.parse(options);
|
|
256
|
+
} catch (err) {
|
|
257
|
+
if (err instanceof z.ZodError) {
|
|
258
|
+
const messages = err.issues.map((i) => i.message).join('; ');
|
|
259
|
+
throw new Error(`HttpSyncServer config: ${messages}`);
|
|
260
|
+
}
|
|
261
|
+
throw err;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
this._httpPort = parsed.httpPort;
|
|
265
|
+
this._graph = parsed.graph;
|
|
266
|
+
this._path = parsed.path;
|
|
267
|
+
this._host = parsed.host;
|
|
268
|
+
this._maxRequestBytes = parsed.maxRequestBytes;
|
|
220
269
|
this._server = null;
|
|
221
|
-
const authInit = initAuth(auth, allowedWriters);
|
|
270
|
+
const authInit = initAuth(parsed.auth, parsed.allowedWriters);
|
|
222
271
|
this._auth = authInit.auth;
|
|
223
272
|
this._authMode = authInit.authMode;
|
|
224
|
-
if (allowedWriters && !authInit.auth) {
|
|
225
|
-
throw new Error('allowedWriters requires auth.keys to be configured');
|
|
226
|
-
}
|
|
227
273
|
}
|
|
228
274
|
|
|
229
275
|
/**
|
|
@@ -234,7 +280,7 @@ export default class HttpSyncServer {
|
|
|
234
280
|
* null so the request proceeds.
|
|
235
281
|
*
|
|
236
282
|
* @param {{ method: string, url: string, headers: { [x: string]: string }, body: Buffer|undefined }} request
|
|
237
|
-
* @param {
|
|
283
|
+
* @param {Record<string, unknown>} parsed - Parsed sync request body
|
|
238
284
|
* @returns {Promise<{ status: number, headers: Object, body: string }|null>}
|
|
239
285
|
* @private
|
|
240
286
|
*/
|
|
@@ -264,29 +310,31 @@ export default class HttpSyncServer {
|
|
|
264
310
|
return null;
|
|
265
311
|
}
|
|
266
312
|
|
|
267
|
-
/** @param {{ method: string, url: string, headers:
|
|
313
|
+
/** @param {{ method: string, url: string, headers: Object, body: Buffer|undefined }} request */
|
|
268
314
|
async _handleRequest(request) {
|
|
269
|
-
|
|
315
|
+
/** @type {{ method: string, url: string, headers: Record<string, string>, body: Buffer|undefined }} */
|
|
316
|
+
const req = { ...request, headers: /** @type {Record<string, string>} */ (request.headers) };
|
|
317
|
+
const contentTypeError = checkContentType(req.headers);
|
|
270
318
|
if (contentTypeError) {
|
|
271
319
|
return contentTypeError;
|
|
272
320
|
}
|
|
273
321
|
|
|
274
|
-
const routeError = validateRoute(
|
|
322
|
+
const routeError = validateRoute(req, this._path, this._host);
|
|
275
323
|
if (routeError) {
|
|
276
324
|
return routeError;
|
|
277
325
|
}
|
|
278
326
|
|
|
279
|
-
const sizeError = checkBodySize(
|
|
327
|
+
const sizeError = checkBodySize(req.body, this._maxRequestBytes);
|
|
280
328
|
if (sizeError) {
|
|
281
329
|
return sizeError;
|
|
282
330
|
}
|
|
283
331
|
|
|
284
|
-
const { error, parsed } = parseBody(
|
|
332
|
+
const { error, parsed } = parseBody(req.body);
|
|
285
333
|
if (error) {
|
|
286
334
|
return error;
|
|
287
335
|
}
|
|
288
336
|
|
|
289
|
-
const authError = await this._authorize(
|
|
337
|
+
const authError = await this._authorize(req, parsed);
|
|
290
338
|
if (authError) {
|
|
291
339
|
return authError;
|
|
292
340
|
}
|
|
@@ -294,8 +342,8 @@ export default class HttpSyncServer {
|
|
|
294
342
|
try {
|
|
295
343
|
const response = await this._graph.processSyncRequest(parsed);
|
|
296
344
|
return jsonResponse(response);
|
|
297
|
-
} catch (err) {
|
|
298
|
-
return errorResponse(500,
|
|
345
|
+
} catch (/** @type {unknown} */ err) {
|
|
346
|
+
return errorResponse(500, err instanceof Error ? err.message : 'Sync failed');
|
|
299
347
|
}
|
|
300
348
|
}
|
|
301
349
|
|
|
@@ -311,11 +359,14 @@ export default class HttpSyncServer {
|
|
|
311
359
|
throw new Error('listen() requires a numeric port');
|
|
312
360
|
}
|
|
313
361
|
|
|
314
|
-
|
|
362
|
+
/** @type {{ listen: Function, close: Function, address: Function }} */
|
|
363
|
+
const server = this._httpPort.createServer(
|
|
364
|
+
(/** @type {{ method: string, url: string, headers: Object, body: Buffer|undefined }} */ request) => this._handleRequest(request),
|
|
365
|
+
);
|
|
315
366
|
this._server = server;
|
|
316
367
|
|
|
317
368
|
await /** @type {Promise<void>} */ (new Promise((resolve, reject) => {
|
|
318
|
-
server.listen(port, this._host, (/** @type {
|
|
369
|
+
server.listen(port, this._host, (/** @type {Error|null} */ err) => {
|
|
319
370
|
if (err) {
|
|
320
371
|
reject(err);
|
|
321
372
|
} else {
|
|
@@ -332,7 +383,7 @@ export default class HttpSyncServer {
|
|
|
332
383
|
url,
|
|
333
384
|
close: () =>
|
|
334
385
|
/** @type {Promise<void>} */ (new Promise((resolve, reject) => {
|
|
335
|
-
server.close((/** @type {
|
|
386
|
+
server.close((/** @type {Error|null} */ err) => {
|
|
336
387
|
if (err) {
|
|
337
388
|
reject(err);
|
|
338
389
|
} else {
|
|
@@ -50,7 +50,7 @@ export default class IndexRebuildService {
|
|
|
50
50
|
* @throws {Error} If graphService is not provided
|
|
51
51
|
* @throws {Error} If storage adapter is not provided
|
|
52
52
|
*/
|
|
53
|
-
constructor({ graphService, storage, logger = nullLogger, codec, crypto } = /** @type {
|
|
53
|
+
constructor({ graphService, storage, logger = nullLogger, codec, crypto } = /** @type {{ graphService: { iterateNodes: (opts: { ref: string, limit: number }) => AsyncIterable<{ sha: string, parents: string[] }> }, storage: import('../../ports/IndexStoragePort.js').default }} */ ({})) {
|
|
54
54
|
if (!graphService) {
|
|
55
55
|
throw new Error('IndexRebuildService requires a graphService');
|
|
56
56
|
}
|
|
@@ -156,7 +156,7 @@ export default class IndexRebuildService {
|
|
|
156
156
|
operation: 'rebuild',
|
|
157
157
|
ref,
|
|
158
158
|
mode,
|
|
159
|
-
error:
|
|
159
|
+
error: err instanceof Error ? err.message : String(err),
|
|
160
160
|
durationMs,
|
|
161
161
|
});
|
|
162
162
|
throw err;
|
|
@@ -247,12 +247,12 @@ export default class IndexRebuildService {
|
|
|
247
247
|
* @private
|
|
248
248
|
*/
|
|
249
249
|
async _rebuildStreaming(ref, { limit, maxMemoryBytes, onFlush, onProgress, signal, frontier }) {
|
|
250
|
-
const builder = new StreamingBitmapIndexBuilder(
|
|
250
|
+
const builder = new StreamingBitmapIndexBuilder({
|
|
251
251
|
storage: this.storage,
|
|
252
252
|
maxMemoryBytes,
|
|
253
253
|
onFlush,
|
|
254
254
|
crypto: this._crypto,
|
|
255
|
-
})
|
|
255
|
+
});
|
|
256
256
|
|
|
257
257
|
let processedNodes = 0;
|
|
258
258
|
|
|
@@ -266,7 +266,7 @@ export default class IndexRebuildService {
|
|
|
266
266
|
if (processedNodes % 10000 === 0) {
|
|
267
267
|
checkAborted(signal, 'rebuild');
|
|
268
268
|
if (onProgress) {
|
|
269
|
-
const stats = /** @type {
|
|
269
|
+
const stats = /** @type {{ estimatedBitmapBytes: number }} */ (builder.getMemoryStats());
|
|
270
270
|
onProgress({
|
|
271
271
|
processedNodes,
|
|
272
272
|
currentMemoryBytes: stats.estimatedBitmapBytes,
|
|
@@ -275,7 +275,7 @@ export default class IndexRebuildService {
|
|
|
275
275
|
}
|
|
276
276
|
}
|
|
277
277
|
|
|
278
|
-
return await
|
|
278
|
+
return await builder.finalize({ signal, frontier });
|
|
279
279
|
}
|
|
280
280
|
|
|
281
281
|
/**
|
|
@@ -389,7 +389,7 @@ export default class IndexRebuildService {
|
|
|
389
389
|
|
|
390
390
|
// Staleness check
|
|
391
391
|
if (currentFrontier) {
|
|
392
|
-
const indexFrontier = await loadIndexFrontier(shardOids, /** @type {
|
|
392
|
+
const indexFrontier = await loadIndexFrontier(shardOids, /** @type {import('../../ports/IndexStoragePort.js').default & import('../../ports/BlobPort.js').default} */ (this.storage), { codec: this._codec });
|
|
393
393
|
if (indexFrontier) {
|
|
394
394
|
const result = checkStaleness(indexFrontier, currentFrontier);
|
|
395
395
|
if (result.stale) {
|
|
@@ -6,12 +6,13 @@
|
|
|
6
6
|
import defaultCodec from '../utils/defaultCodec.js';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* @param {
|
|
9
|
+
* @param {unknown} envelope
|
|
10
10
|
* @param {string} label
|
|
11
11
|
* @private
|
|
12
12
|
*/
|
|
13
13
|
function validateEnvelope(envelope, label) {
|
|
14
|
-
|
|
14
|
+
const rec = /** @type {Record<string, unknown>} */ (envelope);
|
|
15
|
+
if (!rec || typeof rec !== 'object' || !rec.frontier || typeof rec.frontier !== 'object') {
|
|
15
16
|
throw new Error(`invalid frontier envelope for ${label}`);
|
|
16
17
|
}
|
|
17
18
|
}
|
|
@@ -25,7 +26,7 @@ function validateEnvelope(envelope, label) {
|
|
|
25
26
|
* @param {import('../../ports/CodecPort.js').default} [options.codec] - Codec for deserialization
|
|
26
27
|
* @returns {Promise<Map<string, string>|null>} Frontier map, or null if not present (legacy index)
|
|
27
28
|
*/
|
|
28
|
-
export async function loadIndexFrontier(shardOids, storage, { codec } =
|
|
29
|
+
export async function loadIndexFrontier(shardOids, storage, { codec } = {}) {
|
|
29
30
|
const c = codec || defaultCodec;
|
|
30
31
|
const cborOid = shardOids['frontier.cbor'];
|
|
31
32
|
if (cborOid) {
|
|
@@ -29,7 +29,7 @@ export {
|
|
|
29
29
|
* @typedef {Object} WarpStateV5
|
|
30
30
|
* @property {import('../crdt/ORSet.js').ORSet} nodeAlive - ORSet of alive nodes
|
|
31
31
|
* @property {import('../crdt/ORSet.js').ORSet} edgeAlive - ORSet of alive edges
|
|
32
|
-
* @property {Map<string, import('../crdt/LWW.js').LWWRegister
|
|
32
|
+
* @property {Map<string, import('../crdt/LWW.js').LWWRegister<unknown>>} prop - Properties with LWW
|
|
33
33
|
* @property {import('../crdt/VersionVector.js').VersionVector} observedFrontier - Observed version vector
|
|
34
34
|
* @property {Map<string, import('../utils/EventId.js').EventId>} edgeBirthEvent - EdgeKey → EventId of most recent EdgeAdd (for clean-slate prop visibility)
|
|
35
35
|
*/
|
|
@@ -81,7 +81,7 @@ export function createEmptyStateV5() {
|
|
|
81
81
|
* @param {string} [op.to] - Target node ID (for EdgeAdd, EdgeRemove)
|
|
82
82
|
* @param {string} [op.label] - Edge label (for EdgeAdd, EdgeRemove)
|
|
83
83
|
* @param {string} [op.key] - Property key (for PropSet)
|
|
84
|
-
* @param {
|
|
84
|
+
* @param {unknown} [op.value] - Property value (for PropSet)
|
|
85
85
|
* @param {import('../utils/EventId.js').EventId} eventId - Event ID for causality tracking
|
|
86
86
|
* @returns {void}
|
|
87
87
|
*/
|
|
@@ -114,7 +114,7 @@ export function applyOpV2(state, op, eventId) {
|
|
|
114
114
|
// Uses EventId-based LWW, same as v4
|
|
115
115
|
const key = encodePropKey(/** @type {string} */ (op.node), /** @type {string} */ (op.key));
|
|
116
116
|
const current = state.prop.get(key);
|
|
117
|
-
state.prop.set(key, /** @type {import('../crdt/LWW.js').LWWRegister
|
|
117
|
+
state.prop.set(key, /** @type {import('../crdt/LWW.js').LWWRegister<unknown>} */ (lwwMax(current, lwwSet(eventId, op.value))));
|
|
118
118
|
break;
|
|
119
119
|
}
|
|
120
120
|
default:
|
|
@@ -290,11 +290,11 @@ function edgeRemoveOutcome(orset, op) {
|
|
|
290
290
|
* - `superseded`: An existing value with higher EventId wins
|
|
291
291
|
* - `redundant`: Exact same write (identical EventId)
|
|
292
292
|
*
|
|
293
|
-
* @param {Map<string, import('../crdt/LWW.js').LWWRegister
|
|
293
|
+
* @param {Map<string, import('../crdt/LWW.js').LWWRegister<unknown>>} propMap - The properties map keyed by encoded prop keys
|
|
294
294
|
* @param {Object} op - The PropSet operation
|
|
295
295
|
* @param {string} op.node - Node ID owning the property
|
|
296
296
|
* @param {string} op.key - Property key/name
|
|
297
|
-
* @param {
|
|
297
|
+
* @param {unknown} op.value - Property value to set
|
|
298
298
|
* @param {import('../utils/EventId.js').EventId} eventId - The event ID for this operation, used for LWW comparison
|
|
299
299
|
* @returns {{target: string, result: 'applied'|'superseded'|'redundant', reason?: string}}
|
|
300
300
|
* Outcome with encoded prop key as target; includes reason when superseded
|
|
@@ -347,7 +347,7 @@ function propSetOutcome(propMap, op, eventId) {
|
|
|
347
347
|
* @param {Object} patch - The patch to apply
|
|
348
348
|
* @param {string} patch.writer - Writer ID who created this patch
|
|
349
349
|
* @param {number} patch.lamport - Lamport timestamp of this patch
|
|
350
|
-
* @param {Array<{type: string, node?: string, dot?: import('../crdt/Dot.js').Dot, observedDots?: string[], from?: string, to?: string, label?: string, key?: string, value?:
|
|
350
|
+
* @param {Array<{type: string, node?: string, dot?: import('../crdt/Dot.js').Dot, observedDots?: string[], from?: string, to?: string, label?: string, key?: string, value?: unknown, oid?: string}>} patch.ops - Array of operations to apply
|
|
351
351
|
* @param {Map<string, number>|{[x: string]: number}} patch.context - Version vector context (Map or serialized form)
|
|
352
352
|
* @param {string} patchSha - The Git SHA of the patch commit (used for EventId creation)
|
|
353
353
|
* @param {boolean} [collectReceipts=false] - When true, computes and returns receipt data
|
|
@@ -470,16 +470,16 @@ export function joinStates(a, b) {
|
|
|
470
470
|
*
|
|
471
471
|
* This is a pure function that does not mutate its inputs.
|
|
472
472
|
*
|
|
473
|
-
* @param {Map<string, import('../crdt/LWW.js').LWWRegister
|
|
474
|
-
* @param {Map<string, import('../crdt/LWW.js').LWWRegister
|
|
475
|
-
* @returns {Map<string, import('../crdt/LWW.js').LWWRegister
|
|
473
|
+
* @param {Map<string, import('../crdt/LWW.js').LWWRegister<unknown>>} a - First property map
|
|
474
|
+
* @param {Map<string, import('../crdt/LWW.js').LWWRegister<unknown>>} b - Second property map
|
|
475
|
+
* @returns {Map<string, import('../crdt/LWW.js').LWWRegister<unknown>>} New map containing merged properties
|
|
476
476
|
*/
|
|
477
477
|
function mergeProps(a, b) {
|
|
478
478
|
const result = new Map(a);
|
|
479
479
|
|
|
480
480
|
for (const [key, regB] of b) {
|
|
481
481
|
const regA = result.get(key);
|
|
482
|
-
result.set(key, /** @type {import('../crdt/LWW.js').LWWRegister
|
|
482
|
+
result.set(key, /** @type {import('../crdt/LWW.js').LWWRegister<unknown>} */ (lwwMax(regA, regB)));
|
|
483
483
|
}
|
|
484
484
|
|
|
485
485
|
return result;
|
|
@@ -530,7 +530,7 @@ function mergeEdgeBirthEvent(a, b) {
|
|
|
530
530
|
* - When `options.receipts` is true, returns a TickReceipt per patch for
|
|
531
531
|
* provenance tracking and debugging.
|
|
532
532
|
*
|
|
533
|
-
* @param {Array<{patch: {writer: string, lamport: number, ops: Array<{type: string, node?: string, dot?: import('../crdt/Dot.js').Dot, observedDots?: string[], from?: string, to?: string, label?: string, key?: string, value?:
|
|
533
|
+
* @param {Array<{patch: {writer: string, lamport: number, ops: Array<{type: string, node?: string, dot?: import('../crdt/Dot.js').Dot, observedDots?: string[], from?: string, to?: string, label?: string, key?: string, value?: unknown, oid?: string}>, context: Map<string, number>|{[x: string]: number}}, sha: string}>} patches - Array of patch objects with their Git SHAs
|
|
534
534
|
* @param {WarpStateV5} [initialState] - Optional starting state (for incremental materialization from checkpoint)
|
|
535
535
|
* @param {Object} [options] - Optional configuration
|
|
536
536
|
* @param {boolean} [options.receipts=false] - When true, collect and return TickReceipts
|
|
@@ -152,7 +152,7 @@ export default class LogicalTraversal {
|
|
|
152
152
|
* @throws {TraversalError} If the labelFilter is invalid (INVALID_LABEL_FILTER)
|
|
153
153
|
*/
|
|
154
154
|
async _prepare(start, { dir, labelFilter, maxDepth }) {
|
|
155
|
-
const materialized = await /** @type {
|
|
155
|
+
const materialized = await /** @type {{ _materializeGraph: () => Promise<{adjacency: {outgoing: Map<string, Array<{neighborId: string, label: string}>>, incoming: Map<string, Array<{neighborId: string, label: string}>>}}> }} */ (this._graph)._materializeGraph();
|
|
156
156
|
|
|
157
157
|
if (!(await this._graph.hasNode(start))) {
|
|
158
158
|
throw new TraversalError(`Start node not found: ${start}`, {
|
|
@@ -66,7 +66,7 @@ const SHA256_PATTERN = /^[0-9a-f]{64}$/;
|
|
|
66
66
|
// -----------------------------------------------------------------------------
|
|
67
67
|
|
|
68
68
|
// Lazy singleton codec instance
|
|
69
|
-
/** @type {
|
|
69
|
+
/** @type {TrailerCodec|null} */
|
|
70
70
|
let _codec = null;
|
|
71
71
|
|
|
72
72
|
/**
|
|
@@ -16,7 +16,7 @@ import { createVersionVector, vvIncrement } from '../crdt/VersionVector.js';
|
|
|
16
16
|
* @param {Object} v4State - The V4 materialized state (visible projection)
|
|
17
17
|
* @param {Map<string, {value: boolean}>} v4State.nodeAlive - V4 node alive map
|
|
18
18
|
* @param {Map<string, {value: boolean}>} v4State.edgeAlive - V4 edge alive map
|
|
19
|
-
* @param {Map<string,
|
|
19
|
+
* @param {Map<string, import('../crdt/LWW.js').LWWRegister<unknown>>} v4State.prop - V4 property map
|
|
20
20
|
* @param {string} migrationWriterId - Writer ID to use for synthetic dots
|
|
21
21
|
* @returns {import('./JoinReducer.js').WarpStateV5} The migrated V5 state
|
|
22
22
|
*/
|
|
@@ -43,10 +43,10 @@ function matchGlob(pattern, str) {
|
|
|
43
43
|
* - If `expose` is provided and non-empty, only keys in `expose` are included.
|
|
44
44
|
* - If `expose` is absent/empty, all non-redacted keys are included.
|
|
45
45
|
*
|
|
46
|
-
* @param {Map<string,
|
|
46
|
+
* @param {Map<string, unknown>} propsMap - The full properties Map
|
|
47
47
|
* @param {string[]|undefined} expose - Whitelist of property keys to include
|
|
48
48
|
* @param {string[]|undefined} redact - Blacklist of property keys to exclude
|
|
49
|
-
* @returns {Map<string,
|
|
49
|
+
* @returns {Map<string, unknown>} Filtered properties Map
|
|
50
50
|
*/
|
|
51
51
|
function filterProps(propsMap, expose, redact) {
|
|
52
52
|
const redactSet = redact && redact.length > 0 ? new Set(redact) : null;
|
|
@@ -102,7 +102,7 @@ export default class ObserverView {
|
|
|
102
102
|
this._graph = graph;
|
|
103
103
|
|
|
104
104
|
/** @type {LogicalTraversal} */
|
|
105
|
-
this.traverse = new LogicalTraversal(/** @type {
|
|
105
|
+
this.traverse = new LogicalTraversal(/** @type {import('../WarpGraph.js').default} */ (/** @type {unknown} */ (this)));
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
/**
|
|
@@ -124,11 +124,11 @@ export default class ObserverView {
|
|
|
124
124
|
* Builds a filtered adjacency structure that only includes edges
|
|
125
125
|
* where both endpoints pass the match filter.
|
|
126
126
|
*
|
|
127
|
-
* @returns {Promise<{state:
|
|
127
|
+
* @returns {Promise<{state: unknown, stateHash: string, adjacency: {outgoing: Map<string, unknown[]>, incoming: Map<string, unknown[]>}}>}
|
|
128
128
|
* @private
|
|
129
129
|
*/
|
|
130
130
|
async _materializeGraph() {
|
|
131
|
-
const materialized = await /** @type {
|
|
131
|
+
const materialized = await /** @type {{ _materializeGraph: () => Promise<{state: import('./JoinReducer.js').WarpStateV5, stateHash: string, adjacency: {outgoing: Map<string, Array<{neighborId: string, label: string}>>, incoming: Map<string, Array<{neighborId: string, label: string}>>}}> }} */ (this._graph)._materializeGraph();
|
|
132
132
|
const { state, stateHash } = materialized;
|
|
133
133
|
|
|
134
134
|
// Build filtered adjacency: only edges where both endpoints match
|
|
@@ -212,7 +212,7 @@ export default class ObserverView {
|
|
|
212
212
|
* the observer pattern.
|
|
213
213
|
*
|
|
214
214
|
* @param {string} nodeId - The node ID to get properties for
|
|
215
|
-
* @returns {Promise<Map<string,
|
|
215
|
+
* @returns {Promise<Map<string, unknown>|null>} Filtered properties Map, or null
|
|
216
216
|
*/
|
|
217
217
|
async getNodeProps(nodeId) {
|
|
218
218
|
if (!matchGlob(this._matchPattern, nodeId)) {
|
|
@@ -234,7 +234,7 @@ export default class ObserverView {
|
|
|
234
234
|
*
|
|
235
235
|
* An edge is visible only when both endpoints match the observer pattern.
|
|
236
236
|
*
|
|
237
|
-
* @returns {Promise<Array<{from: string, to: string, label: string, props: Record<string,
|
|
237
|
+
* @returns {Promise<Array<{from: string, to: string, label: string, props: Record<string, unknown>}>>}
|
|
238
238
|
*/
|
|
239
239
|
async getEdges() {
|
|
240
240
|
const allEdges = await this._graph.getEdges();
|
|
@@ -260,6 +260,6 @@ export default class ObserverView {
|
|
|
260
260
|
* @returns {QueryBuilder} A query builder scoped to this observer
|
|
261
261
|
*/
|
|
262
262
|
query() {
|
|
263
|
-
return new QueryBuilder(/** @type {
|
|
263
|
+
return new QueryBuilder(/** @type {import('../WarpGraph.js').default} */ (/** @type {unknown} */ (this)));
|
|
264
264
|
}
|
|
265
265
|
}
|