@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 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 an incoming ingest message to the API.
4209
- * - snapshot: rebuild state from nodes/edges (clears prior history)
4210
- * - update: apply incremental update
4211
- * - history: initialize from a set of frames (clears prior history)
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
- async apply(msg) {
4214
- switch (msg.type) {
4215
- case "snapshot": {
4216
- await this.api.replaceSnapshot(msg.nodes, msg.edges, msg.description);
4217
- break;
4218
- }
4219
- case "update": {
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
- // src/api/sources/WebSocketSource.ts
4240
- var WebSocketSource = class {
4241
- url;
4242
- ws = null;
4243
- onMessage;
4244
- onStatus;
4245
- reconnectMs;
4246
- closedByUser = false;
4247
- connectStartTime = null;
4248
- totalTimeoutMs = 1e4;
4249
- totalTimeoutTimer = null;
4250
- constructor(url, onMessage, onStatus, reconnectMs = 1500) {
4251
- this.url = url;
4252
- this.onMessage = onMessage;
4253
- this.onStatus = onStatus;
4254
- this.reconnectMs = reconnectMs;
4255
- }
4256
- connect() {
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
- this.onStatus?.("closed");
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
- startTotalTimeout() {
4275
- this.clearTotalTimeout();
4276
- this.totalTimeoutTimer = window.setTimeout(() => {
4277
- if (!this.closedByUser && this.ws?.readyState !== WebSocket.OPEN) {
4278
- this.closedByUser = true;
4279
- if (this.ws) {
4280
- try {
4281
- this.ws.close();
4282
- } catch {
4283
- }
4284
- this.ws = null;
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
- this.clearTotalTimeout();
4287
- this.onStatus?.("error", new Error("Connection timeout after 10 seconds"));
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
- open() {
4299
- if (this.connectStartTime && Date.now() - this.connectStartTime >= this.totalTimeoutMs) {
4300
- if (!this.closedByUser) {
4301
- this.closedByUser = true;
4302
- this.clearTotalTimeout();
4303
- this.onStatus?.("error", new Error("Connection timeout after 10 seconds"));
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 {