@sylphx/lens-solid 2.3.8 → 2.3.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.
Files changed (2) hide show
  1. package/dist/index.js +342 -9
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -857,7 +857,6 @@ class ReconnectionMetricsTracker {
857
857
  totalFailures = 0;
858
858
  totalSubscriptionsProcessed = 0;
859
859
  totalBytesTransferred = 0;
860
- totalBytesCompressed = 0;
861
860
  statusCounts = {
862
861
  current: 0,
863
862
  patched: 0,
@@ -962,10 +961,6 @@ class ReconnectionMetricsTracker {
962
961
  }
963
962
  });
964
963
  }
965
- recordCompression(originalSize, compressedSize) {
966
- this.totalBytesTransferred += compressedSize;
967
- this.totalBytesCompressed += originalSize - compressedSize;
968
- }
969
964
  getMetrics() {
970
965
  const avgLatency = this.latencies.length > 0 ? this.latencies.reduce((a, b) => a + b, 0) / this.latencies.length : 0;
971
966
  const p50 = this.percentile(50);
@@ -982,9 +977,7 @@ class ReconnectionMetricsTracker {
982
977
  p99Latency: p99,
983
978
  totalSubscriptionsProcessed: this.totalSubscriptionsProcessed,
984
979
  statusBreakdown: { ...this.statusCounts },
985
- bytesTransferred: this.totalBytesTransferred,
986
- bytesSaved: this.totalBytesCompressed,
987
- compressionRatio: this.totalBytesTransferred > 0 ? (this.totalBytesTransferred - this.totalBytesCompressed) / this.totalBytesTransferred : 1
980
+ bytesTransferred: this.totalBytesTransferred
988
981
  };
989
982
  }
990
983
  getHealth() {
@@ -1027,7 +1020,6 @@ class ReconnectionMetricsTracker {
1027
1020
  this.totalFailures = 0;
1028
1021
  this.totalSubscriptionsProcessed = 0;
1029
1022
  this.totalBytesTransferred = 0;
1030
- this.totalBytesCompressed = 0;
1031
1023
  this.statusCounts = { current: 0, patched: 0, snapshot: 0, deleted: 0, error: 0 };
1032
1024
  this.latencies = [];
1033
1025
  }
@@ -2474,6 +2466,7 @@ function createPollingObservable(baseUrl, op2, options) {
2474
2466
  if (isError(message)) {
2475
2467
  retries++;
2476
2468
  if (retries > options.maxRetries) {
2469
+ active = false;
2477
2470
  observer.error?.(new Error(message.error));
2478
2471
  return;
2479
2472
  }
@@ -2494,6 +2487,7 @@ function createPollingObservable(baseUrl, op2, options) {
2494
2487
  }
2495
2488
  } catch (error) {
2496
2489
  if (active) {
2490
+ active = false;
2497
2491
  observer.error?.(error);
2498
2492
  }
2499
2493
  }
@@ -2535,6 +2529,345 @@ http.server = function httpServer(options) {
2535
2529
  }
2536
2530
  };
2537
2531
  };
2532
+ class SseConnectionManager {
2533
+ config;
2534
+ subscriptions = new Map;
2535
+ connectionState = "disconnected";
2536
+ constructor(config) {
2537
+ this.config = config;
2538
+ }
2539
+ getConnectionState() {
2540
+ return this.connectionState;
2541
+ }
2542
+ getSubscriptionCount() {
2543
+ return this.subscriptions.size;
2544
+ }
2545
+ setConnectionState(state) {
2546
+ if (this.connectionState !== state) {
2547
+ this.connectionState = state;
2548
+ this.config.onConnectionStateChange?.(state);
2549
+ }
2550
+ }
2551
+ getRetryDelay(attempt) {
2552
+ const { baseDelay, maxDelay } = this.config.retry;
2553
+ const exponentialDelay = baseDelay * Math.pow(2, attempt);
2554
+ const jitter = Math.random() * 0.3 * exponentialDelay;
2555
+ return Math.min(exponentialDelay + jitter, maxDelay);
2556
+ }
2557
+ createSubscription(op2) {
2558
+ return {
2559
+ subscribe: (observer) => {
2560
+ const subId = op2.id;
2561
+ let retryCount = 0;
2562
+ const connect = () => {
2563
+ const sseUrl = new URL(`${this.config.baseUrl}/${op2.path}`);
2564
+ if (op2.input !== undefined) {
2565
+ sseUrl.searchParams.set("input", JSON.stringify(op2.input));
2566
+ }
2567
+ sseUrl.searchParams.set("_sse", "1");
2568
+ if (this.config.headers) {
2569
+ for (const [key, value] of Object.entries(this.config.headers)) {
2570
+ sseUrl.searchParams.set(`_h_${key}`, value);
2571
+ }
2572
+ }
2573
+ const eventSource = new this.config.EventSource(sseUrl.toString());
2574
+ this.subscriptions.set(subId, { eventSource, observer, retryCount });
2575
+ if (this.subscriptions.size === 1) {
2576
+ this.setConnectionState("connecting");
2577
+ }
2578
+ eventSource.onopen = () => {
2579
+ retryCount = 0;
2580
+ const sub = this.subscriptions.get(subId);
2581
+ if (sub) {
2582
+ sub.retryCount = 0;
2583
+ }
2584
+ this.setConnectionState("connected");
2585
+ };
2586
+ eventSource.onmessage = (event) => {
2587
+ try {
2588
+ const message = JSON.parse(event.data);
2589
+ observer.next?.(message);
2590
+ } catch (error) {
2591
+ observer.error?.(error);
2592
+ }
2593
+ };
2594
+ eventSource.addEventListener("error", (_event) => {
2595
+ if (eventSource.readyState === this.config.EventSource.CLOSED) {
2596
+ if (this.config.retry.enabled && retryCount < this.config.retry.maxAttempts) {
2597
+ this.setConnectionState("reconnecting");
2598
+ retryCount++;
2599
+ const sub = this.subscriptions.get(subId);
2600
+ if (sub) {
2601
+ sub.retryCount = retryCount;
2602
+ }
2603
+ const delay = this.getRetryDelay(retryCount);
2604
+ setTimeout(() => {
2605
+ if (this.subscriptions.has(subId)) {
2606
+ this.subscriptions.delete(subId);
2607
+ connect();
2608
+ }
2609
+ }, delay);
2610
+ } else {
2611
+ observer.error?.(new Error("SSE connection failed"));
2612
+ this.subscriptions.delete(subId);
2613
+ if (this.subscriptions.size === 0) {
2614
+ this.setConnectionState("disconnected");
2615
+ }
2616
+ }
2617
+ }
2618
+ });
2619
+ eventSource.addEventListener("complete", () => {
2620
+ observer.complete?.();
2621
+ eventSource.close();
2622
+ this.subscriptions.delete(subId);
2623
+ if (this.subscriptions.size === 0) {
2624
+ this.setConnectionState("disconnected");
2625
+ }
2626
+ });
2627
+ eventSource.addEventListener("lens-error", (event) => {
2628
+ try {
2629
+ const errorData = JSON.parse(event.data);
2630
+ observer.error?.(new Error(errorData.message || "SSE error"));
2631
+ } catch {
2632
+ observer.error?.(new Error("SSE error"));
2633
+ }
2634
+ });
2635
+ };
2636
+ connect();
2637
+ return {
2638
+ unsubscribe: () => {
2639
+ const sub = this.subscriptions.get(subId);
2640
+ if (sub) {
2641
+ sub.eventSource.close();
2642
+ this.subscriptions.delete(subId);
2643
+ if (this.subscriptions.size === 0) {
2644
+ this.setConnectionState("disconnected");
2645
+ }
2646
+ }
2647
+ }
2648
+ };
2649
+ }
2650
+ };
2651
+ }
2652
+ close() {
2653
+ for (const [_id, sub] of this.subscriptions) {
2654
+ sub.eventSource.close();
2655
+ }
2656
+ this.subscriptions.clear();
2657
+ this.setConnectionState("disconnected");
2658
+ }
2659
+ }
2660
+ class SubscriptionRegistry2 {
2661
+ subscriptions = new Map;
2662
+ entityIndex = new Map;
2663
+ add(sub) {
2664
+ const tracked = {
2665
+ ...sub,
2666
+ state: "pending",
2667
+ lastDataHash: sub.lastData ? hashEntityState(sub.lastData) : null,
2668
+ createdAt: Date.now(),
2669
+ lastUpdateAt: null
2670
+ };
2671
+ this.subscriptions.set(sub.id, tracked);
2672
+ const entityKey = `${sub.entity}:${sub.entityId}`;
2673
+ let ids = this.entityIndex.get(entityKey);
2674
+ if (!ids) {
2675
+ ids = new Set;
2676
+ this.entityIndex.set(entityKey, ids);
2677
+ }
2678
+ ids.add(sub.id);
2679
+ }
2680
+ get(id) {
2681
+ return this.subscriptions.get(id);
2682
+ }
2683
+ has(id) {
2684
+ return this.subscriptions.has(id);
2685
+ }
2686
+ remove(id) {
2687
+ const sub = this.subscriptions.get(id);
2688
+ if (!sub)
2689
+ return;
2690
+ this.subscriptions.delete(id);
2691
+ const entityKey = `${sub.entity}:${sub.entityId}`;
2692
+ const ids = this.entityIndex.get(entityKey);
2693
+ if (ids) {
2694
+ ids.delete(id);
2695
+ if (ids.size === 0) {
2696
+ this.entityIndex.delete(entityKey);
2697
+ }
2698
+ }
2699
+ }
2700
+ getByEntity(entity3, entityId) {
2701
+ const entityKey = `${entity3}:${entityId}`;
2702
+ const ids = this.entityIndex.get(entityKey);
2703
+ if (!ids)
2704
+ return [];
2705
+ const result = [];
2706
+ for (const id of ids) {
2707
+ const sub = this.subscriptions.get(id);
2708
+ if (sub) {
2709
+ result.push(sub);
2710
+ }
2711
+ }
2712
+ return result;
2713
+ }
2714
+ updateVersion(id, version, data) {
2715
+ const sub = this.subscriptions.get(id);
2716
+ if (!sub)
2717
+ return;
2718
+ sub.version = version;
2719
+ sub.lastUpdateAt = Date.now();
2720
+ if (data !== undefined) {
2721
+ sub.lastData = data;
2722
+ sub.lastDataHash = hashEntityState(data);
2723
+ }
2724
+ if (sub.state === "pending" || sub.state === "reconnecting") {
2725
+ sub.state = "active";
2726
+ }
2727
+ }
2728
+ updateData(id, data) {
2729
+ const sub = this.subscriptions.get(id);
2730
+ if (!sub)
2731
+ return;
2732
+ sub.lastData = data;
2733
+ sub.lastDataHash = hashEntityState(data);
2734
+ }
2735
+ getLastData(id) {
2736
+ return this.subscriptions.get(id)?.lastData ?? null;
2737
+ }
2738
+ getVersion(id) {
2739
+ return this.subscriptions.get(id)?.version ?? null;
2740
+ }
2741
+ markActive(id) {
2742
+ const sub = this.subscriptions.get(id);
2743
+ if (sub) {
2744
+ sub.state = "active";
2745
+ }
2746
+ }
2747
+ markError(id) {
2748
+ const sub = this.subscriptions.get(id);
2749
+ if (sub) {
2750
+ sub.state = "error";
2751
+ }
2752
+ }
2753
+ markAllReconnecting() {
2754
+ for (const sub of this.subscriptions.values()) {
2755
+ if (sub.state === "active") {
2756
+ sub.state = "reconnecting";
2757
+ }
2758
+ }
2759
+ }
2760
+ getByState(state) {
2761
+ const result = [];
2762
+ for (const sub of this.subscriptions.values()) {
2763
+ if (sub.state === state) {
2764
+ result.push(sub);
2765
+ }
2766
+ }
2767
+ return result;
2768
+ }
2769
+ getAllForReconnect() {
2770
+ const result = [];
2771
+ for (const sub of this.subscriptions.values()) {
2772
+ if (sub.state === "reconnecting" || sub.state === "active") {
2773
+ const reconnectSub = {
2774
+ id: sub.id,
2775
+ entity: sub.entity,
2776
+ entityId: sub.entityId,
2777
+ fields: sub.fields,
2778
+ version: sub.version,
2779
+ input: sub.input
2780
+ };
2781
+ if (sub.lastDataHash) {
2782
+ reconnectSub.dataHash = sub.lastDataHash;
2783
+ }
2784
+ result.push(reconnectSub);
2785
+ }
2786
+ }
2787
+ return result;
2788
+ }
2789
+ processReconnectResult(id, version, data) {
2790
+ const sub = this.subscriptions.get(id);
2791
+ if (!sub)
2792
+ return;
2793
+ sub.version = version;
2794
+ sub.state = "active";
2795
+ sub.lastUpdateAt = Date.now();
2796
+ if (data !== undefined) {
2797
+ sub.lastData = data;
2798
+ sub.lastDataHash = hashEntityState(data);
2799
+ }
2800
+ }
2801
+ getObserver(id) {
2802
+ return this.subscriptions.get(id)?.observer;
2803
+ }
2804
+ updateObserver(id, observer) {
2805
+ const sub = this.subscriptions.get(id);
2806
+ if (sub) {
2807
+ sub.observer = observer;
2808
+ }
2809
+ }
2810
+ notifyNext(id, data) {
2811
+ const sub = this.subscriptions.get(id);
2812
+ sub?.observer.next?.({ data, version: sub.version });
2813
+ }
2814
+ notifyError(id, error) {
2815
+ this.subscriptions.get(id)?.observer.error?.(error);
2816
+ }
2817
+ notifyAllReconnectingError(error) {
2818
+ for (const sub of this.subscriptions.values()) {
2819
+ if (sub.state === "reconnecting") {
2820
+ sub.observer.error?.(error);
2821
+ }
2822
+ }
2823
+ }
2824
+ get size() {
2825
+ return this.subscriptions.size;
2826
+ }
2827
+ getIds() {
2828
+ return Array.from(this.subscriptions.keys());
2829
+ }
2830
+ values() {
2831
+ return this.subscriptions.values();
2832
+ }
2833
+ getStats() {
2834
+ const byState = {
2835
+ pending: 0,
2836
+ active: 0,
2837
+ reconnecting: 0,
2838
+ error: 0
2839
+ };
2840
+ const byEntity = {};
2841
+ for (const sub of this.subscriptions.values()) {
2842
+ byState[sub.state]++;
2843
+ const entityKey = `${sub.entity}:${sub.entityId}`;
2844
+ byEntity[entityKey] = (byEntity[entityKey] ?? 0) + 1;
2845
+ }
2846
+ return {
2847
+ total: this.subscriptions.size,
2848
+ byState,
2849
+ byEntity
2850
+ };
2851
+ }
2852
+ clear() {
2853
+ for (const sub of this.subscriptions.values()) {
2854
+ sub.observer.complete?.();
2855
+ }
2856
+ this.subscriptions.clear();
2857
+ this.entityIndex.clear();
2858
+ }
2859
+ clearErrors() {
2860
+ const toRemove = [];
2861
+ for (const [id, sub] of this.subscriptions) {
2862
+ if (sub.state === "error") {
2863
+ toRemove.push(id);
2864
+ }
2865
+ }
2866
+ for (const id of toRemove) {
2867
+ this.remove(id);
2868
+ }
2869
+ }
2870
+ }
2538
2871
 
2539
2872
  // src/create.ts
2540
2873
  import { createEffect, createSignal, on, onCleanup } from "solid-js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sylphx/lens-solid",
3
- "version": "2.3.8",
3
+ "version": "2.3.9",
4
4
  "description": "SolidJS bindings for Lens API framework",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -31,8 +31,8 @@
31
31
  "author": "SylphxAI",
32
32
  "license": "MIT",
33
33
  "dependencies": {
34
- "@sylphx/lens-client": "^2.7.3",
35
- "@sylphx/lens-core": "^2.12.1"
34
+ "@sylphx/lens-client": "^2.7.4",
35
+ "@sylphx/lens-core": "^2.12.2"
36
36
  },
37
37
  "peerDependencies": {
38
38
  "solid-js": ">=1.8.0"