@git-stunts/git-warp 10.1.2 → 10.4.2
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 +31 -4
- package/bin/warp-graph.js +1242 -59
- package/index.d.ts +31 -0
- package/index.js +4 -0
- package/package.json +13 -3
- package/src/domain/WarpGraph.js +487 -140
- package/src/domain/crdt/LWW.js +1 -1
- package/src/domain/crdt/ORSet.js +10 -6
- package/src/domain/crdt/VersionVector.js +5 -1
- package/src/domain/errors/EmptyMessageError.js +2 -4
- package/src/domain/errors/ForkError.js +4 -0
- package/src/domain/errors/IndexError.js +4 -0
- package/src/domain/errors/OperationAbortedError.js +4 -0
- package/src/domain/errors/QueryError.js +4 -0
- package/src/domain/errors/SchemaUnsupportedError.js +4 -0
- package/src/domain/errors/ShardCorruptionError.js +2 -6
- package/src/domain/errors/ShardLoadError.js +2 -6
- package/src/domain/errors/ShardValidationError.js +2 -7
- package/src/domain/errors/StorageError.js +2 -6
- package/src/domain/errors/SyncError.js +4 -0
- package/src/domain/errors/TraversalError.js +4 -0
- package/src/domain/errors/WarpError.js +2 -4
- package/src/domain/errors/WormholeError.js +4 -0
- package/src/domain/services/AnchorMessageCodec.js +1 -4
- package/src/domain/services/BitmapIndexBuilder.js +10 -6
- package/src/domain/services/BitmapIndexReader.js +27 -21
- package/src/domain/services/BoundaryTransitionRecord.js +22 -15
- package/src/domain/services/CheckpointMessageCodec.js +1 -7
- package/src/domain/services/CheckpointSerializerV5.js +20 -19
- package/src/domain/services/CheckpointService.js +18 -18
- package/src/domain/services/CommitDagTraversalService.js +13 -1
- package/src/domain/services/DagPathFinding.js +40 -18
- package/src/domain/services/DagTopology.js +7 -6
- package/src/domain/services/DagTraversal.js +5 -3
- package/src/domain/services/Frontier.js +7 -6
- package/src/domain/services/HealthCheckService.js +15 -14
- package/src/domain/services/HookInstaller.js +64 -13
- package/src/domain/services/HttpSyncServer.js +15 -14
- package/src/domain/services/IndexRebuildService.js +12 -12
- package/src/domain/services/IndexStalenessChecker.js +13 -6
- package/src/domain/services/JoinReducer.js +28 -27
- package/src/domain/services/LogicalTraversal.js +7 -6
- package/src/domain/services/MessageCodecInternal.js +2 -0
- package/src/domain/services/ObserverView.js +6 -6
- package/src/domain/services/PatchBuilderV2.js +9 -9
- package/src/domain/services/PatchMessageCodec.js +1 -7
- package/src/domain/services/ProvenanceIndex.js +6 -8
- package/src/domain/services/ProvenancePayload.js +1 -2
- package/src/domain/services/QueryBuilder.js +29 -23
- package/src/domain/services/StateDiff.js +7 -7
- package/src/domain/services/StateSerializerV5.js +8 -6
- package/src/domain/services/StreamingBitmapIndexBuilder.js +29 -23
- package/src/domain/services/SyncProtocol.js +23 -26
- package/src/domain/services/TemporalQuery.js +4 -3
- package/src/domain/services/TranslationCost.js +4 -4
- package/src/domain/services/WormholeService.js +19 -15
- package/src/domain/types/TickReceipt.js +10 -6
- package/src/domain/types/WarpTypesV2.js +2 -3
- package/src/domain/utils/CachedValue.js +1 -1
- package/src/domain/utils/LRUCache.js +3 -3
- package/src/domain/utils/MinHeap.js +2 -2
- package/src/domain/utils/RefLayout.js +106 -15
- package/src/domain/utils/WriterId.js +2 -2
- package/src/domain/utils/defaultCodec.js +9 -2
- package/src/domain/utils/defaultCrypto.js +36 -0
- package/src/domain/utils/parseCursorBlob.js +51 -0
- package/src/domain/utils/roaring.js +5 -5
- package/src/domain/utils/seekCacheKey.js +32 -0
- package/src/domain/warp/PatchSession.js +3 -3
- package/src/domain/warp/Writer.js +2 -2
- package/src/infrastructure/adapters/BunHttpAdapter.js +21 -8
- package/src/infrastructure/adapters/CasSeekCacheAdapter.js +311 -0
- package/src/infrastructure/adapters/ClockAdapter.js +2 -2
- package/src/infrastructure/adapters/DenoHttpAdapter.js +22 -9
- package/src/infrastructure/adapters/GitGraphAdapter.js +16 -27
- package/src/infrastructure/adapters/NodeCryptoAdapter.js +16 -3
- package/src/infrastructure/adapters/NodeHttpAdapter.js +33 -11
- package/src/infrastructure/adapters/WebCryptoAdapter.js +21 -11
- package/src/infrastructure/codecs/CborCodec.js +16 -8
- package/src/ports/BlobPort.js +2 -2
- package/src/ports/CodecPort.js +2 -2
- package/src/ports/CommitPort.js +8 -21
- package/src/ports/ConfigPort.js +3 -3
- package/src/ports/CryptoPort.js +7 -7
- package/src/ports/GraphPersistencePort.js +12 -14
- package/src/ports/HttpServerPort.js +1 -5
- package/src/ports/IndexStoragePort.js +1 -0
- package/src/ports/LoggerPort.js +9 -9
- package/src/ports/RefPort.js +5 -5
- package/src/ports/SeekCachePort.js +73 -0
- package/src/ports/TreePort.js +3 -3
- package/src/visualization/layouts/converters.js +14 -7
- package/src/visualization/layouts/elkAdapter.js +24 -11
- package/src/visualization/layouts/elkLayout.js +23 -7
- package/src/visualization/layouts/index.js +3 -3
- package/src/visualization/renderers/ascii/check.js +30 -17
- package/src/visualization/renderers/ascii/graph.js +122 -16
- package/src/visualization/renderers/ascii/history.js +29 -90
- package/src/visualization/renderers/ascii/index.js +1 -1
- package/src/visualization/renderers/ascii/info.js +9 -7
- package/src/visualization/renderers/ascii/materialize.js +20 -16
- package/src/visualization/renderers/ascii/opSummary.js +81 -0
- package/src/visualization/renderers/ascii/path.js +1 -1
- package/src/visualization/renderers/ascii/seek.js +344 -0
- package/src/visualization/renderers/ascii/table.js +1 -1
- package/src/visualization/renderers/svg/index.js +5 -1
|
@@ -6,79 +6,20 @@
|
|
|
6
6
|
import { colors } from './colors.js';
|
|
7
7
|
import { createBox } from './box.js';
|
|
8
8
|
import { padRight, padLeft } from '../../utils/unicode.js';
|
|
9
|
-
import { truncate } from '../../utils/truncate.js';
|
|
10
9
|
import { TIMELINE } from './symbols.js';
|
|
11
|
-
|
|
12
|
-
// Default pagination settings
|
|
13
|
-
const DEFAULT_PAGE_SIZE = 20;
|
|
14
|
-
|
|
15
|
-
// Operation type to display info mapping
|
|
16
|
-
const OP_DISPLAY = {
|
|
17
|
-
NodeAdd: { symbol: '+', label: 'node', color: colors.success },
|
|
18
|
-
NodeTombstone: { symbol: '-', label: 'node', color: colors.error },
|
|
19
|
-
EdgeAdd: { symbol: '+', label: 'edge', color: colors.success },
|
|
20
|
-
EdgeTombstone: { symbol: '-', label: 'edge', color: colors.error },
|
|
21
|
-
PropSet: { symbol: '~', label: 'prop', color: colors.warning },
|
|
22
|
-
BlobValue: { symbol: '+', label: 'blob', color: colors.primary },
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
// Default empty operation summary
|
|
26
|
-
const EMPTY_OP_SUMMARY = Object.freeze({
|
|
27
|
-
NodeAdd: 0,
|
|
28
|
-
EdgeAdd: 0,
|
|
29
|
-
PropSet: 0,
|
|
30
|
-
NodeTombstone: 0,
|
|
31
|
-
EdgeTombstone: 0,
|
|
32
|
-
BlobValue: 0,
|
|
33
|
-
});
|
|
10
|
+
import { OP_DISPLAY, EMPTY_OP_SUMMARY, summarizeOps, formatOpSummary } from './opSummary.js';
|
|
34
11
|
|
|
35
12
|
/**
|
|
36
|
-
*
|
|
37
|
-
* @param {Object[]} ops - Array of patch operations
|
|
38
|
-
* @returns {Object} Summary with counts by operation type
|
|
13
|
+
* @typedef {{ sha?: string, lamport?: number, writerId?: string, opSummary?: Record<string, number>, ops?: Array<{ type: string }> }} PatchEntry
|
|
39
14
|
*/
|
|
40
|
-
function summarizeOps(ops) {
|
|
41
|
-
const summary = { ...EMPTY_OP_SUMMARY };
|
|
42
|
-
for (const op of ops) {
|
|
43
|
-
if (op.type && summary[op.type] !== undefined) {
|
|
44
|
-
summary[op.type]++;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
return summary;
|
|
48
|
-
}
|
|
49
15
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
* @param {Object} summary - Operation counts by type
|
|
53
|
-
* @param {number} maxWidth - Maximum width for the summary string
|
|
54
|
-
* @returns {string} Formatted summary string
|
|
55
|
-
*/
|
|
56
|
-
function formatOpSummary(summary, maxWidth = 40) {
|
|
57
|
-
const order = ['NodeAdd', 'EdgeAdd', 'PropSet', 'NodeTombstone', 'EdgeTombstone', 'BlobValue'];
|
|
58
|
-
const parts = order
|
|
59
|
-
.filter((opType) => summary[opType] > 0)
|
|
60
|
-
.map((opType) => {
|
|
61
|
-
const display = OP_DISPLAY[opType];
|
|
62
|
-
return { text: `${display.symbol}${summary[opType]}${display.label}`, color: display.color };
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
if (parts.length === 0) {
|
|
66
|
-
return colors.muted('(empty)');
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Truncate plain text first to avoid breaking ANSI escape sequences
|
|
70
|
-
const plain = parts.map((p) => p.text).join(' ');
|
|
71
|
-
const truncated = truncate(plain, maxWidth);
|
|
72
|
-
if (truncated === plain) {
|
|
73
|
-
return parts.map((p) => p.color(p.text)).join(' ');
|
|
74
|
-
}
|
|
75
|
-
return colors.muted(truncated);
|
|
76
|
-
}
|
|
16
|
+
// Default pagination settings
|
|
17
|
+
const DEFAULT_PAGE_SIZE = 20;
|
|
77
18
|
|
|
78
19
|
/**
|
|
79
20
|
* Ensures entry has an opSummary, computing one if needed.
|
|
80
|
-
* @param {
|
|
81
|
-
* @returns {
|
|
21
|
+
* @param {PatchEntry} entry - Patch entry
|
|
22
|
+
* @returns {Record<string, number>} Operation summary
|
|
82
23
|
*/
|
|
83
24
|
function ensureOpSummary(entry) {
|
|
84
25
|
if (entry.opSummary) {
|
|
@@ -92,10 +33,10 @@ function ensureOpSummary(entry) {
|
|
|
92
33
|
|
|
93
34
|
/**
|
|
94
35
|
* Paginates entries, returning display entries and truncation info.
|
|
95
|
-
* @param {
|
|
36
|
+
* @param {PatchEntry[]} entries - All entries
|
|
96
37
|
* @param {number} pageSize - Page size
|
|
97
38
|
* @param {boolean} showAll - Whether to show all
|
|
98
|
-
* @returns {{displayEntries:
|
|
39
|
+
* @returns {{displayEntries: PatchEntry[], truncated: boolean, hiddenCount: number}}
|
|
99
40
|
*/
|
|
100
41
|
function paginateEntries(entries, pageSize, showAll) {
|
|
101
42
|
if (showAll || entries.length <= pageSize) {
|
|
@@ -127,17 +68,22 @@ function renderTruncationIndicator(truncated, hiddenCount) {
|
|
|
127
68
|
/**
|
|
128
69
|
* Renders a single patch entry line.
|
|
129
70
|
* @param {Object} params - Entry parameters
|
|
71
|
+
* @param {PatchEntry} params.entry - Patch entry
|
|
72
|
+
* @param {boolean} params.isLast - Whether this is the last entry
|
|
73
|
+
* @param {number} params.lamportWidth - Width for lamport timestamp padding
|
|
74
|
+
* @param {string} [params.writerStr] - Writer string
|
|
75
|
+
* @param {number} [params.maxWriterIdLen] - Max writer ID length for padding
|
|
130
76
|
* @returns {string} Formatted entry line
|
|
131
77
|
*/
|
|
132
78
|
function renderEntryLine({ entry, isLast, lamportWidth, writerStr, maxWriterIdLen }) {
|
|
133
79
|
const connector = isLast ? TIMELINE.end : TIMELINE.connector;
|
|
134
80
|
const shortSha = (entry.sha || '').slice(0, 7);
|
|
135
|
-
const lamportStr = padLeft(String(entry.lamport), lamportWidth);
|
|
81
|
+
const lamportStr = padLeft(String(entry.lamport ?? 0), lamportWidth);
|
|
136
82
|
const opSummary = ensureOpSummary(entry);
|
|
137
83
|
const opSummaryStr = formatOpSummary(opSummary, writerStr ? 30 : 40);
|
|
138
84
|
|
|
139
85
|
if (writerStr) {
|
|
140
|
-
const paddedWriter = padRight(writerStr, maxWriterIdLen);
|
|
86
|
+
const paddedWriter = padRight(writerStr, maxWriterIdLen ?? 6);
|
|
141
87
|
return ` ${connector}${TIMELINE.dot} ${colors.muted(`L${lamportStr}`)} ${colors.primary(paddedWriter)}:${colors.muted(shortSha)} ${opSummaryStr}`;
|
|
142
88
|
}
|
|
143
89
|
return ` ${connector}${TIMELINE.dot} ${colors.muted(`L${lamportStr}`)} ${colors.primary(shortSha)} ${opSummaryStr}`;
|
|
@@ -164,8 +110,8 @@ function renderSingleWriterFooter(totalCount) {
|
|
|
164
110
|
|
|
165
111
|
/**
|
|
166
112
|
* Renders single-writer timeline view.
|
|
167
|
-
* @param {
|
|
168
|
-
* @param {
|
|
113
|
+
* @param {{ entries: PatchEntry[], writer: string }} payload - History payload
|
|
114
|
+
* @param {{ pageSize?: number, showAll?: boolean }} options - Rendering options
|
|
169
115
|
* @returns {string[]} Lines for the timeline
|
|
170
116
|
*/
|
|
171
117
|
function renderSingleWriterTimeline(payload, options) {
|
|
@@ -184,7 +130,7 @@ function renderSingleWriterTimeline(payload, options) {
|
|
|
184
130
|
lines.push(colors.muted(' (no patches)'));
|
|
185
131
|
return lines;
|
|
186
132
|
}
|
|
187
|
-
const maxLamport = Math.max(...displayEntries.map((e) => e.lamport));
|
|
133
|
+
const maxLamport = Math.max(...displayEntries.map((e) => e.lamport ?? 0));
|
|
188
134
|
const lamportWidth = String(maxLamport).length;
|
|
189
135
|
|
|
190
136
|
lines.push(...renderTruncationIndicator(truncated, hiddenCount));
|
|
@@ -200,8 +146,8 @@ function renderSingleWriterTimeline(payload, options) {
|
|
|
200
146
|
|
|
201
147
|
/**
|
|
202
148
|
* Merges and sorts entries from all writers by lamport timestamp.
|
|
203
|
-
* @param {
|
|
204
|
-
* @returns {
|
|
149
|
+
* @param {Record<string, PatchEntry[]>} writers - Map of writerId to entries
|
|
150
|
+
* @returns {PatchEntry[]} Sorted entries with writerId attached
|
|
205
151
|
*/
|
|
206
152
|
function mergeWriterEntries(writers) {
|
|
207
153
|
const allEntries = [];
|
|
@@ -210,7 +156,7 @@ function mergeWriterEntries(writers) {
|
|
|
210
156
|
allEntries.push({ ...entry, writerId });
|
|
211
157
|
}
|
|
212
158
|
}
|
|
213
|
-
allEntries.sort((a, b) => a.lamport - b.lamport || a.writerId.localeCompare(b.writerId));
|
|
159
|
+
allEntries.sort((a, b) => (a.lamport ?? 0) - (b.lamport ?? 0) || (a.writerId ?? '').localeCompare(b.writerId ?? ''));
|
|
214
160
|
return allEntries;
|
|
215
161
|
}
|
|
216
162
|
|
|
@@ -241,8 +187,8 @@ function renderMultiWriterFooter(totalCount, writerCount) {
|
|
|
241
187
|
|
|
242
188
|
/**
|
|
243
189
|
* Renders multi-writer timeline view with parallel columns.
|
|
244
|
-
* @param {
|
|
245
|
-
* @param {
|
|
190
|
+
* @param {{ writers: Record<string, PatchEntry[]>, graph: string }} payload - History payload with allWriters data
|
|
191
|
+
* @param {{ pageSize?: number, showAll?: boolean }} options - Rendering options
|
|
246
192
|
* @returns {string[]} Lines for the timeline
|
|
247
193
|
*/
|
|
248
194
|
function renderMultiWriterTimeline(payload, options) {
|
|
@@ -269,7 +215,7 @@ function renderMultiWriterTimeline(payload, options) {
|
|
|
269
215
|
lines.push(colors.muted(' (no patches)'));
|
|
270
216
|
return lines;
|
|
271
217
|
}
|
|
272
|
-
const maxLamport = Math.max(...displayEntries.map((e) => e.lamport));
|
|
218
|
+
const maxLamport = Math.max(...displayEntries.map((e) => e.lamport ?? 0));
|
|
273
219
|
const lamportWidth = String(maxLamport).length;
|
|
274
220
|
const maxWriterIdLen = Math.max(...writerIds.map((id) => id.length), 6);
|
|
275
221
|
|
|
@@ -293,15 +239,8 @@ function renderMultiWriterTimeline(payload, options) {
|
|
|
293
239
|
|
|
294
240
|
/**
|
|
295
241
|
* Renders the history view with ASCII timeline.
|
|
296
|
-
* @param {
|
|
297
|
-
* @param {
|
|
298
|
-
* @param {string} [payload.writer] - Writer ID (single writer mode)
|
|
299
|
-
* @param {string|null} [payload.nodeFilter] - Node filter if applied
|
|
300
|
-
* @param {Object[]} [payload.entries] - Array of patch entries (single writer mode)
|
|
301
|
-
* @param {Object} [payload.writers] - Map of writerId to entries (multi-writer mode)
|
|
302
|
-
* @param {Object} [options] - Rendering options
|
|
303
|
-
* @param {number} [options.pageSize=20] - Number of patches to show per page
|
|
304
|
-
* @param {boolean} [options.showAll=false] - Show all patches (no pagination)
|
|
242
|
+
* @param {{ graph: string, writer?: string, nodeFilter?: string | null, entries?: PatchEntry[], writers?: Record<string, PatchEntry[]> }} payload - History payload from handleHistory
|
|
243
|
+
* @param {{ pageSize?: number, showAll?: boolean }} [options] - Rendering options
|
|
305
244
|
* @returns {string} Formatted ASCII output
|
|
306
245
|
*/
|
|
307
246
|
export function renderHistoryView(payload, options = {}) {
|
|
@@ -311,8 +250,8 @@ export function renderHistoryView(payload, options = {}) {
|
|
|
311
250
|
|
|
312
251
|
const isMultiWriter = payload.writers && typeof payload.writers === 'object';
|
|
313
252
|
const contentLines = isMultiWriter
|
|
314
|
-
? renderMultiWriterTimeline(payload, options)
|
|
315
|
-
: renderSingleWriterTimeline(payload, options);
|
|
253
|
+
? renderMultiWriterTimeline(/** @type {{ writers: Record<string, PatchEntry[]>, graph: string }} */ (payload), options)
|
|
254
|
+
: renderSingleWriterTimeline(/** @type {{ entries: PatchEntry[], writer: string }} */ (payload), options);
|
|
316
255
|
|
|
317
256
|
// Add node filter indicator if present
|
|
318
257
|
if (payload.nodeFilter) {
|
|
@@ -330,6 +269,6 @@ export function renderHistoryView(payload, options = {}) {
|
|
|
330
269
|
return `${box}\n`;
|
|
331
270
|
}
|
|
332
271
|
|
|
333
|
-
export { summarizeOps };
|
|
272
|
+
export { summarizeOps, formatOpSummary, OP_DISPLAY, EMPTY_OP_SUMMARY };
|
|
334
273
|
|
|
335
274
|
export default { renderHistoryView, summarizeOps };
|
|
@@ -9,6 +9,6 @@ export { progressBar } from './progress.js';
|
|
|
9
9
|
export { renderInfoView } from './info.js';
|
|
10
10
|
export { renderCheckView } from './check.js';
|
|
11
11
|
export { renderMaterializeView } from './materialize.js';
|
|
12
|
-
export { renderHistoryView, summarizeOps } from './history.js';
|
|
12
|
+
export { renderHistoryView, summarizeOps, formatOpSummary, OP_DISPLAY, EMPTY_OP_SUMMARY } from './history.js';
|
|
13
13
|
export { renderPathView } from './path.js';
|
|
14
14
|
export { renderGraphView } from './graph.js';
|
|
@@ -10,6 +10,10 @@ import { padRight } from '../../utils/unicode.js';
|
|
|
10
10
|
import { timeAgo } from '../../utils/time.js';
|
|
11
11
|
import { TIMELINE } from './symbols.js';
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {{ name: string, writers?: { count?: number, ids?: string[] }, checkpoint?: { sha?: string, date?: string | Date }, coverage?: { sha?: string }, writerPatches?: Record<string, number> }} GraphInfo
|
|
15
|
+
*/
|
|
16
|
+
|
|
13
17
|
// Box drawing characters (info.js uses verbose key names for card rendering)
|
|
14
18
|
const BOX = {
|
|
15
19
|
topLeft: '\u250C', // ┌
|
|
@@ -77,7 +81,7 @@ function formatWriterNames(writerIds) {
|
|
|
77
81
|
|
|
78
82
|
/**
|
|
79
83
|
* Renders the header lines for a graph card.
|
|
80
|
-
* @param {
|
|
84
|
+
* @param {GraphInfo} graph - Graph info object
|
|
81
85
|
* @param {number} contentWidth - Available content width
|
|
82
86
|
* @returns {string[]} Header lines
|
|
83
87
|
*/
|
|
@@ -102,7 +106,7 @@ function renderCardHeader(graph, contentWidth) {
|
|
|
102
106
|
|
|
103
107
|
/**
|
|
104
108
|
* Renders writer timeline lines for a graph card.
|
|
105
|
-
* @param {
|
|
109
|
+
* @param {Record<string, number> | undefined} writerPatches - Map of writerId to patch count
|
|
106
110
|
* @param {number} contentWidth - Available content width
|
|
107
111
|
* @returns {string[]} Timeline lines
|
|
108
112
|
*/
|
|
@@ -133,7 +137,7 @@ function renderWriterTimelines(writerPatches, contentWidth) {
|
|
|
133
137
|
|
|
134
138
|
/**
|
|
135
139
|
* Renders checkpoint and coverage lines for a graph card.
|
|
136
|
-
* @param {
|
|
140
|
+
* @param {GraphInfo} graph - Graph info object
|
|
137
141
|
* @param {number} contentWidth - Available content width
|
|
138
142
|
* @returns {string[]} Status lines
|
|
139
143
|
*/
|
|
@@ -169,7 +173,7 @@ function renderCardStatus(graph, contentWidth) {
|
|
|
169
173
|
|
|
170
174
|
/**
|
|
171
175
|
* Renders a single graph card.
|
|
172
|
-
* @param {
|
|
176
|
+
* @param {GraphInfo} graph - Graph info object
|
|
173
177
|
* @param {number} innerWidth - Available width inside the card
|
|
174
178
|
* @returns {string[]} Array of lines for this graph card
|
|
175
179
|
*/
|
|
@@ -186,9 +190,7 @@ function renderGraphCard(graph, innerWidth) {
|
|
|
186
190
|
|
|
187
191
|
/**
|
|
188
192
|
* Renders the info view with ASCII box art.
|
|
189
|
-
* @param {
|
|
190
|
-
* @param {string} data.repo - Repository path
|
|
191
|
-
* @param {Object[]} data.graphs - Array of graph info objects
|
|
193
|
+
* @param {{ repo?: string, graphs: GraphInfo[] }} data - Info payload from handleInfo
|
|
192
194
|
* @returns {string} Formatted ASCII output
|
|
193
195
|
*/
|
|
194
196
|
export function renderInfoView(data) {
|
|
@@ -12,6 +12,11 @@ import { padRight } from '../../utils/unicode.js';
|
|
|
12
12
|
import { truncate } from '../../utils/truncate.js';
|
|
13
13
|
import { formatNumber, formatSha } from './formatters.js';
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {{ graph: string, error?: string, noOp?: boolean, patchCount?: number, nodes?: number, edges?: number, properties?: number, writers?: Record<string, number>, checkpoint?: string | null }} GraphResult
|
|
17
|
+
* @typedef {{ maxNodes: number, maxEdges: number, maxProps: number }} MaxValues
|
|
18
|
+
*/
|
|
19
|
+
|
|
15
20
|
// Bar chart settings
|
|
16
21
|
const BAR_WIDTH = 20;
|
|
17
22
|
const STAT_LABEL_WIDTH = 12;
|
|
@@ -55,15 +60,15 @@ function renderErrorState(errorMessage) {
|
|
|
55
60
|
|
|
56
61
|
/**
|
|
57
62
|
* Render no-op state (already materialized).
|
|
58
|
-
* @param {
|
|
63
|
+
* @param {GraphResult} graph - Graph data
|
|
59
64
|
* @returns {string[]} No-op state lines
|
|
60
65
|
*/
|
|
61
66
|
function renderNoOpState(graph) {
|
|
62
67
|
const lines = [
|
|
63
68
|
` ${colors.success('\u2713')} Already materialized (no new patches)`,
|
|
64
69
|
'',
|
|
65
|
-
` ${padRight('Nodes:', STAT_LABEL_WIDTH)} ${formatNumber(graph.nodes)}`,
|
|
66
|
-
` ${padRight('Edges:', STAT_LABEL_WIDTH)} ${formatNumber(graph.edges)}`,
|
|
70
|
+
` ${padRight('Nodes:', STAT_LABEL_WIDTH)} ${formatNumber(graph.nodes || 0)}`,
|
|
71
|
+
` ${padRight('Edges:', STAT_LABEL_WIDTH)} ${formatNumber(graph.edges || 0)}`,
|
|
67
72
|
];
|
|
68
73
|
if (typeof graph.properties === 'number') {
|
|
69
74
|
lines.push(` ${padRight('Properties:', STAT_LABEL_WIDTH)} ${formatNumber(graph.properties)}`);
|
|
@@ -73,7 +78,7 @@ function renderNoOpState(graph) {
|
|
|
73
78
|
|
|
74
79
|
/**
|
|
75
80
|
* Render empty graph state (0 patches).
|
|
76
|
-
* @param {
|
|
81
|
+
* @param {GraphResult} graph - Graph data
|
|
77
82
|
* @returns {string[]} Empty state lines
|
|
78
83
|
*/
|
|
79
84
|
function renderEmptyState(graph) {
|
|
@@ -86,7 +91,7 @@ function renderEmptyState(graph) {
|
|
|
86
91
|
|
|
87
92
|
/**
|
|
88
93
|
* Render writer progress section.
|
|
89
|
-
* @param {
|
|
94
|
+
* @param {Record<string, number> | undefined} writers - Writer patch counts
|
|
90
95
|
* @returns {string[]} Writer lines
|
|
91
96
|
*/
|
|
92
97
|
function renderWriterSection(writers) {
|
|
@@ -108,15 +113,15 @@ function renderWriterSection(writers) {
|
|
|
108
113
|
|
|
109
114
|
/**
|
|
110
115
|
* Render statistics section with bar charts.
|
|
111
|
-
* @param {
|
|
112
|
-
* @param {
|
|
116
|
+
* @param {GraphResult} graph - Graph data
|
|
117
|
+
* @param {MaxValues} maxValues - Max values for scaling
|
|
113
118
|
* @returns {string[]} Statistics lines
|
|
114
119
|
*/
|
|
115
120
|
function renderStatsSection(graph, { maxNodes, maxEdges, maxProps }) {
|
|
116
121
|
const lines = [
|
|
117
122
|
` ${colors.dim('Statistics:')}`,
|
|
118
|
-
` ${padRight('Nodes:', STAT_LABEL_WIDTH)} ${statBar(graph.nodes, maxNodes)} ${formatNumber(graph.nodes)}`,
|
|
119
|
-
` ${padRight('Edges:', STAT_LABEL_WIDTH)} ${statBar(graph.edges, maxEdges)} ${formatNumber(graph.edges)}`,
|
|
123
|
+
` ${padRight('Nodes:', STAT_LABEL_WIDTH)} ${statBar(graph.nodes || 0, maxNodes)} ${formatNumber(graph.nodes || 0)}`,
|
|
124
|
+
` ${padRight('Edges:', STAT_LABEL_WIDTH)} ${statBar(graph.edges || 0, maxEdges)} ${formatNumber(graph.edges || 0)}`,
|
|
120
125
|
];
|
|
121
126
|
if (typeof graph.properties === 'number') {
|
|
122
127
|
lines.push(` ${padRight('Properties:', STAT_LABEL_WIDTH)} ${statBar(graph.properties, maxProps)} ${formatNumber(graph.properties)}`);
|
|
@@ -139,8 +144,8 @@ function renderCheckpointInfo(checkpoint) {
|
|
|
139
144
|
|
|
140
145
|
/**
|
|
141
146
|
* Render a single graph's materialization result.
|
|
142
|
-
* @param {
|
|
143
|
-
* @param {
|
|
147
|
+
* @param {GraphResult} graph - Graph result from materialize
|
|
148
|
+
* @param {MaxValues} maxValues - Max values for scaling bars
|
|
144
149
|
* @returns {string[]} Array of lines for this graph
|
|
145
150
|
*/
|
|
146
151
|
function renderGraphResult(graph, maxValues) {
|
|
@@ -158,14 +163,14 @@ function renderGraphResult(graph, maxValues) {
|
|
|
158
163
|
|
|
159
164
|
lines.push(...renderWriterSection(graph.writers));
|
|
160
165
|
lines.push(...renderStatsSection(graph, maxValues));
|
|
161
|
-
lines.push(...renderCheckpointInfo(graph.checkpoint));
|
|
166
|
+
lines.push(...renderCheckpointInfo(graph.checkpoint ?? null));
|
|
162
167
|
return lines;
|
|
163
168
|
}
|
|
164
169
|
|
|
165
170
|
/**
|
|
166
171
|
* Calculate max values for scaling bar charts.
|
|
167
|
-
* @param {
|
|
168
|
-
* @returns {
|
|
172
|
+
* @param {GraphResult[]} graphs - Array of graph results
|
|
173
|
+
* @returns {MaxValues} Max values object
|
|
169
174
|
*/
|
|
170
175
|
function calculateMaxValues(graphs) {
|
|
171
176
|
const successfulGraphs = graphs.filter((g) => !g.error);
|
|
@@ -212,8 +217,7 @@ function getBorderColor(successCount, errorCount) {
|
|
|
212
217
|
|
|
213
218
|
/**
|
|
214
219
|
* Render the materialize view dashboard.
|
|
215
|
-
* @param {
|
|
216
|
-
* @param {Object[]} payload.graphs - Array of graph results
|
|
220
|
+
* @param {{ graphs: GraphResult[] }} payload - The materialize command payload
|
|
217
221
|
* @returns {string} Formatted dashboard string
|
|
218
222
|
*/
|
|
219
223
|
export function renderMaterializeView(payload) {
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared operation summary utilities for ASCII renderers.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from history.js so other views (e.g. seek) can reuse the same
|
|
5
|
+
* op-type ordering, symbols, and formatting.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { colors } from './colors.js';
|
|
9
|
+
import { truncate } from '../../utils/truncate.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {'NodeAdd' | 'EdgeAdd' | 'PropSet' | 'NodeTombstone' | 'EdgeTombstone' | 'BlobValue'} OpType
|
|
13
|
+
* @typedef {Record<OpType, number>} OpSummary
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// Operation type to display info mapping
|
|
17
|
+
export const OP_DISPLAY = Object.freeze({
|
|
18
|
+
NodeAdd: { symbol: '+', label: 'node', color: colors.success },
|
|
19
|
+
NodeTombstone: { symbol: '-', label: 'node', color: colors.error },
|
|
20
|
+
EdgeAdd: { symbol: '+', label: 'edge', color: colors.success },
|
|
21
|
+
EdgeTombstone: { symbol: '-', label: 'edge', color: colors.error },
|
|
22
|
+
PropSet: { symbol: '~', label: 'prop', color: colors.warning },
|
|
23
|
+
BlobValue: { symbol: '+', label: 'blob', color: colors.primary },
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Default empty operation summary
|
|
27
|
+
export const EMPTY_OP_SUMMARY = Object.freeze({
|
|
28
|
+
NodeAdd: 0,
|
|
29
|
+
EdgeAdd: 0,
|
|
30
|
+
PropSet: 0,
|
|
31
|
+
NodeTombstone: 0,
|
|
32
|
+
EdgeTombstone: 0,
|
|
33
|
+
BlobValue: 0,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Summarizes operations in a patch.
|
|
38
|
+
* @param {Array<{ type: string }>} ops - Array of patch operations
|
|
39
|
+
* @returns {OpSummary} Summary with counts by operation type
|
|
40
|
+
*/
|
|
41
|
+
export function summarizeOps(ops) {
|
|
42
|
+
/** @type {OpSummary} */
|
|
43
|
+
const summary = { ...EMPTY_OP_SUMMARY };
|
|
44
|
+
for (const op of ops) {
|
|
45
|
+
const t = /** @type {OpType} */ (op.type);
|
|
46
|
+
if (t && summary[t] !== undefined) {
|
|
47
|
+
summary[t]++;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return summary;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Formats operation summary as a colored string.
|
|
55
|
+
* @param {OpSummary | Record<string, number>} summary - Operation counts by type
|
|
56
|
+
* @param {number} maxWidth - Maximum width for the summary string
|
|
57
|
+
* @returns {string} Formatted summary string
|
|
58
|
+
*/
|
|
59
|
+
export function formatOpSummary(summary, maxWidth = 40) {
|
|
60
|
+
/** @type {OpType[]} */
|
|
61
|
+
const order = ['NodeAdd', 'EdgeAdd', 'PropSet', 'NodeTombstone', 'EdgeTombstone', 'BlobValue'];
|
|
62
|
+
const parts = order
|
|
63
|
+
.filter((opType) => (/** @type {Record<string, number>} */ (summary))[opType] > 0)
|
|
64
|
+
.map((opType) => {
|
|
65
|
+
const display = OP_DISPLAY[opType];
|
|
66
|
+
return { text: `${display.symbol}${(/** @type {Record<string, number>} */ (summary))[opType]}${display.label}`, color: display.color };
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
if (parts.length === 0) {
|
|
70
|
+
return colors.muted('(empty)');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Truncate plain text first to avoid breaking ANSI escape sequences
|
|
74
|
+
const plain = parts.map((p) => p.text).join(' ');
|
|
75
|
+
const truncated = truncate(plain, maxWidth);
|
|
76
|
+
if (truncated === plain) {
|
|
77
|
+
return parts.map((p) => p.color(p.text)).join(' ');
|
|
78
|
+
}
|
|
79
|
+
return colors.muted(truncated);
|
|
80
|
+
}
|
|
81
|
+
|
|
@@ -75,7 +75,7 @@ function createPathSegment({ nodeId, index, pathLength, edges }) {
|
|
|
75
75
|
* Builds path segments that fit within the terminal width.
|
|
76
76
|
* Wraps long paths to multiple lines.
|
|
77
77
|
* @param {string[]} path - Array of node IDs
|
|
78
|
-
* @param {string[]}
|
|
78
|
+
* @param {string[] | undefined} edges - Optional array of edge labels (one fewer than nodes)
|
|
79
79
|
* @param {number} maxWidth - Maximum line width
|
|
80
80
|
* @returns {string[]} Array of line strings
|
|
81
81
|
*/
|