@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.cjs
CHANGED
|
@@ -3669,6 +3669,225 @@ var Updater = class _Updater {
|
|
|
3669
3669
|
}
|
|
3670
3670
|
};
|
|
3671
3671
|
|
|
3672
|
+
// src/api/ingest.ts
|
|
3673
|
+
var Ingest = class {
|
|
3674
|
+
constructor(api) {
|
|
3675
|
+
this.api = api;
|
|
3676
|
+
}
|
|
3677
|
+
/**
|
|
3678
|
+
* Apply an incoming ingest message to the API.
|
|
3679
|
+
* - snapshot: rebuild state from nodes/edges (clears prior history)
|
|
3680
|
+
* - update: apply incremental update
|
|
3681
|
+
* - history: initialize from a set of frames (clears prior history)
|
|
3682
|
+
*/
|
|
3683
|
+
async apply(msg) {
|
|
3684
|
+
switch (msg.type) {
|
|
3685
|
+
case "snapshot": {
|
|
3686
|
+
await this.api.replaceSnapshot(msg.nodes, msg.edges, msg.description);
|
|
3687
|
+
break;
|
|
3688
|
+
}
|
|
3689
|
+
case "update": {
|
|
3690
|
+
await this.api.update((u) => {
|
|
3691
|
+
if (msg.addNodes) u.addNodes(...msg.addNodes);
|
|
3692
|
+
if (msg.removeNodes) u.deleteNodes(...msg.removeNodes);
|
|
3693
|
+
if (msg.updateNodes) u.updateNodes(...msg.updateNodes);
|
|
3694
|
+
if (msg.addEdges) u.addEdges(...msg.addEdges);
|
|
3695
|
+
if (msg.removeEdges) u.deleteEdges(...msg.removeEdges);
|
|
3696
|
+
if (msg.updateEdges) u.updateEdges(...msg.updateEdges);
|
|
3697
|
+
if (msg.description) u.describe(msg.description);
|
|
3698
|
+
});
|
|
3699
|
+
break;
|
|
3700
|
+
}
|
|
3701
|
+
case "history": {
|
|
3702
|
+
await this.api.replaceHistory(msg.frames);
|
|
3703
|
+
break;
|
|
3704
|
+
}
|
|
3705
|
+
}
|
|
3706
|
+
}
|
|
3707
|
+
};
|
|
3708
|
+
|
|
3709
|
+
// src/api/sources/WebSocketSource.ts
|
|
3710
|
+
var WebSocketSource = class {
|
|
3711
|
+
url;
|
|
3712
|
+
ws = null;
|
|
3713
|
+
onMessage;
|
|
3714
|
+
onStatus;
|
|
3715
|
+
reconnectMs;
|
|
3716
|
+
closedByUser = false;
|
|
3717
|
+
connectStartTime = null;
|
|
3718
|
+
totalTimeoutMs = 1e4;
|
|
3719
|
+
totalTimeoutTimer = null;
|
|
3720
|
+
constructor(url, onMessage, onStatus, reconnectMs = 1500) {
|
|
3721
|
+
this.url = url;
|
|
3722
|
+
this.onMessage = onMessage;
|
|
3723
|
+
this.onStatus = onStatus;
|
|
3724
|
+
this.reconnectMs = reconnectMs;
|
|
3725
|
+
}
|
|
3726
|
+
connect() {
|
|
3727
|
+
this.closedByUser = false;
|
|
3728
|
+
this.connectStartTime = Date.now();
|
|
3729
|
+
this.startTotalTimeout();
|
|
3730
|
+
this.open();
|
|
3731
|
+
}
|
|
3732
|
+
disconnect() {
|
|
3733
|
+
this.closedByUser = true;
|
|
3734
|
+
this.clearTotalTimeout();
|
|
3735
|
+
if (this.ws) {
|
|
3736
|
+
try {
|
|
3737
|
+
this.ws.close();
|
|
3738
|
+
} catch {
|
|
3739
|
+
}
|
|
3740
|
+
this.ws = null;
|
|
3741
|
+
}
|
|
3742
|
+
this.onStatus?.("closed");
|
|
3743
|
+
}
|
|
3744
|
+
startTotalTimeout() {
|
|
3745
|
+
this.clearTotalTimeout();
|
|
3746
|
+
this.totalTimeoutTimer = window.setTimeout(() => {
|
|
3747
|
+
if (!this.closedByUser && this.ws?.readyState !== WebSocket.OPEN) {
|
|
3748
|
+
this.closedByUser = true;
|
|
3749
|
+
if (this.ws) {
|
|
3750
|
+
try {
|
|
3751
|
+
this.ws.close();
|
|
3752
|
+
} catch {
|
|
3753
|
+
}
|
|
3754
|
+
this.ws = null;
|
|
3755
|
+
}
|
|
3756
|
+
this.clearTotalTimeout();
|
|
3757
|
+
this.onStatus?.("error", new Error("Connection timeout after 10 seconds"));
|
|
3758
|
+
}
|
|
3759
|
+
}, this.totalTimeoutMs);
|
|
3760
|
+
}
|
|
3761
|
+
clearTotalTimeout() {
|
|
3762
|
+
if (this.totalTimeoutTimer !== null) {
|
|
3763
|
+
clearTimeout(this.totalTimeoutTimer);
|
|
3764
|
+
this.totalTimeoutTimer = null;
|
|
3765
|
+
}
|
|
3766
|
+
this.connectStartTime = null;
|
|
3767
|
+
}
|
|
3768
|
+
open() {
|
|
3769
|
+
if (this.connectStartTime && Date.now() - this.connectStartTime >= this.totalTimeoutMs) {
|
|
3770
|
+
if (!this.closedByUser) {
|
|
3771
|
+
this.closedByUser = true;
|
|
3772
|
+
this.clearTotalTimeout();
|
|
3773
|
+
this.onStatus?.("error", new Error("Connection timeout after 10 seconds"));
|
|
3774
|
+
}
|
|
3775
|
+
return;
|
|
3776
|
+
}
|
|
3777
|
+
this.onStatus?.(this.ws ? "reconnecting" : "connecting");
|
|
3778
|
+
const ws = new WebSocket(this.url);
|
|
3779
|
+
this.ws = ws;
|
|
3780
|
+
ws.onopen = () => {
|
|
3781
|
+
this.clearTotalTimeout();
|
|
3782
|
+
this.onStatus?.("connected");
|
|
3783
|
+
};
|
|
3784
|
+
ws.onerror = (e) => {
|
|
3785
|
+
this.onStatus?.("error", e);
|
|
3786
|
+
};
|
|
3787
|
+
ws.onclose = () => {
|
|
3788
|
+
if (this.closedByUser) {
|
|
3789
|
+
this.onStatus?.("closed");
|
|
3790
|
+
return;
|
|
3791
|
+
}
|
|
3792
|
+
if (this.connectStartTime && Date.now() - this.connectStartTime >= this.totalTimeoutMs) {
|
|
3793
|
+
this.closedByUser = true;
|
|
3794
|
+
this.clearTotalTimeout();
|
|
3795
|
+
this.onStatus?.("error", new Error("Connection timeout after 10 seconds"));
|
|
3796
|
+
return;
|
|
3797
|
+
}
|
|
3798
|
+
this.onStatus?.("reconnecting");
|
|
3799
|
+
setTimeout(() => this.open(), this.reconnectMs);
|
|
3800
|
+
};
|
|
3801
|
+
ws.onmessage = (ev) => {
|
|
3802
|
+
const data = typeof ev.data === "string" ? ev.data : "";
|
|
3803
|
+
const lines = data.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
3804
|
+
for (const line of lines) {
|
|
3805
|
+
try {
|
|
3806
|
+
const obj = JSON.parse(line);
|
|
3807
|
+
this.onMessage(obj);
|
|
3808
|
+
} catch {
|
|
3809
|
+
}
|
|
3810
|
+
}
|
|
3811
|
+
};
|
|
3812
|
+
}
|
|
3813
|
+
};
|
|
3814
|
+
|
|
3815
|
+
// src/api/sources/FileSource.ts
|
|
3816
|
+
var FileSource = class {
|
|
3817
|
+
url;
|
|
3818
|
+
onMessage;
|
|
3819
|
+
onStatus;
|
|
3820
|
+
timer = null;
|
|
3821
|
+
lastETag = null;
|
|
3822
|
+
lastContent = "";
|
|
3823
|
+
intervalMs = 1e3;
|
|
3824
|
+
closed = false;
|
|
3825
|
+
constructor(url, onMessage, onStatus, intervalMs = 1e3) {
|
|
3826
|
+
this.url = url;
|
|
3827
|
+
this.onMessage = onMessage;
|
|
3828
|
+
this.onStatus = onStatus;
|
|
3829
|
+
this.intervalMs = intervalMs;
|
|
3830
|
+
}
|
|
3831
|
+
async connect() {
|
|
3832
|
+
this.closed = false;
|
|
3833
|
+
this.lastETag = null;
|
|
3834
|
+
this.lastContent = "";
|
|
3835
|
+
this.onStatus?.("opened");
|
|
3836
|
+
this.startPolling();
|
|
3837
|
+
}
|
|
3838
|
+
close() {
|
|
3839
|
+
this.closed = true;
|
|
3840
|
+
if (this.timer) {
|
|
3841
|
+
window.clearInterval(this.timer);
|
|
3842
|
+
this.timer = null;
|
|
3843
|
+
}
|
|
3844
|
+
this.onStatus?.("closed");
|
|
3845
|
+
}
|
|
3846
|
+
startPolling() {
|
|
3847
|
+
if (this.timer) window.clearInterval(this.timer);
|
|
3848
|
+
this.timer = window.setInterval(() => this.poll(), this.intervalMs);
|
|
3849
|
+
this.poll();
|
|
3850
|
+
}
|
|
3851
|
+
async poll() {
|
|
3852
|
+
if (this.closed) return;
|
|
3853
|
+
try {
|
|
3854
|
+
this.onStatus?.("reading");
|
|
3855
|
+
const headers = {};
|
|
3856
|
+
if (this.lastETag) {
|
|
3857
|
+
headers["If-None-Match"] = this.lastETag;
|
|
3858
|
+
}
|
|
3859
|
+
const response = await fetch(this.url, { headers });
|
|
3860
|
+
if (response.status === 304) {
|
|
3861
|
+
return;
|
|
3862
|
+
}
|
|
3863
|
+
if (!response.ok) {
|
|
3864
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
3865
|
+
}
|
|
3866
|
+
const etag = response.headers.get("ETag");
|
|
3867
|
+
if (etag) {
|
|
3868
|
+
this.lastETag = etag;
|
|
3869
|
+
}
|
|
3870
|
+
const content = await response.text();
|
|
3871
|
+
if (content === this.lastContent) {
|
|
3872
|
+
return;
|
|
3873
|
+
}
|
|
3874
|
+
const lines = content.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
3875
|
+
const lastContentLines = this.lastContent.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
3876
|
+
const newLines = lines.slice(lastContentLines.length);
|
|
3877
|
+
for (const line of newLines) {
|
|
3878
|
+
try {
|
|
3879
|
+
const obj = JSON.parse(line);
|
|
3880
|
+
this.onMessage(obj);
|
|
3881
|
+
} catch {
|
|
3882
|
+
}
|
|
3883
|
+
}
|
|
3884
|
+
this.lastContent = content;
|
|
3885
|
+
} catch (e) {
|
|
3886
|
+
this.onStatus?.("error", e);
|
|
3887
|
+
}
|
|
3888
|
+
}
|
|
3889
|
+
};
|
|
3890
|
+
|
|
3672
3891
|
// src/api/api.ts
|
|
3673
3892
|
var log11 = logger("api");
|
|
3674
3893
|
var API = class {
|
|
@@ -3687,11 +3906,16 @@ var API = class {
|
|
|
3687
3906
|
nextNodeId;
|
|
3688
3907
|
nextEdgeId;
|
|
3689
3908
|
events;
|
|
3909
|
+
ingest;
|
|
3910
|
+
ingestionSource;
|
|
3911
|
+
ingestionConfig;
|
|
3912
|
+
prevProps = {};
|
|
3690
3913
|
root;
|
|
3691
3914
|
constructor(args) {
|
|
3692
3915
|
this.root = args.root;
|
|
3693
3916
|
this.options = applyDefaults(args.options);
|
|
3694
3917
|
this.events = args.events || {};
|
|
3918
|
+
this.ingestionConfig = args.ingestion;
|
|
3695
3919
|
this.reset();
|
|
3696
3920
|
this.canvas = new Canvas(this, {
|
|
3697
3921
|
...this.options.canvas,
|
|
@@ -3705,6 +3929,15 @@ var API = class {
|
|
|
3705
3929
|
} else {
|
|
3706
3930
|
this.history = [];
|
|
3707
3931
|
}
|
|
3932
|
+
this.prevProps = {
|
|
3933
|
+
nodes: args.nodes,
|
|
3934
|
+
edges: args.edges,
|
|
3935
|
+
history: args.history,
|
|
3936
|
+
options: args.options
|
|
3937
|
+
};
|
|
3938
|
+
if (this.ingestionConfig) {
|
|
3939
|
+
this.ingest = new Ingest(this);
|
|
3940
|
+
}
|
|
3708
3941
|
}
|
|
3709
3942
|
reset() {
|
|
3710
3943
|
let graph2 = new Graph({ options: this.options.graph });
|
|
@@ -3727,10 +3960,50 @@ var API = class {
|
|
|
3727
3960
|
if (!root) throw new Error("root element not found");
|
|
3728
3961
|
root.appendChild(this.canvas.container);
|
|
3729
3962
|
await this.applyHistory();
|
|
3963
|
+
if (this.ingestionConfig && this.ingest) {
|
|
3964
|
+
this.connectIngestion();
|
|
3965
|
+
}
|
|
3730
3966
|
if (this.events.onInit) {
|
|
3731
3967
|
this.events.onInit();
|
|
3732
3968
|
}
|
|
3733
3969
|
}
|
|
3970
|
+
/** Connect to the configured ingestion source */
|
|
3971
|
+
connectIngestion() {
|
|
3972
|
+
if (!this.ingestionConfig || !this.ingest) return;
|
|
3973
|
+
const handleMessage = (msg) => {
|
|
3974
|
+
this.ingest.apply(msg);
|
|
3975
|
+
};
|
|
3976
|
+
switch (this.ingestionConfig.type) {
|
|
3977
|
+
case "websocket":
|
|
3978
|
+
this.ingestionSource = new WebSocketSource(
|
|
3979
|
+
this.ingestionConfig.url,
|
|
3980
|
+
handleMessage,
|
|
3981
|
+
void 0,
|
|
3982
|
+
this.ingestionConfig.reconnectMs
|
|
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
|
+
}
|
|
3996
|
+
}
|
|
3997
|
+
/** Disconnect from the ingestion source */
|
|
3998
|
+
disconnectIngestion() {
|
|
3999
|
+
if (!this.ingestionSource) return;
|
|
4000
|
+
if (this.ingestionSource instanceof WebSocketSource) {
|
|
4001
|
+
this.ingestionSource.disconnect();
|
|
4002
|
+
} else if (this.ingestionSource instanceof FileSource) {
|
|
4003
|
+
this.ingestionSource.close();
|
|
4004
|
+
}
|
|
4005
|
+
this.ingestionSource = void 0;
|
|
4006
|
+
}
|
|
3734
4007
|
async applyHistory() {
|
|
3735
4008
|
for (const update of this.history)
|
|
3736
4009
|
await this.applyUpdate(update);
|
|
@@ -4193,154 +4466,117 @@ var API = class {
|
|
|
4193
4466
|
setColorMode(colorMode) {
|
|
4194
4467
|
this.canvas?.setColorMode(colorMode);
|
|
4195
4468
|
}
|
|
4196
|
-
/** Cleanup resources when the graph is destroyed */
|
|
4197
|
-
destroy() {
|
|
4198
|
-
this.canvas?.destroy();
|
|
4199
|
-
}
|
|
4200
|
-
};
|
|
4201
|
-
|
|
4202
|
-
// src/api/ingest.ts
|
|
4203
|
-
var Ingest = class {
|
|
4204
|
-
constructor(api) {
|
|
4205
|
-
this.api = api;
|
|
4206
|
-
}
|
|
4207
4469
|
/**
|
|
4208
|
-
* Apply
|
|
4209
|
-
*
|
|
4210
|
-
*
|
|
4211
|
-
*
|
|
4470
|
+
* Apply prop changes by diffing against previously applied props.
|
|
4471
|
+
* This is a convenience method for framework wrappers that centralizes
|
|
4472
|
+
* the logic for detecting and applying changes to nodes, edges, history, and options.
|
|
4473
|
+
* The API stores the previous props internally, so you just pass the new props.
|
|
4474
|
+
*
|
|
4475
|
+
* @param props - The new props to apply
|
|
4212
4476
|
*/
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
await this.api.update((u) => {
|
|
4221
|
-
if (msg.addNodes) u.addNodes(...msg.addNodes);
|
|
4222
|
-
if (msg.removeNodes) u.deleteNodes(...msg.removeNodes);
|
|
4223
|
-
if (msg.updateNodes) u.updateNodes(...msg.updateNodes);
|
|
4224
|
-
if (msg.addEdges) u.addEdges(...msg.addEdges);
|
|
4225
|
-
if (msg.removeEdges) u.deleteEdges(...msg.removeEdges);
|
|
4226
|
-
if (msg.updateEdges) u.updateEdges(...msg.updateEdges);
|
|
4227
|
-
if (msg.description) u.describe(msg.description);
|
|
4228
|
-
});
|
|
4229
|
-
break;
|
|
4230
|
-
}
|
|
4231
|
-
case "history": {
|
|
4232
|
-
await this.api.replaceHistory(msg.frames);
|
|
4233
|
-
break;
|
|
4477
|
+
applyProps(props) {
|
|
4478
|
+
const prev = this.prevProps;
|
|
4479
|
+
const nodesChanged = !shallowEqualArray(props.nodes, prev.nodes);
|
|
4480
|
+
const edgesChanged = !shallowEqualArray(props.edges, prev.edges);
|
|
4481
|
+
if (nodesChanged || edgesChanged) {
|
|
4482
|
+
if (props.nodes) {
|
|
4483
|
+
this.replaceSnapshot(props.nodes, props.edges || [], void 0);
|
|
4234
4484
|
}
|
|
4235
4485
|
}
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
this.closedByUser = false;
|
|
4258
|
-
this.connectStartTime = Date.now();
|
|
4259
|
-
this.startTotalTimeout();
|
|
4260
|
-
this.open();
|
|
4261
|
-
}
|
|
4262
|
-
disconnect() {
|
|
4263
|
-
this.closedByUser = true;
|
|
4264
|
-
this.clearTotalTimeout();
|
|
4265
|
-
if (this.ws) {
|
|
4266
|
-
try {
|
|
4267
|
-
this.ws.close();
|
|
4268
|
-
} catch {
|
|
4486
|
+
if (!nodesChanged && !edgesChanged && props.history !== prev.history) {
|
|
4487
|
+
if (props.history === void 0) {
|
|
4488
|
+
if (props.nodes) {
|
|
4489
|
+
this.replaceSnapshot(props.nodes, props.edges || [], void 0);
|
|
4490
|
+
}
|
|
4491
|
+
} else if (prev.history && isHistoryPrefix(prev.history, props.history)) {
|
|
4492
|
+
const prevLength = prev.history.length;
|
|
4493
|
+
const newFrames = props.history.slice(prevLength);
|
|
4494
|
+
for (const frame of newFrames) {
|
|
4495
|
+
this.update((u) => {
|
|
4496
|
+
if (frame.addNodes) u.addNodes(...frame.addNodes);
|
|
4497
|
+
if (frame.removeNodes) u.deleteNodes(...frame.removeNodes);
|
|
4498
|
+
if (frame.updateNodes) u.updateNodes(...frame.updateNodes);
|
|
4499
|
+
if (frame.addEdges) u.addEdges(...frame.addEdges);
|
|
4500
|
+
if (frame.removeEdges) u.deleteEdges(...frame.removeEdges);
|
|
4501
|
+
if (frame.updateEdges) u.updateEdges(...frame.updateEdges);
|
|
4502
|
+
if (frame.description) u.describe(frame.description);
|
|
4503
|
+
});
|
|
4504
|
+
}
|
|
4505
|
+
} else {
|
|
4506
|
+
this.replaceHistory(props.history);
|
|
4269
4507
|
}
|
|
4270
|
-
this.ws = null;
|
|
4271
4508
|
}
|
|
4272
|
-
|
|
4509
|
+
const prevCanvas = prev.options?.canvas;
|
|
4510
|
+
const currCanvas = props.options?.canvas;
|
|
4511
|
+
const colorModeChanged = prevCanvas?.colorMode !== currCanvas?.colorMode;
|
|
4512
|
+
if (colorModeChanged && currCanvas?.colorMode) {
|
|
4513
|
+
this.setColorMode(currCanvas.colorMode);
|
|
4514
|
+
}
|
|
4515
|
+
const themeChanged = prevCanvas?.theme !== currCanvas?.theme;
|
|
4516
|
+
const nodeTypesChanged = prevCanvas?.nodeTypes !== currCanvas?.nodeTypes;
|
|
4517
|
+
const edgeTypesChanged = prevCanvas?.edgeTypes !== currCanvas?.edgeTypes;
|
|
4518
|
+
if (themeChanged || nodeTypesChanged || edgeTypesChanged) {
|
|
4519
|
+
this.updateStyles({
|
|
4520
|
+
theme: currCanvas?.theme,
|
|
4521
|
+
nodeTypes: currCanvas?.nodeTypes,
|
|
4522
|
+
edgeTypes: currCanvas?.edgeTypes
|
|
4523
|
+
});
|
|
4524
|
+
}
|
|
4525
|
+
this.prevProps = {
|
|
4526
|
+
nodes: props.nodes,
|
|
4527
|
+
edges: props.edges,
|
|
4528
|
+
history: props.history,
|
|
4529
|
+
options: props.options
|
|
4530
|
+
};
|
|
4273
4531
|
}
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
this.
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4532
|
+
/** Cleanup resources when the graph is destroyed */
|
|
4533
|
+
destroy() {
|
|
4534
|
+
this.disconnectIngestion();
|
|
4535
|
+
this.canvas?.destroy();
|
|
4536
|
+
}
|
|
4537
|
+
};
|
|
4538
|
+
function shallowEqualArray(a, b) {
|
|
4539
|
+
if (a === b) return true;
|
|
4540
|
+
if (!a || !b) return false;
|
|
4541
|
+
if (a.length !== b.length) return false;
|
|
4542
|
+
for (let i = 0; i < a.length; i++) {
|
|
4543
|
+
if (a[i] !== b[i]) {
|
|
4544
|
+
if (typeof a[i] === "object" && a[i] !== null && typeof b[i] === "object" && b[i] !== null) {
|
|
4545
|
+
const aObj = a[i];
|
|
4546
|
+
const bObj = b[i];
|
|
4547
|
+
const aKeys = Object.keys(aObj);
|
|
4548
|
+
const bKeys = Object.keys(bObj);
|
|
4549
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
4550
|
+
for (const key of aKeys) {
|
|
4551
|
+
if (aObj[key] !== bObj[key]) return false;
|
|
4285
4552
|
}
|
|
4286
|
-
|
|
4287
|
-
|
|
4553
|
+
} else {
|
|
4554
|
+
return false;
|
|
4288
4555
|
}
|
|
4289
|
-
}, this.totalTimeoutMs);
|
|
4290
|
-
}
|
|
4291
|
-
clearTotalTimeout() {
|
|
4292
|
-
if (this.totalTimeoutTimer !== null) {
|
|
4293
|
-
clearTimeout(this.totalTimeoutTimer);
|
|
4294
|
-
this.totalTimeoutTimer = null;
|
|
4295
4556
|
}
|
|
4296
|
-
this.connectStartTime = null;
|
|
4297
4557
|
}
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
return;
|
|
4558
|
+
return true;
|
|
4559
|
+
}
|
|
4560
|
+
function isHistoryPrefix(oldHistory, newHistory) {
|
|
4561
|
+
if (newHistory.length < oldHistory.length) return false;
|
|
4562
|
+
for (let i = 0; i < oldHistory.length; i++) {
|
|
4563
|
+
if (!shallowEqualUpdate(oldHistory[i], newHistory[i])) {
|
|
4564
|
+
return false;
|
|
4306
4565
|
}
|
|
4307
|
-
this.onStatus?.(this.ws ? "reconnecting" : "connecting");
|
|
4308
|
-
const ws = new WebSocket(this.url);
|
|
4309
|
-
this.ws = ws;
|
|
4310
|
-
ws.onopen = () => {
|
|
4311
|
-
this.clearTotalTimeout();
|
|
4312
|
-
this.onStatus?.("connected");
|
|
4313
|
-
};
|
|
4314
|
-
ws.onerror = (e) => {
|
|
4315
|
-
this.onStatus?.("error", e);
|
|
4316
|
-
};
|
|
4317
|
-
ws.onclose = () => {
|
|
4318
|
-
if (this.closedByUser) {
|
|
4319
|
-
this.onStatus?.("closed");
|
|
4320
|
-
return;
|
|
4321
|
-
}
|
|
4322
|
-
if (this.connectStartTime && Date.now() - this.connectStartTime >= this.totalTimeoutMs) {
|
|
4323
|
-
this.closedByUser = true;
|
|
4324
|
-
this.clearTotalTimeout();
|
|
4325
|
-
this.onStatus?.("error", new Error("Connection timeout after 10 seconds"));
|
|
4326
|
-
return;
|
|
4327
|
-
}
|
|
4328
|
-
this.onStatus?.("reconnecting");
|
|
4329
|
-
setTimeout(() => this.open(), this.reconnectMs);
|
|
4330
|
-
};
|
|
4331
|
-
ws.onmessage = (ev) => {
|
|
4332
|
-
const data = typeof ev.data === "string" ? ev.data : "";
|
|
4333
|
-
const lines = data.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
4334
|
-
for (const line of lines) {
|
|
4335
|
-
try {
|
|
4336
|
-
const obj = JSON.parse(line);
|
|
4337
|
-
this.onMessage(obj);
|
|
4338
|
-
} catch {
|
|
4339
|
-
}
|
|
4340
|
-
}
|
|
4341
|
-
};
|
|
4342
4566
|
}
|
|
4343
|
-
|
|
4567
|
+
return true;
|
|
4568
|
+
}
|
|
4569
|
+
function shallowEqualUpdate(a, b) {
|
|
4570
|
+
if (a === b) return true;
|
|
4571
|
+
if (a.description !== b.description) return false;
|
|
4572
|
+
if (!shallowEqualArray(a.addNodes, b.addNodes)) return false;
|
|
4573
|
+
if (!shallowEqualArray(a.removeNodes, b.removeNodes)) return false;
|
|
4574
|
+
if (!shallowEqualArray(a.updateNodes, b.updateNodes)) return false;
|
|
4575
|
+
if (!shallowEqualArray(a.addEdges, b.addEdges)) return false;
|
|
4576
|
+
if (!shallowEqualArray(a.removeEdges, b.removeEdges)) return false;
|
|
4577
|
+
if (!shallowEqualArray(a.updateEdges, b.updateEdges)) return false;
|
|
4578
|
+
return true;
|
|
4579
|
+
}
|
|
4344
4580
|
|
|
4345
4581
|
// src/api/sources/FileSystemSource.ts
|
|
4346
4582
|
var FileSystemSource = class {
|
|
@@ -4404,82 +4640,6 @@ var FileSystemSource = class {
|
|
|
4404
4640
|
}
|
|
4405
4641
|
};
|
|
4406
4642
|
|
|
4407
|
-
// src/api/sources/FileSource.ts
|
|
4408
|
-
var FileSource = class {
|
|
4409
|
-
url;
|
|
4410
|
-
onMessage;
|
|
4411
|
-
onStatus;
|
|
4412
|
-
timer = null;
|
|
4413
|
-
lastETag = null;
|
|
4414
|
-
lastContent = "";
|
|
4415
|
-
intervalMs = 1e3;
|
|
4416
|
-
closed = false;
|
|
4417
|
-
constructor(url, onMessage, onStatus, intervalMs = 1e3) {
|
|
4418
|
-
this.url = url;
|
|
4419
|
-
this.onMessage = onMessage;
|
|
4420
|
-
this.onStatus = onStatus;
|
|
4421
|
-
this.intervalMs = intervalMs;
|
|
4422
|
-
}
|
|
4423
|
-
async connect() {
|
|
4424
|
-
this.closed = false;
|
|
4425
|
-
this.lastETag = null;
|
|
4426
|
-
this.lastContent = "";
|
|
4427
|
-
this.onStatus?.("opened");
|
|
4428
|
-
this.startPolling();
|
|
4429
|
-
}
|
|
4430
|
-
close() {
|
|
4431
|
-
this.closed = true;
|
|
4432
|
-
if (this.timer) {
|
|
4433
|
-
window.clearInterval(this.timer);
|
|
4434
|
-
this.timer = null;
|
|
4435
|
-
}
|
|
4436
|
-
this.onStatus?.("closed");
|
|
4437
|
-
}
|
|
4438
|
-
startPolling() {
|
|
4439
|
-
if (this.timer) window.clearInterval(this.timer);
|
|
4440
|
-
this.timer = window.setInterval(() => this.poll(), this.intervalMs);
|
|
4441
|
-
this.poll();
|
|
4442
|
-
}
|
|
4443
|
-
async poll() {
|
|
4444
|
-
if (this.closed) return;
|
|
4445
|
-
try {
|
|
4446
|
-
this.onStatus?.("reading");
|
|
4447
|
-
const headers = {};
|
|
4448
|
-
if (this.lastETag) {
|
|
4449
|
-
headers["If-None-Match"] = this.lastETag;
|
|
4450
|
-
}
|
|
4451
|
-
const response = await fetch(this.url, { headers });
|
|
4452
|
-
if (response.status === 304) {
|
|
4453
|
-
return;
|
|
4454
|
-
}
|
|
4455
|
-
if (!response.ok) {
|
|
4456
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
4457
|
-
}
|
|
4458
|
-
const etag = response.headers.get("ETag");
|
|
4459
|
-
if (etag) {
|
|
4460
|
-
this.lastETag = etag;
|
|
4461
|
-
}
|
|
4462
|
-
const content = await response.text();
|
|
4463
|
-
if (content === this.lastContent) {
|
|
4464
|
-
return;
|
|
4465
|
-
}
|
|
4466
|
-
const lines = content.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
4467
|
-
const lastContentLines = this.lastContent.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
4468
|
-
const newLines = lines.slice(lastContentLines.length);
|
|
4469
|
-
for (const line of newLines) {
|
|
4470
|
-
try {
|
|
4471
|
-
const obj = JSON.parse(line);
|
|
4472
|
-
this.onMessage(obj);
|
|
4473
|
-
} catch {
|
|
4474
|
-
}
|
|
4475
|
-
}
|
|
4476
|
-
this.lastContent = content;
|
|
4477
|
-
} catch (e) {
|
|
4478
|
-
this.onStatus?.("error", e);
|
|
4479
|
-
}
|
|
4480
|
-
}
|
|
4481
|
-
};
|
|
4482
|
-
|
|
4483
4643
|
// src/playground/playground.ts
|
|
4484
4644
|
var import_styles2 = __toESM(require("./styles.css?raw"), 1);
|
|
4485
4645
|
var Playground = class {
|