dynamodb-reactive 0.1.21 → 0.2.1
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 +378 -153
- package/dist/client-r_tnzwqi.d.ts +224 -0
- package/dist/client.cjs +1023 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.d.cts +6 -0
- package/dist/client.d.ts +4 -70
- package/dist/client.js +1013 -1
- package/dist/client.js.map +1 -1
- package/dist/core.cjs +232 -0
- package/dist/core.cjs.map +1 -0
- package/dist/core.d.cts +4 -0
- package/dist/core.d.ts +3 -3
- package/dist/core.js +222 -1
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +232 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +157 -0
- package/dist/index.d.ts +103 -56
- package/dist/index.js +222 -1
- package/dist/index.js.map +1 -1
- package/dist/infra.cjs +623 -0
- package/dist/infra.cjs.map +1 -0
- package/dist/infra.d.cts +119 -0
- package/dist/infra.d.ts +50 -102
- package/dist/infra.js +371 -144
- package/dist/infra.js.map +1 -1
- package/dist/react.cjs +1250 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +68 -0
- package/dist/react.d.ts +68 -3
- package/dist/react.js +1237 -1
- package/dist/react.js.map +1 -1
- package/dist/server.cjs +2351 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +364 -0
- package/dist/server.d.ts +58 -313
- package/dist/server.js +1855 -1208
- package/dist/server.js.map +1 -1
- package/dist/{table-tN3D4Tm-.d.ts → table-BijJ73pX.d.ts} +2 -34
- package/dist/{types-ISMJfyIv.d.ts → types-LMLk8JVC.d.ts} +56 -30
- package/package.json +79 -41
- package/dist/chunk-2A7GJ2UO.js +0 -1088
- package/dist/chunk-2A7GJ2UO.js.map +0 -1
- package/dist/chunk-ZAJA4KBE.js +0 -150
- package/dist/chunk-ZAJA4KBE.js.map +0 -1
- package/dist/react-B2b5RIz_.d.ts +0 -549
package/dist/chunk-2A7GJ2UO.js
DELETED
|
@@ -1,1088 +0,0 @@
|
|
|
1
|
-
import jsonpatch from 'fast-json-patch';
|
|
2
|
-
import { error, debug, warn } from 'ag-common/dist/common/helpers/log.js';
|
|
3
|
-
import { createContext, useState, useEffect, useContext, useSyncExternalStore, useRef } from 'react';
|
|
4
|
-
import { jsx } from 'react/jsx-runtime';
|
|
5
|
-
|
|
6
|
-
// ../client/src/patcher.ts
|
|
7
|
-
var { applyPatch } = jsonpatch;
|
|
8
|
-
function applyPatches(document2, patches) {
|
|
9
|
-
if (patches.length === 0) {
|
|
10
|
-
return document2;
|
|
11
|
-
}
|
|
12
|
-
const operations = patches.map((patch) => {
|
|
13
|
-
const op = {
|
|
14
|
-
op: patch.op,
|
|
15
|
-
path: patch.path
|
|
16
|
-
};
|
|
17
|
-
if ("value" in patch && patch.value !== void 0) {
|
|
18
|
-
op.value = patch.value;
|
|
19
|
-
}
|
|
20
|
-
if ("from" in patch && patch.from !== void 0) {
|
|
21
|
-
op.from = patch.from;
|
|
22
|
-
}
|
|
23
|
-
return op;
|
|
24
|
-
});
|
|
25
|
-
try {
|
|
26
|
-
const result = applyPatch(
|
|
27
|
-
structuredClone(document2),
|
|
28
|
-
operations,
|
|
29
|
-
true,
|
|
30
|
-
// Validate operations
|
|
31
|
-
false
|
|
32
|
-
// Don't mutate the original
|
|
33
|
-
);
|
|
34
|
-
return result.newDocument;
|
|
35
|
-
} catch (error2) {
|
|
36
|
-
throw new Error("Failed to apply patches", { cause: error2 });
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
var WebSocketManager = class {
|
|
40
|
-
config;
|
|
41
|
-
ws = null;
|
|
42
|
-
state = "disconnected";
|
|
43
|
-
reconnectAttempts = 0;
|
|
44
|
-
reconnectTimer = null;
|
|
45
|
-
messageQueue = [];
|
|
46
|
-
messageHandlers = /* @__PURE__ */ new Set();
|
|
47
|
-
stateHandlers = /* @__PURE__ */ new Set();
|
|
48
|
-
_connectionId = null;
|
|
49
|
-
connectionIdResolvers = [];
|
|
50
|
-
/** Flag to indicate intentional disconnect (skip auto-reconnect) */
|
|
51
|
-
intentionalDisconnect = false;
|
|
52
|
-
constructor(config) {
|
|
53
|
-
this.config = {
|
|
54
|
-
autoReconnect: true,
|
|
55
|
-
reconnectDelay: 1e3,
|
|
56
|
-
maxReconnectAttempts: 10,
|
|
57
|
-
...config
|
|
58
|
-
};
|
|
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
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Get current connection state
|
|
79
|
-
*/
|
|
80
|
-
getState() {
|
|
81
|
-
return this.state;
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Connect to the WebSocket server
|
|
85
|
-
*/
|
|
86
|
-
async connect() {
|
|
87
|
-
if (this.state === "connected" || this.state === "connecting") {
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
this.setState("connecting");
|
|
91
|
-
try {
|
|
92
|
-
const url = await this.buildUrl();
|
|
93
|
-
this.ws = new WebSocket(url);
|
|
94
|
-
this.ws.onopen = () => {
|
|
95
|
-
this.reconnectAttempts = 0;
|
|
96
|
-
this.ws?.send(JSON.stringify({ type: "init" }));
|
|
97
|
-
};
|
|
98
|
-
this.ws.onclose = () => {
|
|
99
|
-
this.ws = null;
|
|
100
|
-
this._connectionId = null;
|
|
101
|
-
this.setState("disconnected");
|
|
102
|
-
this.config.onDisconnect?.();
|
|
103
|
-
if (this.config.autoReconnect && !this.intentionalDisconnect) {
|
|
104
|
-
this.scheduleReconnect();
|
|
105
|
-
}
|
|
106
|
-
this.intentionalDisconnect = false;
|
|
107
|
-
};
|
|
108
|
-
this.ws.onerror = () => {
|
|
109
|
-
const error2 = new Error("WebSocket error");
|
|
110
|
-
this.config.onError?.(error2);
|
|
111
|
-
};
|
|
112
|
-
this.ws.onmessage = (event) => {
|
|
113
|
-
try {
|
|
114
|
-
const message = JSON.parse(event.data);
|
|
115
|
-
if (message.type === "connected") {
|
|
116
|
-
this._connectionId = message.connectionId;
|
|
117
|
-
this.ws?.send(JSON.stringify({ type: "ping" }));
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
if (message.type === "pong") {
|
|
121
|
-
this.setState("connected");
|
|
122
|
-
this.flushMessageQueue();
|
|
123
|
-
this.config.onConnect?.();
|
|
124
|
-
for (const resolve of this.connectionIdResolvers) {
|
|
125
|
-
resolve(this._connectionId);
|
|
126
|
-
}
|
|
127
|
-
this.connectionIdResolvers = [];
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
this.handleMessage(message);
|
|
131
|
-
} catch (e) {
|
|
132
|
-
error("Failed to parse WebSocket message:", e);
|
|
133
|
-
}
|
|
134
|
-
};
|
|
135
|
-
} catch (e) {
|
|
136
|
-
this.setState("disconnected");
|
|
137
|
-
this.config.onError?.(e instanceof Error ? e : new Error(String(e)));
|
|
138
|
-
if (this.config.autoReconnect) {
|
|
139
|
-
this.scheduleReconnect();
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
/**
|
|
144
|
-
* Disconnect from the WebSocket server
|
|
145
|
-
*/
|
|
146
|
-
disconnect() {
|
|
147
|
-
this.intentionalDisconnect = true;
|
|
148
|
-
if (this.reconnectTimer) {
|
|
149
|
-
clearTimeout(this.reconnectTimer);
|
|
150
|
-
this.reconnectTimer = null;
|
|
151
|
-
}
|
|
152
|
-
if (this.ws) {
|
|
153
|
-
this.ws.close(1e3, "Client disconnect");
|
|
154
|
-
this.ws = null;
|
|
155
|
-
}
|
|
156
|
-
this._connectionId = null;
|
|
157
|
-
this.setState("disconnected");
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* Force reconnect - closes existing connection and reconnects
|
|
161
|
-
* Useful for recovering from stale connections
|
|
162
|
-
*/
|
|
163
|
-
forceReconnect() {
|
|
164
|
-
if (this.reconnectTimer) {
|
|
165
|
-
clearTimeout(this.reconnectTimer);
|
|
166
|
-
this.reconnectTimer = null;
|
|
167
|
-
}
|
|
168
|
-
if (this.ws) {
|
|
169
|
-
this.ws.close(1e3, "Force reconnect");
|
|
170
|
-
this.ws = null;
|
|
171
|
-
}
|
|
172
|
-
this._connectionId = null;
|
|
173
|
-
this.reconnectAttempts = 0;
|
|
174
|
-
this.setState("disconnected");
|
|
175
|
-
void this.connect();
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* Send a message to the server
|
|
179
|
-
*/
|
|
180
|
-
send(message) {
|
|
181
|
-
if (this.state === "connected" && this.ws?.readyState === WebSocket.OPEN) {
|
|
182
|
-
this.ws.send(JSON.stringify(message));
|
|
183
|
-
} else {
|
|
184
|
-
this.messageQueue.push(message);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
/**
|
|
188
|
-
* Subscribe to incoming messages
|
|
189
|
-
*/
|
|
190
|
-
onMessage(handler) {
|
|
191
|
-
this.messageHandlers.add(handler);
|
|
192
|
-
return () => this.messageHandlers.delete(handler);
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
|
-
* Subscribe to connection state changes
|
|
196
|
-
*/
|
|
197
|
-
onStateChange(handler) {
|
|
198
|
-
this.stateHandlers.add(handler);
|
|
199
|
-
return () => this.stateHandlers.delete(handler);
|
|
200
|
-
}
|
|
201
|
-
async buildUrl() {
|
|
202
|
-
let url = this.config.url;
|
|
203
|
-
if (this.config.auth) {
|
|
204
|
-
const token = typeof this.config.auth === "function" ? await this.config.auth() : this.config.auth;
|
|
205
|
-
const separator = url.includes("?") ? "&" : "?";
|
|
206
|
-
url = `${url}${separator}token=${encodeURIComponent(token)}`;
|
|
207
|
-
}
|
|
208
|
-
return url;
|
|
209
|
-
}
|
|
210
|
-
setState(state) {
|
|
211
|
-
this.state = state;
|
|
212
|
-
for (const handler of this.stateHandlers) {
|
|
213
|
-
handler(state);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
handleMessage(message) {
|
|
217
|
-
for (const handler of this.messageHandlers) {
|
|
218
|
-
handler(message);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
scheduleReconnect() {
|
|
222
|
-
if (this.reconnectAttempts >= this.config.maxReconnectAttempts) {
|
|
223
|
-
error("Max reconnection attempts reached");
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
this.setState("reconnecting");
|
|
227
|
-
this.reconnectAttempts++;
|
|
228
|
-
const delay = Math.min(
|
|
229
|
-
this.config.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1),
|
|
230
|
-
3e4
|
|
231
|
-
// Max 30 seconds
|
|
232
|
-
) * (0.5 + Math.random() * 0.5);
|
|
233
|
-
this.reconnectTimer = setTimeout(() => {
|
|
234
|
-
this.reconnectTimer = null;
|
|
235
|
-
void this.connect();
|
|
236
|
-
}, delay);
|
|
237
|
-
}
|
|
238
|
-
flushMessageQueue() {
|
|
239
|
-
const queue = this.messageQueue;
|
|
240
|
-
this.messageQueue = [];
|
|
241
|
-
for (const message of queue) {
|
|
242
|
-
this.send(message);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
};
|
|
246
|
-
var DEFAULT_HTTP_URL = "/api/reactive";
|
|
247
|
-
function generateId() {
|
|
248
|
-
return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
249
|
-
}
|
|
250
|
-
var ReactiveClient = class {
|
|
251
|
-
wsManager;
|
|
252
|
-
httpUrl;
|
|
253
|
-
authGetter;
|
|
254
|
-
headersGetter;
|
|
255
|
-
subscriptions = /* @__PURE__ */ new Map();
|
|
256
|
-
pendingCalls = /* @__PURE__ */ new Map();
|
|
257
|
-
connectionState = "disconnected";
|
|
258
|
-
stateListeners = /* @__PURE__ */ new Set();
|
|
259
|
-
// Stores original data for rollback when optimistic updates fail
|
|
260
|
-
optimisticUpdates = /* @__PURE__ */ new Map();
|
|
261
|
-
constructor(config) {
|
|
262
|
-
this.wsManager = new WebSocketManager(config);
|
|
263
|
-
this.httpUrl = config.httpUrl ?? DEFAULT_HTTP_URL;
|
|
264
|
-
if (typeof config.auth === "function") {
|
|
265
|
-
this.authGetter = config.auth;
|
|
266
|
-
} else if (config.auth) {
|
|
267
|
-
this.authGetter = () => config.auth;
|
|
268
|
-
}
|
|
269
|
-
if (typeof config.headers === "function") {
|
|
270
|
-
this.headersGetter = config.headers;
|
|
271
|
-
} else if (config.headers) {
|
|
272
|
-
this.headersGetter = () => config.headers;
|
|
273
|
-
}
|
|
274
|
-
this.wsManager.onMessage((message) => this.handleMessage(message));
|
|
275
|
-
this.wsManager.onStateChange((state) => {
|
|
276
|
-
this.connectionState = state;
|
|
277
|
-
this.notifyStateListeners();
|
|
278
|
-
if (state === "connected") {
|
|
279
|
-
this.resubscribeAll();
|
|
280
|
-
}
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
/**
|
|
284
|
-
* Connect to the server
|
|
285
|
-
*/
|
|
286
|
-
async connect() {
|
|
287
|
-
await this.wsManager.connect();
|
|
288
|
-
}
|
|
289
|
-
/**
|
|
290
|
-
* Disconnect from the server
|
|
291
|
-
*/
|
|
292
|
-
disconnect() {
|
|
293
|
-
this.wsManager.disconnect();
|
|
294
|
-
}
|
|
295
|
-
/**
|
|
296
|
-
* Force reconnect - closes existing connection and reconnects
|
|
297
|
-
* Useful for recovering from stale connections
|
|
298
|
-
*/
|
|
299
|
-
forceReconnect() {
|
|
300
|
-
this.wsManager.forceReconnect();
|
|
301
|
-
}
|
|
302
|
-
/**
|
|
303
|
-
* Update the headers getter for HTTP requests.
|
|
304
|
-
* Useful for dynamically setting auth or user context headers.
|
|
305
|
-
*/
|
|
306
|
-
setHeaders(headers) {
|
|
307
|
-
if (typeof headers === "function") {
|
|
308
|
-
this.headersGetter = headers;
|
|
309
|
-
} else {
|
|
310
|
-
this.headersGetter = () => headers;
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
/**
|
|
314
|
-
* Get current connection state
|
|
315
|
-
*/
|
|
316
|
-
getConnectionState() {
|
|
317
|
-
return this.connectionState;
|
|
318
|
-
}
|
|
319
|
-
/**
|
|
320
|
-
* Subscribe to connection state changes
|
|
321
|
-
*/
|
|
322
|
-
onConnectionStateChange(listener) {
|
|
323
|
-
this.stateListeners.add(listener);
|
|
324
|
-
return () => this.stateListeners.delete(listener);
|
|
325
|
-
}
|
|
326
|
-
/**
|
|
327
|
-
* Subscribe to a procedure via HTTP.
|
|
328
|
-
* Snapshot is returned via HTTP, patches come via WebSocket.
|
|
329
|
-
*/
|
|
330
|
-
subscribe(path, options = {}) {
|
|
331
|
-
const id = generateId();
|
|
332
|
-
const state = {
|
|
333
|
-
id,
|
|
334
|
-
path,
|
|
335
|
-
input: options.input,
|
|
336
|
-
data: void 0,
|
|
337
|
-
loading: true,
|
|
338
|
-
error: void 0,
|
|
339
|
-
listeners: /* @__PURE__ */ new Set(),
|
|
340
|
-
options
|
|
341
|
-
};
|
|
342
|
-
this.subscriptions.set(id, state);
|
|
343
|
-
this.sendSubscribeRequest(id, path, options.input).catch((error2) => {
|
|
344
|
-
state.error = error2 instanceof Error ? error2 : new Error(String(error2));
|
|
345
|
-
state.loading = false;
|
|
346
|
-
this.notifySubscriptionListeners(id);
|
|
347
|
-
options.onError?.(state.error);
|
|
348
|
-
});
|
|
349
|
-
const subscription = {
|
|
350
|
-
id,
|
|
351
|
-
get data() {
|
|
352
|
-
return state.data;
|
|
353
|
-
},
|
|
354
|
-
get loading() {
|
|
355
|
-
return state.loading;
|
|
356
|
-
},
|
|
357
|
-
get error() {
|
|
358
|
-
return state.error;
|
|
359
|
-
},
|
|
360
|
-
unsubscribe: () => this.unsubscribe(id),
|
|
361
|
-
refetch: () => this.refetch(id)
|
|
362
|
-
};
|
|
363
|
-
return subscription;
|
|
364
|
-
}
|
|
365
|
-
/**
|
|
366
|
-
* Send subscribe request via HTTP
|
|
367
|
-
*/
|
|
368
|
-
async sendSubscribeRequest(subscriptionId, path, input) {
|
|
369
|
-
const connectionId = await this.wsManager.waitForConnectionId();
|
|
370
|
-
const headers = {
|
|
371
|
-
"Content-Type": "application/json"
|
|
372
|
-
};
|
|
373
|
-
if (this.headersGetter) {
|
|
374
|
-
const customHeaders = await this.headersGetter();
|
|
375
|
-
Object.assign(headers, customHeaders);
|
|
376
|
-
}
|
|
377
|
-
if (this.authGetter) {
|
|
378
|
-
const token = await this.authGetter();
|
|
379
|
-
headers["Authorization"] = `Bearer ${token}`;
|
|
380
|
-
}
|
|
381
|
-
const response = await fetch(this.httpUrl, {
|
|
382
|
-
method: "POST",
|
|
383
|
-
headers,
|
|
384
|
-
body: JSON.stringify({
|
|
385
|
-
type: "subscribe",
|
|
386
|
-
connectionId,
|
|
387
|
-
subscriptionId,
|
|
388
|
-
path,
|
|
389
|
-
input
|
|
390
|
-
})
|
|
391
|
-
});
|
|
392
|
-
if (!response.ok) {
|
|
393
|
-
throw new Error(
|
|
394
|
-
`Subscribe failed: ${response.status} ${response.statusText}`
|
|
395
|
-
);
|
|
396
|
-
}
|
|
397
|
-
const result = await response.json();
|
|
398
|
-
if (result.type === "error") {
|
|
399
|
-
throw new Error(result.message ?? "Unknown error");
|
|
400
|
-
}
|
|
401
|
-
if (result.type === "snapshot") {
|
|
402
|
-
this.handleSnapshot(subscriptionId, result.data);
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
/**
|
|
406
|
-
* Call a mutation procedure via HTTP with optimistic update support.
|
|
407
|
-
* All optimistic updates are applied INSTANTLY (before HTTP request).
|
|
408
|
-
*/
|
|
409
|
-
async call(path, input, options) {
|
|
410
|
-
const invalidatesPath = options?.invalidates;
|
|
411
|
-
const updateType = options?.optimisticUpdate;
|
|
412
|
-
let targetSubscriptionId;
|
|
413
|
-
if (invalidatesPath) {
|
|
414
|
-
targetSubscriptionId = this.findSubscriptionByPath(invalidatesPath);
|
|
415
|
-
debug(
|
|
416
|
-
"[optimistic] Looking for subscription:",
|
|
417
|
-
invalidatesPath,
|
|
418
|
-
"-> found:",
|
|
419
|
-
targetSubscriptionId ?? "NONE"
|
|
420
|
-
);
|
|
421
|
-
}
|
|
422
|
-
if (targetSubscriptionId) {
|
|
423
|
-
debug(
|
|
424
|
-
"[optimistic] Applying update type:",
|
|
425
|
-
updateType,
|
|
426
|
-
"to subscription:",
|
|
427
|
-
targetSubscriptionId
|
|
428
|
-
);
|
|
429
|
-
const state = this.subscriptions.get(targetSubscriptionId);
|
|
430
|
-
const currentData = state?.data;
|
|
431
|
-
if (updateType === "remove") {
|
|
432
|
-
const inputId = input.id;
|
|
433
|
-
if (inputId) {
|
|
434
|
-
this.applyOptimisticUpdate(targetSubscriptionId, (data) => {
|
|
435
|
-
if (!Array.isArray(data)) return data;
|
|
436
|
-
return data.filter((item) => item.id !== inputId);
|
|
437
|
-
});
|
|
438
|
-
}
|
|
439
|
-
} else if (updateType === "merge") {
|
|
440
|
-
const inputWithId = input;
|
|
441
|
-
if (inputWithId.id) {
|
|
442
|
-
this.applyOptimisticUpdate(targetSubscriptionId, (data) => {
|
|
443
|
-
if (!Array.isArray(data)) return data;
|
|
444
|
-
const idx = data.findIndex(
|
|
445
|
-
(item) => item.id === inputWithId.id
|
|
446
|
-
);
|
|
447
|
-
if (idx >= 0) {
|
|
448
|
-
return [
|
|
449
|
-
...data.slice(0, idx),
|
|
450
|
-
{ ...data[idx], ...input },
|
|
451
|
-
...data.slice(idx + 1)
|
|
452
|
-
];
|
|
453
|
-
}
|
|
454
|
-
return data;
|
|
455
|
-
});
|
|
456
|
-
} else if (options?.optimisticData) {
|
|
457
|
-
const optimistic = typeof options.optimisticData === "function" ? options.optimisticData(input, Array.isArray(currentData) ? currentData : []) : options.optimisticData;
|
|
458
|
-
if (optimistic) {
|
|
459
|
-
this.applyOptimisticUpdate(targetSubscriptionId, (data) => {
|
|
460
|
-
if (!Array.isArray(data)) return data;
|
|
461
|
-
return [optimistic, ...data];
|
|
462
|
-
});
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
} else if (typeof updateType === "function") {
|
|
466
|
-
this.applyOptimisticUpdate(targetSubscriptionId, (data) => {
|
|
467
|
-
if (!Array.isArray(data)) return data;
|
|
468
|
-
return updateType(data, input, void 0);
|
|
469
|
-
});
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
const connectionId = await this.wsManager.waitForConnectionId();
|
|
473
|
-
const headers = {
|
|
474
|
-
"Content-Type": "application/json"
|
|
475
|
-
};
|
|
476
|
-
if (this.headersGetter) {
|
|
477
|
-
const customHeaders = await this.headersGetter();
|
|
478
|
-
Object.assign(headers, customHeaders);
|
|
479
|
-
}
|
|
480
|
-
if (this.authGetter) {
|
|
481
|
-
const token = await this.authGetter();
|
|
482
|
-
headers["Authorization"] = `Bearer ${token}`;
|
|
483
|
-
}
|
|
484
|
-
try {
|
|
485
|
-
const response = await fetch(this.httpUrl, {
|
|
486
|
-
method: "POST",
|
|
487
|
-
headers,
|
|
488
|
-
body: JSON.stringify({
|
|
489
|
-
type: "call",
|
|
490
|
-
connectionId,
|
|
491
|
-
path,
|
|
492
|
-
input
|
|
493
|
-
})
|
|
494
|
-
});
|
|
495
|
-
if (!response.ok) {
|
|
496
|
-
throw new Error(
|
|
497
|
-
`Call failed: ${response.status} ${response.statusText}`
|
|
498
|
-
);
|
|
499
|
-
}
|
|
500
|
-
const result = await response.json();
|
|
501
|
-
if (result.type === "error") {
|
|
502
|
-
throw new Error(result.message ?? "Unknown error");
|
|
503
|
-
}
|
|
504
|
-
const data = result.data;
|
|
505
|
-
if (targetSubscriptionId && updateType === "merge") {
|
|
506
|
-
const resultWithId = data;
|
|
507
|
-
if (resultWithId.id) {
|
|
508
|
-
this.applyOptimisticUpdate(targetSubscriptionId, (currentData) => {
|
|
509
|
-
if (!Array.isArray(currentData)) return currentData;
|
|
510
|
-
const idx = currentData.findIndex(
|
|
511
|
-
(item) => item.id === resultWithId.id
|
|
512
|
-
);
|
|
513
|
-
if (idx >= 0) {
|
|
514
|
-
return [
|
|
515
|
-
...currentData.slice(0, idx),
|
|
516
|
-
data,
|
|
517
|
-
...currentData.slice(idx + 1)
|
|
518
|
-
];
|
|
519
|
-
} else {
|
|
520
|
-
return [
|
|
521
|
-
data,
|
|
522
|
-
...currentData.filter(
|
|
523
|
-
(item) => item.id && !item.id.startsWith("temp-")
|
|
524
|
-
)
|
|
525
|
-
];
|
|
526
|
-
}
|
|
527
|
-
});
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
return data;
|
|
531
|
-
} catch (error2) {
|
|
532
|
-
if (targetSubscriptionId) {
|
|
533
|
-
this.rollbackOptimisticUpdate(targetSubscriptionId);
|
|
534
|
-
}
|
|
535
|
-
throw error2;
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
/**
|
|
539
|
-
* Unsubscribe from a subscription
|
|
540
|
-
*/
|
|
541
|
-
unsubscribe(id) {
|
|
542
|
-
const state = this.subscriptions.get(id);
|
|
543
|
-
if (!state) return;
|
|
544
|
-
this.subscriptions.delete(id);
|
|
545
|
-
this.wsManager.send({
|
|
546
|
-
type: "unsubscribe",
|
|
547
|
-
subscriptionId: id
|
|
548
|
-
});
|
|
549
|
-
}
|
|
550
|
-
/**
|
|
551
|
-
* Refetch a subscription via HTTP
|
|
552
|
-
*/
|
|
553
|
-
async refetch(id) {
|
|
554
|
-
const state = this.subscriptions.get(id);
|
|
555
|
-
if (!state) return;
|
|
556
|
-
state.loading = true;
|
|
557
|
-
this.notifySubscriptionListeners(id);
|
|
558
|
-
try {
|
|
559
|
-
await this.sendSubscribeRequest(id, state.path, state.input);
|
|
560
|
-
} catch (error2) {
|
|
561
|
-
state.error = error2 instanceof Error ? error2 : new Error(String(error2));
|
|
562
|
-
state.loading = false;
|
|
563
|
-
this.notifySubscriptionListeners(id);
|
|
564
|
-
state.options.onError?.(state.error);
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
/**
|
|
568
|
-
* Handle incoming server messages.
|
|
569
|
-
* Note: 'connected' messages are handled by WebSocketManager before reaching here.
|
|
570
|
-
*/
|
|
571
|
-
handleMessage(message) {
|
|
572
|
-
switch (message.type) {
|
|
573
|
-
case "connected":
|
|
574
|
-
break;
|
|
575
|
-
case "pong":
|
|
576
|
-
break;
|
|
577
|
-
case "snapshot":
|
|
578
|
-
this.handleSnapshot(message.subscriptionId, message.data);
|
|
579
|
-
break;
|
|
580
|
-
case "patch":
|
|
581
|
-
this.handlePatch(message.subscriptionId, message.patches);
|
|
582
|
-
break;
|
|
583
|
-
case "result":
|
|
584
|
-
this.handleResult(message.callId, message.data);
|
|
585
|
-
break;
|
|
586
|
-
case "error":
|
|
587
|
-
this.handleError(message);
|
|
588
|
-
break;
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
/**
|
|
592
|
-
* Handle a snapshot message
|
|
593
|
-
*/
|
|
594
|
-
handleSnapshot(subscriptionId, data) {
|
|
595
|
-
const state = this.subscriptions.get(subscriptionId);
|
|
596
|
-
if (!state) return;
|
|
597
|
-
this.clearOptimisticUpdate(subscriptionId);
|
|
598
|
-
state.data = data;
|
|
599
|
-
state.loading = false;
|
|
600
|
-
state.error = void 0;
|
|
601
|
-
this.notifySubscriptionListeners(subscriptionId);
|
|
602
|
-
state.options.onData?.(data);
|
|
603
|
-
}
|
|
604
|
-
/**
|
|
605
|
-
* Handle a patch message
|
|
606
|
-
*/
|
|
607
|
-
handlePatch(subscriptionId, patches) {
|
|
608
|
-
const state = this.subscriptions.get(subscriptionId);
|
|
609
|
-
if (!state || state.data === void 0) return;
|
|
610
|
-
const originalData = this.optimisticUpdates.get(subscriptionId);
|
|
611
|
-
const baseData = originalData !== void 0 ? originalData : state.data;
|
|
612
|
-
this.clearOptimisticUpdate(subscriptionId);
|
|
613
|
-
try {
|
|
614
|
-
state.data = applyPatches(baseData, patches);
|
|
615
|
-
this.notifySubscriptionListeners(subscriptionId);
|
|
616
|
-
state.options.onData?.(state.data);
|
|
617
|
-
} catch (error2) {
|
|
618
|
-
state.error = error2 instanceof Error ? error2 : new Error(String(error2));
|
|
619
|
-
this.notifySubscriptionListeners(subscriptionId);
|
|
620
|
-
state.options.onError?.(state.error);
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
/**
|
|
624
|
-
* Handle a result message
|
|
625
|
-
*/
|
|
626
|
-
handleResult(callId, data) {
|
|
627
|
-
const pending = this.pendingCalls.get(callId);
|
|
628
|
-
if (!pending) return;
|
|
629
|
-
this.pendingCalls.delete(callId);
|
|
630
|
-
pending.resolve(data);
|
|
631
|
-
}
|
|
632
|
-
/**
|
|
633
|
-
* Handle an error message
|
|
634
|
-
*/
|
|
635
|
-
handleError(message) {
|
|
636
|
-
const error2 = new Error(message.message);
|
|
637
|
-
if (message.subscriptionId) {
|
|
638
|
-
const state = this.subscriptions.get(message.subscriptionId);
|
|
639
|
-
if (state) {
|
|
640
|
-
state.error = error2;
|
|
641
|
-
state.loading = false;
|
|
642
|
-
this.notifySubscriptionListeners(message.subscriptionId);
|
|
643
|
-
state.options.onError?.(error2);
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
if (message.callId) {
|
|
647
|
-
const pending = this.pendingCalls.get(message.callId);
|
|
648
|
-
if (pending) {
|
|
649
|
-
this.pendingCalls.delete(message.callId);
|
|
650
|
-
pending.reject(error2);
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
/**
|
|
655
|
-
* Resubscribe to all subscriptions after reconnect via HTTP
|
|
656
|
-
*/
|
|
657
|
-
resubscribeAll() {
|
|
658
|
-
for (const [id, state] of this.subscriptions) {
|
|
659
|
-
if (state.options.resubscribeOnReconnect !== false) {
|
|
660
|
-
state.loading = true;
|
|
661
|
-
this.notifySubscriptionListeners(id);
|
|
662
|
-
this.sendSubscribeRequest(id, state.path, state.input).catch(
|
|
663
|
-
(error2) => {
|
|
664
|
-
state.error = error2 instanceof Error ? error2 : new Error(String(error2));
|
|
665
|
-
state.loading = false;
|
|
666
|
-
this.notifySubscriptionListeners(id);
|
|
667
|
-
state.options.onError?.(state.error);
|
|
668
|
-
}
|
|
669
|
-
);
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
/**
|
|
674
|
-
* Notify subscription listeners of state changes
|
|
675
|
-
*/
|
|
676
|
-
notifySubscriptionListeners(id) {
|
|
677
|
-
const state = this.subscriptions.get(id);
|
|
678
|
-
if (!state) return;
|
|
679
|
-
for (const listener of state.listeners) {
|
|
680
|
-
listener();
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
/**
|
|
684
|
-
* Notify connection state listeners
|
|
685
|
-
*/
|
|
686
|
-
notifyStateListeners() {
|
|
687
|
-
for (const listener of this.stateListeners) {
|
|
688
|
-
listener(this.connectionState);
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
/**
|
|
692
|
-
* Add a listener for subscription state changes
|
|
693
|
-
* Used internally by React hooks
|
|
694
|
-
*/
|
|
695
|
-
addSubscriptionListener(id, listener) {
|
|
696
|
-
const state = this.subscriptions.get(id);
|
|
697
|
-
if (!state) return () => {
|
|
698
|
-
};
|
|
699
|
-
state.listeners.add(listener);
|
|
700
|
-
return () => state.listeners.delete(listener);
|
|
701
|
-
}
|
|
702
|
-
/**
|
|
703
|
-
* Get subscription state
|
|
704
|
-
* Used internally by React hooks
|
|
705
|
-
*/
|
|
706
|
-
getSubscriptionState(id) {
|
|
707
|
-
return this.subscriptions.get(id);
|
|
708
|
-
}
|
|
709
|
-
/**
|
|
710
|
-
* Find subscription by path
|
|
711
|
-
*/
|
|
712
|
-
findSubscriptionByPath(path) {
|
|
713
|
-
debug(
|
|
714
|
-
"[optimistic] Searching for path:",
|
|
715
|
-
path,
|
|
716
|
-
"in subscriptions:",
|
|
717
|
-
Array.from(this.subscriptions.entries()).map(([id, s]) => ({
|
|
718
|
-
id,
|
|
719
|
-
path: s.path
|
|
720
|
-
}))
|
|
721
|
-
);
|
|
722
|
-
for (const [id, state] of this.subscriptions) {
|
|
723
|
-
if (state.path === path) {
|
|
724
|
-
return id;
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
return void 0;
|
|
728
|
-
}
|
|
729
|
-
/**
|
|
730
|
-
* Apply an optimistic update to a subscription's data
|
|
731
|
-
*/
|
|
732
|
-
applyOptimisticUpdate(subscriptionId, updater) {
|
|
733
|
-
const state = this.subscriptions.get(subscriptionId);
|
|
734
|
-
if (!state) {
|
|
735
|
-
debug(
|
|
736
|
-
"[optimistic] applyOptimisticUpdate: subscription not found:",
|
|
737
|
-
subscriptionId
|
|
738
|
-
);
|
|
739
|
-
return;
|
|
740
|
-
}
|
|
741
|
-
if (state.data === void 0) {
|
|
742
|
-
debug(
|
|
743
|
-
"[optimistic] applyOptimisticUpdate: subscription data is undefined (still loading?):",
|
|
744
|
-
subscriptionId
|
|
745
|
-
);
|
|
746
|
-
return;
|
|
747
|
-
}
|
|
748
|
-
debug(
|
|
749
|
-
"[optimistic] applyOptimisticUpdate: applying update, current data length:",
|
|
750
|
-
Array.isArray(state.data) ? state.data.length : "not array"
|
|
751
|
-
);
|
|
752
|
-
if (!this.optimisticUpdates.has(subscriptionId)) {
|
|
753
|
-
this.optimisticUpdates.set(
|
|
754
|
-
subscriptionId,
|
|
755
|
-
JSON.parse(JSON.stringify(state.data))
|
|
756
|
-
);
|
|
757
|
-
}
|
|
758
|
-
state.data = updater(state.data);
|
|
759
|
-
this.notifySubscriptionListeners(subscriptionId);
|
|
760
|
-
}
|
|
761
|
-
/**
|
|
762
|
-
* Rollback an optimistic update
|
|
763
|
-
*/
|
|
764
|
-
rollbackOptimisticUpdate(subscriptionId) {
|
|
765
|
-
const original = this.optimisticUpdates.get(subscriptionId);
|
|
766
|
-
if (original === void 0) return;
|
|
767
|
-
const state = this.subscriptions.get(subscriptionId);
|
|
768
|
-
if (state) {
|
|
769
|
-
state.data = original;
|
|
770
|
-
this.notifySubscriptionListeners(subscriptionId);
|
|
771
|
-
}
|
|
772
|
-
this.optimisticUpdates.delete(subscriptionId);
|
|
773
|
-
}
|
|
774
|
-
/**
|
|
775
|
-
* Clear optimistic state (called when real data arrives)
|
|
776
|
-
*/
|
|
777
|
-
clearOptimisticUpdate(subscriptionId) {
|
|
778
|
-
this.optimisticUpdates.delete(subscriptionId);
|
|
779
|
-
}
|
|
780
|
-
};
|
|
781
|
-
function createReactiveClient(config) {
|
|
782
|
-
const client = new ReactiveClient(config);
|
|
783
|
-
return createTypedProxy(client, []);
|
|
784
|
-
}
|
|
785
|
-
function createTypedProxy(client, path) {
|
|
786
|
-
return new Proxy(
|
|
787
|
-
{},
|
|
788
|
-
{
|
|
789
|
-
get(_target, prop) {
|
|
790
|
-
if (prop === "_client") return client;
|
|
791
|
-
if (prop === "connect") return () => client.connect();
|
|
792
|
-
if (prop === "disconnect") return () => client.disconnect();
|
|
793
|
-
if (typeof prop !== "string") return void 0;
|
|
794
|
-
const newPath = [...path, prop];
|
|
795
|
-
if (prop === "useSubscription") {
|
|
796
|
-
return (input, options) => {
|
|
797
|
-
const parentPath = path.join(".");
|
|
798
|
-
return client.subscribe(parentPath, { ...options, input });
|
|
799
|
-
};
|
|
800
|
-
}
|
|
801
|
-
if (prop === "mutate") {
|
|
802
|
-
return async (input) => {
|
|
803
|
-
const parentPath = path.join(".");
|
|
804
|
-
const invalidatesPath = path.length >= 2 ? `${path.slice(0, -1).join(".")}.list` : void 0;
|
|
805
|
-
const updateType = parentPath.endsWith(".delete") ? "remove" : "merge";
|
|
806
|
-
return client.call(parentPath, input, {
|
|
807
|
-
invalidates: invalidatesPath,
|
|
808
|
-
optimisticUpdate: updateType
|
|
809
|
-
});
|
|
810
|
-
};
|
|
811
|
-
}
|
|
812
|
-
return createTypedProxy(client, newPath);
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
);
|
|
816
|
-
}
|
|
817
|
-
var ReactiveClientContext = createContext(null);
|
|
818
|
-
function ReactiveClientProvider({
|
|
819
|
-
children,
|
|
820
|
-
config,
|
|
821
|
-
client: externalClient
|
|
822
|
-
}) {
|
|
823
|
-
const url = config?.url;
|
|
824
|
-
const hasUrl = Boolean(url);
|
|
825
|
-
const [client, setClient] = useState(
|
|
826
|
-
externalClient ?? null
|
|
827
|
-
);
|
|
828
|
-
useEffect(() => {
|
|
829
|
-
if (externalClient || !url) return;
|
|
830
|
-
if (!client) {
|
|
831
|
-
const newClient = new ReactiveClient({ ...config, url });
|
|
832
|
-
setClient(newClient);
|
|
833
|
-
}
|
|
834
|
-
}, [url, externalClient]);
|
|
835
|
-
useEffect(() => {
|
|
836
|
-
if (!client) return;
|
|
837
|
-
void client.connect();
|
|
838
|
-
return () => {
|
|
839
|
-
client.disconnect();
|
|
840
|
-
};
|
|
841
|
-
}, [client]);
|
|
842
|
-
useEffect(() => {
|
|
843
|
-
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
844
|
-
return;
|
|
845
|
-
}
|
|
846
|
-
if (!client) return;
|
|
847
|
-
const attemptReconnect = () => {
|
|
848
|
-
const state = client.getConnectionState();
|
|
849
|
-
if (state === "connected") {
|
|
850
|
-
return;
|
|
851
|
-
}
|
|
852
|
-
if (state === "connecting") {
|
|
853
|
-
return;
|
|
854
|
-
}
|
|
855
|
-
if (state === "reconnecting") {
|
|
856
|
-
debug(
|
|
857
|
-
"[dynamodb-reactive] Tab became active during reconnect backoff, forcing reconnect..."
|
|
858
|
-
);
|
|
859
|
-
client.forceReconnect();
|
|
860
|
-
} else {
|
|
861
|
-
debug(
|
|
862
|
-
"[dynamodb-reactive] Tab became active while disconnected, reconnecting..."
|
|
863
|
-
);
|
|
864
|
-
void client.connect();
|
|
865
|
-
}
|
|
866
|
-
};
|
|
867
|
-
const handleVisibilityChange = () => {
|
|
868
|
-
if (document.visibilityState === "visible") {
|
|
869
|
-
attemptReconnect();
|
|
870
|
-
}
|
|
871
|
-
};
|
|
872
|
-
const handleFocus = () => {
|
|
873
|
-
attemptReconnect();
|
|
874
|
-
};
|
|
875
|
-
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
876
|
-
window.addEventListener("focus", handleFocus);
|
|
877
|
-
return () => {
|
|
878
|
-
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
879
|
-
window.removeEventListener("focus", handleFocus);
|
|
880
|
-
};
|
|
881
|
-
}, [client]);
|
|
882
|
-
useEffect(() => {
|
|
883
|
-
if (!hasUrl && process.env.NODE_ENV === "development") {
|
|
884
|
-
warn(
|
|
885
|
-
"[dynamodb-reactive] WebSocket URL not configured, real-time features disabled"
|
|
886
|
-
);
|
|
887
|
-
}
|
|
888
|
-
}, [hasUrl]);
|
|
889
|
-
return /* @__PURE__ */ jsx(ReactiveClientContext.Provider, { value: client, children });
|
|
890
|
-
}
|
|
891
|
-
function useReactiveClient() {
|
|
892
|
-
return useContext(ReactiveClientContext);
|
|
893
|
-
}
|
|
894
|
-
function useReactiveClientOrThrow() {
|
|
895
|
-
const client = useContext(ReactiveClientContext);
|
|
896
|
-
if (!client) {
|
|
897
|
-
throw new Error(
|
|
898
|
-
"useReactiveClientOrThrow must be used within a ReactiveClientProvider with a configured URL"
|
|
899
|
-
);
|
|
900
|
-
}
|
|
901
|
-
return client;
|
|
902
|
-
}
|
|
903
|
-
function useConnectionState() {
|
|
904
|
-
const client = useReactiveClient();
|
|
905
|
-
return useSyncExternalStore(
|
|
906
|
-
(callback) => {
|
|
907
|
-
if (!client) return () => {
|
|
908
|
-
};
|
|
909
|
-
return client.onConnectionStateChange(callback);
|
|
910
|
-
},
|
|
911
|
-
() => client ? client.getConnectionState() : "disabled",
|
|
912
|
-
() => "disabled"
|
|
913
|
-
);
|
|
914
|
-
}
|
|
915
|
-
function useSubscription(path, options = {}) {
|
|
916
|
-
const client = useReactiveClient();
|
|
917
|
-
const subscriptionRef = useRef(null);
|
|
918
|
-
const [state, setState] = useState({
|
|
919
|
-
data: void 0,
|
|
920
|
-
loading: !client ? false : true,
|
|
921
|
-
error: void 0
|
|
922
|
-
});
|
|
923
|
-
const inputKey = JSON.stringify(options.input);
|
|
924
|
-
const onDataRef = useRef(options.onData);
|
|
925
|
-
const onErrorRef = useRef(options.onError);
|
|
926
|
-
onDataRef.current = options.onData;
|
|
927
|
-
onErrorRef.current = options.onError;
|
|
928
|
-
useEffect(() => {
|
|
929
|
-
if (!client) return;
|
|
930
|
-
const input = inputKey ? JSON.parse(inputKey) : void 0;
|
|
931
|
-
const subscription = client.subscribe(path, {
|
|
932
|
-
input,
|
|
933
|
-
onData: (data) => {
|
|
934
|
-
setState((prev) => ({
|
|
935
|
-
...prev,
|
|
936
|
-
data,
|
|
937
|
-
loading: false,
|
|
938
|
-
error: void 0
|
|
939
|
-
}));
|
|
940
|
-
onDataRef.current?.(data);
|
|
941
|
-
},
|
|
942
|
-
onError: (error2) => {
|
|
943
|
-
setState((prev) => ({
|
|
944
|
-
...prev,
|
|
945
|
-
error: error2,
|
|
946
|
-
loading: false
|
|
947
|
-
}));
|
|
948
|
-
onErrorRef.current?.(error2);
|
|
949
|
-
}
|
|
950
|
-
});
|
|
951
|
-
subscriptionRef.current = subscription;
|
|
952
|
-
const unregisterListener = client.addSubscriptionListener(
|
|
953
|
-
subscription.id,
|
|
954
|
-
() => {
|
|
955
|
-
debug(
|
|
956
|
-
"[optimistic] useSubscription listener fired for:",
|
|
957
|
-
path,
|
|
958
|
-
"data length:",
|
|
959
|
-
Array.isArray(subscription.data) ? subscription.data.length : "not array"
|
|
960
|
-
);
|
|
961
|
-
setState({
|
|
962
|
-
data: subscription.data,
|
|
963
|
-
loading: subscription.loading,
|
|
964
|
-
error: subscription.error
|
|
965
|
-
});
|
|
966
|
-
}
|
|
967
|
-
);
|
|
968
|
-
setState({
|
|
969
|
-
data: subscription.data,
|
|
970
|
-
loading: subscription.loading,
|
|
971
|
-
error: subscription.error
|
|
972
|
-
});
|
|
973
|
-
return () => {
|
|
974
|
-
unregisterListener();
|
|
975
|
-
subscription.unsubscribe();
|
|
976
|
-
subscriptionRef.current = null;
|
|
977
|
-
};
|
|
978
|
-
}, [client, path, inputKey]);
|
|
979
|
-
const refetch = async () => {
|
|
980
|
-
if (subscriptionRef.current) {
|
|
981
|
-
setState((prev) => ({ ...prev, loading: true }));
|
|
982
|
-
await subscriptionRef.current.refetch();
|
|
983
|
-
}
|
|
984
|
-
};
|
|
985
|
-
return {
|
|
986
|
-
...state,
|
|
987
|
-
disabled: !client,
|
|
988
|
-
refetch
|
|
989
|
-
};
|
|
990
|
-
}
|
|
991
|
-
function useMutation(path, options) {
|
|
992
|
-
const client = useReactiveClient();
|
|
993
|
-
const [state, setState] = useState({
|
|
994
|
-
data: void 0,
|
|
995
|
-
loading: false,
|
|
996
|
-
error: void 0
|
|
997
|
-
});
|
|
998
|
-
const autoInvalidates = options?.invalidates ?? (() => {
|
|
999
|
-
const parts = path.split(".");
|
|
1000
|
-
if (parts.length >= 2) {
|
|
1001
|
-
return `${parts.slice(0, -1).join(".")}.list`;
|
|
1002
|
-
}
|
|
1003
|
-
return void 0;
|
|
1004
|
-
})();
|
|
1005
|
-
const autoUpdateType = options?.optimisticUpdate ?? (path.endsWith(".delete") ? "remove" : "merge");
|
|
1006
|
-
const mutate = async (input) => {
|
|
1007
|
-
if (!client) {
|
|
1008
|
-
throw new Error("WebSocket not configured - mutations are disabled");
|
|
1009
|
-
}
|
|
1010
|
-
debug("[optimistic] useMutation.mutate called:", {
|
|
1011
|
-
path,
|
|
1012
|
-
autoInvalidates,
|
|
1013
|
-
autoUpdateType,
|
|
1014
|
-
input
|
|
1015
|
-
});
|
|
1016
|
-
setState((prev) => ({ ...prev, loading: true, error: void 0 }));
|
|
1017
|
-
try {
|
|
1018
|
-
const result = await client.call(path, input, {
|
|
1019
|
-
invalidates: autoInvalidates,
|
|
1020
|
-
optimisticUpdate: autoUpdateType
|
|
1021
|
-
});
|
|
1022
|
-
setState({ data: result, loading: false, error: void 0 });
|
|
1023
|
-
return result;
|
|
1024
|
-
} catch (error2) {
|
|
1025
|
-
const err = error2 instanceof Error ? error2 : new Error(String(error2));
|
|
1026
|
-
setState((prev) => ({ ...prev, loading: false, error: err }));
|
|
1027
|
-
throw err;
|
|
1028
|
-
}
|
|
1029
|
-
};
|
|
1030
|
-
const reset = () => {
|
|
1031
|
-
setState({ data: void 0, loading: false, error: void 0 });
|
|
1032
|
-
};
|
|
1033
|
-
return {
|
|
1034
|
-
mutate,
|
|
1035
|
-
...state,
|
|
1036
|
-
disabled: !client,
|
|
1037
|
-
reset
|
|
1038
|
-
};
|
|
1039
|
-
}
|
|
1040
|
-
function useClient() {
|
|
1041
|
-
const client = useReactiveClient();
|
|
1042
|
-
function buildProxy(path) {
|
|
1043
|
-
return new Proxy(
|
|
1044
|
-
{},
|
|
1045
|
-
{
|
|
1046
|
-
get(_target, prop) {
|
|
1047
|
-
if (prop === "useQuery") {
|
|
1048
|
-
return function useQueryHook(input, options) {
|
|
1049
|
-
const pathStr = path.join(".");
|
|
1050
|
-
return useSubscription(pathStr, { ...options, input });
|
|
1051
|
-
};
|
|
1052
|
-
}
|
|
1053
|
-
if (prop === "useMutation") {
|
|
1054
|
-
return function useMutationHook(options) {
|
|
1055
|
-
const pathStr = path.join(".");
|
|
1056
|
-
return useMutation(pathStr, options);
|
|
1057
|
-
};
|
|
1058
|
-
}
|
|
1059
|
-
if (prop === "setHeaders") {
|
|
1060
|
-
return (headers) => {
|
|
1061
|
-
client?.setHeaders(headers);
|
|
1062
|
-
};
|
|
1063
|
-
}
|
|
1064
|
-
return buildProxy([...path, prop]);
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
);
|
|
1068
|
-
}
|
|
1069
|
-
return buildProxy([]);
|
|
1070
|
-
}
|
|
1071
|
-
function createReactiveHooks() {
|
|
1072
|
-
return {
|
|
1073
|
-
useSubscription: (path, options) => useSubscription(path, options),
|
|
1074
|
-
useMutation: (path, options) => useMutation(path, options),
|
|
1075
|
-
useConnectionState,
|
|
1076
|
-
useReactiveClient
|
|
1077
|
-
};
|
|
1078
|
-
}
|
|
1079
|
-
function createProcedureHooks(path) {
|
|
1080
|
-
return {
|
|
1081
|
-
useSubscription: (input, options) => useSubscription(path, { ...options, input }),
|
|
1082
|
-
useMutation: (options) => useMutation(path, options)
|
|
1083
|
-
};
|
|
1084
|
-
}
|
|
1085
|
-
|
|
1086
|
-
export { ReactiveClient, ReactiveClientProvider, WebSocketManager, applyPatches, createProcedureHooks, createReactiveClient, createReactiveHooks, useClient, useConnectionState, useMutation, useReactiveClient, useReactiveClientOrThrow, useSubscription };
|
|
1087
|
-
//# sourceMappingURL=chunk-2A7GJ2UO.js.map
|
|
1088
|
-
//# sourceMappingURL=chunk-2A7GJ2UO.js.map
|