@sylphx/lens-solid 2.0.5 → 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/README.md +59 -10
- package/dist/create.d.ts +149 -0
- package/dist/create.d.ts.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1272 -17
- package/dist/primitives.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/create.ts +472 -0
- package/src/index.ts +34 -1
- package/src/primitives.ts +39 -6
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] =
|
|
23
|
-
const [loading, setLoading] =
|
|
24
|
-
const [error, setError] =
|
|
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
|
-
|
|
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
|
-
|
|
52
|
-
|
|
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
|
-
|
|
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] =
|
|
76
|
-
const [loading, setLoading] =
|
|
77
|
-
const [error, setError] =
|
|
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] =
|
|
108
|
-
const [loading, setLoading] =
|
|
109
|
-
const [error, setError] =
|
|
110
|
-
const
|
|
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
|
};
|