@git-stunts/git-warp 10.7.0 → 11.2.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/README.md +53 -32
- package/SECURITY.md +64 -0
- package/bin/cli/commands/check.js +168 -0
- package/bin/cli/commands/doctor/checks.js +422 -0
- package/bin/cli/commands/doctor/codes.js +46 -0
- package/bin/cli/commands/doctor/index.js +239 -0
- package/bin/cli/commands/doctor/types.js +89 -0
- package/bin/cli/commands/history.js +73 -0
- package/bin/cli/commands/info.js +139 -0
- package/bin/cli/commands/install-hooks.js +128 -0
- package/bin/cli/commands/materialize.js +99 -0
- package/bin/cli/commands/path.js +88 -0
- package/bin/cli/commands/query.js +194 -0
- package/bin/cli/commands/registry.js +28 -0
- package/bin/cli/commands/seek.js +592 -0
- package/bin/cli/commands/trust.js +154 -0
- package/bin/cli/commands/verify-audit.js +113 -0
- package/bin/cli/commands/view.js +45 -0
- package/bin/cli/infrastructure.js +336 -0
- package/bin/cli/schemas.js +177 -0
- package/bin/cli/shared.js +244 -0
- package/bin/cli/types.js +85 -0
- package/bin/presenters/index.js +214 -0
- package/bin/presenters/json.js +66 -0
- package/bin/presenters/text.js +543 -0
- package/bin/warp-graph.js +19 -2824
- package/index.d.ts +32 -2
- package/index.js +2 -0
- package/package.json +9 -7
- package/src/domain/WarpGraph.js +106 -3252
- package/src/domain/errors/QueryError.js +2 -2
- package/src/domain/errors/TrustError.js +29 -0
- package/src/domain/errors/index.js +1 -0
- package/src/domain/services/AuditMessageCodec.js +137 -0
- package/src/domain/services/AuditReceiptService.js +471 -0
- package/src/domain/services/AuditVerifierService.js +693 -0
- package/src/domain/services/HttpSyncServer.js +36 -22
- package/src/domain/services/MessageCodecInternal.js +3 -0
- package/src/domain/services/MessageSchemaDetector.js +2 -2
- package/src/domain/services/SyncAuthService.js +69 -3
- package/src/domain/services/WarpMessageCodec.js +4 -1
- package/src/domain/trust/TrustCanonical.js +42 -0
- package/src/domain/trust/TrustCrypto.js +111 -0
- package/src/domain/trust/TrustEvaluator.js +180 -0
- package/src/domain/trust/TrustRecordService.js +274 -0
- package/src/domain/trust/TrustStateBuilder.js +209 -0
- package/src/domain/trust/canonical.js +68 -0
- package/src/domain/trust/reasonCodes.js +64 -0
- package/src/domain/trust/schemas.js +160 -0
- package/src/domain/trust/verdict.js +42 -0
- package/src/domain/types/git-cas.d.ts +20 -0
- package/src/domain/utils/RefLayout.js +59 -0
- package/src/domain/warp/PatchSession.js +18 -0
- package/src/domain/warp/Writer.js +18 -3
- package/src/domain/warp/_internal.js +26 -0
- package/src/domain/warp/_wire.js +58 -0
- package/src/domain/warp/_wiredMethods.d.ts +100 -0
- package/src/domain/warp/checkpoint.methods.js +397 -0
- package/src/domain/warp/fork.methods.js +323 -0
- package/src/domain/warp/materialize.methods.js +188 -0
- package/src/domain/warp/materializeAdvanced.methods.js +339 -0
- package/src/domain/warp/patch.methods.js +529 -0
- package/src/domain/warp/provenance.methods.js +284 -0
- package/src/domain/warp/query.methods.js +279 -0
- package/src/domain/warp/subscribe.methods.js +272 -0
- package/src/domain/warp/sync.methods.js +549 -0
- package/src/infrastructure/adapters/GitGraphAdapter.js +67 -1
- package/src/infrastructure/adapters/InMemoryGraphAdapter.js +36 -0
- package/src/ports/CommitPort.js +10 -0
- package/src/ports/RefPort.js +17 -0
- package/src/hooks/post-merge.sh +0 -60
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checkpoint, GC, and coverage methods for WarpGraph.
|
|
3
|
+
*
|
|
4
|
+
* Every function uses `this` bound to a WarpGraph instance at runtime
|
|
5
|
+
* via wireWarpMethods().
|
|
6
|
+
*
|
|
7
|
+
* @module domain/warp/checkpoint.methods
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { QueryError, E_NO_STATE_MSG } from './_internal.js';
|
|
11
|
+
import { buildWriterRef, buildCheckpointRef, buildCoverageRef } from '../utils/RefLayout.js';
|
|
12
|
+
import { createFrontier, updateFrontier } from '../services/Frontier.js';
|
|
13
|
+
import { loadCheckpoint, create as createCheckpointCommit } from '../services/CheckpointService.js';
|
|
14
|
+
import { decodePatchMessage, detectMessageKind, encodeAnchorMessage } from '../services/WarpMessageCodec.js';
|
|
15
|
+
import { shouldRunGC, executeGC } from '../services/GCPolicy.js';
|
|
16
|
+
import { collectGCMetrics } from '../services/GCMetrics.js';
|
|
17
|
+
import { computeAppliedVV } from '../services/CheckpointSerializerV5.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Creates a checkpoint of the current graph state.
|
|
21
|
+
*
|
|
22
|
+
* Discovers all writers, builds a frontier of writer tips, materializes
|
|
23
|
+
* the current state, and creates a checkpoint commit with provenance.
|
|
24
|
+
*
|
|
25
|
+
* @this {import('../WarpGraph.js').default}
|
|
26
|
+
* @returns {Promise<string>} The checkpoint commit SHA
|
|
27
|
+
* @throws {Error} If materialization or commit creation fails
|
|
28
|
+
*/
|
|
29
|
+
export async function createCheckpoint() {
|
|
30
|
+
const t0 = this._clock.now();
|
|
31
|
+
try {
|
|
32
|
+
// 1. Discover all writers
|
|
33
|
+
const writers = await this.discoverWriters();
|
|
34
|
+
|
|
35
|
+
// 2. Build frontier (map of writerId → tip SHA)
|
|
36
|
+
const frontier = createFrontier();
|
|
37
|
+
const parents = [];
|
|
38
|
+
|
|
39
|
+
for (const writerId of writers) {
|
|
40
|
+
const writerRef = buildWriterRef(this._graphName, writerId);
|
|
41
|
+
const sha = await this._persistence.readRef(writerRef);
|
|
42
|
+
if (sha) {
|
|
43
|
+
updateFrontier(frontier, writerId, sha);
|
|
44
|
+
parents.push(sha);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 3. Materialize current state (reuse cached if fresh, guard against recursion)
|
|
49
|
+
const prevCheckpointing = this._checkpointing;
|
|
50
|
+
this._checkpointing = true;
|
|
51
|
+
/** @type {import('../services/JoinReducer.js').WarpStateV5} */
|
|
52
|
+
let state;
|
|
53
|
+
try {
|
|
54
|
+
state = /** @type {import('../services/JoinReducer.js').WarpStateV5} */ ((this._cachedState && !this._stateDirty)
|
|
55
|
+
? this._cachedState
|
|
56
|
+
: await this.materialize());
|
|
57
|
+
} finally {
|
|
58
|
+
this._checkpointing = prevCheckpointing;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 4. Call CheckpointService.create() with provenance index if available
|
|
62
|
+
const checkpointSha = await createCheckpointCommit({
|
|
63
|
+
persistence: /** @type {any} */ (this._persistence), // TODO(ts-cleanup): narrow port type
|
|
64
|
+
graphName: this._graphName,
|
|
65
|
+
state,
|
|
66
|
+
frontier,
|
|
67
|
+
parents,
|
|
68
|
+
provenanceIndex: this._provenanceIndex || undefined,
|
|
69
|
+
crypto: this._crypto,
|
|
70
|
+
codec: this._codec,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// 5. Update checkpoint ref
|
|
74
|
+
const checkpointRef = buildCheckpointRef(this._graphName);
|
|
75
|
+
await this._persistence.updateRef(checkpointRef, checkpointSha);
|
|
76
|
+
|
|
77
|
+
this._logTiming('createCheckpoint', t0);
|
|
78
|
+
|
|
79
|
+
// 6. Return checkpoint SHA
|
|
80
|
+
return checkpointSha;
|
|
81
|
+
} catch (err) {
|
|
82
|
+
this._logTiming('createCheckpoint', t0, { error: /** @type {Error} */ (err) });
|
|
83
|
+
throw err;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Syncs coverage information across writers.
|
|
89
|
+
*
|
|
90
|
+
* Creates an octopus anchor commit with all writer tips as parents,
|
|
91
|
+
* then updates the coverage ref to point to this anchor. The "octopus anchor"
|
|
92
|
+
* is a merge commit that records which writer tips have been observed,
|
|
93
|
+
* enabling efficient replication and consistency checks.
|
|
94
|
+
*
|
|
95
|
+
* @this {import('../WarpGraph.js').default}
|
|
96
|
+
* @returns {Promise<void>}
|
|
97
|
+
* @throws {Error} If ref access or commit creation fails
|
|
98
|
+
*/
|
|
99
|
+
export async function syncCoverage() {
|
|
100
|
+
// 1. Discover all writers
|
|
101
|
+
const writers = await this.discoverWriters();
|
|
102
|
+
|
|
103
|
+
// If no writers exist, do nothing
|
|
104
|
+
if (writers.length === 0) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 2. Get tip SHA for each writer's ref
|
|
109
|
+
const parents = [];
|
|
110
|
+
for (const writerId of writers) {
|
|
111
|
+
const writerRef = buildWriterRef(this._graphName, writerId);
|
|
112
|
+
const sha = await this._persistence.readRef(writerRef);
|
|
113
|
+
if (sha) {
|
|
114
|
+
parents.push(sha);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// If no refs have SHAs, do nothing
|
|
119
|
+
if (parents.length === 0) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 3. Create octopus anchor commit with all tips as parents
|
|
124
|
+
const message = encodeAnchorMessage({ graph: this._graphName });
|
|
125
|
+
const anchorSha = await this._persistence.commitNode({ message, parents });
|
|
126
|
+
|
|
127
|
+
// 4. Update coverage ref
|
|
128
|
+
const coverageRef = buildCoverageRef(this._graphName);
|
|
129
|
+
await this._persistence.updateRef(coverageRef, anchorSha);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Loads the latest checkpoint for this graph.
|
|
134
|
+
*
|
|
135
|
+
* @this {import('../WarpGraph.js').default}
|
|
136
|
+
* @returns {Promise<{state: import('../services/JoinReducer.js').WarpStateV5, frontier: Map<string, string>, stateHash: string, schema: number, provenanceIndex?: import('../services/ProvenanceIndex.js').ProvenanceIndex}|null>} The checkpoint or null
|
|
137
|
+
* @private
|
|
138
|
+
*/
|
|
139
|
+
export async function _loadLatestCheckpoint() {
|
|
140
|
+
const checkpointRef = buildCheckpointRef(this._graphName);
|
|
141
|
+
const checkpointSha = await this._persistence.readRef(checkpointRef);
|
|
142
|
+
|
|
143
|
+
if (!checkpointSha) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
return await loadCheckpoint(this._persistence, checkpointSha, { codec: this._codec });
|
|
149
|
+
} catch {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Loads patches since a checkpoint for incremental materialization.
|
|
156
|
+
*
|
|
157
|
+
* @this {import('../WarpGraph.js').default}
|
|
158
|
+
* @param {{state: import('../services/JoinReducer.js').WarpStateV5, frontier: Map<string, string>, stateHash: string, schema: number}} checkpoint - The checkpoint to start from
|
|
159
|
+
* @returns {Promise<Array<{patch: import('../types/WarpTypesV2.js').PatchV2, sha: string}>>} Patches since checkpoint
|
|
160
|
+
* @private
|
|
161
|
+
*/
|
|
162
|
+
export async function _loadPatchesSince(checkpoint) {
|
|
163
|
+
const writerIds = await this.discoverWriters();
|
|
164
|
+
const allPatches = [];
|
|
165
|
+
|
|
166
|
+
for (const writerId of writerIds) {
|
|
167
|
+
const checkpointSha = checkpoint.frontier?.get(writerId) || null;
|
|
168
|
+
const patches = await this._loadWriterPatches(writerId, checkpointSha);
|
|
169
|
+
|
|
170
|
+
// Validate each patch against checkpoint frontier
|
|
171
|
+
for (const { sha } of patches) {
|
|
172
|
+
await this._validatePatchAgainstCheckpoint(writerId, sha, checkpoint);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
for (const p of patches) {
|
|
176
|
+
allPatches.push(p);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return allPatches;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Validates migration boundary for graphs.
|
|
185
|
+
*
|
|
186
|
+
* Graphs cannot be opened if there is schema:1 history without
|
|
187
|
+
* a migration checkpoint. This ensures data consistency during migration.
|
|
188
|
+
*
|
|
189
|
+
* @this {import('../WarpGraph.js').default}
|
|
190
|
+
* @returns {Promise<void>}
|
|
191
|
+
* @throws {Error} If v1 history exists without migration checkpoint
|
|
192
|
+
* @private
|
|
193
|
+
*/
|
|
194
|
+
export async function _validateMigrationBoundary() {
|
|
195
|
+
const checkpoint = await this._loadLatestCheckpoint();
|
|
196
|
+
if (checkpoint?.schema === 2 || checkpoint?.schema === 3) {
|
|
197
|
+
return; // Already migrated
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const hasSchema1History = await this._hasSchema1Patches();
|
|
201
|
+
if (hasSchema1History) {
|
|
202
|
+
throw new Error(
|
|
203
|
+
'Cannot open graph with v1 history. ' +
|
|
204
|
+
'Run MigrationService.migrate() first to create migration checkpoint.'
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Checks if there are any schema:1 patches in the graph.
|
|
211
|
+
*
|
|
212
|
+
* @this {import('../WarpGraph.js').default}
|
|
213
|
+
* @returns {Promise<boolean>} True if schema:1 patches exist
|
|
214
|
+
* @private
|
|
215
|
+
*/
|
|
216
|
+
export async function _hasSchema1Patches() {
|
|
217
|
+
const writerIds = await this.discoverWriters();
|
|
218
|
+
|
|
219
|
+
for (const writerId of writerIds) {
|
|
220
|
+
const writerRef = buildWriterRef(this._graphName, writerId);
|
|
221
|
+
const tipSha = await this._persistence.readRef(writerRef);
|
|
222
|
+
|
|
223
|
+
if (!tipSha) {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Check the first (most recent) patch from this writer
|
|
228
|
+
const nodeInfo = await this._persistence.getNodeInfo(tipSha);
|
|
229
|
+
const kind = detectMessageKind(nodeInfo.message);
|
|
230
|
+
|
|
231
|
+
if (kind === 'patch') {
|
|
232
|
+
const patchMeta = decodePatchMessage(nodeInfo.message);
|
|
233
|
+
const patchBuffer = await this._persistence.readBlob(patchMeta.patchOid);
|
|
234
|
+
const patch = /** @type {{schema?: number}} */ (this._codec.decode(patchBuffer));
|
|
235
|
+
|
|
236
|
+
// If any patch has schema:1, we have v1 history
|
|
237
|
+
if (patch.schema === 1 || patch.schema === undefined) {
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Post-materialize GC check. Warn by default; execute only when enabled.
|
|
248
|
+
* GC failure never breaks materialize.
|
|
249
|
+
*
|
|
250
|
+
* @this {import('../WarpGraph.js').default}
|
|
251
|
+
* @param {import('../services/JoinReducer.js').WarpStateV5} state
|
|
252
|
+
* @private
|
|
253
|
+
*/
|
|
254
|
+
export function _maybeRunGC(state) {
|
|
255
|
+
try {
|
|
256
|
+
const metrics = collectGCMetrics(state);
|
|
257
|
+
/** @type {import('../services/GCPolicy.js').GCInputMetrics} */
|
|
258
|
+
const inputMetrics = {
|
|
259
|
+
...metrics,
|
|
260
|
+
patchesSinceCompaction: this._patchesSinceGC,
|
|
261
|
+
timeSinceCompaction: this._lastGCTime > 0 ? this._clock.now() - this._lastGCTime : 0,
|
|
262
|
+
};
|
|
263
|
+
const { shouldRun, reasons } = shouldRunGC(inputMetrics, /** @type {import('../services/GCPolicy.js').GCPolicy} */ (this._gcPolicy));
|
|
264
|
+
|
|
265
|
+
if (!shouldRun) {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (/** @type {import('../services/GCPolicy.js').GCPolicy} */ (this._gcPolicy).enabled) {
|
|
270
|
+
const appliedVV = computeAppliedVV(state);
|
|
271
|
+
const result = executeGC(state, appliedVV);
|
|
272
|
+
this._lastGCTime = this._clock.now();
|
|
273
|
+
this._patchesSinceGC = 0;
|
|
274
|
+
if (this._logger) {
|
|
275
|
+
this._logger.info('Auto-GC completed', { ...result, reasons });
|
|
276
|
+
}
|
|
277
|
+
} else if (this._logger) {
|
|
278
|
+
this._logger.warn(
|
|
279
|
+
'GC thresholds exceeded but auto-GC is disabled. Set gcPolicy: { enabled: true } to auto-compact.',
|
|
280
|
+
{ reasons },
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
} catch {
|
|
284
|
+
// GC failure never breaks materialize
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Checks if GC should run based on current metrics and policy.
|
|
290
|
+
* If thresholds are exceeded, runs GC on the cached state.
|
|
291
|
+
*
|
|
292
|
+
* **Requires a cached state.**
|
|
293
|
+
*
|
|
294
|
+
* @this {import('../WarpGraph.js').default}
|
|
295
|
+
* @returns {{ran: boolean, result: Object|null, reasons: string[]}} GC result
|
|
296
|
+
*
|
|
297
|
+
* @example
|
|
298
|
+
* await graph.materialize();
|
|
299
|
+
* const { ran, result, reasons } = graph.maybeRunGC();
|
|
300
|
+
* if (ran) {
|
|
301
|
+
* console.log(`GC ran: ${result.tombstonesRemoved} tombstones removed`);
|
|
302
|
+
* }
|
|
303
|
+
*/
|
|
304
|
+
export function maybeRunGC() {
|
|
305
|
+
if (!this._cachedState) {
|
|
306
|
+
return { ran: false, result: null, reasons: [] };
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const rawMetrics = collectGCMetrics(this._cachedState);
|
|
310
|
+
/** @type {import('../services/GCPolicy.js').GCInputMetrics} */
|
|
311
|
+
const metrics = {
|
|
312
|
+
...rawMetrics,
|
|
313
|
+
patchesSinceCompaction: this._patchesSinceGC,
|
|
314
|
+
timeSinceCompaction: this._lastGCTime > 0 ? this._clock.now() - this._lastGCTime : 0,
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
const { shouldRun, reasons } = shouldRunGC(metrics, /** @type {import('../services/GCPolicy.js').GCPolicy} */ (this._gcPolicy));
|
|
318
|
+
|
|
319
|
+
if (!shouldRun) {
|
|
320
|
+
return { ran: false, result: null, reasons: [] };
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const result = this.runGC();
|
|
324
|
+
return { ran: true, result, reasons };
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Explicitly runs GC on the cached state.
|
|
329
|
+
* Compacts tombstoned dots that are covered by the appliedVV.
|
|
330
|
+
*
|
|
331
|
+
* **Requires a cached state.**
|
|
332
|
+
*
|
|
333
|
+
* @this {import('../WarpGraph.js').default}
|
|
334
|
+
* @returns {{nodesCompacted: number, edgesCompacted: number, tombstonesRemoved: number, durationMs: number}}
|
|
335
|
+
* @throws {QueryError} If no cached state exists (code: `E_NO_STATE`)
|
|
336
|
+
*
|
|
337
|
+
* @example
|
|
338
|
+
* await graph.materialize();
|
|
339
|
+
* const result = graph.runGC();
|
|
340
|
+
* console.log(`Removed ${result.tombstonesRemoved} tombstones in ${result.durationMs}ms`);
|
|
341
|
+
*/
|
|
342
|
+
export function runGC() {
|
|
343
|
+
const t0 = this._clock.now();
|
|
344
|
+
try {
|
|
345
|
+
if (!this._cachedState) {
|
|
346
|
+
throw new QueryError(E_NO_STATE_MSG, {
|
|
347
|
+
code: 'E_NO_STATE',
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Compute appliedVV from current state
|
|
352
|
+
const appliedVV = computeAppliedVV(this._cachedState);
|
|
353
|
+
|
|
354
|
+
// Execute GC (mutates cached state)
|
|
355
|
+
const result = executeGC(this._cachedState, appliedVV);
|
|
356
|
+
|
|
357
|
+
// Update GC tracking
|
|
358
|
+
this._lastGCTime = this._clock.now();
|
|
359
|
+
this._patchesSinceGC = 0;
|
|
360
|
+
|
|
361
|
+
this._logTiming('runGC', t0, { metrics: `${result.tombstonesRemoved} tombstones removed` });
|
|
362
|
+
|
|
363
|
+
return result;
|
|
364
|
+
} catch (err) {
|
|
365
|
+
this._logTiming('runGC', t0, { error: /** @type {Error} */ (err) });
|
|
366
|
+
throw err;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Gets current GC metrics for the cached state.
|
|
372
|
+
*
|
|
373
|
+
* @this {import('../WarpGraph.js').default}
|
|
374
|
+
* @returns {{
|
|
375
|
+
* nodeCount: number,
|
|
376
|
+
* edgeCount: number,
|
|
377
|
+
* tombstoneCount: number,
|
|
378
|
+
* tombstoneRatio: number,
|
|
379
|
+
* patchesSinceCompaction: number,
|
|
380
|
+
* lastCompactionTime: number
|
|
381
|
+
* }|null} GC metrics or null if no cached state
|
|
382
|
+
*/
|
|
383
|
+
export function getGCMetrics() {
|
|
384
|
+
if (!this._cachedState) {
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const rawMetrics = collectGCMetrics(this._cachedState);
|
|
389
|
+
return {
|
|
390
|
+
nodeCount: rawMetrics.nodeLiveDots,
|
|
391
|
+
edgeCount: rawMetrics.edgeLiveDots,
|
|
392
|
+
tombstoneCount: rawMetrics.totalTombstones,
|
|
393
|
+
tombstoneRatio: rawMetrics.tombstoneRatio,
|
|
394
|
+
patchesSinceCompaction: this._patchesSinceGC,
|
|
395
|
+
lastCompactionTime: this._lastGCTime,
|
|
396
|
+
};
|
|
397
|
+
}
|