@3plate/graph-core 0.1.9 → 0.1.12
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/dist/index.cjs +110 -104
- package/dist/index.d.cts +107 -74
- package/dist/index.d.ts +107 -74
- package/dist/index.js +110 -104
- package/package.json +7 -5
package/dist/index.cjs
CHANGED
|
@@ -3678,7 +3678,7 @@ var Ingest = class {
|
|
|
3678
3678
|
* Apply an incoming ingest message to the API.
|
|
3679
3679
|
* - snapshot: rebuild state from nodes/edges (clears prior history)
|
|
3680
3680
|
* - update: apply incremental update
|
|
3681
|
-
* - history: initialize from a set of
|
|
3681
|
+
* - history: initialize from a set of updates (clears prior history)
|
|
3682
3682
|
*/
|
|
3683
3683
|
async apply(msg) {
|
|
3684
3684
|
switch (msg.type) {
|
|
@@ -3699,7 +3699,7 @@ var Ingest = class {
|
|
|
3699
3699
|
break;
|
|
3700
3700
|
}
|
|
3701
3701
|
case "history": {
|
|
3702
|
-
await this.api.replaceHistory(msg.
|
|
3702
|
+
await this.api.replaceHistory(msg.history);
|
|
3703
3703
|
break;
|
|
3704
3704
|
}
|
|
3705
3705
|
}
|
|
@@ -3717,11 +3717,11 @@ var WebSocketSource = class {
|
|
|
3717
3717
|
connectStartTime = null;
|
|
3718
3718
|
totalTimeoutMs = 1e4;
|
|
3719
3719
|
totalTimeoutTimer = null;
|
|
3720
|
-
constructor(
|
|
3721
|
-
this.url = url;
|
|
3722
|
-
this.onMessage = onMessage;
|
|
3723
|
-
this.onStatus = onStatus;
|
|
3724
|
-
this.reconnectMs = reconnectMs;
|
|
3720
|
+
constructor(args) {
|
|
3721
|
+
this.url = args.url;
|
|
3722
|
+
this.onMessage = args.onMessage;
|
|
3723
|
+
this.onStatus = args.onStatus;
|
|
3724
|
+
this.reconnectMs = args.reconnectMs ?? 1500;
|
|
3725
3725
|
}
|
|
3726
3726
|
connect() {
|
|
3727
3727
|
this.closedByUser = false;
|
|
@@ -3822,11 +3822,11 @@ var FileSource = class {
|
|
|
3822
3822
|
lastContent = "";
|
|
3823
3823
|
intervalMs = 1e3;
|
|
3824
3824
|
closed = false;
|
|
3825
|
-
constructor(
|
|
3826
|
-
this.url = url;
|
|
3827
|
-
this.onMessage = onMessage;
|
|
3828
|
-
this.onStatus = onStatus;
|
|
3829
|
-
this.intervalMs = intervalMs;
|
|
3825
|
+
constructor(args) {
|
|
3826
|
+
this.url = args.url;
|
|
3827
|
+
this.onMessage = args.onMessage;
|
|
3828
|
+
this.onStatus = args.onStatus;
|
|
3829
|
+
this.intervalMs = args.intervalMs ?? 1e3;
|
|
3830
3830
|
}
|
|
3831
3831
|
async connect() {
|
|
3832
3832
|
this.closed = false;
|
|
@@ -3888,6 +3888,68 @@ var FileSource = class {
|
|
|
3888
3888
|
}
|
|
3889
3889
|
};
|
|
3890
3890
|
|
|
3891
|
+
// src/api/sources/FileSystemSource.ts
|
|
3892
|
+
var FileSystemSource = class {
|
|
3893
|
+
handle = null;
|
|
3894
|
+
onMessage;
|
|
3895
|
+
onStatus;
|
|
3896
|
+
timer = null;
|
|
3897
|
+
lastSize = 0;
|
|
3898
|
+
filename;
|
|
3899
|
+
intervalMs;
|
|
3900
|
+
constructor(args) {
|
|
3901
|
+
this.filename = args.filename;
|
|
3902
|
+
this.onMessage = args.onMessage;
|
|
3903
|
+
this.onStatus = args.onStatus;
|
|
3904
|
+
this.intervalMs = args.intervalMs ?? 1e3;
|
|
3905
|
+
}
|
|
3906
|
+
async openDirectory() {
|
|
3907
|
+
try {
|
|
3908
|
+
const dir = await window.showDirectoryPicker?.();
|
|
3909
|
+
if (!dir) throw new Error("File System Access not supported or cancelled");
|
|
3910
|
+
const handle = await dir.getFileHandle(this.filename, { create: false });
|
|
3911
|
+
this.handle = handle;
|
|
3912
|
+
this.onStatus?.("opened", { file: this.filename });
|
|
3913
|
+
this.lastSize = 0;
|
|
3914
|
+
this.startPolling();
|
|
3915
|
+
} catch (e) {
|
|
3916
|
+
this.onStatus?.("error", e);
|
|
3917
|
+
}
|
|
3918
|
+
}
|
|
3919
|
+
close() {
|
|
3920
|
+
if (this.timer) {
|
|
3921
|
+
window.clearInterval(this.timer);
|
|
3922
|
+
this.timer = null;
|
|
3923
|
+
}
|
|
3924
|
+
this.handle = null;
|
|
3925
|
+
this.onStatus?.("closed");
|
|
3926
|
+
}
|
|
3927
|
+
startPolling() {
|
|
3928
|
+
if (this.timer) window.clearInterval(this.timer);
|
|
3929
|
+
this.timer = window.setInterval(() => this.readNewLines(), this.intervalMs);
|
|
3930
|
+
}
|
|
3931
|
+
async readNewLines() {
|
|
3932
|
+
try {
|
|
3933
|
+
if (!this.handle) return;
|
|
3934
|
+
this.onStatus?.("reading");
|
|
3935
|
+
const file = await this.handle.getFile();
|
|
3936
|
+
if (file.size === this.lastSize) return;
|
|
3937
|
+
const slice = await file.slice(this.lastSize).text();
|
|
3938
|
+
this.lastSize = file.size;
|
|
3939
|
+
const lines = slice.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
3940
|
+
for (const line of lines) {
|
|
3941
|
+
try {
|
|
3942
|
+
const obj = JSON.parse(line);
|
|
3943
|
+
this.onMessage(obj);
|
|
3944
|
+
} catch {
|
|
3945
|
+
}
|
|
3946
|
+
}
|
|
3947
|
+
} catch (e) {
|
|
3948
|
+
this.onStatus?.("error", e);
|
|
3949
|
+
}
|
|
3950
|
+
}
|
|
3951
|
+
};
|
|
3952
|
+
|
|
3891
3953
|
// src/api/api.ts
|
|
3892
3954
|
var log11 = logger("api");
|
|
3893
3955
|
var API = class {
|
|
@@ -3970,29 +4032,19 @@ var API = class {
|
|
|
3970
4032
|
/** Connect to the configured ingestion source */
|
|
3971
4033
|
connectIngestion() {
|
|
3972
4034
|
if (!this.ingestionConfig || !this.ingest) return;
|
|
3973
|
-
const
|
|
3974
|
-
this.
|
|
4035
|
+
const args = {
|
|
4036
|
+
...this.ingestionConfig,
|
|
4037
|
+
onMessage: (msg) => {
|
|
4038
|
+
this.ingest.apply(msg);
|
|
4039
|
+
}
|
|
3975
4040
|
};
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
);
|
|
3984
|
-
this.ingestionSource.connect();
|
|
3985
|
-
break;
|
|
3986
|
-
case "file":
|
|
3987
|
-
this.ingestionSource = new FileSource(
|
|
3988
|
-
this.ingestionConfig.url,
|
|
3989
|
-
handleMessage,
|
|
3990
|
-
void 0,
|
|
3991
|
-
this.ingestionConfig.intervalMs
|
|
3992
|
-
);
|
|
3993
|
-
this.ingestionSource.connect();
|
|
3994
|
-
break;
|
|
3995
|
-
}
|
|
4041
|
+
const source = {
|
|
4042
|
+
"websocket": WebSocketSource,
|
|
4043
|
+
"file": FileSource,
|
|
4044
|
+
"filesystem": FileSystemSource
|
|
4045
|
+
}[this.ingestionConfig.type];
|
|
4046
|
+
this.ingestionSource = new source[this.ingestionConfig.type](args);
|
|
4047
|
+
this.ingestionSource?.connect();
|
|
3996
4048
|
}
|
|
3997
4049
|
/** Disconnect from the ingestion source */
|
|
3998
4050
|
disconnectIngestion() {
|
|
@@ -4021,9 +4073,9 @@ var API = class {
|
|
|
4021
4073
|
this.canvas.editMode.editable = editable;
|
|
4022
4074
|
}
|
|
4023
4075
|
/** Replace entire history (clears prior) */
|
|
4024
|
-
async replaceHistory(
|
|
4076
|
+
async replaceHistory(history) {
|
|
4025
4077
|
this.reset();
|
|
4026
|
-
this.history =
|
|
4078
|
+
this.history = history;
|
|
4027
4079
|
await this.applyHistory();
|
|
4028
4080
|
}
|
|
4029
4081
|
/** Rebuild from snapshot (nodes/edges) */
|
|
@@ -4490,8 +4542,8 @@ var API = class {
|
|
|
4490
4542
|
}
|
|
4491
4543
|
} else if (prev.history && isHistoryPrefix(prev.history, props.history)) {
|
|
4492
4544
|
const prevLength = prev.history.length;
|
|
4493
|
-
const
|
|
4494
|
-
for (const frame of
|
|
4545
|
+
const newUpdates = props.history.slice(prevLength);
|
|
4546
|
+
for (const frame of newUpdates) {
|
|
4495
4547
|
this.update((u) => {
|
|
4496
4548
|
if (frame.addNodes) u.addNodes(...frame.addNodes);
|
|
4497
4549
|
if (frame.removeNodes) u.deleteNodes(...frame.removeNodes);
|
|
@@ -4578,68 +4630,6 @@ function shallowEqualUpdate(a, b) {
|
|
|
4578
4630
|
return true;
|
|
4579
4631
|
}
|
|
4580
4632
|
|
|
4581
|
-
// src/api/sources/FileSystemSource.ts
|
|
4582
|
-
var FileSystemSource = class {
|
|
4583
|
-
handle = null;
|
|
4584
|
-
onMessage;
|
|
4585
|
-
onStatus;
|
|
4586
|
-
timer = null;
|
|
4587
|
-
lastSize = 0;
|
|
4588
|
-
filename;
|
|
4589
|
-
intervalMs;
|
|
4590
|
-
constructor(onMessage, onStatus, filename = "graph.ndjson", intervalMs = 1e3) {
|
|
4591
|
-
this.onMessage = onMessage;
|
|
4592
|
-
this.onStatus = onStatus;
|
|
4593
|
-
this.filename = filename;
|
|
4594
|
-
this.intervalMs = intervalMs;
|
|
4595
|
-
}
|
|
4596
|
-
async openDirectory() {
|
|
4597
|
-
try {
|
|
4598
|
-
const dir = await window.showDirectoryPicker?.();
|
|
4599
|
-
if (!dir) throw new Error("File System Access not supported or cancelled");
|
|
4600
|
-
const handle = await dir.getFileHandle(this.filename, { create: false });
|
|
4601
|
-
this.handle = handle;
|
|
4602
|
-
this.onStatus?.("opened", { file: this.filename });
|
|
4603
|
-
this.lastSize = 0;
|
|
4604
|
-
this.startPolling();
|
|
4605
|
-
} catch (e) {
|
|
4606
|
-
this.onStatus?.("error", e);
|
|
4607
|
-
}
|
|
4608
|
-
}
|
|
4609
|
-
close() {
|
|
4610
|
-
if (this.timer) {
|
|
4611
|
-
window.clearInterval(this.timer);
|
|
4612
|
-
this.timer = null;
|
|
4613
|
-
}
|
|
4614
|
-
this.handle = null;
|
|
4615
|
-
this.onStatus?.("closed");
|
|
4616
|
-
}
|
|
4617
|
-
startPolling() {
|
|
4618
|
-
if (this.timer) window.clearInterval(this.timer);
|
|
4619
|
-
this.timer = window.setInterval(() => this.readNewLines(), this.intervalMs);
|
|
4620
|
-
}
|
|
4621
|
-
async readNewLines() {
|
|
4622
|
-
try {
|
|
4623
|
-
if (!this.handle) return;
|
|
4624
|
-
this.onStatus?.("reading");
|
|
4625
|
-
const file = await this.handle.getFile();
|
|
4626
|
-
if (file.size === this.lastSize) return;
|
|
4627
|
-
const slice = await file.slice(this.lastSize).text();
|
|
4628
|
-
this.lastSize = file.size;
|
|
4629
|
-
const lines = slice.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
4630
|
-
for (const line of lines) {
|
|
4631
|
-
try {
|
|
4632
|
-
const obj = JSON.parse(line);
|
|
4633
|
-
this.onMessage(obj);
|
|
4634
|
-
} catch {
|
|
4635
|
-
}
|
|
4636
|
-
}
|
|
4637
|
-
} catch (e) {
|
|
4638
|
-
this.onStatus?.("error", e);
|
|
4639
|
-
}
|
|
4640
|
-
}
|
|
4641
|
-
};
|
|
4642
|
-
|
|
4643
4633
|
// src/playground/playground.ts
|
|
4644
4634
|
var import_styles2 = __toESM(require("./styles.css?raw"), 1);
|
|
4645
4635
|
var Playground = class {
|
|
@@ -4918,10 +4908,18 @@ var Playground = class {
|
|
|
4918
4908
|
this.disconnectAllSources();
|
|
4919
4909
|
if (example.source.type === "websocket") {
|
|
4920
4910
|
this.wsUrl = example.source.url;
|
|
4921
|
-
this.wsSource = new WebSocketSource(
|
|
4911
|
+
this.wsSource = new WebSocketSource({
|
|
4912
|
+
url: example.source.url,
|
|
4913
|
+
onMessage: this.handleIngestMessage.bind(this),
|
|
4914
|
+
onStatus: this.updateWsStatus
|
|
4915
|
+
});
|
|
4922
4916
|
this.wsSource.connect();
|
|
4923
4917
|
} else if (example.source.type === "file") {
|
|
4924
|
-
this.fileSource = new FileSource(
|
|
4918
|
+
this.fileSource = new FileSource({
|
|
4919
|
+
url: example.source.path,
|
|
4920
|
+
onMessage: this.handleIngestMessage.bind(this),
|
|
4921
|
+
onStatus: this.updateFileStatus
|
|
4922
|
+
});
|
|
4925
4923
|
this.fileSource.connect();
|
|
4926
4924
|
}
|
|
4927
4925
|
}
|
|
@@ -5321,7 +5319,11 @@ var Playground = class {
|
|
|
5321
5319
|
if (this.wsSource) {
|
|
5322
5320
|
this.wsSource.disconnect();
|
|
5323
5321
|
}
|
|
5324
|
-
this.wsSource = new WebSocketSource(
|
|
5322
|
+
this.wsSource = new WebSocketSource({
|
|
5323
|
+
url,
|
|
5324
|
+
onMessage: this.handleIngestMessage.bind(this),
|
|
5325
|
+
onStatus: this.updateWsStatus
|
|
5326
|
+
});
|
|
5325
5327
|
this.wsSource.connect();
|
|
5326
5328
|
this.updateSourceModal();
|
|
5327
5329
|
}
|
|
@@ -5342,7 +5344,11 @@ var Playground = class {
|
|
|
5342
5344
|
}
|
|
5343
5345
|
async handleOpenFolder() {
|
|
5344
5346
|
if (!this.fsSource) {
|
|
5345
|
-
this.fsSource = new FileSystemSource(
|
|
5347
|
+
this.fsSource = new FileSystemSource({
|
|
5348
|
+
filename: "graph.ndjson",
|
|
5349
|
+
onMessage: this.handleIngestMessage.bind(this),
|
|
5350
|
+
onStatus: this.updateFsStatus
|
|
5351
|
+
});
|
|
5346
5352
|
}
|
|
5347
5353
|
this.updateSourceModal();
|
|
5348
5354
|
await this.fsSource.openDirectory();
|
package/dist/index.d.cts
CHANGED
|
@@ -3,6 +3,7 @@ type MergeOrder = Side[];
|
|
|
3
3
|
type NodeAlign = 'natural' | 'top' | 'bottom' | 'left' | 'right';
|
|
4
4
|
type Orientation = 'TB' | 'BT' | 'LR' | 'RL';
|
|
5
5
|
type Nav = 'first' | 'last' | 'prev' | 'next';
|
|
6
|
+
type PortStyle = 'inside' | 'outside' | 'custom';
|
|
6
7
|
type LayoutStep = 'alignChildren' | 'alignParents' | 'compact';
|
|
7
8
|
type Dims = {
|
|
8
9
|
w: number;
|
|
@@ -11,19 +12,112 @@ type Dims = {
|
|
|
11
12
|
|
|
12
13
|
type MarkerType = 'arrow' | 'circle' | 'diamond' | 'bar' | 'none';
|
|
13
14
|
|
|
15
|
+
type SnapshotMessage<N, E> = {
|
|
16
|
+
type: 'snapshot';
|
|
17
|
+
nodes: N[];
|
|
18
|
+
edges: E[];
|
|
19
|
+
description?: string;
|
|
20
|
+
};
|
|
21
|
+
type UpdateMessage<N, E> = {
|
|
22
|
+
type: 'update';
|
|
23
|
+
description?: string;
|
|
24
|
+
} & Update<N, E>;
|
|
25
|
+
type HistoryMessage<N, E> = {
|
|
26
|
+
type: 'history';
|
|
27
|
+
history: Update<N, E>[];
|
|
28
|
+
};
|
|
29
|
+
type IngestMessage<N, E> = SnapshotMessage<N, E> | UpdateMessage<N, E> | HistoryMessage<N, E>;
|
|
14
30
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
31
|
+
* Ingest class handles applying ingest messages to an API instance.
|
|
32
|
+
* This is the core ingestion functionality, separate from UI concerns.
|
|
17
33
|
*/
|
|
18
|
-
|
|
34
|
+
declare class Ingest<N, E> {
|
|
35
|
+
api: API<N, E>;
|
|
36
|
+
constructor(api: API<N, E>);
|
|
37
|
+
/**
|
|
38
|
+
* Apply an incoming ingest message to the API.
|
|
39
|
+
* - snapshot: rebuild state from nodes/edges (clears prior history)
|
|
40
|
+
* - update: apply incremental update
|
|
41
|
+
* - history: initialize from a set of updates (clears prior history)
|
|
42
|
+
*/
|
|
43
|
+
apply(msg: IngestMessage<N, E>): Promise<void>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type WebSocketStatus = 'connecting' | 'connected' | 'reconnecting' | 'closed' | 'error';
|
|
47
|
+
type WebSocketStatusListener = (status: WebSocketStatus, detail?: any) => void;
|
|
48
|
+
type WebSocketSourceArgs<N, E> = {
|
|
49
|
+
url: string;
|
|
50
|
+
onMessage: (msg: IngestMessage<N, E>) => void;
|
|
51
|
+
onStatus?: WebSocketStatusListener;
|
|
52
|
+
reconnectMs?: number;
|
|
53
|
+
};
|
|
54
|
+
declare class WebSocketSource<N, E> {
|
|
55
|
+
private url;
|
|
56
|
+
private ws;
|
|
57
|
+
private onMessage;
|
|
58
|
+
private onStatus?;
|
|
59
|
+
private reconnectMs;
|
|
60
|
+
private closedByUser;
|
|
61
|
+
private connectStartTime;
|
|
62
|
+
private totalTimeoutMs;
|
|
63
|
+
private totalTimeoutTimer;
|
|
64
|
+
constructor(args: WebSocketSourceArgs<N, E>);
|
|
65
|
+
connect(): void;
|
|
66
|
+
disconnect(): void;
|
|
67
|
+
private startTotalTimeout;
|
|
68
|
+
private clearTotalTimeout;
|
|
69
|
+
private open;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
type FileStatus = 'idle' | 'opened' | 'reading' | 'error' | 'closed';
|
|
73
|
+
type FileStatusListener = (status: FileStatus, detail?: any) => void;
|
|
74
|
+
type FileSourceArgs<N, E> = {
|
|
75
|
+
url: string;
|
|
76
|
+
onMessage: (msg: IngestMessage<N, E>) => void;
|
|
77
|
+
onStatus?: FileStatusListener;
|
|
78
|
+
intervalMs?: number;
|
|
79
|
+
};
|
|
80
|
+
declare class FileSource<N, E> {
|
|
81
|
+
private url;
|
|
82
|
+
private onMessage;
|
|
83
|
+
private onStatus?;
|
|
84
|
+
private timer;
|
|
85
|
+
private lastETag;
|
|
86
|
+
private lastContent;
|
|
87
|
+
private intervalMs;
|
|
88
|
+
private closed;
|
|
89
|
+
constructor(args: FileSourceArgs<N, E>);
|
|
90
|
+
connect(): Promise<void>;
|
|
91
|
+
close(): void;
|
|
92
|
+
private startPolling;
|
|
93
|
+
private poll;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** WebSocket ingestion configuration */
|
|
97
|
+
type WebSocketIngestionConfig = {
|
|
19
98
|
type: 'websocket';
|
|
99
|
+
/** WebSocket URL */
|
|
20
100
|
url: string;
|
|
101
|
+
/** Reconnect interval in milliseconds */
|
|
21
102
|
reconnectMs?: number;
|
|
22
|
-
|
|
103
|
+
/** Status listener */
|
|
104
|
+
onStatus?: WebSocketStatusListener;
|
|
105
|
+
};
|
|
106
|
+
/** File ingestion configuration */
|
|
107
|
+
type FileIngestionConfig = {
|
|
23
108
|
type: 'file';
|
|
109
|
+
/** File URL */
|
|
24
110
|
url: string;
|
|
111
|
+
/** Polling interval in milliseconds */
|
|
25
112
|
intervalMs?: number;
|
|
113
|
+
/** Status listener */
|
|
114
|
+
onStatus?: FileStatusListener;
|
|
26
115
|
};
|
|
116
|
+
/**
|
|
117
|
+
* Ingestion source configuration.
|
|
118
|
+
* Used to connect to an external data source for graph updates.
|
|
119
|
+
*/
|
|
120
|
+
type IngestionConfig = WebSocketIngestionConfig | FileIngestionConfig;
|
|
27
121
|
/**
|
|
28
122
|
* Arguments to the API constructor.
|
|
29
123
|
*
|
|
@@ -406,7 +500,7 @@ declare class API<N, E> {
|
|
|
406
500
|
/** Toggle canvas editable mode without re-creating the graph */
|
|
407
501
|
setEditable(editable: boolean): void;
|
|
408
502
|
/** Replace entire history (clears prior) */
|
|
409
|
-
replaceHistory(
|
|
503
|
+
replaceHistory(history: Update<N, E>[]): Promise<void>;
|
|
410
504
|
/** Rebuild from snapshot (nodes/edges) */
|
|
411
505
|
replaceSnapshot(nodes: N[], edges: E[], description?: string): Promise<void>;
|
|
412
506
|
private get graph();
|
|
@@ -487,57 +581,13 @@ declare class API<N, E> {
|
|
|
487
581
|
destroy(): void;
|
|
488
582
|
}
|
|
489
583
|
|
|
490
|
-
type
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
type UpdateMessage<N, E> = {
|
|
497
|
-
type: 'update';
|
|
498
|
-
description?: string;
|
|
499
|
-
} & Update<N, E>;
|
|
500
|
-
type HistoryMessage<N, E> = {
|
|
501
|
-
type: 'history';
|
|
502
|
-
frames: Update<N, E>[];
|
|
584
|
+
type StatusListener = (status: 'idle' | 'opened' | 'reading' | 'error' | 'closed', detail?: any) => void;
|
|
585
|
+
type FileSystemSourceArgs<N, E> = {
|
|
586
|
+
filename: string;
|
|
587
|
+
onMessage: (msg: IngestMessage<N, E>) => void;
|
|
588
|
+
onStatus?: StatusListener;
|
|
589
|
+
intervalMs?: number;
|
|
503
590
|
};
|
|
504
|
-
type IngestMessage<N, E> = SnapshotMessage<N, E> | UpdateMessage<N, E> | HistoryMessage<N, E>;
|
|
505
|
-
/**
|
|
506
|
-
* Ingest class handles applying ingest messages to an API instance.
|
|
507
|
-
* This is the core ingestion functionality, separate from UI concerns.
|
|
508
|
-
*/
|
|
509
|
-
declare class Ingest<N, E> {
|
|
510
|
-
api: API<N, E>;
|
|
511
|
-
constructor(api: API<N, E>);
|
|
512
|
-
/**
|
|
513
|
-
* Apply an incoming ingest message to the API.
|
|
514
|
-
* - snapshot: rebuild state from nodes/edges (clears prior history)
|
|
515
|
-
* - update: apply incremental update
|
|
516
|
-
* - history: initialize from a set of frames (clears prior history)
|
|
517
|
-
*/
|
|
518
|
-
apply(msg: IngestMessage<N, E>): Promise<void>;
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
type StatusListener$2 = (status: 'connecting' | 'connected' | 'reconnecting' | 'closed' | 'error', detail?: any) => void;
|
|
522
|
-
declare class WebSocketSource<N, E> {
|
|
523
|
-
private url;
|
|
524
|
-
private ws;
|
|
525
|
-
private onMessage;
|
|
526
|
-
private onStatus?;
|
|
527
|
-
private reconnectMs;
|
|
528
|
-
private closedByUser;
|
|
529
|
-
private connectStartTime;
|
|
530
|
-
private totalTimeoutMs;
|
|
531
|
-
private totalTimeoutTimer;
|
|
532
|
-
constructor(url: string, onMessage: (msg: IngestMessage<N, E>) => void, onStatus?: StatusListener$2, reconnectMs?: number);
|
|
533
|
-
connect(): void;
|
|
534
|
-
disconnect(): void;
|
|
535
|
-
private startTotalTimeout;
|
|
536
|
-
private clearTotalTimeout;
|
|
537
|
-
private open;
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
type StatusListener$1 = (status: 'idle' | 'opened' | 'reading' | 'error' | 'closed', detail?: any) => void;
|
|
541
591
|
declare class FileSystemSource<N, E> {
|
|
542
592
|
private handle;
|
|
543
593
|
private onMessage;
|
|
@@ -546,30 +596,13 @@ declare class FileSystemSource<N, E> {
|
|
|
546
596
|
private lastSize;
|
|
547
597
|
private filename;
|
|
548
598
|
private intervalMs;
|
|
549
|
-
constructor(
|
|
599
|
+
constructor(args: FileSystemSourceArgs<N, E>);
|
|
550
600
|
openDirectory(): Promise<void>;
|
|
551
601
|
close(): void;
|
|
552
602
|
private startPolling;
|
|
553
603
|
private readNewLines;
|
|
554
604
|
}
|
|
555
605
|
|
|
556
|
-
type StatusListener = (status: 'idle' | 'opened' | 'reading' | 'error' | 'closed', detail?: any) => void;
|
|
557
|
-
declare class FileSource<N, E> {
|
|
558
|
-
private url;
|
|
559
|
-
private onMessage;
|
|
560
|
-
private onStatus?;
|
|
561
|
-
private timer;
|
|
562
|
-
private lastETag;
|
|
563
|
-
private lastContent;
|
|
564
|
-
private intervalMs;
|
|
565
|
-
private closed;
|
|
566
|
-
constructor(url: string, onMessage: (msg: IngestMessage<N, E>) => void, onStatus?: StatusListener, intervalMs?: number);
|
|
567
|
-
connect(): Promise<void>;
|
|
568
|
-
close(): void;
|
|
569
|
-
private startPolling;
|
|
570
|
-
private poll;
|
|
571
|
-
}
|
|
572
|
-
|
|
573
606
|
/**
|
|
574
607
|
* Types for the Playground component
|
|
575
608
|
*/
|
|
@@ -685,4 +718,4 @@ declare class Playground {
|
|
|
685
718
|
|
|
686
719
|
declare function graph<N, E>(args?: APIArguments<N, E>): Promise<API<N, E>>;
|
|
687
720
|
|
|
688
|
-
export { API, type APIArguments, type APIOptions, type EventsOptions, type Example, type ExampleEdge, type ExampleNode, type ExampleOptions, FileSource, FileSystemSource, type HistoryMessage, Ingest, type IngestMessage, type IngestionConfig, Playground, type PlaygroundOptions, type SnapshotMessage, type Update, type UpdateMessage, Updater, WebSocketSource, graph as default, graph };
|
|
721
|
+
export { API, type APIArguments, type APIOptions, type CanvasTheme, type ColorMode, type EdgeProps, type EdgeTheme, type EventsOptions, type Example, type ExampleEdge, type ExampleNode, type ExampleOptions, FileSource, type FileSourceArgs, type FileStatus, type FileStatusListener, FileSystemSource, type FileSystemSourceArgs, type HistoryMessage, Ingest, type IngestMessage, type IngestionConfig, type NewEdge, type NewNode, type NodeAlign, type NodeProps, type NodeTheme, type Orientation, Playground, type PlaygroundOptions, type PortProps, type PortStyle, type PortTheme, type RenderNode, type SnapshotMessage, type ThemeVars, type Update, type UpdateMessage, Updater, WebSocketSource, type WebSocketSourceArgs, type WebSocketStatus, type WebSocketStatusListener, graph as default, graph };
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ type MergeOrder = Side[];
|
|
|
3
3
|
type NodeAlign = 'natural' | 'top' | 'bottom' | 'left' | 'right';
|
|
4
4
|
type Orientation = 'TB' | 'BT' | 'LR' | 'RL';
|
|
5
5
|
type Nav = 'first' | 'last' | 'prev' | 'next';
|
|
6
|
+
type PortStyle = 'inside' | 'outside' | 'custom';
|
|
6
7
|
type LayoutStep = 'alignChildren' | 'alignParents' | 'compact';
|
|
7
8
|
type Dims = {
|
|
8
9
|
w: number;
|
|
@@ -11,19 +12,112 @@ type Dims = {
|
|
|
11
12
|
|
|
12
13
|
type MarkerType = 'arrow' | 'circle' | 'diamond' | 'bar' | 'none';
|
|
13
14
|
|
|
15
|
+
type SnapshotMessage<N, E> = {
|
|
16
|
+
type: 'snapshot';
|
|
17
|
+
nodes: N[];
|
|
18
|
+
edges: E[];
|
|
19
|
+
description?: string;
|
|
20
|
+
};
|
|
21
|
+
type UpdateMessage<N, E> = {
|
|
22
|
+
type: 'update';
|
|
23
|
+
description?: string;
|
|
24
|
+
} & Update<N, E>;
|
|
25
|
+
type HistoryMessage<N, E> = {
|
|
26
|
+
type: 'history';
|
|
27
|
+
history: Update<N, E>[];
|
|
28
|
+
};
|
|
29
|
+
type IngestMessage<N, E> = SnapshotMessage<N, E> | UpdateMessage<N, E> | HistoryMessage<N, E>;
|
|
14
30
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
31
|
+
* Ingest class handles applying ingest messages to an API instance.
|
|
32
|
+
* This is the core ingestion functionality, separate from UI concerns.
|
|
17
33
|
*/
|
|
18
|
-
|
|
34
|
+
declare class Ingest<N, E> {
|
|
35
|
+
api: API<N, E>;
|
|
36
|
+
constructor(api: API<N, E>);
|
|
37
|
+
/**
|
|
38
|
+
* Apply an incoming ingest message to the API.
|
|
39
|
+
* - snapshot: rebuild state from nodes/edges (clears prior history)
|
|
40
|
+
* - update: apply incremental update
|
|
41
|
+
* - history: initialize from a set of updates (clears prior history)
|
|
42
|
+
*/
|
|
43
|
+
apply(msg: IngestMessage<N, E>): Promise<void>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type WebSocketStatus = 'connecting' | 'connected' | 'reconnecting' | 'closed' | 'error';
|
|
47
|
+
type WebSocketStatusListener = (status: WebSocketStatus, detail?: any) => void;
|
|
48
|
+
type WebSocketSourceArgs<N, E> = {
|
|
49
|
+
url: string;
|
|
50
|
+
onMessage: (msg: IngestMessage<N, E>) => void;
|
|
51
|
+
onStatus?: WebSocketStatusListener;
|
|
52
|
+
reconnectMs?: number;
|
|
53
|
+
};
|
|
54
|
+
declare class WebSocketSource<N, E> {
|
|
55
|
+
private url;
|
|
56
|
+
private ws;
|
|
57
|
+
private onMessage;
|
|
58
|
+
private onStatus?;
|
|
59
|
+
private reconnectMs;
|
|
60
|
+
private closedByUser;
|
|
61
|
+
private connectStartTime;
|
|
62
|
+
private totalTimeoutMs;
|
|
63
|
+
private totalTimeoutTimer;
|
|
64
|
+
constructor(args: WebSocketSourceArgs<N, E>);
|
|
65
|
+
connect(): void;
|
|
66
|
+
disconnect(): void;
|
|
67
|
+
private startTotalTimeout;
|
|
68
|
+
private clearTotalTimeout;
|
|
69
|
+
private open;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
type FileStatus = 'idle' | 'opened' | 'reading' | 'error' | 'closed';
|
|
73
|
+
type FileStatusListener = (status: FileStatus, detail?: any) => void;
|
|
74
|
+
type FileSourceArgs<N, E> = {
|
|
75
|
+
url: string;
|
|
76
|
+
onMessage: (msg: IngestMessage<N, E>) => void;
|
|
77
|
+
onStatus?: FileStatusListener;
|
|
78
|
+
intervalMs?: number;
|
|
79
|
+
};
|
|
80
|
+
declare class FileSource<N, E> {
|
|
81
|
+
private url;
|
|
82
|
+
private onMessage;
|
|
83
|
+
private onStatus?;
|
|
84
|
+
private timer;
|
|
85
|
+
private lastETag;
|
|
86
|
+
private lastContent;
|
|
87
|
+
private intervalMs;
|
|
88
|
+
private closed;
|
|
89
|
+
constructor(args: FileSourceArgs<N, E>);
|
|
90
|
+
connect(): Promise<void>;
|
|
91
|
+
close(): void;
|
|
92
|
+
private startPolling;
|
|
93
|
+
private poll;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** WebSocket ingestion configuration */
|
|
97
|
+
type WebSocketIngestionConfig = {
|
|
19
98
|
type: 'websocket';
|
|
99
|
+
/** WebSocket URL */
|
|
20
100
|
url: string;
|
|
101
|
+
/** Reconnect interval in milliseconds */
|
|
21
102
|
reconnectMs?: number;
|
|
22
|
-
|
|
103
|
+
/** Status listener */
|
|
104
|
+
onStatus?: WebSocketStatusListener;
|
|
105
|
+
};
|
|
106
|
+
/** File ingestion configuration */
|
|
107
|
+
type FileIngestionConfig = {
|
|
23
108
|
type: 'file';
|
|
109
|
+
/** File URL */
|
|
24
110
|
url: string;
|
|
111
|
+
/** Polling interval in milliseconds */
|
|
25
112
|
intervalMs?: number;
|
|
113
|
+
/** Status listener */
|
|
114
|
+
onStatus?: FileStatusListener;
|
|
26
115
|
};
|
|
116
|
+
/**
|
|
117
|
+
* Ingestion source configuration.
|
|
118
|
+
* Used to connect to an external data source for graph updates.
|
|
119
|
+
*/
|
|
120
|
+
type IngestionConfig = WebSocketIngestionConfig | FileIngestionConfig;
|
|
27
121
|
/**
|
|
28
122
|
* Arguments to the API constructor.
|
|
29
123
|
*
|
|
@@ -406,7 +500,7 @@ declare class API<N, E> {
|
|
|
406
500
|
/** Toggle canvas editable mode without re-creating the graph */
|
|
407
501
|
setEditable(editable: boolean): void;
|
|
408
502
|
/** Replace entire history (clears prior) */
|
|
409
|
-
replaceHistory(
|
|
503
|
+
replaceHistory(history: Update<N, E>[]): Promise<void>;
|
|
410
504
|
/** Rebuild from snapshot (nodes/edges) */
|
|
411
505
|
replaceSnapshot(nodes: N[], edges: E[], description?: string): Promise<void>;
|
|
412
506
|
private get graph();
|
|
@@ -487,57 +581,13 @@ declare class API<N, E> {
|
|
|
487
581
|
destroy(): void;
|
|
488
582
|
}
|
|
489
583
|
|
|
490
|
-
type
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
type UpdateMessage<N, E> = {
|
|
497
|
-
type: 'update';
|
|
498
|
-
description?: string;
|
|
499
|
-
} & Update<N, E>;
|
|
500
|
-
type HistoryMessage<N, E> = {
|
|
501
|
-
type: 'history';
|
|
502
|
-
frames: Update<N, E>[];
|
|
584
|
+
type StatusListener = (status: 'idle' | 'opened' | 'reading' | 'error' | 'closed', detail?: any) => void;
|
|
585
|
+
type FileSystemSourceArgs<N, E> = {
|
|
586
|
+
filename: string;
|
|
587
|
+
onMessage: (msg: IngestMessage<N, E>) => void;
|
|
588
|
+
onStatus?: StatusListener;
|
|
589
|
+
intervalMs?: number;
|
|
503
590
|
};
|
|
504
|
-
type IngestMessage<N, E> = SnapshotMessage<N, E> | UpdateMessage<N, E> | HistoryMessage<N, E>;
|
|
505
|
-
/**
|
|
506
|
-
* Ingest class handles applying ingest messages to an API instance.
|
|
507
|
-
* This is the core ingestion functionality, separate from UI concerns.
|
|
508
|
-
*/
|
|
509
|
-
declare class Ingest<N, E> {
|
|
510
|
-
api: API<N, E>;
|
|
511
|
-
constructor(api: API<N, E>);
|
|
512
|
-
/**
|
|
513
|
-
* Apply an incoming ingest message to the API.
|
|
514
|
-
* - snapshot: rebuild state from nodes/edges (clears prior history)
|
|
515
|
-
* - update: apply incremental update
|
|
516
|
-
* - history: initialize from a set of frames (clears prior history)
|
|
517
|
-
*/
|
|
518
|
-
apply(msg: IngestMessage<N, E>): Promise<void>;
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
type StatusListener$2 = (status: 'connecting' | 'connected' | 'reconnecting' | 'closed' | 'error', detail?: any) => void;
|
|
522
|
-
declare class WebSocketSource<N, E> {
|
|
523
|
-
private url;
|
|
524
|
-
private ws;
|
|
525
|
-
private onMessage;
|
|
526
|
-
private onStatus?;
|
|
527
|
-
private reconnectMs;
|
|
528
|
-
private closedByUser;
|
|
529
|
-
private connectStartTime;
|
|
530
|
-
private totalTimeoutMs;
|
|
531
|
-
private totalTimeoutTimer;
|
|
532
|
-
constructor(url: string, onMessage: (msg: IngestMessage<N, E>) => void, onStatus?: StatusListener$2, reconnectMs?: number);
|
|
533
|
-
connect(): void;
|
|
534
|
-
disconnect(): void;
|
|
535
|
-
private startTotalTimeout;
|
|
536
|
-
private clearTotalTimeout;
|
|
537
|
-
private open;
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
type StatusListener$1 = (status: 'idle' | 'opened' | 'reading' | 'error' | 'closed', detail?: any) => void;
|
|
541
591
|
declare class FileSystemSource<N, E> {
|
|
542
592
|
private handle;
|
|
543
593
|
private onMessage;
|
|
@@ -546,30 +596,13 @@ declare class FileSystemSource<N, E> {
|
|
|
546
596
|
private lastSize;
|
|
547
597
|
private filename;
|
|
548
598
|
private intervalMs;
|
|
549
|
-
constructor(
|
|
599
|
+
constructor(args: FileSystemSourceArgs<N, E>);
|
|
550
600
|
openDirectory(): Promise<void>;
|
|
551
601
|
close(): void;
|
|
552
602
|
private startPolling;
|
|
553
603
|
private readNewLines;
|
|
554
604
|
}
|
|
555
605
|
|
|
556
|
-
type StatusListener = (status: 'idle' | 'opened' | 'reading' | 'error' | 'closed', detail?: any) => void;
|
|
557
|
-
declare class FileSource<N, E> {
|
|
558
|
-
private url;
|
|
559
|
-
private onMessage;
|
|
560
|
-
private onStatus?;
|
|
561
|
-
private timer;
|
|
562
|
-
private lastETag;
|
|
563
|
-
private lastContent;
|
|
564
|
-
private intervalMs;
|
|
565
|
-
private closed;
|
|
566
|
-
constructor(url: string, onMessage: (msg: IngestMessage<N, E>) => void, onStatus?: StatusListener, intervalMs?: number);
|
|
567
|
-
connect(): Promise<void>;
|
|
568
|
-
close(): void;
|
|
569
|
-
private startPolling;
|
|
570
|
-
private poll;
|
|
571
|
-
}
|
|
572
|
-
|
|
573
606
|
/**
|
|
574
607
|
* Types for the Playground component
|
|
575
608
|
*/
|
|
@@ -685,4 +718,4 @@ declare class Playground {
|
|
|
685
718
|
|
|
686
719
|
declare function graph<N, E>(args?: APIArguments<N, E>): Promise<API<N, E>>;
|
|
687
720
|
|
|
688
|
-
export { API, type APIArguments, type APIOptions, type EventsOptions, type Example, type ExampleEdge, type ExampleNode, type ExampleOptions, FileSource, FileSystemSource, type HistoryMessage, Ingest, type IngestMessage, type IngestionConfig, Playground, type PlaygroundOptions, type SnapshotMessage, type Update, type UpdateMessage, Updater, WebSocketSource, graph as default, graph };
|
|
721
|
+
export { API, type APIArguments, type APIOptions, type CanvasTheme, type ColorMode, type EdgeProps, type EdgeTheme, type EventsOptions, type Example, type ExampleEdge, type ExampleNode, type ExampleOptions, FileSource, type FileSourceArgs, type FileStatus, type FileStatusListener, FileSystemSource, type FileSystemSourceArgs, type HistoryMessage, Ingest, type IngestMessage, type IngestionConfig, type NewEdge, type NewNode, type NodeAlign, type NodeProps, type NodeTheme, type Orientation, Playground, type PlaygroundOptions, type PortProps, type PortStyle, type PortTheme, type RenderNode, type SnapshotMessage, type ThemeVars, type Update, type UpdateMessage, Updater, WebSocketSource, type WebSocketSourceArgs, type WebSocketStatus, type WebSocketStatusListener, graph as default, graph };
|
package/dist/index.js
CHANGED
|
@@ -3635,7 +3635,7 @@ var Ingest = class {
|
|
|
3635
3635
|
* Apply an incoming ingest message to the API.
|
|
3636
3636
|
* - snapshot: rebuild state from nodes/edges (clears prior history)
|
|
3637
3637
|
* - update: apply incremental update
|
|
3638
|
-
* - history: initialize from a set of
|
|
3638
|
+
* - history: initialize from a set of updates (clears prior history)
|
|
3639
3639
|
*/
|
|
3640
3640
|
async apply(msg) {
|
|
3641
3641
|
switch (msg.type) {
|
|
@@ -3656,7 +3656,7 @@ var Ingest = class {
|
|
|
3656
3656
|
break;
|
|
3657
3657
|
}
|
|
3658
3658
|
case "history": {
|
|
3659
|
-
await this.api.replaceHistory(msg.
|
|
3659
|
+
await this.api.replaceHistory(msg.history);
|
|
3660
3660
|
break;
|
|
3661
3661
|
}
|
|
3662
3662
|
}
|
|
@@ -3674,11 +3674,11 @@ var WebSocketSource = class {
|
|
|
3674
3674
|
connectStartTime = null;
|
|
3675
3675
|
totalTimeoutMs = 1e4;
|
|
3676
3676
|
totalTimeoutTimer = null;
|
|
3677
|
-
constructor(
|
|
3678
|
-
this.url = url;
|
|
3679
|
-
this.onMessage = onMessage;
|
|
3680
|
-
this.onStatus = onStatus;
|
|
3681
|
-
this.reconnectMs = reconnectMs;
|
|
3677
|
+
constructor(args) {
|
|
3678
|
+
this.url = args.url;
|
|
3679
|
+
this.onMessage = args.onMessage;
|
|
3680
|
+
this.onStatus = args.onStatus;
|
|
3681
|
+
this.reconnectMs = args.reconnectMs ?? 1500;
|
|
3682
3682
|
}
|
|
3683
3683
|
connect() {
|
|
3684
3684
|
this.closedByUser = false;
|
|
@@ -3779,11 +3779,11 @@ var FileSource = class {
|
|
|
3779
3779
|
lastContent = "";
|
|
3780
3780
|
intervalMs = 1e3;
|
|
3781
3781
|
closed = false;
|
|
3782
|
-
constructor(
|
|
3783
|
-
this.url = url;
|
|
3784
|
-
this.onMessage = onMessage;
|
|
3785
|
-
this.onStatus = onStatus;
|
|
3786
|
-
this.intervalMs = intervalMs;
|
|
3782
|
+
constructor(args) {
|
|
3783
|
+
this.url = args.url;
|
|
3784
|
+
this.onMessage = args.onMessage;
|
|
3785
|
+
this.onStatus = args.onStatus;
|
|
3786
|
+
this.intervalMs = args.intervalMs ?? 1e3;
|
|
3787
3787
|
}
|
|
3788
3788
|
async connect() {
|
|
3789
3789
|
this.closed = false;
|
|
@@ -3845,6 +3845,68 @@ var FileSource = class {
|
|
|
3845
3845
|
}
|
|
3846
3846
|
};
|
|
3847
3847
|
|
|
3848
|
+
// src/api/sources/FileSystemSource.ts
|
|
3849
|
+
var FileSystemSource = class {
|
|
3850
|
+
handle = null;
|
|
3851
|
+
onMessage;
|
|
3852
|
+
onStatus;
|
|
3853
|
+
timer = null;
|
|
3854
|
+
lastSize = 0;
|
|
3855
|
+
filename;
|
|
3856
|
+
intervalMs;
|
|
3857
|
+
constructor(args) {
|
|
3858
|
+
this.filename = args.filename;
|
|
3859
|
+
this.onMessage = args.onMessage;
|
|
3860
|
+
this.onStatus = args.onStatus;
|
|
3861
|
+
this.intervalMs = args.intervalMs ?? 1e3;
|
|
3862
|
+
}
|
|
3863
|
+
async openDirectory() {
|
|
3864
|
+
try {
|
|
3865
|
+
const dir = await window.showDirectoryPicker?.();
|
|
3866
|
+
if (!dir) throw new Error("File System Access not supported or cancelled");
|
|
3867
|
+
const handle = await dir.getFileHandle(this.filename, { create: false });
|
|
3868
|
+
this.handle = handle;
|
|
3869
|
+
this.onStatus?.("opened", { file: this.filename });
|
|
3870
|
+
this.lastSize = 0;
|
|
3871
|
+
this.startPolling();
|
|
3872
|
+
} catch (e) {
|
|
3873
|
+
this.onStatus?.("error", e);
|
|
3874
|
+
}
|
|
3875
|
+
}
|
|
3876
|
+
close() {
|
|
3877
|
+
if (this.timer) {
|
|
3878
|
+
window.clearInterval(this.timer);
|
|
3879
|
+
this.timer = null;
|
|
3880
|
+
}
|
|
3881
|
+
this.handle = null;
|
|
3882
|
+
this.onStatus?.("closed");
|
|
3883
|
+
}
|
|
3884
|
+
startPolling() {
|
|
3885
|
+
if (this.timer) window.clearInterval(this.timer);
|
|
3886
|
+
this.timer = window.setInterval(() => this.readNewLines(), this.intervalMs);
|
|
3887
|
+
}
|
|
3888
|
+
async readNewLines() {
|
|
3889
|
+
try {
|
|
3890
|
+
if (!this.handle) return;
|
|
3891
|
+
this.onStatus?.("reading");
|
|
3892
|
+
const file = await this.handle.getFile();
|
|
3893
|
+
if (file.size === this.lastSize) return;
|
|
3894
|
+
const slice = await file.slice(this.lastSize).text();
|
|
3895
|
+
this.lastSize = file.size;
|
|
3896
|
+
const lines = slice.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
3897
|
+
for (const line of lines) {
|
|
3898
|
+
try {
|
|
3899
|
+
const obj = JSON.parse(line);
|
|
3900
|
+
this.onMessage(obj);
|
|
3901
|
+
} catch {
|
|
3902
|
+
}
|
|
3903
|
+
}
|
|
3904
|
+
} catch (e) {
|
|
3905
|
+
this.onStatus?.("error", e);
|
|
3906
|
+
}
|
|
3907
|
+
}
|
|
3908
|
+
};
|
|
3909
|
+
|
|
3848
3910
|
// src/api/api.ts
|
|
3849
3911
|
var log11 = logger("api");
|
|
3850
3912
|
var API = class {
|
|
@@ -3927,29 +3989,19 @@ var API = class {
|
|
|
3927
3989
|
/** Connect to the configured ingestion source */
|
|
3928
3990
|
connectIngestion() {
|
|
3929
3991
|
if (!this.ingestionConfig || !this.ingest) return;
|
|
3930
|
-
const
|
|
3931
|
-
this.
|
|
3992
|
+
const args = {
|
|
3993
|
+
...this.ingestionConfig,
|
|
3994
|
+
onMessage: (msg) => {
|
|
3995
|
+
this.ingest.apply(msg);
|
|
3996
|
+
}
|
|
3932
3997
|
};
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
);
|
|
3941
|
-
this.ingestionSource.connect();
|
|
3942
|
-
break;
|
|
3943
|
-
case "file":
|
|
3944
|
-
this.ingestionSource = new FileSource(
|
|
3945
|
-
this.ingestionConfig.url,
|
|
3946
|
-
handleMessage,
|
|
3947
|
-
void 0,
|
|
3948
|
-
this.ingestionConfig.intervalMs
|
|
3949
|
-
);
|
|
3950
|
-
this.ingestionSource.connect();
|
|
3951
|
-
break;
|
|
3952
|
-
}
|
|
3998
|
+
const source = {
|
|
3999
|
+
"websocket": WebSocketSource,
|
|
4000
|
+
"file": FileSource,
|
|
4001
|
+
"filesystem": FileSystemSource
|
|
4002
|
+
}[this.ingestionConfig.type];
|
|
4003
|
+
this.ingestionSource = new source[this.ingestionConfig.type](args);
|
|
4004
|
+
this.ingestionSource?.connect();
|
|
3953
4005
|
}
|
|
3954
4006
|
/** Disconnect from the ingestion source */
|
|
3955
4007
|
disconnectIngestion() {
|
|
@@ -3978,9 +4030,9 @@ var API = class {
|
|
|
3978
4030
|
this.canvas.editMode.editable = editable;
|
|
3979
4031
|
}
|
|
3980
4032
|
/** Replace entire history (clears prior) */
|
|
3981
|
-
async replaceHistory(
|
|
4033
|
+
async replaceHistory(history) {
|
|
3982
4034
|
this.reset();
|
|
3983
|
-
this.history =
|
|
4035
|
+
this.history = history;
|
|
3984
4036
|
await this.applyHistory();
|
|
3985
4037
|
}
|
|
3986
4038
|
/** Rebuild from snapshot (nodes/edges) */
|
|
@@ -4447,8 +4499,8 @@ var API = class {
|
|
|
4447
4499
|
}
|
|
4448
4500
|
} else if (prev.history && isHistoryPrefix(prev.history, props.history)) {
|
|
4449
4501
|
const prevLength = prev.history.length;
|
|
4450
|
-
const
|
|
4451
|
-
for (const frame of
|
|
4502
|
+
const newUpdates = props.history.slice(prevLength);
|
|
4503
|
+
for (const frame of newUpdates) {
|
|
4452
4504
|
this.update((u) => {
|
|
4453
4505
|
if (frame.addNodes) u.addNodes(...frame.addNodes);
|
|
4454
4506
|
if (frame.removeNodes) u.deleteNodes(...frame.removeNodes);
|
|
@@ -4535,68 +4587,6 @@ function shallowEqualUpdate(a, b) {
|
|
|
4535
4587
|
return true;
|
|
4536
4588
|
}
|
|
4537
4589
|
|
|
4538
|
-
// src/api/sources/FileSystemSource.ts
|
|
4539
|
-
var FileSystemSource = class {
|
|
4540
|
-
handle = null;
|
|
4541
|
-
onMessage;
|
|
4542
|
-
onStatus;
|
|
4543
|
-
timer = null;
|
|
4544
|
-
lastSize = 0;
|
|
4545
|
-
filename;
|
|
4546
|
-
intervalMs;
|
|
4547
|
-
constructor(onMessage, onStatus, filename = "graph.ndjson", intervalMs = 1e3) {
|
|
4548
|
-
this.onMessage = onMessage;
|
|
4549
|
-
this.onStatus = onStatus;
|
|
4550
|
-
this.filename = filename;
|
|
4551
|
-
this.intervalMs = intervalMs;
|
|
4552
|
-
}
|
|
4553
|
-
async openDirectory() {
|
|
4554
|
-
try {
|
|
4555
|
-
const dir = await window.showDirectoryPicker?.();
|
|
4556
|
-
if (!dir) throw new Error("File System Access not supported or cancelled");
|
|
4557
|
-
const handle = await dir.getFileHandle(this.filename, { create: false });
|
|
4558
|
-
this.handle = handle;
|
|
4559
|
-
this.onStatus?.("opened", { file: this.filename });
|
|
4560
|
-
this.lastSize = 0;
|
|
4561
|
-
this.startPolling();
|
|
4562
|
-
} catch (e) {
|
|
4563
|
-
this.onStatus?.("error", e);
|
|
4564
|
-
}
|
|
4565
|
-
}
|
|
4566
|
-
close() {
|
|
4567
|
-
if (this.timer) {
|
|
4568
|
-
window.clearInterval(this.timer);
|
|
4569
|
-
this.timer = null;
|
|
4570
|
-
}
|
|
4571
|
-
this.handle = null;
|
|
4572
|
-
this.onStatus?.("closed");
|
|
4573
|
-
}
|
|
4574
|
-
startPolling() {
|
|
4575
|
-
if (this.timer) window.clearInterval(this.timer);
|
|
4576
|
-
this.timer = window.setInterval(() => this.readNewLines(), this.intervalMs);
|
|
4577
|
-
}
|
|
4578
|
-
async readNewLines() {
|
|
4579
|
-
try {
|
|
4580
|
-
if (!this.handle) return;
|
|
4581
|
-
this.onStatus?.("reading");
|
|
4582
|
-
const file = await this.handle.getFile();
|
|
4583
|
-
if (file.size === this.lastSize) return;
|
|
4584
|
-
const slice = await file.slice(this.lastSize).text();
|
|
4585
|
-
this.lastSize = file.size;
|
|
4586
|
-
const lines = slice.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
4587
|
-
for (const line of lines) {
|
|
4588
|
-
try {
|
|
4589
|
-
const obj = JSON.parse(line);
|
|
4590
|
-
this.onMessage(obj);
|
|
4591
|
-
} catch {
|
|
4592
|
-
}
|
|
4593
|
-
}
|
|
4594
|
-
} catch (e) {
|
|
4595
|
-
this.onStatus?.("error", e);
|
|
4596
|
-
}
|
|
4597
|
-
}
|
|
4598
|
-
};
|
|
4599
|
-
|
|
4600
4590
|
// src/playground/playground.ts
|
|
4601
4591
|
import styles2 from "./styles.css?raw";
|
|
4602
4592
|
var Playground = class {
|
|
@@ -4875,10 +4865,18 @@ var Playground = class {
|
|
|
4875
4865
|
this.disconnectAllSources();
|
|
4876
4866
|
if (example.source.type === "websocket") {
|
|
4877
4867
|
this.wsUrl = example.source.url;
|
|
4878
|
-
this.wsSource = new WebSocketSource(
|
|
4868
|
+
this.wsSource = new WebSocketSource({
|
|
4869
|
+
url: example.source.url,
|
|
4870
|
+
onMessage: this.handleIngestMessage.bind(this),
|
|
4871
|
+
onStatus: this.updateWsStatus
|
|
4872
|
+
});
|
|
4879
4873
|
this.wsSource.connect();
|
|
4880
4874
|
} else if (example.source.type === "file") {
|
|
4881
|
-
this.fileSource = new FileSource(
|
|
4875
|
+
this.fileSource = new FileSource({
|
|
4876
|
+
url: example.source.path,
|
|
4877
|
+
onMessage: this.handleIngestMessage.bind(this),
|
|
4878
|
+
onStatus: this.updateFileStatus
|
|
4879
|
+
});
|
|
4882
4880
|
this.fileSource.connect();
|
|
4883
4881
|
}
|
|
4884
4882
|
}
|
|
@@ -5278,7 +5276,11 @@ var Playground = class {
|
|
|
5278
5276
|
if (this.wsSource) {
|
|
5279
5277
|
this.wsSource.disconnect();
|
|
5280
5278
|
}
|
|
5281
|
-
this.wsSource = new WebSocketSource(
|
|
5279
|
+
this.wsSource = new WebSocketSource({
|
|
5280
|
+
url,
|
|
5281
|
+
onMessage: this.handleIngestMessage.bind(this),
|
|
5282
|
+
onStatus: this.updateWsStatus
|
|
5283
|
+
});
|
|
5282
5284
|
this.wsSource.connect();
|
|
5283
5285
|
this.updateSourceModal();
|
|
5284
5286
|
}
|
|
@@ -5299,7 +5301,11 @@ var Playground = class {
|
|
|
5299
5301
|
}
|
|
5300
5302
|
async handleOpenFolder() {
|
|
5301
5303
|
if (!this.fsSource) {
|
|
5302
|
-
this.fsSource = new FileSystemSource(
|
|
5304
|
+
this.fsSource = new FileSystemSource({
|
|
5305
|
+
filename: "graph.ndjson",
|
|
5306
|
+
onMessage: this.handleIngestMessage.bind(this),
|
|
5307
|
+
onStatus: this.updateFsStatus
|
|
5308
|
+
});
|
|
5303
5309
|
}
|
|
5304
5310
|
this.updateSourceModal();
|
|
5305
5311
|
await this.fsSource.openDirectory();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@3plate/graph-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "GPL-3.0",
|
|
6
6
|
"repository": {
|
|
@@ -8,13 +8,15 @@
|
|
|
8
8
|
"url": "https://github.com/3plt/graph",
|
|
9
9
|
"directory": "packages/core"
|
|
10
10
|
},
|
|
11
|
-
"main": "./dist/index.
|
|
12
|
-
"module": "./dist/index.
|
|
11
|
+
"main": "./dist/index.cjs",
|
|
12
|
+
"module": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
13
14
|
"exports": {
|
|
14
15
|
".": {
|
|
15
16
|
"source": "./src/index.ts",
|
|
16
|
-
"
|
|
17
|
-
"
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"import": "./dist/index.js",
|
|
19
|
+
"require": "./dist/index.cjs"
|
|
18
20
|
}
|
|
19
21
|
},
|
|
20
22
|
"files": [
|