@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.
- package/dist/index.js +342 -9
- 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.
|
|
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.
|
|
35
|
-
"@sylphx/lens-core": "^2.12.
|
|
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"
|