dynamodb-reactive 0.1.0 → 0.1.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/dist/chunk-HZ6JHAJJ.js +131 -0
- package/dist/chunk-HZ6JHAJJ.js.map +1 -0
- package/dist/chunk-KRZQWA2W.js +657 -0
- package/dist/chunk-KRZQWA2W.js.map +1 -0
- package/dist/client.d.ts +55 -2
- package/dist/client.js +2 -17
- package/dist/client.js.map +1 -1
- package/dist/core.d.ts +4 -2
- package/dist/core.js +2 -17
- package/dist/core.js.map +1 -1
- package/dist/index.d.ts +171 -2
- package/dist/index.js +2 -17
- package/dist/index.js.map +1 -1
- package/dist/infra.d.ts +189 -2
- package/dist/infra.js +358 -15
- package/dist/infra.js.map +1 -1
- package/dist/react-BMZQ8Mth.d.ts +371 -0
- package/dist/react.d.ts +3 -2
- package/dist/react.js +2 -17
- package/dist/react.js.map +1 -1
- package/dist/server.d.ts +631 -2
- package/dist/server.js +1687 -16
- package/dist/server.js.map +1 -1
- package/dist/table-CSJysZPQ.d.ts +85 -0
- package/dist/types-Ci7IieDA.d.ts +141 -0
- package/package.json +10 -6
- package/dist/client.d.ts.map +0 -1
- package/dist/core.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/infra.d.ts.map +0 -1
- package/dist/react.d.ts.map +0 -1
- package/dist/server.d.ts.map +0 -1
|
@@ -0,0 +1,657 @@
|
|
|
1
|
+
import { applyPatch } from 'fast-json-patch';
|
|
2
|
+
import { createContext, useState, useEffect, useContext, useSyncExternalStore, useRef } from 'react';
|
|
3
|
+
import { jsx } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
// ../client/src/patcher.ts
|
|
6
|
+
function applyPatches(document, patches) {
|
|
7
|
+
if (patches.length === 0) {
|
|
8
|
+
return document;
|
|
9
|
+
}
|
|
10
|
+
const operations = patches.map((patch) => {
|
|
11
|
+
const op = {
|
|
12
|
+
op: patch.op,
|
|
13
|
+
path: patch.path
|
|
14
|
+
};
|
|
15
|
+
if ("value" in patch && patch.value !== void 0) {
|
|
16
|
+
op.value = patch.value;
|
|
17
|
+
}
|
|
18
|
+
if ("from" in patch && patch.from !== void 0) {
|
|
19
|
+
op.from = patch.from;
|
|
20
|
+
}
|
|
21
|
+
return op;
|
|
22
|
+
});
|
|
23
|
+
try {
|
|
24
|
+
const result = applyPatch(
|
|
25
|
+
structuredClone(document),
|
|
26
|
+
operations,
|
|
27
|
+
true,
|
|
28
|
+
// Validate operations
|
|
29
|
+
false
|
|
30
|
+
// Don't mutate the original
|
|
31
|
+
);
|
|
32
|
+
return result.newDocument;
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error("Failed to apply patches:", error);
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ../client/src/websocket.ts
|
|
40
|
+
var WebSocketManager = class {
|
|
41
|
+
config;
|
|
42
|
+
ws = null;
|
|
43
|
+
state = "disconnected";
|
|
44
|
+
reconnectAttempts = 0;
|
|
45
|
+
reconnectTimer = null;
|
|
46
|
+
messageQueue = [];
|
|
47
|
+
messageHandlers = /* @__PURE__ */ new Set();
|
|
48
|
+
stateHandlers = /* @__PURE__ */ new Set();
|
|
49
|
+
constructor(config) {
|
|
50
|
+
this.config = {
|
|
51
|
+
autoReconnect: true,
|
|
52
|
+
reconnectDelay: 1e3,
|
|
53
|
+
maxReconnectAttempts: 10,
|
|
54
|
+
...config
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Get current connection state
|
|
59
|
+
*/
|
|
60
|
+
getState() {
|
|
61
|
+
return this.state;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Connect to the WebSocket server
|
|
65
|
+
*/
|
|
66
|
+
async connect() {
|
|
67
|
+
if (this.state === "connected" || this.state === "connecting") {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
this.setState("connecting");
|
|
71
|
+
try {
|
|
72
|
+
const url = await this.buildUrl();
|
|
73
|
+
this.ws = new WebSocket(url);
|
|
74
|
+
this.ws.onopen = () => {
|
|
75
|
+
this.setState("connected");
|
|
76
|
+
this.reconnectAttempts = 0;
|
|
77
|
+
this.flushMessageQueue();
|
|
78
|
+
this.config.onConnect?.();
|
|
79
|
+
};
|
|
80
|
+
this.ws.onclose = (event) => {
|
|
81
|
+
this.ws = null;
|
|
82
|
+
this.setState("disconnected");
|
|
83
|
+
this.config.onDisconnect?.();
|
|
84
|
+
if (this.config.autoReconnect && !event.wasClean) {
|
|
85
|
+
this.scheduleReconnect();
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
this.ws.onerror = () => {
|
|
89
|
+
const error = new Error("WebSocket error");
|
|
90
|
+
this.config.onError?.(error);
|
|
91
|
+
};
|
|
92
|
+
this.ws.onmessage = (event) => {
|
|
93
|
+
try {
|
|
94
|
+
const message = JSON.parse(event.data);
|
|
95
|
+
this.handleMessage(message);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.error("Failed to parse WebSocket message:", error);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
} catch (error) {
|
|
101
|
+
this.setState("disconnected");
|
|
102
|
+
this.config.onError?.(
|
|
103
|
+
error instanceof Error ? error : new Error(String(error))
|
|
104
|
+
);
|
|
105
|
+
if (this.config.autoReconnect) {
|
|
106
|
+
this.scheduleReconnect();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Disconnect from the WebSocket server
|
|
112
|
+
*/
|
|
113
|
+
disconnect() {
|
|
114
|
+
if (this.reconnectTimer) {
|
|
115
|
+
clearTimeout(this.reconnectTimer);
|
|
116
|
+
this.reconnectTimer = null;
|
|
117
|
+
}
|
|
118
|
+
if (this.ws) {
|
|
119
|
+
this.ws.close(1e3, "Client disconnect");
|
|
120
|
+
this.ws = null;
|
|
121
|
+
}
|
|
122
|
+
this.setState("disconnected");
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Send a message to the server
|
|
126
|
+
*/
|
|
127
|
+
send(message) {
|
|
128
|
+
if (this.state === "connected" && this.ws?.readyState === WebSocket.OPEN) {
|
|
129
|
+
this.ws.send(JSON.stringify(message));
|
|
130
|
+
} else {
|
|
131
|
+
this.messageQueue.push(message);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Subscribe to incoming messages
|
|
136
|
+
*/
|
|
137
|
+
onMessage(handler) {
|
|
138
|
+
this.messageHandlers.add(handler);
|
|
139
|
+
return () => this.messageHandlers.delete(handler);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Subscribe to connection state changes
|
|
143
|
+
*/
|
|
144
|
+
onStateChange(handler) {
|
|
145
|
+
this.stateHandlers.add(handler);
|
|
146
|
+
return () => this.stateHandlers.delete(handler);
|
|
147
|
+
}
|
|
148
|
+
async buildUrl() {
|
|
149
|
+
let url = this.config.url;
|
|
150
|
+
if (this.config.auth) {
|
|
151
|
+
const token = typeof this.config.auth === "function" ? await this.config.auth() : this.config.auth;
|
|
152
|
+
const separator = url.includes("?") ? "&" : "?";
|
|
153
|
+
url = `${url}${separator}token=${encodeURIComponent(token)}`;
|
|
154
|
+
}
|
|
155
|
+
return url;
|
|
156
|
+
}
|
|
157
|
+
setState(state) {
|
|
158
|
+
this.state = state;
|
|
159
|
+
for (const handler of this.stateHandlers) {
|
|
160
|
+
handler(state);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
handleMessage(message) {
|
|
164
|
+
for (const handler of this.messageHandlers) {
|
|
165
|
+
handler(message);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
scheduleReconnect() {
|
|
169
|
+
if (this.reconnectAttempts >= this.config.maxReconnectAttempts) {
|
|
170
|
+
console.error("Max reconnection attempts reached");
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
this.setState("reconnecting");
|
|
174
|
+
this.reconnectAttempts++;
|
|
175
|
+
const delay = Math.min(
|
|
176
|
+
this.config.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1),
|
|
177
|
+
3e4
|
|
178
|
+
// Max 30 seconds
|
|
179
|
+
) * (0.5 + Math.random() * 0.5);
|
|
180
|
+
this.reconnectTimer = setTimeout(() => {
|
|
181
|
+
this.reconnectTimer = null;
|
|
182
|
+
void this.connect();
|
|
183
|
+
}, delay);
|
|
184
|
+
}
|
|
185
|
+
flushMessageQueue() {
|
|
186
|
+
const queue = this.messageQueue;
|
|
187
|
+
this.messageQueue = [];
|
|
188
|
+
for (const message of queue) {
|
|
189
|
+
this.send(message);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// ../client/src/client.ts
|
|
195
|
+
function generateId() {
|
|
196
|
+
return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
197
|
+
}
|
|
198
|
+
var ReactiveClient = class {
|
|
199
|
+
wsManager;
|
|
200
|
+
subscriptions = /* @__PURE__ */ new Map();
|
|
201
|
+
pendingCalls = /* @__PURE__ */ new Map();
|
|
202
|
+
connectionState = "disconnected";
|
|
203
|
+
stateListeners = /* @__PURE__ */ new Set();
|
|
204
|
+
constructor(config) {
|
|
205
|
+
this.wsManager = new WebSocketManager(config);
|
|
206
|
+
this.wsManager.onMessage((message) => this.handleMessage(message));
|
|
207
|
+
this.wsManager.onStateChange((state) => {
|
|
208
|
+
this.connectionState = state;
|
|
209
|
+
this.notifyStateListeners();
|
|
210
|
+
if (state === "connected") {
|
|
211
|
+
this.resubscribeAll();
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Connect to the server
|
|
217
|
+
*/
|
|
218
|
+
async connect() {
|
|
219
|
+
await this.wsManager.connect();
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Disconnect from the server
|
|
223
|
+
*/
|
|
224
|
+
disconnect() {
|
|
225
|
+
this.wsManager.disconnect();
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Get current connection state
|
|
229
|
+
*/
|
|
230
|
+
getConnectionState() {
|
|
231
|
+
return this.connectionState;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Subscribe to connection state changes
|
|
235
|
+
*/
|
|
236
|
+
onConnectionStateChange(listener) {
|
|
237
|
+
this.stateListeners.add(listener);
|
|
238
|
+
return () => this.stateListeners.delete(listener);
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Subscribe to a procedure
|
|
242
|
+
*/
|
|
243
|
+
subscribe(path, options = {}) {
|
|
244
|
+
const id = generateId();
|
|
245
|
+
const state = {
|
|
246
|
+
id,
|
|
247
|
+
path,
|
|
248
|
+
input: options.input,
|
|
249
|
+
data: void 0,
|
|
250
|
+
loading: true,
|
|
251
|
+
error: void 0,
|
|
252
|
+
listeners: /* @__PURE__ */ new Set(),
|
|
253
|
+
options
|
|
254
|
+
};
|
|
255
|
+
this.subscriptions.set(id, state);
|
|
256
|
+
this.wsManager.send({
|
|
257
|
+
type: "subscribe",
|
|
258
|
+
subscriptionId: id,
|
|
259
|
+
path,
|
|
260
|
+
input: options.input
|
|
261
|
+
});
|
|
262
|
+
const subscription = {
|
|
263
|
+
get data() {
|
|
264
|
+
return state.data;
|
|
265
|
+
},
|
|
266
|
+
get loading() {
|
|
267
|
+
return state.loading;
|
|
268
|
+
},
|
|
269
|
+
get error() {
|
|
270
|
+
return state.error;
|
|
271
|
+
},
|
|
272
|
+
unsubscribe: () => this.unsubscribe(id),
|
|
273
|
+
refetch: () => this.refetch(id)
|
|
274
|
+
};
|
|
275
|
+
return subscription;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Call a mutation procedure
|
|
279
|
+
*/
|
|
280
|
+
async call(path, input) {
|
|
281
|
+
const callId = generateId();
|
|
282
|
+
return new Promise((resolve, reject) => {
|
|
283
|
+
this.pendingCalls.set(callId, {
|
|
284
|
+
resolve,
|
|
285
|
+
reject
|
|
286
|
+
});
|
|
287
|
+
this.wsManager.send({
|
|
288
|
+
type: "call",
|
|
289
|
+
callId,
|
|
290
|
+
path,
|
|
291
|
+
input
|
|
292
|
+
});
|
|
293
|
+
setTimeout(() => {
|
|
294
|
+
if (this.pendingCalls.has(callId)) {
|
|
295
|
+
this.pendingCalls.delete(callId);
|
|
296
|
+
reject(new Error("Call timeout"));
|
|
297
|
+
}
|
|
298
|
+
}, 3e4);
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Unsubscribe from a subscription
|
|
303
|
+
*/
|
|
304
|
+
unsubscribe(id) {
|
|
305
|
+
const state = this.subscriptions.get(id);
|
|
306
|
+
if (!state) return;
|
|
307
|
+
this.subscriptions.delete(id);
|
|
308
|
+
this.wsManager.send({
|
|
309
|
+
type: "unsubscribe",
|
|
310
|
+
subscriptionId: id
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Refetch a subscription
|
|
315
|
+
*/
|
|
316
|
+
async refetch(id) {
|
|
317
|
+
const state = this.subscriptions.get(id);
|
|
318
|
+
if (!state) return;
|
|
319
|
+
state.loading = true;
|
|
320
|
+
this.notifySubscriptionListeners(id);
|
|
321
|
+
this.wsManager.send({
|
|
322
|
+
type: "subscribe",
|
|
323
|
+
subscriptionId: id,
|
|
324
|
+
path: state.path,
|
|
325
|
+
input: state.input
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Handle incoming server messages
|
|
330
|
+
*/
|
|
331
|
+
handleMessage(message) {
|
|
332
|
+
switch (message.type) {
|
|
333
|
+
case "snapshot":
|
|
334
|
+
this.handleSnapshot(message.subscriptionId, message.data);
|
|
335
|
+
break;
|
|
336
|
+
case "patch":
|
|
337
|
+
this.handlePatch(message.subscriptionId, message.patches);
|
|
338
|
+
break;
|
|
339
|
+
case "result":
|
|
340
|
+
this.handleResult(message.callId, message.data);
|
|
341
|
+
break;
|
|
342
|
+
case "error":
|
|
343
|
+
this.handleError(message);
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Handle a snapshot message
|
|
349
|
+
*/
|
|
350
|
+
handleSnapshot(subscriptionId, data) {
|
|
351
|
+
const state = this.subscriptions.get(subscriptionId);
|
|
352
|
+
if (!state) return;
|
|
353
|
+
state.data = data;
|
|
354
|
+
state.loading = false;
|
|
355
|
+
state.error = void 0;
|
|
356
|
+
this.notifySubscriptionListeners(subscriptionId);
|
|
357
|
+
state.options.onData?.(data);
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Handle a patch message
|
|
361
|
+
*/
|
|
362
|
+
handlePatch(subscriptionId, patches) {
|
|
363
|
+
const state = this.subscriptions.get(subscriptionId);
|
|
364
|
+
if (!state || state.data === void 0) return;
|
|
365
|
+
try {
|
|
366
|
+
state.data = applyPatches(state.data, patches);
|
|
367
|
+
this.notifySubscriptionListeners(subscriptionId);
|
|
368
|
+
state.options.onData?.(state.data);
|
|
369
|
+
} catch (error) {
|
|
370
|
+
state.error = error instanceof Error ? error : new Error(String(error));
|
|
371
|
+
this.notifySubscriptionListeners(subscriptionId);
|
|
372
|
+
state.options.onError?.(state.error);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Handle a result message
|
|
377
|
+
*/
|
|
378
|
+
handleResult(callId, data) {
|
|
379
|
+
const pending = this.pendingCalls.get(callId);
|
|
380
|
+
if (!pending) return;
|
|
381
|
+
this.pendingCalls.delete(callId);
|
|
382
|
+
pending.resolve(data);
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Handle an error message
|
|
386
|
+
*/
|
|
387
|
+
handleError(message) {
|
|
388
|
+
const error = new Error(message.message);
|
|
389
|
+
if (message.subscriptionId) {
|
|
390
|
+
const state = this.subscriptions.get(message.subscriptionId);
|
|
391
|
+
if (state) {
|
|
392
|
+
state.error = error;
|
|
393
|
+
state.loading = false;
|
|
394
|
+
this.notifySubscriptionListeners(message.subscriptionId);
|
|
395
|
+
state.options.onError?.(error);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
if (message.callId) {
|
|
399
|
+
const pending = this.pendingCalls.get(message.callId);
|
|
400
|
+
if (pending) {
|
|
401
|
+
this.pendingCalls.delete(message.callId);
|
|
402
|
+
pending.reject(error);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Resubscribe to all subscriptions after reconnect
|
|
408
|
+
*/
|
|
409
|
+
resubscribeAll() {
|
|
410
|
+
for (const [id, state] of this.subscriptions) {
|
|
411
|
+
if (state.options.resubscribeOnReconnect !== false) {
|
|
412
|
+
state.loading = true;
|
|
413
|
+
this.notifySubscriptionListeners(id);
|
|
414
|
+
this.wsManager.send({
|
|
415
|
+
type: "subscribe",
|
|
416
|
+
subscriptionId: id,
|
|
417
|
+
path: state.path,
|
|
418
|
+
input: state.input
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Notify subscription listeners of state changes
|
|
425
|
+
*/
|
|
426
|
+
notifySubscriptionListeners(id) {
|
|
427
|
+
const state = this.subscriptions.get(id);
|
|
428
|
+
if (!state) return;
|
|
429
|
+
for (const listener of state.listeners) {
|
|
430
|
+
listener();
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Notify connection state listeners
|
|
435
|
+
*/
|
|
436
|
+
notifyStateListeners() {
|
|
437
|
+
for (const listener of this.stateListeners) {
|
|
438
|
+
listener(this.connectionState);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Add a listener for subscription state changes
|
|
443
|
+
* Used internally by React hooks
|
|
444
|
+
*/
|
|
445
|
+
addSubscriptionListener(id, listener) {
|
|
446
|
+
const state = this.subscriptions.get(id);
|
|
447
|
+
if (!state) return () => {
|
|
448
|
+
};
|
|
449
|
+
state.listeners.add(listener);
|
|
450
|
+
return () => state.listeners.delete(listener);
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Get subscription state
|
|
454
|
+
* Used internally by React hooks
|
|
455
|
+
*/
|
|
456
|
+
getSubscriptionState(id) {
|
|
457
|
+
return this.subscriptions.get(id);
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
function createReactiveClient(config) {
|
|
461
|
+
const client = new ReactiveClient(config);
|
|
462
|
+
return createTypedProxy(client, []);
|
|
463
|
+
}
|
|
464
|
+
function createTypedProxy(client, path) {
|
|
465
|
+
return new Proxy(
|
|
466
|
+
{},
|
|
467
|
+
{
|
|
468
|
+
get(_target, prop) {
|
|
469
|
+
if (prop === "_client") return client;
|
|
470
|
+
if (prop === "connect") return () => client.connect();
|
|
471
|
+
if (prop === "disconnect") return () => client.disconnect();
|
|
472
|
+
if (typeof prop !== "string") return void 0;
|
|
473
|
+
const newPath = [...path, prop];
|
|
474
|
+
if (prop === "useSubscription") {
|
|
475
|
+
return (input, options) => {
|
|
476
|
+
const parentPath = path.join(".");
|
|
477
|
+
return client.subscribe(parentPath, { ...options, input });
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
if (prop === "mutate") {
|
|
481
|
+
return async (input) => {
|
|
482
|
+
const parentPath = path.join(".");
|
|
483
|
+
return client.call(parentPath, input);
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
return createTypedProxy(client, newPath);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
var ReactiveClientContext = createContext(null);
|
|
492
|
+
function ReactiveClientProvider({
|
|
493
|
+
children,
|
|
494
|
+
config,
|
|
495
|
+
client: externalClient
|
|
496
|
+
}) {
|
|
497
|
+
const url = config?.url;
|
|
498
|
+
const hasUrl = Boolean(url);
|
|
499
|
+
const [client, setClient] = useState(
|
|
500
|
+
externalClient ?? null
|
|
501
|
+
);
|
|
502
|
+
useEffect(() => {
|
|
503
|
+
if (externalClient || !url) return;
|
|
504
|
+
if (!client) {
|
|
505
|
+
const newClient = new ReactiveClient({ ...config, url });
|
|
506
|
+
setClient(newClient);
|
|
507
|
+
}
|
|
508
|
+
}, [url, externalClient]);
|
|
509
|
+
useEffect(() => {
|
|
510
|
+
if (!client) return;
|
|
511
|
+
void client.connect();
|
|
512
|
+
return () => {
|
|
513
|
+
client.disconnect();
|
|
514
|
+
};
|
|
515
|
+
}, [client]);
|
|
516
|
+
useEffect(() => {
|
|
517
|
+
if (!hasUrl && process.env.NODE_ENV === "development") {
|
|
518
|
+
console.warn(
|
|
519
|
+
"[dynamodb-reactive] WebSocket URL not configured, real-time features disabled"
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
}, [hasUrl]);
|
|
523
|
+
return /* @__PURE__ */ jsx(ReactiveClientContext.Provider, { value: client, children });
|
|
524
|
+
}
|
|
525
|
+
function useReactiveClient() {
|
|
526
|
+
return useContext(ReactiveClientContext);
|
|
527
|
+
}
|
|
528
|
+
function useReactiveClientOrThrow() {
|
|
529
|
+
const client = useContext(ReactiveClientContext);
|
|
530
|
+
if (!client) {
|
|
531
|
+
throw new Error(
|
|
532
|
+
"useReactiveClientOrThrow must be used within a ReactiveClientProvider with a configured URL"
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
return client;
|
|
536
|
+
}
|
|
537
|
+
function useConnectionState() {
|
|
538
|
+
const client = useReactiveClient();
|
|
539
|
+
return useSyncExternalStore(
|
|
540
|
+
(callback) => {
|
|
541
|
+
if (!client) return () => {
|
|
542
|
+
};
|
|
543
|
+
return client.onConnectionStateChange(callback);
|
|
544
|
+
},
|
|
545
|
+
() => client ? client.getConnectionState() : "disabled",
|
|
546
|
+
() => "disabled"
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
function useSubscription(path, options = {}) {
|
|
550
|
+
const client = useReactiveClient();
|
|
551
|
+
const subscriptionRef = useRef(null);
|
|
552
|
+
const [state, setState] = useState({
|
|
553
|
+
data: void 0,
|
|
554
|
+
loading: !client ? false : true,
|
|
555
|
+
error: void 0
|
|
556
|
+
});
|
|
557
|
+
const inputKey = JSON.stringify(options.input);
|
|
558
|
+
const onDataRef = useRef(options.onData);
|
|
559
|
+
const onErrorRef = useRef(options.onError);
|
|
560
|
+
onDataRef.current = options.onData;
|
|
561
|
+
onErrorRef.current = options.onError;
|
|
562
|
+
useEffect(() => {
|
|
563
|
+
if (!client) return;
|
|
564
|
+
const input = inputKey ? JSON.parse(inputKey) : void 0;
|
|
565
|
+
const subscription = client.subscribe(path, {
|
|
566
|
+
input,
|
|
567
|
+
onData: (data) => {
|
|
568
|
+
setState((prev) => ({
|
|
569
|
+
...prev,
|
|
570
|
+
data,
|
|
571
|
+
loading: false,
|
|
572
|
+
error: void 0
|
|
573
|
+
}));
|
|
574
|
+
onDataRef.current?.(data);
|
|
575
|
+
},
|
|
576
|
+
onError: (error) => {
|
|
577
|
+
setState((prev) => ({
|
|
578
|
+
...prev,
|
|
579
|
+
error,
|
|
580
|
+
loading: false
|
|
581
|
+
}));
|
|
582
|
+
onErrorRef.current?.(error);
|
|
583
|
+
}
|
|
584
|
+
});
|
|
585
|
+
subscriptionRef.current = subscription;
|
|
586
|
+
setState({
|
|
587
|
+
data: subscription.data,
|
|
588
|
+
loading: subscription.loading,
|
|
589
|
+
error: subscription.error
|
|
590
|
+
});
|
|
591
|
+
return () => {
|
|
592
|
+
subscription.unsubscribe();
|
|
593
|
+
subscriptionRef.current = null;
|
|
594
|
+
};
|
|
595
|
+
}, [client, path, inputKey]);
|
|
596
|
+
const refetch = async () => {
|
|
597
|
+
if (subscriptionRef.current) {
|
|
598
|
+
setState((prev) => ({ ...prev, loading: true }));
|
|
599
|
+
await subscriptionRef.current.refetch();
|
|
600
|
+
}
|
|
601
|
+
};
|
|
602
|
+
return {
|
|
603
|
+
...state,
|
|
604
|
+
disabled: !client,
|
|
605
|
+
refetch
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
function useMutation(path) {
|
|
609
|
+
const client = useReactiveClient();
|
|
610
|
+
const [state, setState] = useState({
|
|
611
|
+
data: void 0,
|
|
612
|
+
loading: false,
|
|
613
|
+
error: void 0
|
|
614
|
+
});
|
|
615
|
+
const mutate = async (input) => {
|
|
616
|
+
if (!client) {
|
|
617
|
+
throw new Error("WebSocket not configured - mutations are disabled");
|
|
618
|
+
}
|
|
619
|
+
setState((prev) => ({ ...prev, loading: true, error: void 0 }));
|
|
620
|
+
try {
|
|
621
|
+
const result = await client.call(path, input);
|
|
622
|
+
setState({ data: result, loading: false, error: void 0 });
|
|
623
|
+
return result;
|
|
624
|
+
} catch (error) {
|
|
625
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
626
|
+
setState((prev) => ({ ...prev, loading: false, error: err }));
|
|
627
|
+
throw err;
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
const reset = () => {
|
|
631
|
+
setState({ data: void 0, loading: false, error: void 0 });
|
|
632
|
+
};
|
|
633
|
+
return {
|
|
634
|
+
mutate,
|
|
635
|
+
...state,
|
|
636
|
+
disabled: !client,
|
|
637
|
+
reset
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
function createReactiveHooks() {
|
|
641
|
+
return {
|
|
642
|
+
useSubscription: (path, options) => useSubscription(path, options),
|
|
643
|
+
useMutation: (path) => useMutation(path),
|
|
644
|
+
useConnectionState,
|
|
645
|
+
useReactiveClient
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
function createProcedureHooks(path) {
|
|
649
|
+
return {
|
|
650
|
+
useSubscription: (input, options) => useSubscription(path, { ...options, input }),
|
|
651
|
+
useMutation: () => useMutation(path)
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
export { ReactiveClient, ReactiveClientProvider, WebSocketManager, applyPatches, createProcedureHooks, createReactiveClient, createReactiveHooks, useConnectionState, useMutation, useReactiveClient, useReactiveClientOrThrow, useSubscription };
|
|
656
|
+
//# sourceMappingURL=chunk-KRZQWA2W.js.map
|
|
657
|
+
//# sourceMappingURL=chunk-KRZQWA2W.js.map
|