dynamodb-reactive 0.1.3 → 0.1.4
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 +114 -24
- package/dist/{chunk-IPEBRXIL.js → chunk-MI2ZLLB2.js} +361 -50
- package/dist/chunk-MI2ZLLB2.js.map +1 -0
- package/dist/client.d.ts +12 -2
- package/dist/client.js +1 -1
- package/dist/infra.js +1 -1
- package/dist/infra.js.map +1 -1
- package/dist/{react-BMZQ8Mth.d.ts → react-B8Q_XoCk.d.ts} +183 -16
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/server.d.ts +41 -13
- package/dist/server.js +95 -10
- package/dist/server.js.map +1 -1
- package/package.json +4 -2
- package/dist/chunk-IPEBRXIL.js.map +0 -1
|
@@ -47,6 +47,8 @@ var WebSocketManager = class {
|
|
|
47
47
|
messageQueue = [];
|
|
48
48
|
messageHandlers = /* @__PURE__ */ new Set();
|
|
49
49
|
stateHandlers = /* @__PURE__ */ new Set();
|
|
50
|
+
_connectionId = null;
|
|
51
|
+
connectionIdResolvers = [];
|
|
50
52
|
constructor(config) {
|
|
51
53
|
this.config = {
|
|
52
54
|
autoReconnect: true,
|
|
@@ -55,6 +57,23 @@ var WebSocketManager = class {
|
|
|
55
57
|
...config
|
|
56
58
|
};
|
|
57
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Get the current connection ID (null if not connected)
|
|
62
|
+
*/
|
|
63
|
+
get connectionId() {
|
|
64
|
+
return this._connectionId;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Wait for the connection ID to be available
|
|
68
|
+
*/
|
|
69
|
+
waitForConnectionId() {
|
|
70
|
+
if (this._connectionId) {
|
|
71
|
+
return Promise.resolve(this._connectionId);
|
|
72
|
+
}
|
|
73
|
+
return new Promise((resolve) => {
|
|
74
|
+
this.connectionIdResolvers.push(resolve);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
58
77
|
/**
|
|
59
78
|
* Get current connection state
|
|
60
79
|
*/
|
|
@@ -73,13 +92,12 @@ var WebSocketManager = class {
|
|
|
73
92
|
const url = await this.buildUrl();
|
|
74
93
|
this.ws = new WebSocket(url);
|
|
75
94
|
this.ws.onopen = () => {
|
|
76
|
-
this.setState("connected");
|
|
77
95
|
this.reconnectAttempts = 0;
|
|
78
|
-
this.
|
|
79
|
-
this.config.onConnect?.();
|
|
96
|
+
this.ws?.send(JSON.stringify({ type: "init" }));
|
|
80
97
|
};
|
|
81
98
|
this.ws.onclose = (event) => {
|
|
82
99
|
this.ws = null;
|
|
100
|
+
this._connectionId = null;
|
|
83
101
|
this.setState("disconnected");
|
|
84
102
|
this.config.onDisconnect?.();
|
|
85
103
|
if (this.config.autoReconnect && !event.wasClean) {
|
|
@@ -93,6 +111,17 @@ var WebSocketManager = class {
|
|
|
93
111
|
this.ws.onmessage = (event) => {
|
|
94
112
|
try {
|
|
95
113
|
const message = JSON.parse(event.data);
|
|
114
|
+
if (message.type === "connected") {
|
|
115
|
+
this._connectionId = message.connectionId;
|
|
116
|
+
this.setState("connected");
|
|
117
|
+
this.flushMessageQueue();
|
|
118
|
+
this.config.onConnect?.();
|
|
119
|
+
for (const resolve of this.connectionIdResolvers) {
|
|
120
|
+
resolve(message.connectionId);
|
|
121
|
+
}
|
|
122
|
+
this.connectionIdResolvers = [];
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
96
125
|
this.handleMessage(message);
|
|
97
126
|
} catch (error) {
|
|
98
127
|
console.error("Failed to parse WebSocket message:", error);
|
|
@@ -120,6 +149,7 @@ var WebSocketManager = class {
|
|
|
120
149
|
this.ws.close(1e3, "Client disconnect");
|
|
121
150
|
this.ws = null;
|
|
122
151
|
}
|
|
152
|
+
this._connectionId = null;
|
|
123
153
|
this.setState("disconnected");
|
|
124
154
|
}
|
|
125
155
|
/**
|
|
@@ -193,17 +223,34 @@ var WebSocketManager = class {
|
|
|
193
223
|
};
|
|
194
224
|
|
|
195
225
|
// ../client/src/client.ts
|
|
226
|
+
var DEFAULT_HTTP_URL = "/api/reactive";
|
|
196
227
|
function generateId() {
|
|
197
228
|
return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
198
229
|
}
|
|
199
230
|
var ReactiveClient = class {
|
|
200
231
|
wsManager;
|
|
232
|
+
httpUrl;
|
|
233
|
+
authGetter;
|
|
234
|
+
headersGetter;
|
|
201
235
|
subscriptions = /* @__PURE__ */ new Map();
|
|
202
236
|
pendingCalls = /* @__PURE__ */ new Map();
|
|
203
237
|
connectionState = "disconnected";
|
|
204
238
|
stateListeners = /* @__PURE__ */ new Set();
|
|
239
|
+
// Stores original data for rollback when optimistic updates fail
|
|
240
|
+
optimisticUpdates = /* @__PURE__ */ new Map();
|
|
205
241
|
constructor(config) {
|
|
206
242
|
this.wsManager = new WebSocketManager(config);
|
|
243
|
+
this.httpUrl = config.httpUrl ?? DEFAULT_HTTP_URL;
|
|
244
|
+
if (typeof config.auth === "function") {
|
|
245
|
+
this.authGetter = config.auth;
|
|
246
|
+
} else if (config.auth) {
|
|
247
|
+
this.authGetter = () => config.auth;
|
|
248
|
+
}
|
|
249
|
+
if (typeof config.headers === "function") {
|
|
250
|
+
this.headersGetter = config.headers;
|
|
251
|
+
} else if (config.headers) {
|
|
252
|
+
this.headersGetter = () => config.headers;
|
|
253
|
+
}
|
|
207
254
|
this.wsManager.onMessage((message) => this.handleMessage(message));
|
|
208
255
|
this.wsManager.onStateChange((state) => {
|
|
209
256
|
this.connectionState = state;
|
|
@@ -225,6 +272,17 @@ var ReactiveClient = class {
|
|
|
225
272
|
disconnect() {
|
|
226
273
|
this.wsManager.disconnect();
|
|
227
274
|
}
|
|
275
|
+
/**
|
|
276
|
+
* Update the headers getter for HTTP requests.
|
|
277
|
+
* Useful for dynamically setting auth or user context headers.
|
|
278
|
+
*/
|
|
279
|
+
setHeaders(headers) {
|
|
280
|
+
if (typeof headers === "function") {
|
|
281
|
+
this.headersGetter = headers;
|
|
282
|
+
} else {
|
|
283
|
+
this.headersGetter = () => headers;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
228
286
|
/**
|
|
229
287
|
* Get current connection state
|
|
230
288
|
*/
|
|
@@ -239,7 +297,8 @@ var ReactiveClient = class {
|
|
|
239
297
|
return () => this.stateListeners.delete(listener);
|
|
240
298
|
}
|
|
241
299
|
/**
|
|
242
|
-
* Subscribe to a procedure
|
|
300
|
+
* Subscribe to a procedure via HTTP.
|
|
301
|
+
* Snapshot is returned via HTTP, patches come via WebSocket.
|
|
243
302
|
*/
|
|
244
303
|
subscribe(path, options = {}) {
|
|
245
304
|
const id = generateId();
|
|
@@ -254,13 +313,14 @@ var ReactiveClient = class {
|
|
|
254
313
|
options
|
|
255
314
|
};
|
|
256
315
|
this.subscriptions.set(id, state);
|
|
257
|
-
this.
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
316
|
+
this.sendSubscribeRequest(id, path, options.input).catch((error) => {
|
|
317
|
+
state.error = error instanceof Error ? error : new Error(String(error));
|
|
318
|
+
state.loading = false;
|
|
319
|
+
this.notifySubscriptionListeners(id);
|
|
320
|
+
options.onError?.(state.error);
|
|
262
321
|
});
|
|
263
322
|
const subscription = {
|
|
323
|
+
id,
|
|
264
324
|
get data() {
|
|
265
325
|
return state.data;
|
|
266
326
|
},
|
|
@@ -276,28 +336,165 @@ var ReactiveClient = class {
|
|
|
276
336
|
return subscription;
|
|
277
337
|
}
|
|
278
338
|
/**
|
|
279
|
-
*
|
|
339
|
+
* Send subscribe request via HTTP
|
|
280
340
|
*/
|
|
281
|
-
async
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
341
|
+
async sendSubscribeRequest(subscriptionId, path, input) {
|
|
342
|
+
const connectionId = await this.wsManager.waitForConnectionId();
|
|
343
|
+
const headers = {
|
|
344
|
+
"Content-Type": "application/json"
|
|
345
|
+
};
|
|
346
|
+
if (this.headersGetter) {
|
|
347
|
+
const customHeaders = await this.headersGetter();
|
|
348
|
+
Object.assign(headers, customHeaders);
|
|
349
|
+
}
|
|
350
|
+
if (this.authGetter) {
|
|
351
|
+
const token = await this.authGetter();
|
|
352
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
353
|
+
}
|
|
354
|
+
const response = await fetch(this.httpUrl, {
|
|
355
|
+
method: "POST",
|
|
356
|
+
headers,
|
|
357
|
+
body: JSON.stringify({
|
|
358
|
+
type: "subscribe",
|
|
359
|
+
connectionId,
|
|
360
|
+
subscriptionId,
|
|
291
361
|
path,
|
|
292
362
|
input
|
|
363
|
+
})
|
|
364
|
+
});
|
|
365
|
+
if (!response.ok) {
|
|
366
|
+
throw new Error(
|
|
367
|
+
`Subscribe failed: ${response.status} ${response.statusText}`
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
const result = await response.json();
|
|
371
|
+
if (result.type === "error") {
|
|
372
|
+
throw new Error(result.message ?? "Unknown error");
|
|
373
|
+
}
|
|
374
|
+
if (result.type === "snapshot") {
|
|
375
|
+
this.handleSnapshot(subscriptionId, result.data);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Call a mutation procedure via HTTP with optimistic update support.
|
|
380
|
+
* All optimistic updates are applied INSTANTLY (before HTTP request).
|
|
381
|
+
*/
|
|
382
|
+
async call(path, input, options) {
|
|
383
|
+
const invalidatesPath = options?.invalidates;
|
|
384
|
+
const updateType = options?.optimisticUpdate;
|
|
385
|
+
let targetSubscriptionId;
|
|
386
|
+
if (invalidatesPath) {
|
|
387
|
+
targetSubscriptionId = this.findSubscriptionByPath(invalidatesPath);
|
|
388
|
+
}
|
|
389
|
+
if (targetSubscriptionId) {
|
|
390
|
+
const state = this.subscriptions.get(targetSubscriptionId);
|
|
391
|
+
const currentData = state?.data;
|
|
392
|
+
if (updateType === "remove") {
|
|
393
|
+
const inputId = input.id;
|
|
394
|
+
if (inputId) {
|
|
395
|
+
this.applyOptimisticUpdate(targetSubscriptionId, (data) => {
|
|
396
|
+
if (!Array.isArray(data)) return data;
|
|
397
|
+
return data.filter((item) => item.id !== inputId);
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
} else if (updateType === "merge") {
|
|
401
|
+
const inputWithId = input;
|
|
402
|
+
if (inputWithId.id) {
|
|
403
|
+
this.applyOptimisticUpdate(targetSubscriptionId, (data) => {
|
|
404
|
+
if (!Array.isArray(data)) return data;
|
|
405
|
+
const idx = data.findIndex(
|
|
406
|
+
(item) => item.id === inputWithId.id
|
|
407
|
+
);
|
|
408
|
+
if (idx >= 0) {
|
|
409
|
+
return [
|
|
410
|
+
...data.slice(0, idx),
|
|
411
|
+
{ ...data[idx], ...input },
|
|
412
|
+
...data.slice(idx + 1)
|
|
413
|
+
];
|
|
414
|
+
}
|
|
415
|
+
return data;
|
|
416
|
+
});
|
|
417
|
+
} else if (options?.optimisticData) {
|
|
418
|
+
const optimistic = typeof options.optimisticData === "function" ? options.optimisticData(input, Array.isArray(currentData) ? currentData : []) : options.optimisticData;
|
|
419
|
+
if (optimistic) {
|
|
420
|
+
this.applyOptimisticUpdate(targetSubscriptionId, (data) => {
|
|
421
|
+
if (!Array.isArray(data)) return data;
|
|
422
|
+
return [optimistic, ...data];
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
} else if (typeof updateType === "function") {
|
|
427
|
+
this.applyOptimisticUpdate(targetSubscriptionId, (data) => {
|
|
428
|
+
if (!Array.isArray(data)) return data;
|
|
429
|
+
return updateType(data, input, void 0);
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
const connectionId = await this.wsManager.waitForConnectionId();
|
|
434
|
+
const headers = {
|
|
435
|
+
"Content-Type": "application/json"
|
|
436
|
+
};
|
|
437
|
+
if (this.headersGetter) {
|
|
438
|
+
const customHeaders = await this.headersGetter();
|
|
439
|
+
Object.assign(headers, customHeaders);
|
|
440
|
+
}
|
|
441
|
+
if (this.authGetter) {
|
|
442
|
+
const token = await this.authGetter();
|
|
443
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
444
|
+
}
|
|
445
|
+
try {
|
|
446
|
+
const response = await fetch(this.httpUrl, {
|
|
447
|
+
method: "POST",
|
|
448
|
+
headers,
|
|
449
|
+
body: JSON.stringify({
|
|
450
|
+
type: "call",
|
|
451
|
+
connectionId,
|
|
452
|
+
path,
|
|
453
|
+
input
|
|
454
|
+
})
|
|
293
455
|
});
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
456
|
+
if (!response.ok) {
|
|
457
|
+
throw new Error(
|
|
458
|
+
`Call failed: ${response.status} ${response.statusText}`
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
const result = await response.json();
|
|
462
|
+
if (result.type === "error") {
|
|
463
|
+
throw new Error(result.message ?? "Unknown error");
|
|
464
|
+
}
|
|
465
|
+
const data = result.data;
|
|
466
|
+
if (targetSubscriptionId && updateType === "merge") {
|
|
467
|
+
const resultWithId = data;
|
|
468
|
+
if (resultWithId && resultWithId.id) {
|
|
469
|
+
this.applyOptimisticUpdate(targetSubscriptionId, (currentData) => {
|
|
470
|
+
if (!Array.isArray(currentData)) return currentData;
|
|
471
|
+
const idx = currentData.findIndex(
|
|
472
|
+
(item) => item.id === resultWithId.id
|
|
473
|
+
);
|
|
474
|
+
if (idx >= 0) {
|
|
475
|
+
return [
|
|
476
|
+
...currentData.slice(0, idx),
|
|
477
|
+
data,
|
|
478
|
+
...currentData.slice(idx + 1)
|
|
479
|
+
];
|
|
480
|
+
} else {
|
|
481
|
+
return [
|
|
482
|
+
data,
|
|
483
|
+
...currentData.filter(
|
|
484
|
+
(item) => item.id && !item.id.startsWith("temp-")
|
|
485
|
+
)
|
|
486
|
+
];
|
|
487
|
+
}
|
|
488
|
+
});
|
|
298
489
|
}
|
|
299
|
-
}
|
|
300
|
-
|
|
490
|
+
}
|
|
491
|
+
return data;
|
|
492
|
+
} catch (error) {
|
|
493
|
+
if (targetSubscriptionId) {
|
|
494
|
+
this.rollbackOptimisticUpdate(targetSubscriptionId);
|
|
495
|
+
}
|
|
496
|
+
throw error;
|
|
497
|
+
}
|
|
301
498
|
}
|
|
302
499
|
/**
|
|
303
500
|
* Unsubscribe from a subscription
|
|
@@ -312,25 +509,30 @@ var ReactiveClient = class {
|
|
|
312
509
|
});
|
|
313
510
|
}
|
|
314
511
|
/**
|
|
315
|
-
* Refetch a subscription
|
|
512
|
+
* Refetch a subscription via HTTP
|
|
316
513
|
*/
|
|
317
514
|
async refetch(id) {
|
|
318
515
|
const state = this.subscriptions.get(id);
|
|
319
516
|
if (!state) return;
|
|
320
517
|
state.loading = true;
|
|
321
518
|
this.notifySubscriptionListeners(id);
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
519
|
+
try {
|
|
520
|
+
await this.sendSubscribeRequest(id, state.path, state.input);
|
|
521
|
+
} catch (error) {
|
|
522
|
+
state.error = error instanceof Error ? error : new Error(String(error));
|
|
523
|
+
state.loading = false;
|
|
524
|
+
this.notifySubscriptionListeners(id);
|
|
525
|
+
state.options.onError?.(state.error);
|
|
526
|
+
}
|
|
328
527
|
}
|
|
329
528
|
/**
|
|
330
|
-
* Handle incoming server messages
|
|
529
|
+
* Handle incoming server messages.
|
|
530
|
+
* Note: 'connected' messages are handled by WebSocketManager before reaching here.
|
|
331
531
|
*/
|
|
332
532
|
handleMessage(message) {
|
|
333
533
|
switch (message.type) {
|
|
534
|
+
case "connected":
|
|
535
|
+
break;
|
|
334
536
|
case "snapshot":
|
|
335
537
|
this.handleSnapshot(message.subscriptionId, message.data);
|
|
336
538
|
break;
|
|
@@ -351,6 +553,7 @@ var ReactiveClient = class {
|
|
|
351
553
|
handleSnapshot(subscriptionId, data) {
|
|
352
554
|
const state = this.subscriptions.get(subscriptionId);
|
|
353
555
|
if (!state) return;
|
|
556
|
+
this.clearOptimisticUpdate(subscriptionId);
|
|
354
557
|
state.data = data;
|
|
355
558
|
state.loading = false;
|
|
356
559
|
state.error = void 0;
|
|
@@ -363,8 +566,11 @@ var ReactiveClient = class {
|
|
|
363
566
|
handlePatch(subscriptionId, patches) {
|
|
364
567
|
const state = this.subscriptions.get(subscriptionId);
|
|
365
568
|
if (!state || state.data === void 0) return;
|
|
569
|
+
const originalData = this.optimisticUpdates.get(subscriptionId);
|
|
570
|
+
const baseData = originalData !== void 0 ? originalData : state.data;
|
|
571
|
+
this.clearOptimisticUpdate(subscriptionId);
|
|
366
572
|
try {
|
|
367
|
-
state.data = applyPatches(
|
|
573
|
+
state.data = applyPatches(baseData, patches);
|
|
368
574
|
this.notifySubscriptionListeners(subscriptionId);
|
|
369
575
|
state.options.onData?.(state.data);
|
|
370
576
|
} catch (error) {
|
|
@@ -405,19 +611,21 @@ var ReactiveClient = class {
|
|
|
405
611
|
}
|
|
406
612
|
}
|
|
407
613
|
/**
|
|
408
|
-
* Resubscribe to all subscriptions after reconnect
|
|
614
|
+
* Resubscribe to all subscriptions after reconnect via HTTP
|
|
409
615
|
*/
|
|
410
616
|
resubscribeAll() {
|
|
411
617
|
for (const [id, state] of this.subscriptions) {
|
|
412
618
|
if (state.options.resubscribeOnReconnect !== false) {
|
|
413
619
|
state.loading = true;
|
|
414
620
|
this.notifySubscriptionListeners(id);
|
|
415
|
-
this.
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
621
|
+
this.sendSubscribeRequest(id, state.path, state.input).catch(
|
|
622
|
+
(error) => {
|
|
623
|
+
state.error = error instanceof Error ? error : new Error(String(error));
|
|
624
|
+
state.loading = false;
|
|
625
|
+
this.notifySubscriptionListeners(id);
|
|
626
|
+
state.options.onError?.(state.error);
|
|
627
|
+
}
|
|
628
|
+
);
|
|
421
629
|
}
|
|
422
630
|
}
|
|
423
631
|
}
|
|
@@ -457,6 +665,51 @@ var ReactiveClient = class {
|
|
|
457
665
|
getSubscriptionState(id) {
|
|
458
666
|
return this.subscriptions.get(id);
|
|
459
667
|
}
|
|
668
|
+
/**
|
|
669
|
+
* Find subscription by path
|
|
670
|
+
*/
|
|
671
|
+
findSubscriptionByPath(path) {
|
|
672
|
+
for (const [id, state] of this.subscriptions) {
|
|
673
|
+
if (state.path === path) {
|
|
674
|
+
return id;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
return void 0;
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Apply an optimistic update to a subscription's data
|
|
681
|
+
*/
|
|
682
|
+
applyOptimisticUpdate(subscriptionId, updater) {
|
|
683
|
+
const state = this.subscriptions.get(subscriptionId);
|
|
684
|
+
if (!state || state.data === void 0) return;
|
|
685
|
+
if (!this.optimisticUpdates.has(subscriptionId)) {
|
|
686
|
+
this.optimisticUpdates.set(
|
|
687
|
+
subscriptionId,
|
|
688
|
+
JSON.parse(JSON.stringify(state.data))
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
state.data = updater(state.data);
|
|
692
|
+
this.notifySubscriptionListeners(subscriptionId);
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Rollback an optimistic update
|
|
696
|
+
*/
|
|
697
|
+
rollbackOptimisticUpdate(subscriptionId) {
|
|
698
|
+
const original = this.optimisticUpdates.get(subscriptionId);
|
|
699
|
+
if (original === void 0) return;
|
|
700
|
+
const state = this.subscriptions.get(subscriptionId);
|
|
701
|
+
if (state) {
|
|
702
|
+
state.data = original;
|
|
703
|
+
this.notifySubscriptionListeners(subscriptionId);
|
|
704
|
+
}
|
|
705
|
+
this.optimisticUpdates.delete(subscriptionId);
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* Clear optimistic state (called when real data arrives)
|
|
709
|
+
*/
|
|
710
|
+
clearOptimisticUpdate(subscriptionId) {
|
|
711
|
+
this.optimisticUpdates.delete(subscriptionId);
|
|
712
|
+
}
|
|
460
713
|
};
|
|
461
714
|
function createReactiveClient(config) {
|
|
462
715
|
const client = new ReactiveClient(config);
|
|
@@ -481,7 +734,12 @@ function createTypedProxy(client, path) {
|
|
|
481
734
|
if (prop === "mutate") {
|
|
482
735
|
return async (input) => {
|
|
483
736
|
const parentPath = path.join(".");
|
|
484
|
-
|
|
737
|
+
const invalidatesPath = path.length >= 2 ? `${path.slice(0, -1).join(".")}.list` : void 0;
|
|
738
|
+
const updateType = parentPath.endsWith(".delete") ? "remove" : "merge";
|
|
739
|
+
return client.call(parentPath, input, {
|
|
740
|
+
invalidates: invalidatesPath,
|
|
741
|
+
optimisticUpdate: updateType
|
|
742
|
+
});
|
|
485
743
|
};
|
|
486
744
|
}
|
|
487
745
|
return createTypedProxy(client, newPath);
|
|
@@ -584,12 +842,23 @@ function useSubscription(path, options = {}) {
|
|
|
584
842
|
}
|
|
585
843
|
});
|
|
586
844
|
subscriptionRef.current = subscription;
|
|
845
|
+
const unregisterListener = client.addSubscriptionListener(
|
|
846
|
+
subscription.id,
|
|
847
|
+
() => {
|
|
848
|
+
setState({
|
|
849
|
+
data: subscription.data,
|
|
850
|
+
loading: subscription.loading,
|
|
851
|
+
error: subscription.error
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
);
|
|
587
855
|
setState({
|
|
588
856
|
data: subscription.data,
|
|
589
857
|
loading: subscription.loading,
|
|
590
858
|
error: subscription.error
|
|
591
859
|
});
|
|
592
860
|
return () => {
|
|
861
|
+
unregisterListener();
|
|
593
862
|
subscription.unsubscribe();
|
|
594
863
|
subscriptionRef.current = null;
|
|
595
864
|
};
|
|
@@ -606,20 +875,31 @@ function useSubscription(path, options = {}) {
|
|
|
606
875
|
refetch
|
|
607
876
|
};
|
|
608
877
|
}
|
|
609
|
-
function useMutation(path) {
|
|
878
|
+
function useMutation(path, options) {
|
|
610
879
|
const client = useReactiveClient();
|
|
611
880
|
const [state, setState] = useState({
|
|
612
881
|
data: void 0,
|
|
613
882
|
loading: false,
|
|
614
883
|
error: void 0
|
|
615
884
|
});
|
|
885
|
+
const autoInvalidates = options?.invalidates ?? (() => {
|
|
886
|
+
const parts = path.split(".");
|
|
887
|
+
if (parts.length >= 2) {
|
|
888
|
+
return `${parts.slice(0, -1).join(".")}.list`;
|
|
889
|
+
}
|
|
890
|
+
return void 0;
|
|
891
|
+
})();
|
|
892
|
+
const autoUpdateType = options?.optimisticUpdate ?? (path.endsWith(".delete") ? "remove" : "merge");
|
|
616
893
|
const mutate = async (input) => {
|
|
617
894
|
if (!client) {
|
|
618
895
|
throw new Error("WebSocket not configured - mutations are disabled");
|
|
619
896
|
}
|
|
620
897
|
setState((prev) => ({ ...prev, loading: true, error: void 0 }));
|
|
621
898
|
try {
|
|
622
|
-
const result = await client.call(path, input
|
|
899
|
+
const result = await client.call(path, input, {
|
|
900
|
+
invalidates: autoInvalidates,
|
|
901
|
+
optimisticUpdate: autoUpdateType
|
|
902
|
+
});
|
|
623
903
|
setState({ data: result, loading: false, error: void 0 });
|
|
624
904
|
return result;
|
|
625
905
|
} catch (error) {
|
|
@@ -638,10 +918,41 @@ function useMutation(path) {
|
|
|
638
918
|
reset
|
|
639
919
|
};
|
|
640
920
|
}
|
|
921
|
+
function useClient() {
|
|
922
|
+
const client = useReactiveClient();
|
|
923
|
+
function buildProxy(path) {
|
|
924
|
+
return new Proxy(
|
|
925
|
+
{},
|
|
926
|
+
{
|
|
927
|
+
get(_target, prop) {
|
|
928
|
+
if (prop === "useQuery") {
|
|
929
|
+
return function useQueryHook(input, options) {
|
|
930
|
+
const pathStr = path.join(".");
|
|
931
|
+
return useSubscription(pathStr, { ...options, input });
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
if (prop === "useMutation") {
|
|
935
|
+
return function useMutationHook(options) {
|
|
936
|
+
const pathStr = path.join(".");
|
|
937
|
+
return useMutation(pathStr, options);
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
if (prop === "setHeaders") {
|
|
941
|
+
return (headers) => {
|
|
942
|
+
client?.setHeaders(headers);
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
return buildProxy([...path, prop]);
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
return buildProxy([]);
|
|
951
|
+
}
|
|
641
952
|
function createReactiveHooks() {
|
|
642
953
|
return {
|
|
643
954
|
useSubscription: (path, options) => useSubscription(path, options),
|
|
644
|
-
useMutation: (path) => useMutation(path),
|
|
955
|
+
useMutation: (path, options) => useMutation(path, options),
|
|
645
956
|
useConnectionState,
|
|
646
957
|
useReactiveClient
|
|
647
958
|
};
|
|
@@ -649,10 +960,10 @@ function createReactiveHooks() {
|
|
|
649
960
|
function createProcedureHooks(path) {
|
|
650
961
|
return {
|
|
651
962
|
useSubscription: (input, options) => useSubscription(path, { ...options, input }),
|
|
652
|
-
useMutation: () => useMutation(path)
|
|
963
|
+
useMutation: (options) => useMutation(path, options)
|
|
653
964
|
};
|
|
654
965
|
}
|
|
655
966
|
|
|
656
|
-
export { ReactiveClient, ReactiveClientProvider, WebSocketManager, applyPatches, createProcedureHooks, createReactiveClient, createReactiveHooks, useConnectionState, useMutation, useReactiveClient, useReactiveClientOrThrow, useSubscription };
|
|
657
|
-
//# sourceMappingURL=chunk-
|
|
658
|
-
//# sourceMappingURL=chunk-
|
|
967
|
+
export { ReactiveClient, ReactiveClientProvider, WebSocketManager, applyPatches, createProcedureHooks, createReactiveClient, createReactiveHooks, useClient, useConnectionState, useMutation, useReactiveClient, useReactiveClientOrThrow, useSubscription };
|
|
968
|
+
//# sourceMappingURL=chunk-MI2ZLLB2.js.map
|
|
969
|
+
//# sourceMappingURL=chunk-MI2ZLLB2.js.map
|