@sylphx/lens-solid 2.0.4 → 2.1.0

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 CHANGED
@@ -1,3 +1,1232 @@
1
+ // ../core/dist/index.js
2
+ var {createRequire} = (() => ({}));
3
+ // ../../node_modules/.bun/@sylphx+reify-core@0.1.2/node_modules/@sylphx/reify-core/dist/builder.js
4
+ var DSL_MARKER = Symbol("reify");
5
+ // ../../node_modules/.bun/@sylphx+reify-core@0.1.2/node_modules/@sylphx/reify-core/dist/evaluator.js
6
+ var plugins = new Map;
7
+ // ../core/dist/index.js
8
+ var ENTITY_SYMBOL = Symbol("lens:entity");
9
+ var OPTIMISTIC_PLUGIN_SYMBOL = Symbol.for("lens:optimistic-plugin");
10
+ var DEFAULT_OPERATION_LOG_CONFIG = {
11
+ maxEntries: 1e4,
12
+ maxAge: 5 * 60 * 1000,
13
+ maxMemory: 10 * 1024 * 1024,
14
+ cleanupInterval: 60 * 1000
15
+ };
16
+ function murmurhash3(key, seed = 0) {
17
+ const remainder = key.length % 4;
18
+ const bytes = key.length - remainder;
19
+ let h1 = seed;
20
+ const c1 = 3432918353;
21
+ const c2 = 461845907;
22
+ let i = 0;
23
+ while (i < bytes) {
24
+ let k12 = key.charCodeAt(i) & 255 | (key.charCodeAt(i + 1) & 255) << 8 | (key.charCodeAt(i + 2) & 255) << 16 | (key.charCodeAt(i + 3) & 255) << 24;
25
+ k12 = Math.imul(k12, c1);
26
+ k12 = k12 << 15 | k12 >>> 17;
27
+ k12 = Math.imul(k12, c2);
28
+ h1 ^= k12;
29
+ h1 = h1 << 13 | h1 >>> 19;
30
+ h1 = Math.imul(h1, 5) + 3864292196;
31
+ i += 4;
32
+ }
33
+ let k1 = 0;
34
+ if (remainder >= 3) {
35
+ k1 ^= (key.charCodeAt(i + 2) & 255) << 16;
36
+ }
37
+ if (remainder >= 2) {
38
+ k1 ^= (key.charCodeAt(i + 1) & 255) << 8;
39
+ }
40
+ if (remainder >= 1) {
41
+ k1 ^= key.charCodeAt(i) & 255;
42
+ k1 = Math.imul(k1, c1);
43
+ k1 = k1 << 15 | k1 >>> 17;
44
+ k1 = Math.imul(k1, c2);
45
+ h1 ^= k1;
46
+ }
47
+ h1 ^= key.length;
48
+ h1 ^= h1 >>> 16;
49
+ h1 = Math.imul(h1, 2246822507);
50
+ h1 ^= h1 >>> 13;
51
+ h1 = Math.imul(h1, 3266489909);
52
+ h1 ^= h1 >>> 16;
53
+ return (h1 >>> 0).toString(16).padStart(8, "0");
54
+ }
55
+ function hashValue(value) {
56
+ if (value === null) {
57
+ return "n:null";
58
+ }
59
+ if (value === undefined) {
60
+ return "u:undefined";
61
+ }
62
+ const type = typeof value;
63
+ switch (type) {
64
+ case "string":
65
+ return `s:${murmurhash3(value)}`;
66
+ case "number":
67
+ return `n:${murmurhash3(String(value))}`;
68
+ case "boolean":
69
+ return value ? "b:true" : "b:false";
70
+ case "object":
71
+ return `o:${murmurhash3(stableStringify(value))}`;
72
+ case "bigint":
73
+ return `i:${murmurhash3(String(value))}`;
74
+ case "symbol":
75
+ return `y:${murmurhash3(String(value))}`;
76
+ case "function":
77
+ return `f:${murmurhash3(value.toString())}`;
78
+ default:
79
+ return `x:${murmurhash3(String(value))}`;
80
+ }
81
+ }
82
+ function stableStringify(value) {
83
+ if (value === null || typeof value !== "object") {
84
+ return JSON.stringify(value);
85
+ }
86
+ if (Array.isArray(value)) {
87
+ return `[${value.map(stableStringify).join(",")}]`;
88
+ }
89
+ const keys = Object.keys(value).sort();
90
+ const pairs = keys.map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`);
91
+ return `{${pairs.join(",")}}`;
92
+ }
93
+ function hashEntityState(data) {
94
+ return `e:${murmurhash3(stableStringify(data))}`;
95
+ }
96
+ class HashCache {
97
+ cache = new Map;
98
+ maxSize;
99
+ maxAge;
100
+ constructor(maxSize = 1000, maxAge = 60000) {
101
+ this.maxSize = maxSize;
102
+ this.maxAge = maxAge;
103
+ }
104
+ get(value) {
105
+ if (typeof value !== "object" || value === null) {
106
+ const cached = this.cache.get(value);
107
+ if (cached && Date.now() - cached.timestamp < this.maxAge) {
108
+ return cached.hash;
109
+ }
110
+ const hash = hashValue(value);
111
+ this.set(value, hash);
112
+ return hash;
113
+ }
114
+ return hashValue(value);
115
+ }
116
+ set(value, hash) {
117
+ if (this.cache.size >= this.maxSize) {
118
+ const firstKey = this.cache.keys().next().value;
119
+ if (firstKey !== undefined) {
120
+ this.cache.delete(firstKey);
121
+ }
122
+ }
123
+ this.cache.set(value, { hash, timestamp: Date.now() });
124
+ }
125
+ clear() {
126
+ this.cache.clear();
127
+ }
128
+ getStats() {
129
+ return { size: this.cache.size, maxSize: this.maxSize };
130
+ }
131
+ }
132
+
133
+ class FieldHashMap {
134
+ hashes = new Map;
135
+ hasChanged(field, value) {
136
+ const newHash = hashValue(value);
137
+ const oldHash = this.hashes.get(field);
138
+ if (oldHash === newHash) {
139
+ return false;
140
+ }
141
+ this.hashes.set(field, newHash);
142
+ return true;
143
+ }
144
+ update(field, value) {
145
+ this.hashes.set(field, hashValue(value));
146
+ }
147
+ getHash(field) {
148
+ return this.hashes.get(field);
149
+ }
150
+ delete(field) {
151
+ this.hashes.delete(field);
152
+ }
153
+ clear() {
154
+ this.hashes.clear();
155
+ }
156
+ getAll() {
157
+ return new Map(this.hashes);
158
+ }
159
+ getCombinedHash() {
160
+ const sorted = Array.from(this.hashes.entries()).sort((a, b) => a[0].localeCompare(b[0]));
161
+ return murmurhash3(sorted.map(([k, v]) => `${k}:${v}`).join("|"));
162
+ }
163
+ }
164
+ var DEFAULT_METRICS_CONFIG = {
165
+ enabled: true,
166
+ sampleRate: 1,
167
+ maxHistory: 1000
168
+ };
169
+
170
+ class ReconnectionMetricsTracker {
171
+ config;
172
+ history = [];
173
+ pending = new Map;
174
+ totalAttempts = 0;
175
+ totalSuccesses = 0;
176
+ totalFailures = 0;
177
+ totalSubscriptionsProcessed = 0;
178
+ totalBytesTransferred = 0;
179
+ totalBytesCompressed = 0;
180
+ statusCounts = {
181
+ current: 0,
182
+ patched: 0,
183
+ snapshot: 0,
184
+ deleted: 0,
185
+ error: 0
186
+ };
187
+ latencies = [];
188
+ constructor(config = {}) {
189
+ this.config = { ...DEFAULT_METRICS_CONFIG, ...config };
190
+ }
191
+ startReconnection(reconnectId, subscriptionCount) {
192
+ if (!this.shouldSample())
193
+ return;
194
+ const record = {
195
+ reconnectId,
196
+ startTime: Date.now(),
197
+ subscriptionCount
198
+ };
199
+ this.pending.set(reconnectId, record);
200
+ this.totalAttempts++;
201
+ this.emit({
202
+ type: "reconnect_start",
203
+ timestamp: record.startTime,
204
+ data: {
205
+ reconnectId,
206
+ subscriptionCount
207
+ }
208
+ });
209
+ }
210
+ completeReconnection(reconnectId, results) {
211
+ const pending = this.pending.get(reconnectId);
212
+ if (!pending)
213
+ return;
214
+ this.pending.delete(reconnectId);
215
+ const endTime = Date.now();
216
+ const latency = endTime - pending.startTime;
217
+ this.totalSuccesses++;
218
+ this.totalSubscriptionsProcessed += results.length;
219
+ this.latencies.push(latency);
220
+ for (const result of results) {
221
+ this.statusCounts[result.status]++;
222
+ if (result.data) {
223
+ const size = JSON.stringify(result.data).length;
224
+ this.totalBytesTransferred += size;
225
+ }
226
+ if (result.patches) {
227
+ const size = JSON.stringify(result.patches).length;
228
+ this.totalBytesTransferred += size;
229
+ }
230
+ }
231
+ const record = {
232
+ reconnectId,
233
+ startTime: pending.startTime,
234
+ endTime,
235
+ latency,
236
+ subscriptionCount: pending.subscriptionCount,
237
+ resultCount: results.length,
238
+ statusBreakdown: this.countStatuses(results),
239
+ success: true
240
+ };
241
+ this.addToHistory(record);
242
+ this.emit({
243
+ type: "reconnect_complete",
244
+ timestamp: endTime,
245
+ data: {
246
+ reconnectId,
247
+ latency,
248
+ resultCount: results.length,
249
+ statusBreakdown: record.statusBreakdown
250
+ }
251
+ });
252
+ }
253
+ failReconnection(reconnectId, error) {
254
+ const pending = this.pending.get(reconnectId);
255
+ if (!pending)
256
+ return;
257
+ this.pending.delete(reconnectId);
258
+ const endTime = Date.now();
259
+ const latency = endTime - pending.startTime;
260
+ this.totalFailures++;
261
+ this.latencies.push(latency);
262
+ const record = {
263
+ reconnectId,
264
+ startTime: pending.startTime,
265
+ endTime,
266
+ latency,
267
+ subscriptionCount: pending.subscriptionCount,
268
+ resultCount: 0,
269
+ statusBreakdown: {},
270
+ success: false,
271
+ error: error.message
272
+ };
273
+ this.addToHistory(record);
274
+ this.emit({
275
+ type: "reconnect_error",
276
+ timestamp: endTime,
277
+ data: {
278
+ reconnectId,
279
+ latency,
280
+ error: error.message
281
+ }
282
+ });
283
+ }
284
+ recordCompression(originalSize, compressedSize) {
285
+ this.totalBytesTransferred += compressedSize;
286
+ this.totalBytesCompressed += originalSize - compressedSize;
287
+ }
288
+ getMetrics() {
289
+ const avgLatency = this.latencies.length > 0 ? this.latencies.reduce((a, b) => a + b, 0) / this.latencies.length : 0;
290
+ const p50 = this.percentile(50);
291
+ const p95 = this.percentile(95);
292
+ const p99 = this.percentile(99);
293
+ return {
294
+ totalAttempts: this.totalAttempts,
295
+ successfulReconnects: this.totalSuccesses,
296
+ failedReconnects: this.totalFailures,
297
+ successRate: this.totalAttempts > 0 ? this.totalSuccesses / this.totalAttempts : 1,
298
+ averageLatency: avgLatency,
299
+ p50Latency: p50,
300
+ p95Latency: p95,
301
+ p99Latency: p99,
302
+ totalSubscriptionsProcessed: this.totalSubscriptionsProcessed,
303
+ statusBreakdown: { ...this.statusCounts },
304
+ bytesTransferred: this.totalBytesTransferred,
305
+ bytesSaved: this.totalBytesCompressed,
306
+ compressionRatio: this.totalBytesTransferred > 0 ? (this.totalBytesTransferred - this.totalBytesCompressed) / this.totalBytesTransferred : 1
307
+ };
308
+ }
309
+ getHealth() {
310
+ const metrics = this.getMetrics();
311
+ const recentHistory = this.history.slice(-10);
312
+ let status = "healthy";
313
+ const issues = [];
314
+ if (metrics.successRate < 0.5) {
315
+ status = "unhealthy";
316
+ issues.push(`Low success rate: ${(metrics.successRate * 100).toFixed(1)}%`);
317
+ } else if (metrics.successRate < 0.9) {
318
+ status = "degraded";
319
+ issues.push(`Degraded success rate: ${(metrics.successRate * 100).toFixed(1)}%`);
320
+ }
321
+ if (metrics.p95Latency > 5000) {
322
+ status = status === "healthy" ? "degraded" : status;
323
+ issues.push(`High p95 latency: ${metrics.p95Latency}ms`);
324
+ }
325
+ const recentFailures = recentHistory.filter((r) => !r.success).length;
326
+ if (recentFailures > 5) {
327
+ status = "unhealthy";
328
+ issues.push(`${recentFailures} failures in last 10 reconnections`);
329
+ }
330
+ return {
331
+ status,
332
+ metrics,
333
+ issues,
334
+ lastReconnect: this.history[this.history.length - 1]?.endTime ?? null,
335
+ pendingReconnects: this.pending.size
336
+ };
337
+ }
338
+ getHistory(limit = 100) {
339
+ return this.history.slice(-limit);
340
+ }
341
+ reset() {
342
+ this.history = [];
343
+ this.pending.clear();
344
+ this.totalAttempts = 0;
345
+ this.totalSuccesses = 0;
346
+ this.totalFailures = 0;
347
+ this.totalSubscriptionsProcessed = 0;
348
+ this.totalBytesTransferred = 0;
349
+ this.totalBytesCompressed = 0;
350
+ this.statusCounts = { current: 0, patched: 0, snapshot: 0, deleted: 0, error: 0 };
351
+ this.latencies = [];
352
+ }
353
+ toJSON() {
354
+ return {
355
+ metrics: this.getMetrics(),
356
+ health: this.getHealth(),
357
+ history: this.history.slice(-100)
358
+ };
359
+ }
360
+ shouldSample() {
361
+ if (!this.config.enabled)
362
+ return false;
363
+ if (this.config.sampleRate >= 1)
364
+ return true;
365
+ return Math.random() < this.config.sampleRate;
366
+ }
367
+ emit(event) {
368
+ this.config.collector?.(event);
369
+ }
370
+ addToHistory(record) {
371
+ this.history.push(record);
372
+ if (this.history.length > this.config.maxHistory) {
373
+ this.history.shift();
374
+ }
375
+ }
376
+ countStatuses(results) {
377
+ const counts = {};
378
+ for (const result of results) {
379
+ counts[result.status] = (counts[result.status] ?? 0) + 1;
380
+ }
381
+ return counts;
382
+ }
383
+ percentile(p) {
384
+ if (this.latencies.length === 0)
385
+ return 0;
386
+ const sorted = [...this.latencies].sort((a, b) => a - b);
387
+ const index = Math.ceil(p / 100 * sorted.length) - 1;
388
+ return sorted[Math.max(0, index)];
389
+ }
390
+ }
391
+
392
+ // ../client/dist/index.js
393
+ class ClientImpl {
394
+ transport;
395
+ plugins;
396
+ metadata = null;
397
+ connectPromise = null;
398
+ subscriptions = new Map;
399
+ queryResultCache = new Map;
400
+ callbackWrappers = new WeakMap;
401
+ constructor(config) {
402
+ this.transport = config.transport;
403
+ this.plugins = config.plugins ?? [];
404
+ this.connectPromise = this.transport.connect();
405
+ this.connectPromise.then((metadata) => {
406
+ this.metadata = metadata;
407
+ }).catch(() => {
408
+ this.connectPromise = null;
409
+ });
410
+ }
411
+ async ensureConnected() {
412
+ if (this.metadata)
413
+ return;
414
+ if (!this.connectPromise) {
415
+ this.connectPromise = this.transport.connect();
416
+ }
417
+ this.metadata = await this.connectPromise;
418
+ }
419
+ async execute(op2) {
420
+ await this.ensureConnected();
421
+ let processedOp = op2;
422
+ for (const plugin of this.plugins) {
423
+ if (plugin.beforeRequest) {
424
+ processedOp = await plugin.beforeRequest(processedOp);
425
+ }
426
+ }
427
+ const resultOrObservable = this.transport.execute(processedOp);
428
+ if (this.isObservable(resultOrObservable)) {
429
+ return { data: resultOrObservable };
430
+ }
431
+ let result = await resultOrObservable;
432
+ for (const plugin of this.plugins) {
433
+ if (plugin.afterResponse) {
434
+ result = await plugin.afterResponse(result, processedOp);
435
+ }
436
+ }
437
+ if (result.error) {
438
+ const error = result.error;
439
+ for (const plugin of this.plugins) {
440
+ if (plugin.onError) {
441
+ try {
442
+ result = await plugin.onError(error, processedOp, () => this.execute(processedOp));
443
+ if (!result.error)
444
+ break;
445
+ } catch (e) {
446
+ result = { error: e };
447
+ }
448
+ }
449
+ }
450
+ }
451
+ return result;
452
+ }
453
+ isObservable(value) {
454
+ return value !== null && typeof value === "object" && "subscribe" in value && typeof value.subscribe === "function";
455
+ }
456
+ getOperationMeta(path) {
457
+ if (!this.metadata)
458
+ return;
459
+ const parts = path.split(".");
460
+ let current = this.metadata.operations;
461
+ for (const part of parts) {
462
+ if (!current || typeof current !== "object")
463
+ return;
464
+ current = current[part];
465
+ }
466
+ if (current && typeof current === "object" && "type" in current) {
467
+ return current;
468
+ }
469
+ return;
470
+ }
471
+ generateId(type, path) {
472
+ return `${type}-${path}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
473
+ }
474
+ makeQueryKey(path, input) {
475
+ return `${path}:${JSON.stringify(input ?? null)}`;
476
+ }
477
+ executeQuery(path, input, select) {
478
+ const key = this.makeQueryKey(path, input);
479
+ const cached = this.queryResultCache.get(key);
480
+ if (cached && !select) {
481
+ return cached;
482
+ }
483
+ if (!this.subscriptions.has(key)) {
484
+ this.subscriptions.set(key, {
485
+ data: null,
486
+ callbacks: new Set
487
+ });
488
+ }
489
+ const sub = this.subscriptions.get(key);
490
+ const result = {
491
+ get value() {
492
+ return sub.data;
493
+ },
494
+ subscribe: (callback) => {
495
+ if (callback) {
496
+ const typedCallback = callback;
497
+ let wrapped = this.callbackWrappers.get(typedCallback);
498
+ if (!wrapped) {
499
+ wrapped = (data) => callback(data);
500
+ this.callbackWrappers.set(typedCallback, wrapped);
501
+ }
502
+ sub.callbacks.add(wrapped);
503
+ if (sub.data !== null) {
504
+ callback(sub.data);
505
+ }
506
+ }
507
+ if (!sub.unsubscribe) {
508
+ this.startSubscription(path, input, key);
509
+ }
510
+ return () => {
511
+ if (callback) {
512
+ const typedCallback = callback;
513
+ const wrapped = this.callbackWrappers.get(typedCallback);
514
+ if (wrapped) {
515
+ sub.callbacks.delete(wrapped);
516
+ }
517
+ }
518
+ if (sub.callbacks.size === 0 && sub.unsubscribe) {
519
+ sub.unsubscribe();
520
+ sub.unsubscribe = undefined;
521
+ }
522
+ };
523
+ },
524
+ select: (selection) => {
525
+ return this.executeQuery(path, input, selection);
526
+ },
527
+ then: async (onfulfilled, onrejected) => {
528
+ try {
529
+ const op2 = {
530
+ id: this.generateId("query", path),
531
+ path,
532
+ type: "query",
533
+ input,
534
+ meta: select ? { select } : {}
535
+ };
536
+ const response = await this.execute(op2);
537
+ if (response.error) {
538
+ throw response.error;
539
+ }
540
+ sub.data = response.data;
541
+ for (const cb of sub.callbacks) {
542
+ cb(response.data);
543
+ }
544
+ return onfulfilled ? onfulfilled(response.data) : response.data;
545
+ } catch (error) {
546
+ if (onrejected) {
547
+ return onrejected(error);
548
+ }
549
+ throw error;
550
+ }
551
+ }
552
+ };
553
+ if (!select) {
554
+ this.queryResultCache.set(key, result);
555
+ }
556
+ return result;
557
+ }
558
+ async startSubscription(path, input, key) {
559
+ const sub = this.subscriptions.get(key);
560
+ if (!sub)
561
+ return;
562
+ await this.ensureConnected();
563
+ const meta = this.getOperationMeta(path);
564
+ const isSubscription = meta?.type === "subscription";
565
+ if (isSubscription) {
566
+ const op2 = {
567
+ id: this.generateId("subscription", path),
568
+ path,
569
+ type: "subscription",
570
+ input
571
+ };
572
+ const resultOrObservable = this.transport.execute(op2);
573
+ if (this.isObservable(resultOrObservable)) {
574
+ const subscription = resultOrObservable.subscribe({
575
+ next: (result) => {
576
+ if (result.data !== undefined) {
577
+ sub.data = result.data;
578
+ for (const cb of sub.callbacks) {
579
+ cb(result.data);
580
+ }
581
+ }
582
+ },
583
+ error: () => {},
584
+ complete: () => {}
585
+ });
586
+ sub.unsubscribe = () => subscription.unsubscribe();
587
+ }
588
+ } else {
589
+ this.executeQuery(path, input).then(() => {});
590
+ }
591
+ }
592
+ async executeMutation(path, input, select) {
593
+ await this.ensureConnected();
594
+ const op2 = {
595
+ id: this.generateId("mutation", path),
596
+ path,
597
+ type: "mutation",
598
+ input,
599
+ meta: select ? { select } : {}
600
+ };
601
+ const response = await this.execute(op2);
602
+ if (response.error) {
603
+ throw response.error;
604
+ }
605
+ return { data: response.data };
606
+ }
607
+ createAccessor(path) {
608
+ return (descriptor) => {
609
+ const isNewApi = descriptor === undefined || descriptor === null || typeof descriptor === "object" && descriptor !== null && (("input" in descriptor) || ("select" in descriptor));
610
+ const input = isNewApi ? descriptor?.input : descriptor;
611
+ const select = isNewApi ? descriptor?.select : undefined;
612
+ const queryResult = this.executeQuery(path, input, select);
613
+ const originalThen = queryResult.then.bind(queryResult);
614
+ queryResult.then = async (onfulfilled, onrejected) => {
615
+ try {
616
+ await this.ensureConnected();
617
+ const meta = this.getOperationMeta(path);
618
+ if (meta?.type === "mutation") {
619
+ const inputObj = input ?? {};
620
+ const mutationResult = await this.executeMutation(path, inputObj, select);
621
+ return onfulfilled ? onfulfilled(mutationResult) : mutationResult;
622
+ }
623
+ return originalThen(onfulfilled, onrejected);
624
+ } catch (error) {
625
+ if (onrejected) {
626
+ return onrejected(error);
627
+ }
628
+ throw error;
629
+ }
630
+ };
631
+ return queryResult;
632
+ };
633
+ }
634
+ }
635
+ function createClient(config) {
636
+ const impl = new ClientImpl(config);
637
+ function createNestedProxy(prefix) {
638
+ const handler = {
639
+ get(_target, prop) {
640
+ if (typeof prop === "symbol")
641
+ return;
642
+ const key = prop;
643
+ if (key === "then")
644
+ return;
645
+ if (key.startsWith("_"))
646
+ return;
647
+ const path = prefix ? `${prefix}.${key}` : key;
648
+ return createNestedProxy(path);
649
+ },
650
+ apply(_target, _thisArg, args) {
651
+ const accessor = impl.createAccessor(prefix);
652
+ return accessor(args[0]);
653
+ }
654
+ };
655
+ return new Proxy(() => {}, handler);
656
+ }
657
+ return createNestedProxy("");
658
+ }
659
+ var http = function http2(options) {
660
+ const { url, headers: defaultHeaders = {}, fetch: fetchImpl = fetch, polling = {} } = options;
661
+ const { interval: pollInterval = 1000, maxRetries = 3 } = polling;
662
+ const baseUrl = url.replace(/\/$/, "");
663
+ return {
664
+ async connect() {
665
+ const response = await fetchImpl(`${baseUrl}/__lens/metadata`, {
666
+ method: "GET",
667
+ headers: {
668
+ Accept: "application/json",
669
+ ...defaultHeaders
670
+ }
671
+ });
672
+ if (!response.ok) {
673
+ throw new Error(`Failed to connect: ${response.status} ${response.statusText}`);
674
+ }
675
+ return response.json();
676
+ },
677
+ execute(op2) {
678
+ if (op2.type === "subscription") {
679
+ return createPollingObservable(baseUrl, op2, {
680
+ interval: pollInterval,
681
+ maxRetries,
682
+ headers: defaultHeaders,
683
+ fetch: fetchImpl
684
+ });
685
+ }
686
+ return executeRequest(baseUrl, op2, {
687
+ headers: {
688
+ ...defaultHeaders,
689
+ ...op2.meta?.headers ?? {}
690
+ },
691
+ fetch: fetchImpl,
692
+ timeout: op2.meta?.timeout
693
+ });
694
+ }
695
+ };
696
+ };
697
+ async function executeRequest(baseUrl, op2, options) {
698
+ const { headers, fetch: fetchImpl, timeout: timeout2 } = options;
699
+ try {
700
+ const controller = new AbortController;
701
+ let timeoutId;
702
+ if (timeout2) {
703
+ timeoutId = setTimeout(() => controller.abort(), timeout2);
704
+ }
705
+ const response = await fetchImpl(baseUrl, {
706
+ method: "POST",
707
+ headers: {
708
+ "Content-Type": "application/json",
709
+ Accept: "application/json",
710
+ ...headers
711
+ },
712
+ body: JSON.stringify({
713
+ id: op2.id,
714
+ path: op2.path,
715
+ type: op2.type,
716
+ input: op2.input
717
+ }),
718
+ signal: controller.signal
719
+ });
720
+ if (timeoutId) {
721
+ clearTimeout(timeoutId);
722
+ }
723
+ if (!response.ok) {
724
+ return {
725
+ error: new Error(`HTTP ${response.status}: ${response.statusText}`)
726
+ };
727
+ }
728
+ const result = await response.json();
729
+ return result;
730
+ } catch (error) {
731
+ if (error instanceof Error && error.name === "AbortError") {
732
+ return { error: new Error("Request timeout") };
733
+ }
734
+ return { error };
735
+ }
736
+ }
737
+ function createPollingObservable(baseUrl, op2, options) {
738
+ return {
739
+ subscribe(observer) {
740
+ let active = true;
741
+ let retries = 0;
742
+ let lastValue;
743
+ const poll = async () => {
744
+ if (!active)
745
+ return;
746
+ try {
747
+ const result = await executeRequest(baseUrl, op2, {
748
+ headers: options.headers,
749
+ fetch: options.fetch
750
+ });
751
+ if (!active)
752
+ return;
753
+ if (result.error) {
754
+ retries++;
755
+ if (retries > options.maxRetries) {
756
+ observer.error?.(result.error);
757
+ return;
758
+ }
759
+ } else {
760
+ retries = 0;
761
+ const newValue = JSON.stringify(result.data);
762
+ if (newValue !== JSON.stringify(lastValue)) {
763
+ lastValue = result.data;
764
+ observer.next?.(result);
765
+ }
766
+ }
767
+ if (active) {
768
+ setTimeout(poll, options.interval);
769
+ }
770
+ } catch (error) {
771
+ if (active) {
772
+ observer.error?.(error);
773
+ }
774
+ }
775
+ };
776
+ poll();
777
+ return {
778
+ unsubscribe() {
779
+ active = false;
780
+ }
781
+ };
782
+ }
783
+ };
784
+ }
785
+ http.server = function httpServer(options) {
786
+ const { port, path = "", hostname = "0.0.0.0" } = options;
787
+ return {
788
+ listen(server) {
789
+ Bun.serve({
790
+ port,
791
+ hostname,
792
+ async fetch(req) {
793
+ const url = new URL(req.url);
794
+ const basePath = path.replace(/\/$/, "");
795
+ if (url.pathname === `${basePath}/__lens/metadata` && req.method === "GET") {
796
+ return Response.json(server.getMetadata());
797
+ }
798
+ if (url.pathname === basePath && req.method === "POST") {
799
+ try {
800
+ const body = await req.json();
801
+ const result = await server.execute(body);
802
+ return Response.json(result);
803
+ } catch (error) {
804
+ return Response.json({ error: { message: error.message } }, { status: 500 });
805
+ }
806
+ }
807
+ return new Response("Not Found", { status: 404 });
808
+ }
809
+ });
810
+ }
811
+ };
812
+ };
813
+ class SubscriptionRegistry {
814
+ subscriptions = new Map;
815
+ entityIndex = new Map;
816
+ add(sub) {
817
+ const tracked = {
818
+ ...sub,
819
+ state: "pending",
820
+ lastDataHash: sub.lastData ? hashEntityState(sub.lastData) : null,
821
+ createdAt: Date.now(),
822
+ lastUpdateAt: null
823
+ };
824
+ this.subscriptions.set(sub.id, tracked);
825
+ const entityKey = `${sub.entity}:${sub.entityId}`;
826
+ let ids = this.entityIndex.get(entityKey);
827
+ if (!ids) {
828
+ ids = new Set;
829
+ this.entityIndex.set(entityKey, ids);
830
+ }
831
+ ids.add(sub.id);
832
+ }
833
+ get(id) {
834
+ return this.subscriptions.get(id);
835
+ }
836
+ has(id) {
837
+ return this.subscriptions.has(id);
838
+ }
839
+ remove(id) {
840
+ const sub = this.subscriptions.get(id);
841
+ if (!sub)
842
+ return;
843
+ this.subscriptions.delete(id);
844
+ const entityKey = `${sub.entity}:${sub.entityId}`;
845
+ const ids = this.entityIndex.get(entityKey);
846
+ if (ids) {
847
+ ids.delete(id);
848
+ if (ids.size === 0) {
849
+ this.entityIndex.delete(entityKey);
850
+ }
851
+ }
852
+ }
853
+ getByEntity(entity2, entityId) {
854
+ const entityKey = `${entity2}:${entityId}`;
855
+ const ids = this.entityIndex.get(entityKey);
856
+ if (!ids)
857
+ return [];
858
+ const result = [];
859
+ for (const id of ids) {
860
+ const sub = this.subscriptions.get(id);
861
+ if (sub) {
862
+ result.push(sub);
863
+ }
864
+ }
865
+ return result;
866
+ }
867
+ updateVersion(id, version, data) {
868
+ const sub = this.subscriptions.get(id);
869
+ if (!sub)
870
+ return;
871
+ sub.version = version;
872
+ sub.lastUpdateAt = Date.now();
873
+ if (data !== undefined) {
874
+ sub.lastData = data;
875
+ sub.lastDataHash = hashEntityState(data);
876
+ }
877
+ if (sub.state === "pending" || sub.state === "reconnecting") {
878
+ sub.state = "active";
879
+ }
880
+ }
881
+ updateData(id, data) {
882
+ const sub = this.subscriptions.get(id);
883
+ if (!sub)
884
+ return;
885
+ sub.lastData = data;
886
+ sub.lastDataHash = hashEntityState(data);
887
+ }
888
+ getLastData(id) {
889
+ return this.subscriptions.get(id)?.lastData ?? null;
890
+ }
891
+ getVersion(id) {
892
+ return this.subscriptions.get(id)?.version ?? null;
893
+ }
894
+ markActive(id) {
895
+ const sub = this.subscriptions.get(id);
896
+ if (sub) {
897
+ sub.state = "active";
898
+ }
899
+ }
900
+ markError(id) {
901
+ const sub = this.subscriptions.get(id);
902
+ if (sub) {
903
+ sub.state = "error";
904
+ }
905
+ }
906
+ markAllReconnecting() {
907
+ for (const sub of this.subscriptions.values()) {
908
+ if (sub.state === "active") {
909
+ sub.state = "reconnecting";
910
+ }
911
+ }
912
+ }
913
+ getByState(state) {
914
+ const result = [];
915
+ for (const sub of this.subscriptions.values()) {
916
+ if (sub.state === state) {
917
+ result.push(sub);
918
+ }
919
+ }
920
+ return result;
921
+ }
922
+ getAllForReconnect() {
923
+ const result = [];
924
+ for (const sub of this.subscriptions.values()) {
925
+ if (sub.state === "reconnecting" || sub.state === "active") {
926
+ const reconnectSub = {
927
+ id: sub.id,
928
+ entity: sub.entity,
929
+ entityId: sub.entityId,
930
+ fields: sub.fields,
931
+ version: sub.version,
932
+ input: sub.input
933
+ };
934
+ if (sub.lastDataHash) {
935
+ reconnectSub.dataHash = sub.lastDataHash;
936
+ }
937
+ result.push(reconnectSub);
938
+ }
939
+ }
940
+ return result;
941
+ }
942
+ processReconnectResult(id, version, data) {
943
+ const sub = this.subscriptions.get(id);
944
+ if (!sub)
945
+ return;
946
+ sub.version = version;
947
+ sub.state = "active";
948
+ sub.lastUpdateAt = Date.now();
949
+ if (data !== undefined) {
950
+ sub.lastData = data;
951
+ sub.lastDataHash = hashEntityState(data);
952
+ }
953
+ }
954
+ getObserver(id) {
955
+ return this.subscriptions.get(id)?.observer;
956
+ }
957
+ updateObserver(id, observer) {
958
+ const sub = this.subscriptions.get(id);
959
+ if (sub) {
960
+ sub.observer = observer;
961
+ }
962
+ }
963
+ notifyNext(id, data) {
964
+ const sub = this.subscriptions.get(id);
965
+ sub?.observer.next?.({ data, version: sub.version });
966
+ }
967
+ notifyError(id, error) {
968
+ this.subscriptions.get(id)?.observer.error?.(error);
969
+ }
970
+ notifyAllReconnectingError(error) {
971
+ for (const sub of this.subscriptions.values()) {
972
+ if (sub.state === "reconnecting") {
973
+ sub.observer.error?.(error);
974
+ }
975
+ }
976
+ }
977
+ get size() {
978
+ return this.subscriptions.size;
979
+ }
980
+ getIds() {
981
+ return Array.from(this.subscriptions.keys());
982
+ }
983
+ values() {
984
+ return this.subscriptions.values();
985
+ }
986
+ getStats() {
987
+ const byState = {
988
+ pending: 0,
989
+ active: 0,
990
+ reconnecting: 0,
991
+ error: 0
992
+ };
993
+ const byEntity = {};
994
+ for (const sub of this.subscriptions.values()) {
995
+ byState[sub.state]++;
996
+ const entityKey = `${sub.entity}:${sub.entityId}`;
997
+ byEntity[entityKey] = (byEntity[entityKey] ?? 0) + 1;
998
+ }
999
+ return {
1000
+ total: this.subscriptions.size,
1001
+ byState,
1002
+ byEntity
1003
+ };
1004
+ }
1005
+ clear() {
1006
+ for (const sub of this.subscriptions.values()) {
1007
+ sub.observer.complete?.();
1008
+ }
1009
+ this.subscriptions.clear();
1010
+ this.entityIndex.clear();
1011
+ }
1012
+ clearErrors() {
1013
+ const toRemove = [];
1014
+ for (const [id, sub] of this.subscriptions) {
1015
+ if (sub.state === "error") {
1016
+ toRemove.push(id);
1017
+ }
1018
+ }
1019
+ for (const id of toRemove) {
1020
+ this.remove(id);
1021
+ }
1022
+ }
1023
+ }
1024
+
1025
+ // src/create.ts
1026
+ import { createEffect, createSignal, onCleanup } from "solid-js";
1027
+ function createQueryHook(baseClient, path) {
1028
+ const getEndpoint = (p) => {
1029
+ const parts = p.split(".");
1030
+ let current = baseClient;
1031
+ for (const part of parts) {
1032
+ current = current[part];
1033
+ }
1034
+ return current;
1035
+ };
1036
+ const useQueryPrimitive = (options) => {
1037
+ const [data, setData] = createSignal(null);
1038
+ const [loading, setLoading] = createSignal(!options?.skip);
1039
+ const [error, setError] = createSignal(null);
1040
+ let unsubscribe = null;
1041
+ const executeQuery = () => {
1042
+ if (unsubscribe) {
1043
+ unsubscribe();
1044
+ unsubscribe = null;
1045
+ }
1046
+ if (options?.skip) {
1047
+ setData(null);
1048
+ setLoading(false);
1049
+ setError(null);
1050
+ return;
1051
+ }
1052
+ const endpoint = getEndpoint(path);
1053
+ const query = endpoint({ input: options?.input, select: options?.select });
1054
+ setLoading(true);
1055
+ setError(null);
1056
+ unsubscribe = query.subscribe((value) => {
1057
+ setData(() => value);
1058
+ setLoading(false);
1059
+ setError(null);
1060
+ });
1061
+ query.then((value) => {
1062
+ setData(() => value);
1063
+ setLoading(false);
1064
+ setError(null);
1065
+ }, (err) => {
1066
+ const queryError = err instanceof Error ? err : new Error(String(err));
1067
+ setError(queryError);
1068
+ setLoading(false);
1069
+ });
1070
+ };
1071
+ executeQuery();
1072
+ createEffect(() => {
1073
+ const _ = JSON.stringify(options);
1074
+ executeQuery();
1075
+ });
1076
+ onCleanup(() => {
1077
+ if (unsubscribe) {
1078
+ unsubscribe();
1079
+ unsubscribe = null;
1080
+ }
1081
+ });
1082
+ const refetch = () => {
1083
+ if (unsubscribe) {
1084
+ unsubscribe();
1085
+ unsubscribe = null;
1086
+ }
1087
+ setLoading(true);
1088
+ setError(null);
1089
+ const endpoint = getEndpoint(path);
1090
+ const query = endpoint({ input: options?.input, select: options?.select });
1091
+ if (query) {
1092
+ unsubscribe = query.subscribe((value) => {
1093
+ setData(() => value);
1094
+ setLoading(false);
1095
+ setError(null);
1096
+ });
1097
+ query.then((value) => {
1098
+ setData(() => value);
1099
+ setLoading(false);
1100
+ }, (err) => {
1101
+ const queryError = err instanceof Error ? err : new Error(String(err));
1102
+ setError(queryError);
1103
+ setLoading(false);
1104
+ });
1105
+ }
1106
+ };
1107
+ return { data, loading, error, refetch };
1108
+ };
1109
+ const fetch2 = async (options) => {
1110
+ const endpoint = getEndpoint(path);
1111
+ const queryResult = endpoint({ input: options?.input, select: options?.select });
1112
+ return queryResult.then((data) => data);
1113
+ };
1114
+ useQueryPrimitive.fetch = fetch2;
1115
+ return useQueryPrimitive;
1116
+ }
1117
+ function createMutationHook(baseClient, path) {
1118
+ const getEndpoint = (p) => {
1119
+ const parts = p.split(".");
1120
+ let current = baseClient;
1121
+ for (const part of parts) {
1122
+ current = current[part];
1123
+ }
1124
+ return current;
1125
+ };
1126
+ const useMutationPrimitive = (hookOptions) => {
1127
+ const [data, setData] = createSignal(null);
1128
+ const [loading, setLoading] = createSignal(false);
1129
+ const [error, setError] = createSignal(null);
1130
+ const mutate = async (options) => {
1131
+ setLoading(true);
1132
+ setError(null);
1133
+ try {
1134
+ const endpoint = getEndpoint(path);
1135
+ const result = await endpoint({ input: options.input, select: options.select });
1136
+ const mutationResult = result;
1137
+ setData(() => mutationResult.data);
1138
+ setLoading(false);
1139
+ hookOptions?.onSuccess?.(mutationResult.data);
1140
+ hookOptions?.onSettled?.();
1141
+ return mutationResult.data;
1142
+ } catch (err) {
1143
+ const mutationError = err instanceof Error ? err : new Error(String(err));
1144
+ setError(mutationError);
1145
+ setLoading(false);
1146
+ hookOptions?.onError?.(mutationError);
1147
+ hookOptions?.onSettled?.();
1148
+ throw mutationError;
1149
+ }
1150
+ };
1151
+ const reset = () => {
1152
+ setData(null);
1153
+ setLoading(false);
1154
+ setError(null);
1155
+ };
1156
+ return { mutate, loading, error, data, reset };
1157
+ };
1158
+ const fetch2 = async (options) => {
1159
+ const endpoint = getEndpoint(path);
1160
+ const result = await endpoint({ input: options.input, select: options.select });
1161
+ const mutationResult = result;
1162
+ return mutationResult.data;
1163
+ };
1164
+ useMutationPrimitive.fetch = fetch2;
1165
+ return useMutationPrimitive;
1166
+ }
1167
+ var hookCache = new Map;
1168
+ function createClient2(config) {
1169
+ const baseClient = createClient(config);
1170
+ function createProxy(path) {
1171
+ const handler = {
1172
+ get(_target, prop) {
1173
+ if (typeof prop === "symbol")
1174
+ return;
1175
+ const key = prop;
1176
+ if (key === "fetch") {
1177
+ return async (options) => {
1178
+ const parts = path.split(".");
1179
+ let current = baseClient;
1180
+ for (const part of parts) {
1181
+ current = current[part];
1182
+ }
1183
+ const endpointFn = current;
1184
+ const queryResult = endpointFn(options);
1185
+ const result = await queryResult;
1186
+ if (result && typeof result === "object" && "data" in result && Object.keys(result).length === 1) {
1187
+ return result.data;
1188
+ }
1189
+ return result;
1190
+ };
1191
+ }
1192
+ if (key === "then")
1193
+ return;
1194
+ if (key.startsWith("_"))
1195
+ return;
1196
+ const newPath = path ? `${path}.${key}` : key;
1197
+ return createProxy(newPath);
1198
+ },
1199
+ apply(_target, _thisArg, args) {
1200
+ const options = args[0];
1201
+ const isQueryOptions = options && (("input" in options) || ("select" in options) || ("skip" in options));
1202
+ const isMutationOptions = !options || !isQueryOptions && (Object.keys(options).length === 0 || ("onSuccess" in options) || ("onError" in options) || ("onSettled" in options));
1203
+ const cacheKeyQuery = `${path}:query`;
1204
+ const cacheKeyMutation = `${path}:mutation`;
1205
+ if (isQueryOptions) {
1206
+ if (!hookCache.has(cacheKeyQuery)) {
1207
+ hookCache.set(cacheKeyQuery, createQueryHook(baseClient, path));
1208
+ }
1209
+ const hook2 = hookCache.get(cacheKeyQuery);
1210
+ return hook2(options);
1211
+ }
1212
+ if (isMutationOptions) {
1213
+ if (!hookCache.has(cacheKeyMutation)) {
1214
+ hookCache.set(cacheKeyMutation, createMutationHook(baseClient, path));
1215
+ }
1216
+ const hook2 = hookCache.get(cacheKeyMutation);
1217
+ return hook2(options);
1218
+ }
1219
+ if (!hookCache.has(cacheKeyQuery)) {
1220
+ hookCache.set(cacheKeyQuery, createQueryHook(baseClient, path));
1221
+ }
1222
+ const hook = hookCache.get(cacheKeyQuery);
1223
+ return hook(options);
1224
+ }
1225
+ };
1226
+ return new Proxy(() => {}, handler);
1227
+ }
1228
+ return createProxy("");
1229
+ }
1
1230
  // src/context.tsx
2
1231
  import { createContext, useContext } from "solid-js";
3
1232
  var LensClientContext = createContext();
@@ -14,17 +1243,20 @@ function useLensClient() {
14
1243
  return client;
15
1244
  }
16
1245
  // src/primitives.ts
17
- import { createSignal, onCleanup } from "solid-js";
1246
+ import { createEffect as createEffect2, createSignal as createSignal2, onCleanup as onCleanup2 } from "solid-js";
18
1247
  function resolveQuery(input) {
19
1248
  return typeof input === "function" ? input() : input;
20
1249
  }
21
1250
  function createQuery(queryInput, options) {
22
- const [data, setData] = createSignal(null);
23
- const [loading, setLoading] = createSignal(!options?.skip);
24
- const [error, setError] = createSignal(null);
1251
+ const [data, setData] = createSignal2(null);
1252
+ const [loading, setLoading] = createSignal2(!options?.skip);
1253
+ const [error, setError] = createSignal2(null);
25
1254
  let unsubscribe = null;
26
- const executeQuery = () => {
27
- const queryResult = resolveQuery(queryInput);
1255
+ const executeQuery = (queryResult) => {
1256
+ if (unsubscribe) {
1257
+ unsubscribe();
1258
+ unsubscribe = null;
1259
+ }
28
1260
  if (options?.skip || queryResult == null) {
29
1261
  setData(null);
30
1262
  setLoading(false);
@@ -48,8 +1280,15 @@ function createQuery(queryInput, options) {
48
1280
  setLoading(false);
49
1281
  });
50
1282
  };
51
- executeQuery();
52
- onCleanup(() => {
1283
+ const initialQuery = resolveQuery(queryInput);
1284
+ executeQuery(initialQuery);
1285
+ createEffect2(() => {
1286
+ const queryResult = resolveQuery(queryInput);
1287
+ if (queryResult !== initialQuery) {
1288
+ executeQuery(queryResult);
1289
+ }
1290
+ });
1291
+ onCleanup2(() => {
53
1292
  if (unsubscribe) {
54
1293
  unsubscribe();
55
1294
  unsubscribe = null;
@@ -62,7 +1301,22 @@ function createQuery(queryInput, options) {
62
1301
  }
63
1302
  setLoading(true);
64
1303
  setError(null);
65
- executeQuery();
1304
+ const queryResult = resolveQuery(queryInput);
1305
+ if (queryResult) {
1306
+ unsubscribe = queryResult.subscribe((value) => {
1307
+ setData(() => value);
1308
+ setLoading(false);
1309
+ setError(null);
1310
+ });
1311
+ queryResult.then((value) => {
1312
+ setData(() => value);
1313
+ setLoading(false);
1314
+ }, (err) => {
1315
+ const queryError = err instanceof Error ? err : new Error(String(err));
1316
+ setError(queryError);
1317
+ setLoading(false);
1318
+ });
1319
+ }
66
1320
  };
67
1321
  return {
68
1322
  data,
@@ -72,9 +1326,9 @@ function createQuery(queryInput, options) {
72
1326
  };
73
1327
  }
74
1328
  function createMutation(mutationFn) {
75
- const [data, setData] = createSignal(null);
76
- const [loading, setLoading] = createSignal(false);
77
- const [error, setError] = createSignal(null);
1329
+ const [data, setData] = createSignal2(null);
1330
+ const [loading, setLoading] = createSignal2(false);
1331
+ const [error, setError] = createSignal2(null);
78
1332
  const mutate = async (input) => {
79
1333
  setLoading(true);
80
1334
  setError(null);
@@ -104,10 +1358,10 @@ function createMutation(mutationFn) {
104
1358
  };
105
1359
  }
106
1360
  function createLazyQuery(queryInput) {
107
- const [data, setData] = createSignal(null);
108
- const [loading, setLoading] = createSignal(false);
109
- const [error, setError] = createSignal(null);
110
- const execute = async () => {
1361
+ const [data, setData] = createSignal2(null);
1362
+ const [loading, setLoading] = createSignal2(false);
1363
+ const [error, setError] = createSignal2(null);
1364
+ const execute2 = async () => {
111
1365
  const queryResult = resolveQuery(queryInput);
112
1366
  if (queryResult == null) {
113
1367
  setData(null);
@@ -137,7 +1391,7 @@ function createLazyQuery(queryInput) {
137
1391
  data,
138
1392
  loading,
139
1393
  error,
140
- execute,
1394
+ execute: execute2,
141
1395
  reset
142
1396
  };
143
1397
  }
@@ -146,5 +1400,6 @@ export {
146
1400
  createQuery,
147
1401
  createMutation,
148
1402
  createLazyQuery,
1403
+ createClient2 as createClient,
149
1404
  LensProvider
150
1405
  };