@git-stunts/git-warp 10.1.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/LICENSE +201 -0
- package/NOTICE +16 -0
- package/README.md +480 -0
- package/SECURITY.md +30 -0
- package/bin/git-warp +24 -0
- package/bin/warp-graph.js +1574 -0
- package/index.d.ts +2366 -0
- package/index.js +180 -0
- package/package.json +129 -0
- package/scripts/install-git-warp.sh +258 -0
- package/scripts/uninstall-git-warp.sh +139 -0
- package/src/domain/WarpGraph.js +3157 -0
- package/src/domain/crdt/Dot.js +160 -0
- package/src/domain/crdt/LWW.js +154 -0
- package/src/domain/crdt/ORSet.js +371 -0
- package/src/domain/crdt/VersionVector.js +222 -0
- package/src/domain/entities/GraphNode.js +60 -0
- package/src/domain/errors/EmptyMessageError.js +47 -0
- package/src/domain/errors/ForkError.js +30 -0
- package/src/domain/errors/IndexError.js +23 -0
- package/src/domain/errors/OperationAbortedError.js +22 -0
- package/src/domain/errors/QueryError.js +39 -0
- package/src/domain/errors/SchemaUnsupportedError.js +17 -0
- package/src/domain/errors/ShardCorruptionError.js +56 -0
- package/src/domain/errors/ShardLoadError.js +57 -0
- package/src/domain/errors/ShardValidationError.js +61 -0
- package/src/domain/errors/StorageError.js +57 -0
- package/src/domain/errors/SyncError.js +30 -0
- package/src/domain/errors/TraversalError.js +23 -0
- package/src/domain/errors/WarpError.js +31 -0
- package/src/domain/errors/WormholeError.js +28 -0
- package/src/domain/errors/WriterError.js +39 -0
- package/src/domain/errors/index.js +21 -0
- package/src/domain/services/AnchorMessageCodec.js +99 -0
- package/src/domain/services/BitmapIndexBuilder.js +225 -0
- package/src/domain/services/BitmapIndexReader.js +435 -0
- package/src/domain/services/BoundaryTransitionRecord.js +463 -0
- package/src/domain/services/CheckpointMessageCodec.js +147 -0
- package/src/domain/services/CheckpointSerializerV5.js +281 -0
- package/src/domain/services/CheckpointService.js +384 -0
- package/src/domain/services/CommitDagTraversalService.js +156 -0
- package/src/domain/services/DagPathFinding.js +712 -0
- package/src/domain/services/DagTopology.js +239 -0
- package/src/domain/services/DagTraversal.js +245 -0
- package/src/domain/services/Frontier.js +108 -0
- package/src/domain/services/GCMetrics.js +101 -0
- package/src/domain/services/GCPolicy.js +122 -0
- package/src/domain/services/GitLogParser.js +205 -0
- package/src/domain/services/HealthCheckService.js +246 -0
- package/src/domain/services/HookInstaller.js +326 -0
- package/src/domain/services/HttpSyncServer.js +262 -0
- package/src/domain/services/IndexRebuildService.js +426 -0
- package/src/domain/services/IndexStalenessChecker.js +103 -0
- package/src/domain/services/JoinReducer.js +582 -0
- package/src/domain/services/KeyCodec.js +113 -0
- package/src/domain/services/LegacyAnchorDetector.js +67 -0
- package/src/domain/services/LogicalTraversal.js +351 -0
- package/src/domain/services/MessageCodecInternal.js +132 -0
- package/src/domain/services/MessageSchemaDetector.js +145 -0
- package/src/domain/services/MigrationService.js +55 -0
- package/src/domain/services/ObserverView.js +265 -0
- package/src/domain/services/PatchBuilderV2.js +669 -0
- package/src/domain/services/PatchMessageCodec.js +140 -0
- package/src/domain/services/ProvenanceIndex.js +337 -0
- package/src/domain/services/ProvenancePayload.js +242 -0
- package/src/domain/services/QueryBuilder.js +835 -0
- package/src/domain/services/StateDiff.js +300 -0
- package/src/domain/services/StateSerializerV5.js +156 -0
- package/src/domain/services/StreamingBitmapIndexBuilder.js +709 -0
- package/src/domain/services/SyncProtocol.js +593 -0
- package/src/domain/services/TemporalQuery.js +201 -0
- package/src/domain/services/TranslationCost.js +221 -0
- package/src/domain/services/TraversalService.js +8 -0
- package/src/domain/services/WarpMessageCodec.js +29 -0
- package/src/domain/services/WarpStateIndexBuilder.js +127 -0
- package/src/domain/services/WormholeService.js +353 -0
- package/src/domain/types/TickReceipt.js +285 -0
- package/src/domain/types/WarpTypes.js +209 -0
- package/src/domain/types/WarpTypesV2.js +200 -0
- package/src/domain/utils/CachedValue.js +140 -0
- package/src/domain/utils/EventId.js +89 -0
- package/src/domain/utils/LRUCache.js +112 -0
- package/src/domain/utils/MinHeap.js +114 -0
- package/src/domain/utils/RefLayout.js +280 -0
- package/src/domain/utils/WriterId.js +205 -0
- package/src/domain/utils/cancellation.js +33 -0
- package/src/domain/utils/canonicalStringify.js +42 -0
- package/src/domain/utils/defaultClock.js +20 -0
- package/src/domain/utils/defaultCodec.js +51 -0
- package/src/domain/utils/nullLogger.js +21 -0
- package/src/domain/utils/roaring.js +181 -0
- package/src/domain/utils/shardVersion.js +9 -0
- package/src/domain/warp/PatchSession.js +217 -0
- package/src/domain/warp/Writer.js +181 -0
- package/src/hooks/post-merge.sh +60 -0
- package/src/infrastructure/adapters/BunHttpAdapter.js +225 -0
- package/src/infrastructure/adapters/ClockAdapter.js +57 -0
- package/src/infrastructure/adapters/ConsoleLogger.js +150 -0
- package/src/infrastructure/adapters/DenoHttpAdapter.js +230 -0
- package/src/infrastructure/adapters/GitGraphAdapter.js +787 -0
- package/src/infrastructure/adapters/GlobalClockAdapter.js +5 -0
- package/src/infrastructure/adapters/NoOpLogger.js +62 -0
- package/src/infrastructure/adapters/NodeCryptoAdapter.js +32 -0
- package/src/infrastructure/adapters/NodeHttpAdapter.js +98 -0
- package/src/infrastructure/adapters/PerformanceClockAdapter.js +5 -0
- package/src/infrastructure/adapters/WebCryptoAdapter.js +121 -0
- package/src/infrastructure/codecs/CborCodec.js +384 -0
- package/src/ports/BlobPort.js +30 -0
- package/src/ports/ClockPort.js +25 -0
- package/src/ports/CodecPort.js +25 -0
- package/src/ports/CommitPort.js +114 -0
- package/src/ports/ConfigPort.js +31 -0
- package/src/ports/CryptoPort.js +38 -0
- package/src/ports/GraphPersistencePort.js +57 -0
- package/src/ports/HttpServerPort.js +25 -0
- package/src/ports/IndexStoragePort.js +39 -0
- package/src/ports/LoggerPort.js +68 -0
- package/src/ports/RefPort.js +51 -0
- package/src/ports/TreePort.js +51 -0
- package/src/visualization/index.js +26 -0
- package/src/visualization/layouts/converters.js +75 -0
- package/src/visualization/layouts/elkAdapter.js +86 -0
- package/src/visualization/layouts/elkLayout.js +95 -0
- package/src/visualization/layouts/index.js +29 -0
- package/src/visualization/renderers/ascii/box.js +16 -0
- package/src/visualization/renderers/ascii/check.js +271 -0
- package/src/visualization/renderers/ascii/colors.js +13 -0
- package/src/visualization/renderers/ascii/formatters.js +73 -0
- package/src/visualization/renderers/ascii/graph.js +344 -0
- package/src/visualization/renderers/ascii/history.js +335 -0
- package/src/visualization/renderers/ascii/index.js +14 -0
- package/src/visualization/renderers/ascii/info.js +245 -0
- package/src/visualization/renderers/ascii/materialize.js +255 -0
- package/src/visualization/renderers/ascii/path.js +240 -0
- package/src/visualization/renderers/ascii/progress.js +32 -0
- package/src/visualization/renderers/ascii/symbols.js +33 -0
- package/src/visualization/renderers/ascii/table.js +19 -0
- package/src/visualization/renderers/browser/index.js +1 -0
- package/src/visualization/renderers/svg/index.js +159 -0
- package/src/visualization/utils/ansi.js +14 -0
- package/src/visualization/utils/time.js +40 -0
- package/src/visualization/utils/truncate.js +40 -0
- package/src/visualization/utils/unicode.js +52 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abstract port for graph persistence operations.
|
|
3
|
+
*
|
|
4
|
+
* Defines the contract for reading and writing graph data to a Git-backed
|
|
5
|
+
* storage layer. Concrete adapters (e.g., GitGraphAdapter) implement this
|
|
6
|
+
* interface to provide actual Git operations.
|
|
7
|
+
*
|
|
8
|
+
* This is a **composite port** that implements the union of five focused ports:
|
|
9
|
+
*
|
|
10
|
+
* - {@link CommitPort} — commit creation, reading, logging, counting, ping
|
|
11
|
+
* - {@link BlobPort} — blob read/write
|
|
12
|
+
* - {@link TreePort} — tree read/write, emptyTree getter
|
|
13
|
+
* - {@link RefPort} — ref update/read/delete
|
|
14
|
+
* - {@link ConfigPort} — git config get/set
|
|
15
|
+
*
|
|
16
|
+
* Domain services should document which focused port(s) they actually depend on
|
|
17
|
+
* via JSDoc, even though they accept the full GraphPersistencePort at runtime.
|
|
18
|
+
* This enables future narrowing without breaking backward compatibility.
|
|
19
|
+
*
|
|
20
|
+
* All methods throw by default and must be overridden by implementations.
|
|
21
|
+
*
|
|
22
|
+
* @abstract
|
|
23
|
+
* @implements {CommitPort}
|
|
24
|
+
* @implements {BlobPort}
|
|
25
|
+
* @implements {TreePort}
|
|
26
|
+
* @implements {RefPort}
|
|
27
|
+
* @implements {ConfigPort}
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import CommitPort from './CommitPort.js';
|
|
31
|
+
import BlobPort from './BlobPort.js';
|
|
32
|
+
import TreePort from './TreePort.js';
|
|
33
|
+
import RefPort from './RefPort.js';
|
|
34
|
+
import ConfigPort from './ConfigPort.js';
|
|
35
|
+
|
|
36
|
+
class GraphPersistencePort {}
|
|
37
|
+
|
|
38
|
+
const focusedPorts = [CommitPort, BlobPort, TreePort, RefPort, ConfigPort];
|
|
39
|
+
const seen = new Map();
|
|
40
|
+
|
|
41
|
+
for (const Port of focusedPorts) {
|
|
42
|
+
const descriptors = Object.getOwnPropertyDescriptors(Port.prototype);
|
|
43
|
+
delete descriptors.constructor;
|
|
44
|
+
|
|
45
|
+
for (const [name, descriptor] of Object.entries(descriptors)) {
|
|
46
|
+
if (seen.has(name)) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`GraphPersistencePort composition collision: "${name}" defined by both ` +
|
|
49
|
+
`${seen.get(name).name} and ${Port.name}`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
seen.set(name, Port);
|
|
53
|
+
Object.defineProperty(GraphPersistencePort.prototype, name, descriptor);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export default GraphPersistencePort;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Port for HTTP server creation.
|
|
3
|
+
*
|
|
4
|
+
* Abstracts platform-specific HTTP server APIs so domain code
|
|
5
|
+
* doesn't depend on node:http directly.
|
|
6
|
+
*/
|
|
7
|
+
export default class HttpServerPort {
|
|
8
|
+
/**
|
|
9
|
+
* Creates an HTTP server with a platform-agnostic request handler.
|
|
10
|
+
*
|
|
11
|
+
* The request handler receives a plain object `{ method, url, headers, body }`
|
|
12
|
+
* and must return `{ status, headers, body }`. No raw req/res objects
|
|
13
|
+
* are exposed to the domain.
|
|
14
|
+
*
|
|
15
|
+
* @param {Function} requestHandler - Async function (request) => response
|
|
16
|
+
* @param {string} requestHandler.method - HTTP method
|
|
17
|
+
* @param {string} requestHandler.url - Request URL
|
|
18
|
+
* @param {Object} requestHandler.headers - Request headers (lowercased keys)
|
|
19
|
+
* @param {Buffer|undefined} requestHandler.body - Request body (undefined if none)
|
|
20
|
+
* @returns {{ listen: Function, close: Function, address: Function }} Server with listen(port, [host], cb(err)), close(cb), and address()
|
|
21
|
+
*/
|
|
22
|
+
createServer(_requestHandler) {
|
|
23
|
+
throw new Error('HttpServerPort.createServer() not implemented');
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Port interface for bitmap index storage operations.
|
|
3
|
+
*
|
|
4
|
+
* This port defines the contract for persisting and retrieving
|
|
5
|
+
* the sharded bitmap index data. Adapters implement this interface
|
|
6
|
+
* to store indexes in different backends (Git, filesystem, etc.).
|
|
7
|
+
*
|
|
8
|
+
* This port is a subset of the focused ports: it uses methods from
|
|
9
|
+
* {@link BlobPort} (writeBlob, readBlob), {@link TreePort} (writeTree,
|
|
10
|
+
* readTreeOids), and {@link RefPort} (updateRef, readRef).
|
|
11
|
+
*
|
|
12
|
+
* @abstract
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import BlobPort from './BlobPort.js';
|
|
16
|
+
import TreePort from './TreePort.js';
|
|
17
|
+
import RefPort from './RefPort.js';
|
|
18
|
+
|
|
19
|
+
class IndexStoragePort {}
|
|
20
|
+
|
|
21
|
+
const picks = [
|
|
22
|
+
[BlobPort, ['writeBlob', 'readBlob']],
|
|
23
|
+
[TreePort, ['writeTree', 'readTreeOids']],
|
|
24
|
+
[RefPort, ['updateRef', 'readRef']],
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
for (const [Port, methods] of picks) {
|
|
28
|
+
const descriptors = Object.getOwnPropertyDescriptors(Port.prototype);
|
|
29
|
+
for (const name of methods) {
|
|
30
|
+
if (!descriptors[name]) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`IndexStoragePort: "${name}" not found on ${Port.name}.prototype`,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
Object.defineProperty(IndexStoragePort.prototype, name, descriptors[name]);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export default IndexStoragePort;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Port interface for structured logging operations.
|
|
3
|
+
*
|
|
4
|
+
* This port defines the contract for logging across the application.
|
|
5
|
+
* Adapters implement this interface to provide different logging
|
|
6
|
+
* backends (console, file, external services, no-op for testing).
|
|
7
|
+
*
|
|
8
|
+
* All methods accept an optional context object for structured metadata.
|
|
9
|
+
* Child loggers inherit and merge parent context.
|
|
10
|
+
*
|
|
11
|
+
* @abstract
|
|
12
|
+
*/
|
|
13
|
+
export default class LoggerPort {
|
|
14
|
+
/**
|
|
15
|
+
* Log a debug-level message.
|
|
16
|
+
* @param {string} message - The log message
|
|
17
|
+
* @param {Record<string, unknown>} [context] - Structured metadata
|
|
18
|
+
* @returns {void}
|
|
19
|
+
* @abstract
|
|
20
|
+
*/
|
|
21
|
+
debug(_message, _context) {
|
|
22
|
+
throw new Error('Not implemented');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Log an info-level message.
|
|
27
|
+
* @param {string} message - The log message
|
|
28
|
+
* @param {Record<string, unknown>} [context] - Structured metadata
|
|
29
|
+
* @returns {void}
|
|
30
|
+
* @abstract
|
|
31
|
+
*/
|
|
32
|
+
info(_message, _context) {
|
|
33
|
+
throw new Error('Not implemented');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Log a warning-level message.
|
|
38
|
+
* @param {string} message - The log message
|
|
39
|
+
* @param {Record<string, unknown>} [context] - Structured metadata
|
|
40
|
+
* @returns {void}
|
|
41
|
+
* @abstract
|
|
42
|
+
*/
|
|
43
|
+
warn(_message, _context) {
|
|
44
|
+
throw new Error('Not implemented');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Log an error-level message.
|
|
49
|
+
* @param {string} message - The log message
|
|
50
|
+
* @param {Record<string, unknown>} [context] - Structured metadata
|
|
51
|
+
* @returns {void}
|
|
52
|
+
* @abstract
|
|
53
|
+
*/
|
|
54
|
+
error(_message, _context) {
|
|
55
|
+
throw new Error('Not implemented');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Create a child logger with additional base context.
|
|
60
|
+
* Child loggers inherit parent context and merge with their own.
|
|
61
|
+
* @param {Record<string, unknown>} context - Base context for the child logger
|
|
62
|
+
* @returns {LoggerPort} A new logger instance with merged context
|
|
63
|
+
* @abstract
|
|
64
|
+
*/
|
|
65
|
+
child(_context) {
|
|
66
|
+
throw new Error('Not implemented');
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Port for Git ref operations.
|
|
3
|
+
*
|
|
4
|
+
* Defines the contract for creating, reading, and deleting Git refs.
|
|
5
|
+
* This is one of five focused ports extracted from GraphPersistencePort.
|
|
6
|
+
*
|
|
7
|
+
* @abstract
|
|
8
|
+
* @see GraphPersistencePort - Composite port implementing all five focused ports
|
|
9
|
+
*/
|
|
10
|
+
export default class RefPort {
|
|
11
|
+
/**
|
|
12
|
+
* Updates a ref to point to an OID.
|
|
13
|
+
* @param {string} ref - The ref name (e.g., 'refs/warp/events/writers/alice')
|
|
14
|
+
* @param {string} oid - The OID to point to
|
|
15
|
+
* @returns {Promise<void>}
|
|
16
|
+
* @throws {Error} If not implemented by a concrete adapter
|
|
17
|
+
*/
|
|
18
|
+
async updateRef(_ref, _oid) {
|
|
19
|
+
throw new Error('RefPort.updateRef() not implemented');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Reads the OID a ref points to.
|
|
24
|
+
* @param {string} ref - The ref name
|
|
25
|
+
* @returns {Promise<string|null>} The OID, or null if the ref does not exist
|
|
26
|
+
* @throws {Error} If not implemented by a concrete adapter
|
|
27
|
+
*/
|
|
28
|
+
async readRef(_ref) {
|
|
29
|
+
throw new Error('RefPort.readRef() not implemented');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Deletes a ref.
|
|
34
|
+
* @param {string} ref - The ref name to delete
|
|
35
|
+
* @returns {Promise<void>}
|
|
36
|
+
* @throws {Error} If not implemented by a concrete adapter
|
|
37
|
+
*/
|
|
38
|
+
async deleteRef(_ref) {
|
|
39
|
+
throw new Error('RefPort.deleteRef() not implemented');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Lists refs matching a prefix.
|
|
44
|
+
* @param {string} prefix - The ref prefix to match (e.g., 'refs/warp/events/writers/')
|
|
45
|
+
* @returns {Promise<string[]>} Array of matching ref names
|
|
46
|
+
* @throws {Error} If not implemented by a concrete adapter
|
|
47
|
+
*/
|
|
48
|
+
async listRefs(_prefix) {
|
|
49
|
+
throw new Error('RefPort.listRefs() not implemented');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Port for Git tree operations.
|
|
3
|
+
*
|
|
4
|
+
* Defines the contract for writing and reading Git tree objects.
|
|
5
|
+
* This is one of five focused ports extracted from GraphPersistencePort.
|
|
6
|
+
*
|
|
7
|
+
* @abstract
|
|
8
|
+
* @see GraphPersistencePort - Composite port implementing all five focused ports
|
|
9
|
+
*/
|
|
10
|
+
export default class TreePort {
|
|
11
|
+
/**
|
|
12
|
+
* Creates a Git tree from mktree-formatted entries.
|
|
13
|
+
* @param {string[]} entries - Lines in git mktree format (e.g., "100644 blob <oid>\t<path>")
|
|
14
|
+
* @returns {Promise<string>} The Git OID of the created tree
|
|
15
|
+
* @throws {Error} If not implemented by a concrete adapter
|
|
16
|
+
*/
|
|
17
|
+
async writeTree(_entries) {
|
|
18
|
+
throw new Error('TreePort.writeTree() not implemented');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Reads a tree and returns a map of path to content.
|
|
23
|
+
* @param {string} treeOid - The tree OID to read
|
|
24
|
+
* @returns {Promise<Record<string, Buffer>>} Map of file path to blob content
|
|
25
|
+
* @throws {Error} If not implemented by a concrete adapter
|
|
26
|
+
*/
|
|
27
|
+
async readTree(_treeOid) {
|
|
28
|
+
throw new Error('TreePort.readTree() not implemented');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Reads a tree and returns a map of path to blob OID.
|
|
33
|
+
* Useful for lazy-loading shards without reading all blob contents.
|
|
34
|
+
* @param {string} treeOid - The tree OID to read
|
|
35
|
+
* @returns {Promise<Record<string, string>>} Map of file path to blob OID
|
|
36
|
+
* @throws {Error} If not implemented by a concrete adapter
|
|
37
|
+
*/
|
|
38
|
+
async readTreeOids(_treeOid) {
|
|
39
|
+
throw new Error('TreePort.readTreeOids() not implemented');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The well-known SHA for Git's empty tree object.
|
|
44
|
+
* All WARP graph commits point to this tree so that no files appear in the working directory.
|
|
45
|
+
* @type {string}
|
|
46
|
+
* @readonly
|
|
47
|
+
*/
|
|
48
|
+
get emptyTree() {
|
|
49
|
+
throw new Error('TreePort.emptyTree not implemented');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Visualization module - main exports
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// ASCII renderers
|
|
6
|
+
export * from './renderers/ascii/index.js';
|
|
7
|
+
|
|
8
|
+
// SVG renderer
|
|
9
|
+
export { renderSvg } from './renderers/svg/index.js';
|
|
10
|
+
|
|
11
|
+
// Layout engine
|
|
12
|
+
export {
|
|
13
|
+
layoutGraph,
|
|
14
|
+
queryResultToGraphData,
|
|
15
|
+
pathResultToGraphData,
|
|
16
|
+
rawGraphToGraphData,
|
|
17
|
+
toElkGraph,
|
|
18
|
+
getDefaultLayoutOptions,
|
|
19
|
+
runLayout,
|
|
20
|
+
} from './layouts/index.js';
|
|
21
|
+
|
|
22
|
+
// Utils
|
|
23
|
+
export { truncate } from './utils/truncate.js';
|
|
24
|
+
export { timeAgo, formatDuration } from './utils/time.js';
|
|
25
|
+
export { padRight, padLeft, center } from './utils/unicode.js';
|
|
26
|
+
export { stripAnsi } from './utils/ansi.js';
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data converters: transform WarpGraph payloads into a normalized graph-data
|
|
3
|
+
* intermediate format consumed by the ELK adapter.
|
|
4
|
+
*
|
|
5
|
+
* Intermediate format:
|
|
6
|
+
* { nodes: [{ id, label, props? }], edges: [{ from, to, label? }] }
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Converts a query result payload + edge array into graph data.
|
|
11
|
+
* Edges are filtered to only those connecting matched nodes.
|
|
12
|
+
*
|
|
13
|
+
* @param {Object} payload - Query result { nodes: [{id, props}] }
|
|
14
|
+
* @param {Array} edges - Edge array from graph.getEdges()
|
|
15
|
+
* @returns {{ nodes: Array, edges: Array }}
|
|
16
|
+
*/
|
|
17
|
+
export function queryResultToGraphData(payload, edges) {
|
|
18
|
+
const nodes = (payload?.nodes ?? []).map((n) => ({
|
|
19
|
+
id: n.id,
|
|
20
|
+
label: n.id,
|
|
21
|
+
props: n.props,
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
const nodeSet = new Set(nodes.map((n) => n.id));
|
|
25
|
+
|
|
26
|
+
const filtered = (edges ?? [])
|
|
27
|
+
.filter((e) => nodeSet.has(e.from) && nodeSet.has(e.to))
|
|
28
|
+
.map((e) => ({ from: e.from, to: e.to, label: e.label }));
|
|
29
|
+
|
|
30
|
+
return { nodes, edges: filtered };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Converts a path result payload into graph data.
|
|
35
|
+
* Builds a linear chain of nodes with labelled edges.
|
|
36
|
+
*
|
|
37
|
+
* @param {Object} payload - Path result { path: string[], edges?: string[] }
|
|
38
|
+
* @returns {{ nodes: Array, edges: Array }}
|
|
39
|
+
*/
|
|
40
|
+
export function pathResultToGraphData(payload) {
|
|
41
|
+
const pathArr = payload?.path ?? [];
|
|
42
|
+
const edgeLabels = payload?.edges ?? [];
|
|
43
|
+
|
|
44
|
+
const nodes = pathArr.map((id) => ({ id, label: id }));
|
|
45
|
+
|
|
46
|
+
const edges = [];
|
|
47
|
+
for (let i = 0; i < pathArr.length - 1; i++) {
|
|
48
|
+
edges.push({
|
|
49
|
+
from: pathArr[i],
|
|
50
|
+
to: pathArr[i + 1],
|
|
51
|
+
label: edgeLabels[i],
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return { nodes, edges };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Converts raw getNodes() + getEdges() output into graph data.
|
|
60
|
+
*
|
|
61
|
+
* @param {string[]} nodeIds - Array of node IDs
|
|
62
|
+
* @param {Array} edges - Edge array from graph.getEdges()
|
|
63
|
+
* @returns {{ nodes: Array, edges: Array }}
|
|
64
|
+
*/
|
|
65
|
+
export function rawGraphToGraphData(nodeIds, edges) {
|
|
66
|
+
const nodes = (nodeIds ?? []).map((id) => ({ id, label: id }));
|
|
67
|
+
|
|
68
|
+
const mapped = (edges ?? []).map((e) => ({
|
|
69
|
+
from: e.from,
|
|
70
|
+
to: e.to,
|
|
71
|
+
label: e.label,
|
|
72
|
+
}));
|
|
73
|
+
|
|
74
|
+
return { nodes, edges: mapped };
|
|
75
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ELK adapter: converts normalised graph data into ELK JSON input.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const LAYOUT_PRESETS = {
|
|
6
|
+
query: {
|
|
7
|
+
'elk.algorithm': 'layered',
|
|
8
|
+
'elk.direction': 'DOWN',
|
|
9
|
+
'elk.spacing.nodeNode': '40',
|
|
10
|
+
'elk.layered.spacing.nodeNodeBetweenLayers': '60',
|
|
11
|
+
},
|
|
12
|
+
path: {
|
|
13
|
+
'elk.algorithm': 'layered',
|
|
14
|
+
'elk.direction': 'RIGHT',
|
|
15
|
+
'elk.spacing.nodeNode': '40',
|
|
16
|
+
'elk.layered.spacing.nodeNodeBetweenLayers': '60',
|
|
17
|
+
},
|
|
18
|
+
slice: {
|
|
19
|
+
'elk.algorithm': 'layered',
|
|
20
|
+
'elk.direction': 'DOWN',
|
|
21
|
+
'elk.spacing.nodeNode': '40',
|
|
22
|
+
'elk.layered.spacing.nodeNodeBetweenLayers': '60',
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const DEFAULT_PRESET = LAYOUT_PRESETS.query;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Returns ELK layout options for a given visualisation type.
|
|
30
|
+
*
|
|
31
|
+
* @param {'query'|'path'|'slice'} type
|
|
32
|
+
* @returns {Object} ELK layout options
|
|
33
|
+
*/
|
|
34
|
+
export function getDefaultLayoutOptions(type) {
|
|
35
|
+
return LAYOUT_PRESETS[type] ?? DEFAULT_PRESET;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Estimates pixel width for a node label.
|
|
40
|
+
* Approximates monospace glyph width at ~9px with 24px padding.
|
|
41
|
+
*/
|
|
42
|
+
function estimateNodeWidth(label) {
|
|
43
|
+
const charWidth = 9;
|
|
44
|
+
const padding = 24;
|
|
45
|
+
const minWidth = 80;
|
|
46
|
+
return Math.max((label?.length ?? 0) * charWidth + padding, minWidth);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const NODE_HEIGHT = 40;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Converts normalised graph data to an ELK graph JSON object.
|
|
53
|
+
*
|
|
54
|
+
* @param {{ nodes: Array, edges: Array }} graphData
|
|
55
|
+
* @param {{ type?: string, layoutOptions?: Object }} [options]
|
|
56
|
+
* @returns {Object} ELK-format graph
|
|
57
|
+
*/
|
|
58
|
+
export function toElkGraph(graphData, options = {}) {
|
|
59
|
+
const { type = 'query', layoutOptions } = options;
|
|
60
|
+
|
|
61
|
+
const children = (graphData.nodes ?? []).map((n) => ({
|
|
62
|
+
id: n.id,
|
|
63
|
+
width: estimateNodeWidth(n.label),
|
|
64
|
+
height: NODE_HEIGHT,
|
|
65
|
+
labels: [{ text: n.label ?? n.id }],
|
|
66
|
+
}));
|
|
67
|
+
|
|
68
|
+
const edges = (graphData.edges ?? []).map((e, i) => {
|
|
69
|
+
const edge = {
|
|
70
|
+
id: `e${i}`,
|
|
71
|
+
sources: [e.from],
|
|
72
|
+
targets: [e.to],
|
|
73
|
+
};
|
|
74
|
+
if (e.label) {
|
|
75
|
+
edge.labels = [{ text: e.label }];
|
|
76
|
+
}
|
|
77
|
+
return edge;
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
id: 'root',
|
|
82
|
+
layoutOptions: layoutOptions ?? getDefaultLayoutOptions(type),
|
|
83
|
+
children,
|
|
84
|
+
edges,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ELK layout runner: lazy-loads elkjs and executes layout.
|
|
3
|
+
*
|
|
4
|
+
* The ELK engine (~2.5 MB) is loaded via dynamic import() only when
|
|
5
|
+
* a layout is actually requested, keeping normal CLI startup fast.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
let elkPromise = null;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Returns (or creates) a singleton ELK instance.
|
|
12
|
+
* @returns {Promise<Object>} ELK instance
|
|
13
|
+
*/
|
|
14
|
+
function getElk() {
|
|
15
|
+
if (!elkPromise) {
|
|
16
|
+
elkPromise = import('elkjs/lib/elk.bundled.js').then((mod) => new mod.default());
|
|
17
|
+
}
|
|
18
|
+
return elkPromise;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Runs ELK layout on a graph and returns a PositionedGraph.
|
|
23
|
+
*
|
|
24
|
+
* @param {Object} elkGraph - ELK-format graph from toElkGraph()
|
|
25
|
+
* @returns {Promise<Object>} PositionedGraph
|
|
26
|
+
*/
|
|
27
|
+
export async function runLayout(elkGraph) {
|
|
28
|
+
let result;
|
|
29
|
+
try {
|
|
30
|
+
const elk = await getElk();
|
|
31
|
+
result = await elk.layout(elkGraph);
|
|
32
|
+
} catch {
|
|
33
|
+
return fallbackLayout(elkGraph);
|
|
34
|
+
}
|
|
35
|
+
return toPositionedGraph(result);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Converts ELK output to a PositionedGraph.
|
|
40
|
+
*/
|
|
41
|
+
function toPositionedGraph(result) {
|
|
42
|
+
const nodes = (result.children ?? []).map((c) => ({
|
|
43
|
+
id: c.id,
|
|
44
|
+
x: c.x ?? 0,
|
|
45
|
+
y: c.y ?? 0,
|
|
46
|
+
width: c.width ?? 80,
|
|
47
|
+
height: c.height ?? 40,
|
|
48
|
+
label: c.labels?.[0]?.text ?? c.id,
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
const edges = (result.edges ?? []).map((e) => ({
|
|
52
|
+
id: e.id,
|
|
53
|
+
source: e.sources?.[0] ?? '',
|
|
54
|
+
target: e.targets?.[0] ?? '',
|
|
55
|
+
label: e.labels?.[0]?.text,
|
|
56
|
+
sections: e.sections ?? [],
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
nodes,
|
|
61
|
+
edges,
|
|
62
|
+
width: result.width ?? 0,
|
|
63
|
+
height: result.height ?? 0,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Fallback: line nodes up horizontally when ELK fails.
|
|
69
|
+
*/
|
|
70
|
+
function fallbackLayout(elkGraph) {
|
|
71
|
+
let x = 20;
|
|
72
|
+
const nodes = (elkGraph.children ?? []).map((c) => {
|
|
73
|
+
const node = {
|
|
74
|
+
id: c.id,
|
|
75
|
+
x,
|
|
76
|
+
y: 20,
|
|
77
|
+
width: c.width ?? 80,
|
|
78
|
+
height: c.height ?? 40,
|
|
79
|
+
label: c.labels?.[0]?.text ?? c.id,
|
|
80
|
+
};
|
|
81
|
+
x += (c.width ?? 80) + 40;
|
|
82
|
+
return node;
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const edges = (elkGraph.edges ?? []).map((e) => ({
|
|
86
|
+
id: e.id,
|
|
87
|
+
source: e.sources?.[0] ?? '',
|
|
88
|
+
target: e.targets?.[0] ?? '',
|
|
89
|
+
label: e.labels?.[0]?.text,
|
|
90
|
+
sections: [],
|
|
91
|
+
}));
|
|
92
|
+
|
|
93
|
+
const totalWidth = x;
|
|
94
|
+
return { nodes, edges, width: totalWidth, height: 80 };
|
|
95
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layout engine facade.
|
|
3
|
+
*
|
|
4
|
+
* Orchestrates: converter → ELK adapter → ELK runner → PositionedGraph.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
queryResultToGraphData,
|
|
9
|
+
pathResultToGraphData,
|
|
10
|
+
rawGraphToGraphData,
|
|
11
|
+
} from './converters.js';
|
|
12
|
+
|
|
13
|
+
export { toElkGraph, getDefaultLayoutOptions } from './elkAdapter.js';
|
|
14
|
+
export { runLayout } from './elkLayout.js';
|
|
15
|
+
|
|
16
|
+
import { toElkGraph } from './elkAdapter.js';
|
|
17
|
+
import { runLayout } from './elkLayout.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Full pipeline: graphData → PositionedGraph.
|
|
21
|
+
*
|
|
22
|
+
* @param {{ nodes: Array, edges: Array }} graphData - Normalised graph data
|
|
23
|
+
* @param {{ type?: string, layoutOptions?: Object }} [options]
|
|
24
|
+
* @returns {Promise<Object>} PositionedGraph
|
|
25
|
+
*/
|
|
26
|
+
export async function layoutGraph(graphData, options = {}) {
|
|
27
|
+
const elkGraph = toElkGraph(graphData, options);
|
|
28
|
+
return await runLayout(elkGraph);
|
|
29
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import boxen from 'boxen';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wraps content in a bordered box using boxen.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} content - The text content to display inside the box
|
|
7
|
+
* @param {Object} [options] - Options forwarded to boxen (e.g. title, borderColor)
|
|
8
|
+
* @returns {string} The boxed content string
|
|
9
|
+
*/
|
|
10
|
+
export function createBox(content, options = {}) {
|
|
11
|
+
return boxen(content, {
|
|
12
|
+
padding: 1,
|
|
13
|
+
borderStyle: 'double',
|
|
14
|
+
...options,
|
|
15
|
+
});
|
|
16
|
+
}
|