@3plate/graph-core 0.1.7 → 0.1.9
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 +371 -211
- package/dist/index.d.cts +40 -3
- package/dist/index.d.ts +40 -3
- package/dist/index.js +371 -211
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3626,6 +3626,225 @@ var Updater = class _Updater {
|
|
|
3626
3626
|
}
|
|
3627
3627
|
};
|
|
3628
3628
|
|
|
3629
|
+
// src/api/ingest.ts
|
|
3630
|
+
var Ingest = class {
|
|
3631
|
+
constructor(api) {
|
|
3632
|
+
this.api = api;
|
|
3633
|
+
}
|
|
3634
|
+
/**
|
|
3635
|
+
* Apply an incoming ingest message to the API.
|
|
3636
|
+
* - snapshot: rebuild state from nodes/edges (clears prior history)
|
|
3637
|
+
* - update: apply incremental update
|
|
3638
|
+
* - history: initialize from a set of frames (clears prior history)
|
|
3639
|
+
*/
|
|
3640
|
+
async apply(msg) {
|
|
3641
|
+
switch (msg.type) {
|
|
3642
|
+
case "snapshot": {
|
|
3643
|
+
await this.api.replaceSnapshot(msg.nodes, msg.edges, msg.description);
|
|
3644
|
+
break;
|
|
3645
|
+
}
|
|
3646
|
+
case "update": {
|
|
3647
|
+
await this.api.update((u) => {
|
|
3648
|
+
if (msg.addNodes) u.addNodes(...msg.addNodes);
|
|
3649
|
+
if (msg.removeNodes) u.deleteNodes(...msg.removeNodes);
|
|
3650
|
+
if (msg.updateNodes) u.updateNodes(...msg.updateNodes);
|
|
3651
|
+
if (msg.addEdges) u.addEdges(...msg.addEdges);
|
|
3652
|
+
if (msg.removeEdges) u.deleteEdges(...msg.removeEdges);
|
|
3653
|
+
if (msg.updateEdges) u.updateEdges(...msg.updateEdges);
|
|
3654
|
+
if (msg.description) u.describe(msg.description);
|
|
3655
|
+
});
|
|
3656
|
+
break;
|
|
3657
|
+
}
|
|
3658
|
+
case "history": {
|
|
3659
|
+
await this.api.replaceHistory(msg.frames);
|
|
3660
|
+
break;
|
|
3661
|
+
}
|
|
3662
|
+
}
|
|
3663
|
+
}
|
|
3664
|
+
};
|
|
3665
|
+
|
|
3666
|
+
// src/api/sources/WebSocketSource.ts
|
|
3667
|
+
var WebSocketSource = class {
|
|
3668
|
+
url;
|
|
3669
|
+
ws = null;
|
|
3670
|
+
onMessage;
|
|
3671
|
+
onStatus;
|
|
3672
|
+
reconnectMs;
|
|
3673
|
+
closedByUser = false;
|
|
3674
|
+
connectStartTime = null;
|
|
3675
|
+
totalTimeoutMs = 1e4;
|
|
3676
|
+
totalTimeoutTimer = null;
|
|
3677
|
+
constructor(url, onMessage, onStatus, reconnectMs = 1500) {
|
|
3678
|
+
this.url = url;
|
|
3679
|
+
this.onMessage = onMessage;
|
|
3680
|
+
this.onStatus = onStatus;
|
|
3681
|
+
this.reconnectMs = reconnectMs;
|
|
3682
|
+
}
|
|
3683
|
+
connect() {
|
|
3684
|
+
this.closedByUser = false;
|
|
3685
|
+
this.connectStartTime = Date.now();
|
|
3686
|
+
this.startTotalTimeout();
|
|
3687
|
+
this.open();
|
|
3688
|
+
}
|
|
3689
|
+
disconnect() {
|
|
3690
|
+
this.closedByUser = true;
|
|
3691
|
+
this.clearTotalTimeout();
|
|
3692
|
+
if (this.ws) {
|
|
3693
|
+
try {
|
|
3694
|
+
this.ws.close();
|
|
3695
|
+
} catch {
|
|
3696
|
+
}
|
|
3697
|
+
this.ws = null;
|
|
3698
|
+
}
|
|
3699
|
+
this.onStatus?.("closed");
|
|
3700
|
+
}
|
|
3701
|
+
startTotalTimeout() {
|
|
3702
|
+
this.clearTotalTimeout();
|
|
3703
|
+
this.totalTimeoutTimer = window.setTimeout(() => {
|
|
3704
|
+
if (!this.closedByUser && this.ws?.readyState !== WebSocket.OPEN) {
|
|
3705
|
+
this.closedByUser = true;
|
|
3706
|
+
if (this.ws) {
|
|
3707
|
+
try {
|
|
3708
|
+
this.ws.close();
|
|
3709
|
+
} catch {
|
|
3710
|
+
}
|
|
3711
|
+
this.ws = null;
|
|
3712
|
+
}
|
|
3713
|
+
this.clearTotalTimeout();
|
|
3714
|
+
this.onStatus?.("error", new Error("Connection timeout after 10 seconds"));
|
|
3715
|
+
}
|
|
3716
|
+
}, this.totalTimeoutMs);
|
|
3717
|
+
}
|
|
3718
|
+
clearTotalTimeout() {
|
|
3719
|
+
if (this.totalTimeoutTimer !== null) {
|
|
3720
|
+
clearTimeout(this.totalTimeoutTimer);
|
|
3721
|
+
this.totalTimeoutTimer = null;
|
|
3722
|
+
}
|
|
3723
|
+
this.connectStartTime = null;
|
|
3724
|
+
}
|
|
3725
|
+
open() {
|
|
3726
|
+
if (this.connectStartTime && Date.now() - this.connectStartTime >= this.totalTimeoutMs) {
|
|
3727
|
+
if (!this.closedByUser) {
|
|
3728
|
+
this.closedByUser = true;
|
|
3729
|
+
this.clearTotalTimeout();
|
|
3730
|
+
this.onStatus?.("error", new Error("Connection timeout after 10 seconds"));
|
|
3731
|
+
}
|
|
3732
|
+
return;
|
|
3733
|
+
}
|
|
3734
|
+
this.onStatus?.(this.ws ? "reconnecting" : "connecting");
|
|
3735
|
+
const ws = new WebSocket(this.url);
|
|
3736
|
+
this.ws = ws;
|
|
3737
|
+
ws.onopen = () => {
|
|
3738
|
+
this.clearTotalTimeout();
|
|
3739
|
+
this.onStatus?.("connected");
|
|
3740
|
+
};
|
|
3741
|
+
ws.onerror = (e) => {
|
|
3742
|
+
this.onStatus?.("error", e);
|
|
3743
|
+
};
|
|
3744
|
+
ws.onclose = () => {
|
|
3745
|
+
if (this.closedByUser) {
|
|
3746
|
+
this.onStatus?.("closed");
|
|
3747
|
+
return;
|
|
3748
|
+
}
|
|
3749
|
+
if (this.connectStartTime && Date.now() - this.connectStartTime >= this.totalTimeoutMs) {
|
|
3750
|
+
this.closedByUser = true;
|
|
3751
|
+
this.clearTotalTimeout();
|
|
3752
|
+
this.onStatus?.("error", new Error("Connection timeout after 10 seconds"));
|
|
3753
|
+
return;
|
|
3754
|
+
}
|
|
3755
|
+
this.onStatus?.("reconnecting");
|
|
3756
|
+
setTimeout(() => this.open(), this.reconnectMs);
|
|
3757
|
+
};
|
|
3758
|
+
ws.onmessage = (ev) => {
|
|
3759
|
+
const data = typeof ev.data === "string" ? ev.data : "";
|
|
3760
|
+
const lines = data.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
3761
|
+
for (const line of lines) {
|
|
3762
|
+
try {
|
|
3763
|
+
const obj = JSON.parse(line);
|
|
3764
|
+
this.onMessage(obj);
|
|
3765
|
+
} catch {
|
|
3766
|
+
}
|
|
3767
|
+
}
|
|
3768
|
+
};
|
|
3769
|
+
}
|
|
3770
|
+
};
|
|
3771
|
+
|
|
3772
|
+
// src/api/sources/FileSource.ts
|
|
3773
|
+
var FileSource = class {
|
|
3774
|
+
url;
|
|
3775
|
+
onMessage;
|
|
3776
|
+
onStatus;
|
|
3777
|
+
timer = null;
|
|
3778
|
+
lastETag = null;
|
|
3779
|
+
lastContent = "";
|
|
3780
|
+
intervalMs = 1e3;
|
|
3781
|
+
closed = false;
|
|
3782
|
+
constructor(url, onMessage, onStatus, intervalMs = 1e3) {
|
|
3783
|
+
this.url = url;
|
|
3784
|
+
this.onMessage = onMessage;
|
|
3785
|
+
this.onStatus = onStatus;
|
|
3786
|
+
this.intervalMs = intervalMs;
|
|
3787
|
+
}
|
|
3788
|
+
async connect() {
|
|
3789
|
+
this.closed = false;
|
|
3790
|
+
this.lastETag = null;
|
|
3791
|
+
this.lastContent = "";
|
|
3792
|
+
this.onStatus?.("opened");
|
|
3793
|
+
this.startPolling();
|
|
3794
|
+
}
|
|
3795
|
+
close() {
|
|
3796
|
+
this.closed = true;
|
|
3797
|
+
if (this.timer) {
|
|
3798
|
+
window.clearInterval(this.timer);
|
|
3799
|
+
this.timer = null;
|
|
3800
|
+
}
|
|
3801
|
+
this.onStatus?.("closed");
|
|
3802
|
+
}
|
|
3803
|
+
startPolling() {
|
|
3804
|
+
if (this.timer) window.clearInterval(this.timer);
|
|
3805
|
+
this.timer = window.setInterval(() => this.poll(), this.intervalMs);
|
|
3806
|
+
this.poll();
|
|
3807
|
+
}
|
|
3808
|
+
async poll() {
|
|
3809
|
+
if (this.closed) return;
|
|
3810
|
+
try {
|
|
3811
|
+
this.onStatus?.("reading");
|
|
3812
|
+
const headers = {};
|
|
3813
|
+
if (this.lastETag) {
|
|
3814
|
+
headers["If-None-Match"] = this.lastETag;
|
|
3815
|
+
}
|
|
3816
|
+
const response = await fetch(this.url, { headers });
|
|
3817
|
+
if (response.status === 304) {
|
|
3818
|
+
return;
|
|
3819
|
+
}
|
|
3820
|
+
if (!response.ok) {
|
|
3821
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
3822
|
+
}
|
|
3823
|
+
const etag = response.headers.get("ETag");
|
|
3824
|
+
if (etag) {
|
|
3825
|
+
this.lastETag = etag;
|
|
3826
|
+
}
|
|
3827
|
+
const content = await response.text();
|
|
3828
|
+
if (content === this.lastContent) {
|
|
3829
|
+
return;
|
|
3830
|
+
}
|
|
3831
|
+
const lines = content.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
3832
|
+
const lastContentLines = this.lastContent.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
3833
|
+
const newLines = lines.slice(lastContentLines.length);
|
|
3834
|
+
for (const line of newLines) {
|
|
3835
|
+
try {
|
|
3836
|
+
const obj = JSON.parse(line);
|
|
3837
|
+
this.onMessage(obj);
|
|
3838
|
+
} catch {
|
|
3839
|
+
}
|
|
3840
|
+
}
|
|
3841
|
+
this.lastContent = content;
|
|
3842
|
+
} catch (e) {
|
|
3843
|
+
this.onStatus?.("error", e);
|
|
3844
|
+
}
|
|
3845
|
+
}
|
|
3846
|
+
};
|
|
3847
|
+
|
|
3629
3848
|
// src/api/api.ts
|
|
3630
3849
|
var log11 = logger("api");
|
|
3631
3850
|
var API = class {
|
|
@@ -3644,11 +3863,16 @@ var API = class {
|
|
|
3644
3863
|
nextNodeId;
|
|
3645
3864
|
nextEdgeId;
|
|
3646
3865
|
events;
|
|
3866
|
+
ingest;
|
|
3867
|
+
ingestionSource;
|
|
3868
|
+
ingestionConfig;
|
|
3869
|
+
prevProps = {};
|
|
3647
3870
|
root;
|
|
3648
3871
|
constructor(args) {
|
|
3649
3872
|
this.root = args.root;
|
|
3650
3873
|
this.options = applyDefaults(args.options);
|
|
3651
3874
|
this.events = args.events || {};
|
|
3875
|
+
this.ingestionConfig = args.ingestion;
|
|
3652
3876
|
this.reset();
|
|
3653
3877
|
this.canvas = new Canvas(this, {
|
|
3654
3878
|
...this.options.canvas,
|
|
@@ -3662,6 +3886,15 @@ var API = class {
|
|
|
3662
3886
|
} else {
|
|
3663
3887
|
this.history = [];
|
|
3664
3888
|
}
|
|
3889
|
+
this.prevProps = {
|
|
3890
|
+
nodes: args.nodes,
|
|
3891
|
+
edges: args.edges,
|
|
3892
|
+
history: args.history,
|
|
3893
|
+
options: args.options
|
|
3894
|
+
};
|
|
3895
|
+
if (this.ingestionConfig) {
|
|
3896
|
+
this.ingest = new Ingest(this);
|
|
3897
|
+
}
|
|
3665
3898
|
}
|
|
3666
3899
|
reset() {
|
|
3667
3900
|
let graph2 = new Graph({ options: this.options.graph });
|
|
@@ -3684,10 +3917,50 @@ var API = class {
|
|
|
3684
3917
|
if (!root) throw new Error("root element not found");
|
|
3685
3918
|
root.appendChild(this.canvas.container);
|
|
3686
3919
|
await this.applyHistory();
|
|
3920
|
+
if (this.ingestionConfig && this.ingest) {
|
|
3921
|
+
this.connectIngestion();
|
|
3922
|
+
}
|
|
3687
3923
|
if (this.events.onInit) {
|
|
3688
3924
|
this.events.onInit();
|
|
3689
3925
|
}
|
|
3690
3926
|
}
|
|
3927
|
+
/** Connect to the configured ingestion source */
|
|
3928
|
+
connectIngestion() {
|
|
3929
|
+
if (!this.ingestionConfig || !this.ingest) return;
|
|
3930
|
+
const handleMessage = (msg) => {
|
|
3931
|
+
this.ingest.apply(msg);
|
|
3932
|
+
};
|
|
3933
|
+
switch (this.ingestionConfig.type) {
|
|
3934
|
+
case "websocket":
|
|
3935
|
+
this.ingestionSource = new WebSocketSource(
|
|
3936
|
+
this.ingestionConfig.url,
|
|
3937
|
+
handleMessage,
|
|
3938
|
+
void 0,
|
|
3939
|
+
this.ingestionConfig.reconnectMs
|
|
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
|
+
}
|
|
3953
|
+
}
|
|
3954
|
+
/** Disconnect from the ingestion source */
|
|
3955
|
+
disconnectIngestion() {
|
|
3956
|
+
if (!this.ingestionSource) return;
|
|
3957
|
+
if (this.ingestionSource instanceof WebSocketSource) {
|
|
3958
|
+
this.ingestionSource.disconnect();
|
|
3959
|
+
} else if (this.ingestionSource instanceof FileSource) {
|
|
3960
|
+
this.ingestionSource.close();
|
|
3961
|
+
}
|
|
3962
|
+
this.ingestionSource = void 0;
|
|
3963
|
+
}
|
|
3691
3964
|
async applyHistory() {
|
|
3692
3965
|
for (const update of this.history)
|
|
3693
3966
|
await this.applyUpdate(update);
|
|
@@ -4150,154 +4423,117 @@ var API = class {
|
|
|
4150
4423
|
setColorMode(colorMode) {
|
|
4151
4424
|
this.canvas?.setColorMode(colorMode);
|
|
4152
4425
|
}
|
|
4153
|
-
/** Cleanup resources when the graph is destroyed */
|
|
4154
|
-
destroy() {
|
|
4155
|
-
this.canvas?.destroy();
|
|
4156
|
-
}
|
|
4157
|
-
};
|
|
4158
|
-
|
|
4159
|
-
// src/api/ingest.ts
|
|
4160
|
-
var Ingest = class {
|
|
4161
|
-
constructor(api) {
|
|
4162
|
-
this.api = api;
|
|
4163
|
-
}
|
|
4164
4426
|
/**
|
|
4165
|
-
* Apply
|
|
4166
|
-
*
|
|
4167
|
-
*
|
|
4168
|
-
*
|
|
4427
|
+
* Apply prop changes by diffing against previously applied props.
|
|
4428
|
+
* This is a convenience method for framework wrappers that centralizes
|
|
4429
|
+
* the logic for detecting and applying changes to nodes, edges, history, and options.
|
|
4430
|
+
* The API stores the previous props internally, so you just pass the new props.
|
|
4431
|
+
*
|
|
4432
|
+
* @param props - The new props to apply
|
|
4169
4433
|
*/
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
await this.api.update((u) => {
|
|
4178
|
-
if (msg.addNodes) u.addNodes(...msg.addNodes);
|
|
4179
|
-
if (msg.removeNodes) u.deleteNodes(...msg.removeNodes);
|
|
4180
|
-
if (msg.updateNodes) u.updateNodes(...msg.updateNodes);
|
|
4181
|
-
if (msg.addEdges) u.addEdges(...msg.addEdges);
|
|
4182
|
-
if (msg.removeEdges) u.deleteEdges(...msg.removeEdges);
|
|
4183
|
-
if (msg.updateEdges) u.updateEdges(...msg.updateEdges);
|
|
4184
|
-
if (msg.description) u.describe(msg.description);
|
|
4185
|
-
});
|
|
4186
|
-
break;
|
|
4187
|
-
}
|
|
4188
|
-
case "history": {
|
|
4189
|
-
await this.api.replaceHistory(msg.frames);
|
|
4190
|
-
break;
|
|
4434
|
+
applyProps(props) {
|
|
4435
|
+
const prev = this.prevProps;
|
|
4436
|
+
const nodesChanged = !shallowEqualArray(props.nodes, prev.nodes);
|
|
4437
|
+
const edgesChanged = !shallowEqualArray(props.edges, prev.edges);
|
|
4438
|
+
if (nodesChanged || edgesChanged) {
|
|
4439
|
+
if (props.nodes) {
|
|
4440
|
+
this.replaceSnapshot(props.nodes, props.edges || [], void 0);
|
|
4191
4441
|
}
|
|
4192
4442
|
}
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
this.closedByUser = false;
|
|
4215
|
-
this.connectStartTime = Date.now();
|
|
4216
|
-
this.startTotalTimeout();
|
|
4217
|
-
this.open();
|
|
4218
|
-
}
|
|
4219
|
-
disconnect() {
|
|
4220
|
-
this.closedByUser = true;
|
|
4221
|
-
this.clearTotalTimeout();
|
|
4222
|
-
if (this.ws) {
|
|
4223
|
-
try {
|
|
4224
|
-
this.ws.close();
|
|
4225
|
-
} catch {
|
|
4443
|
+
if (!nodesChanged && !edgesChanged && props.history !== prev.history) {
|
|
4444
|
+
if (props.history === void 0) {
|
|
4445
|
+
if (props.nodes) {
|
|
4446
|
+
this.replaceSnapshot(props.nodes, props.edges || [], void 0);
|
|
4447
|
+
}
|
|
4448
|
+
} else if (prev.history && isHistoryPrefix(prev.history, props.history)) {
|
|
4449
|
+
const prevLength = prev.history.length;
|
|
4450
|
+
const newFrames = props.history.slice(prevLength);
|
|
4451
|
+
for (const frame of newFrames) {
|
|
4452
|
+
this.update((u) => {
|
|
4453
|
+
if (frame.addNodes) u.addNodes(...frame.addNodes);
|
|
4454
|
+
if (frame.removeNodes) u.deleteNodes(...frame.removeNodes);
|
|
4455
|
+
if (frame.updateNodes) u.updateNodes(...frame.updateNodes);
|
|
4456
|
+
if (frame.addEdges) u.addEdges(...frame.addEdges);
|
|
4457
|
+
if (frame.removeEdges) u.deleteEdges(...frame.removeEdges);
|
|
4458
|
+
if (frame.updateEdges) u.updateEdges(...frame.updateEdges);
|
|
4459
|
+
if (frame.description) u.describe(frame.description);
|
|
4460
|
+
});
|
|
4461
|
+
}
|
|
4462
|
+
} else {
|
|
4463
|
+
this.replaceHistory(props.history);
|
|
4226
4464
|
}
|
|
4227
|
-
this.ws = null;
|
|
4228
4465
|
}
|
|
4229
|
-
|
|
4466
|
+
const prevCanvas = prev.options?.canvas;
|
|
4467
|
+
const currCanvas = props.options?.canvas;
|
|
4468
|
+
const colorModeChanged = prevCanvas?.colorMode !== currCanvas?.colorMode;
|
|
4469
|
+
if (colorModeChanged && currCanvas?.colorMode) {
|
|
4470
|
+
this.setColorMode(currCanvas.colorMode);
|
|
4471
|
+
}
|
|
4472
|
+
const themeChanged = prevCanvas?.theme !== currCanvas?.theme;
|
|
4473
|
+
const nodeTypesChanged = prevCanvas?.nodeTypes !== currCanvas?.nodeTypes;
|
|
4474
|
+
const edgeTypesChanged = prevCanvas?.edgeTypes !== currCanvas?.edgeTypes;
|
|
4475
|
+
if (themeChanged || nodeTypesChanged || edgeTypesChanged) {
|
|
4476
|
+
this.updateStyles({
|
|
4477
|
+
theme: currCanvas?.theme,
|
|
4478
|
+
nodeTypes: currCanvas?.nodeTypes,
|
|
4479
|
+
edgeTypes: currCanvas?.edgeTypes
|
|
4480
|
+
});
|
|
4481
|
+
}
|
|
4482
|
+
this.prevProps = {
|
|
4483
|
+
nodes: props.nodes,
|
|
4484
|
+
edges: props.edges,
|
|
4485
|
+
history: props.history,
|
|
4486
|
+
options: props.options
|
|
4487
|
+
};
|
|
4230
4488
|
}
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
this.
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4489
|
+
/** Cleanup resources when the graph is destroyed */
|
|
4490
|
+
destroy() {
|
|
4491
|
+
this.disconnectIngestion();
|
|
4492
|
+
this.canvas?.destroy();
|
|
4493
|
+
}
|
|
4494
|
+
};
|
|
4495
|
+
function shallowEqualArray(a, b) {
|
|
4496
|
+
if (a === b) return true;
|
|
4497
|
+
if (!a || !b) return false;
|
|
4498
|
+
if (a.length !== b.length) return false;
|
|
4499
|
+
for (let i = 0; i < a.length; i++) {
|
|
4500
|
+
if (a[i] !== b[i]) {
|
|
4501
|
+
if (typeof a[i] === "object" && a[i] !== null && typeof b[i] === "object" && b[i] !== null) {
|
|
4502
|
+
const aObj = a[i];
|
|
4503
|
+
const bObj = b[i];
|
|
4504
|
+
const aKeys = Object.keys(aObj);
|
|
4505
|
+
const bKeys = Object.keys(bObj);
|
|
4506
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
4507
|
+
for (const key of aKeys) {
|
|
4508
|
+
if (aObj[key] !== bObj[key]) return false;
|
|
4242
4509
|
}
|
|
4243
|
-
|
|
4244
|
-
|
|
4510
|
+
} else {
|
|
4511
|
+
return false;
|
|
4245
4512
|
}
|
|
4246
|
-
}, this.totalTimeoutMs);
|
|
4247
|
-
}
|
|
4248
|
-
clearTotalTimeout() {
|
|
4249
|
-
if (this.totalTimeoutTimer !== null) {
|
|
4250
|
-
clearTimeout(this.totalTimeoutTimer);
|
|
4251
|
-
this.totalTimeoutTimer = null;
|
|
4252
4513
|
}
|
|
4253
|
-
this.connectStartTime = null;
|
|
4254
4514
|
}
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
return;
|
|
4515
|
+
return true;
|
|
4516
|
+
}
|
|
4517
|
+
function isHistoryPrefix(oldHistory, newHistory) {
|
|
4518
|
+
if (newHistory.length < oldHistory.length) return false;
|
|
4519
|
+
for (let i = 0; i < oldHistory.length; i++) {
|
|
4520
|
+
if (!shallowEqualUpdate(oldHistory[i], newHistory[i])) {
|
|
4521
|
+
return false;
|
|
4263
4522
|
}
|
|
4264
|
-
this.onStatus?.(this.ws ? "reconnecting" : "connecting");
|
|
4265
|
-
const ws = new WebSocket(this.url);
|
|
4266
|
-
this.ws = ws;
|
|
4267
|
-
ws.onopen = () => {
|
|
4268
|
-
this.clearTotalTimeout();
|
|
4269
|
-
this.onStatus?.("connected");
|
|
4270
|
-
};
|
|
4271
|
-
ws.onerror = (e) => {
|
|
4272
|
-
this.onStatus?.("error", e);
|
|
4273
|
-
};
|
|
4274
|
-
ws.onclose = () => {
|
|
4275
|
-
if (this.closedByUser) {
|
|
4276
|
-
this.onStatus?.("closed");
|
|
4277
|
-
return;
|
|
4278
|
-
}
|
|
4279
|
-
if (this.connectStartTime && Date.now() - this.connectStartTime >= this.totalTimeoutMs) {
|
|
4280
|
-
this.closedByUser = true;
|
|
4281
|
-
this.clearTotalTimeout();
|
|
4282
|
-
this.onStatus?.("error", new Error("Connection timeout after 10 seconds"));
|
|
4283
|
-
return;
|
|
4284
|
-
}
|
|
4285
|
-
this.onStatus?.("reconnecting");
|
|
4286
|
-
setTimeout(() => this.open(), this.reconnectMs);
|
|
4287
|
-
};
|
|
4288
|
-
ws.onmessage = (ev) => {
|
|
4289
|
-
const data = typeof ev.data === "string" ? ev.data : "";
|
|
4290
|
-
const lines = data.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
4291
|
-
for (const line of lines) {
|
|
4292
|
-
try {
|
|
4293
|
-
const obj = JSON.parse(line);
|
|
4294
|
-
this.onMessage(obj);
|
|
4295
|
-
} catch {
|
|
4296
|
-
}
|
|
4297
|
-
}
|
|
4298
|
-
};
|
|
4299
4523
|
}
|
|
4300
|
-
|
|
4524
|
+
return true;
|
|
4525
|
+
}
|
|
4526
|
+
function shallowEqualUpdate(a, b) {
|
|
4527
|
+
if (a === b) return true;
|
|
4528
|
+
if (a.description !== b.description) return false;
|
|
4529
|
+
if (!shallowEqualArray(a.addNodes, b.addNodes)) return false;
|
|
4530
|
+
if (!shallowEqualArray(a.removeNodes, b.removeNodes)) return false;
|
|
4531
|
+
if (!shallowEqualArray(a.updateNodes, b.updateNodes)) return false;
|
|
4532
|
+
if (!shallowEqualArray(a.addEdges, b.addEdges)) return false;
|
|
4533
|
+
if (!shallowEqualArray(a.removeEdges, b.removeEdges)) return false;
|
|
4534
|
+
if (!shallowEqualArray(a.updateEdges, b.updateEdges)) return false;
|
|
4535
|
+
return true;
|
|
4536
|
+
}
|
|
4301
4537
|
|
|
4302
4538
|
// src/api/sources/FileSystemSource.ts
|
|
4303
4539
|
var FileSystemSource = class {
|
|
@@ -4361,82 +4597,6 @@ var FileSystemSource = class {
|
|
|
4361
4597
|
}
|
|
4362
4598
|
};
|
|
4363
4599
|
|
|
4364
|
-
// src/api/sources/FileSource.ts
|
|
4365
|
-
var FileSource = class {
|
|
4366
|
-
url;
|
|
4367
|
-
onMessage;
|
|
4368
|
-
onStatus;
|
|
4369
|
-
timer = null;
|
|
4370
|
-
lastETag = null;
|
|
4371
|
-
lastContent = "";
|
|
4372
|
-
intervalMs = 1e3;
|
|
4373
|
-
closed = false;
|
|
4374
|
-
constructor(url, onMessage, onStatus, intervalMs = 1e3) {
|
|
4375
|
-
this.url = url;
|
|
4376
|
-
this.onMessage = onMessage;
|
|
4377
|
-
this.onStatus = onStatus;
|
|
4378
|
-
this.intervalMs = intervalMs;
|
|
4379
|
-
}
|
|
4380
|
-
async connect() {
|
|
4381
|
-
this.closed = false;
|
|
4382
|
-
this.lastETag = null;
|
|
4383
|
-
this.lastContent = "";
|
|
4384
|
-
this.onStatus?.("opened");
|
|
4385
|
-
this.startPolling();
|
|
4386
|
-
}
|
|
4387
|
-
close() {
|
|
4388
|
-
this.closed = true;
|
|
4389
|
-
if (this.timer) {
|
|
4390
|
-
window.clearInterval(this.timer);
|
|
4391
|
-
this.timer = null;
|
|
4392
|
-
}
|
|
4393
|
-
this.onStatus?.("closed");
|
|
4394
|
-
}
|
|
4395
|
-
startPolling() {
|
|
4396
|
-
if (this.timer) window.clearInterval(this.timer);
|
|
4397
|
-
this.timer = window.setInterval(() => this.poll(), this.intervalMs);
|
|
4398
|
-
this.poll();
|
|
4399
|
-
}
|
|
4400
|
-
async poll() {
|
|
4401
|
-
if (this.closed) return;
|
|
4402
|
-
try {
|
|
4403
|
-
this.onStatus?.("reading");
|
|
4404
|
-
const headers = {};
|
|
4405
|
-
if (this.lastETag) {
|
|
4406
|
-
headers["If-None-Match"] = this.lastETag;
|
|
4407
|
-
}
|
|
4408
|
-
const response = await fetch(this.url, { headers });
|
|
4409
|
-
if (response.status === 304) {
|
|
4410
|
-
return;
|
|
4411
|
-
}
|
|
4412
|
-
if (!response.ok) {
|
|
4413
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
4414
|
-
}
|
|
4415
|
-
const etag = response.headers.get("ETag");
|
|
4416
|
-
if (etag) {
|
|
4417
|
-
this.lastETag = etag;
|
|
4418
|
-
}
|
|
4419
|
-
const content = await response.text();
|
|
4420
|
-
if (content === this.lastContent) {
|
|
4421
|
-
return;
|
|
4422
|
-
}
|
|
4423
|
-
const lines = content.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
4424
|
-
const lastContentLines = this.lastContent.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
4425
|
-
const newLines = lines.slice(lastContentLines.length);
|
|
4426
|
-
for (const line of newLines) {
|
|
4427
|
-
try {
|
|
4428
|
-
const obj = JSON.parse(line);
|
|
4429
|
-
this.onMessage(obj);
|
|
4430
|
-
} catch {
|
|
4431
|
-
}
|
|
4432
|
-
}
|
|
4433
|
-
this.lastContent = content;
|
|
4434
|
-
} catch (e) {
|
|
4435
|
-
this.onStatus?.("error", e);
|
|
4436
|
-
}
|
|
4437
|
-
}
|
|
4438
|
-
};
|
|
4439
|
-
|
|
4440
4600
|
// src/playground/playground.ts
|
|
4441
4601
|
import styles2 from "./styles.css?raw";
|
|
4442
4602
|
var Playground = class {
|