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
|
@@ -29,10 +29,17 @@ interface CallMessage {
|
|
|
29
29
|
path: string;
|
|
30
30
|
input: unknown;
|
|
31
31
|
}
|
|
32
|
-
|
|
32
|
+
interface InitMessage {
|
|
33
|
+
type: 'init';
|
|
34
|
+
}
|
|
35
|
+
type ClientMessage = InitMessage | SubscribeMessage | UnsubscribeMessage | CallMessage;
|
|
33
36
|
/**
|
|
34
37
|
* Messages sent from server to client
|
|
35
38
|
*/
|
|
39
|
+
interface ConnectedMessage {
|
|
40
|
+
type: 'connected';
|
|
41
|
+
connectionId: string;
|
|
42
|
+
}
|
|
36
43
|
interface SnapshotMessage {
|
|
37
44
|
type: 'snapshot';
|
|
38
45
|
subscriptionId: string;
|
|
@@ -54,7 +61,7 @@ interface ErrorMessage {
|
|
|
54
61
|
subscriptionId?: string;
|
|
55
62
|
callId?: string;
|
|
56
63
|
}
|
|
57
|
-
type ServerMessage = SnapshotMessage | PatchMessage | ResultMessage | ErrorMessage;
|
|
64
|
+
type ServerMessage = ConnectedMessage | SnapshotMessage | PatchMessage | ResultMessage | ErrorMessage;
|
|
58
65
|
/**
|
|
59
66
|
* Client configuration
|
|
60
67
|
*/
|
|
@@ -63,10 +70,21 @@ interface ReactiveClientConfig {
|
|
|
63
70
|
* WebSocket URL to connect to
|
|
64
71
|
*/
|
|
65
72
|
url: string;
|
|
73
|
+
/**
|
|
74
|
+
* HTTP URL for subscribe/call requests.
|
|
75
|
+
* Defaults to '/api/reactive' for Next.js apps.
|
|
76
|
+
*/
|
|
77
|
+
httpUrl?: string;
|
|
66
78
|
/**
|
|
67
79
|
* Authentication token or getter function
|
|
68
80
|
*/
|
|
69
81
|
auth?: string | (() => string | Promise<string>);
|
|
82
|
+
/**
|
|
83
|
+
* Custom headers to send with HTTP requests.
|
|
84
|
+
* Can be a static object or a getter function for dynamic headers.
|
|
85
|
+
* Useful for passing auth tokens or user context to the server.
|
|
86
|
+
*/
|
|
87
|
+
headers?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);
|
|
70
88
|
/**
|
|
71
89
|
* Enable automatic reconnection
|
|
72
90
|
* @default true
|
|
@@ -121,6 +139,10 @@ interface SubscriptionOptions<TInput> {
|
|
|
121
139
|
* Active subscription
|
|
122
140
|
*/
|
|
123
141
|
interface Subscription<TData> {
|
|
142
|
+
/**
|
|
143
|
+
* Subscription ID (used for registering listeners)
|
|
144
|
+
*/
|
|
145
|
+
id: string;
|
|
124
146
|
/**
|
|
125
147
|
* Current data
|
|
126
148
|
*/
|
|
@@ -155,6 +177,28 @@ type InferInput<T> = T extends {
|
|
|
155
177
|
type InferOutput<T> = T extends {
|
|
156
178
|
output: infer TOutput;
|
|
157
179
|
} ? TOutput : unknown;
|
|
180
|
+
/**
|
|
181
|
+
* Mutation options for optimistic updates
|
|
182
|
+
*/
|
|
183
|
+
interface MutationOptions<TInput = unknown, TOutput = unknown> {
|
|
184
|
+
/**
|
|
185
|
+
* Subscription path to optimistically update (e.g., 'todos.list')
|
|
186
|
+
* If not provided, auto-detected from mutation path (e.g., 'todos.toggle' -> 'todos.list')
|
|
187
|
+
*/
|
|
188
|
+
invalidates?: string;
|
|
189
|
+
/**
|
|
190
|
+
* How to apply the mutation result to subscription data (applied INSTANTLY before HTTP)
|
|
191
|
+
* - 'merge': Find item by input.id and merge input fields into it
|
|
192
|
+
* - 'remove': Remove item by input.id
|
|
193
|
+
* - Custom function for complex cases
|
|
194
|
+
*/
|
|
195
|
+
optimisticUpdate?: 'merge' | 'remove' | ((currentData: unknown[], input: TInput, result?: TOutput) => unknown[]);
|
|
196
|
+
/**
|
|
197
|
+
* For create operations (no input.id), provide the optimistic item to add.
|
|
198
|
+
* Can be the item object or a function that receives input and current data.
|
|
199
|
+
*/
|
|
200
|
+
optimisticData?: TOutput | ((input: TInput, currentData: unknown[]) => TOutput | undefined);
|
|
201
|
+
}
|
|
158
202
|
|
|
159
203
|
/**
|
|
160
204
|
* Internal subscription state
|
|
@@ -174,10 +218,14 @@ interface SubscriptionState<TData> {
|
|
|
174
218
|
*/
|
|
175
219
|
declare class ReactiveClient {
|
|
176
220
|
private wsManager;
|
|
221
|
+
private httpUrl;
|
|
222
|
+
private authGetter?;
|
|
223
|
+
private headersGetter?;
|
|
177
224
|
private subscriptions;
|
|
178
225
|
private pendingCalls;
|
|
179
226
|
private connectionState;
|
|
180
227
|
private stateListeners;
|
|
228
|
+
private optimisticUpdates;
|
|
181
229
|
constructor(config: ReactiveClientConfig);
|
|
182
230
|
/**
|
|
183
231
|
* Connect to the server
|
|
@@ -187,6 +235,11 @@ declare class ReactiveClient {
|
|
|
187
235
|
* Disconnect from the server
|
|
188
236
|
*/
|
|
189
237
|
disconnect(): void;
|
|
238
|
+
/**
|
|
239
|
+
* Update the headers getter for HTTP requests.
|
|
240
|
+
* Useful for dynamically setting auth or user context headers.
|
|
241
|
+
*/
|
|
242
|
+
setHeaders(headers: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>)): void;
|
|
190
243
|
/**
|
|
191
244
|
* Get current connection state
|
|
192
245
|
*/
|
|
@@ -196,23 +249,30 @@ declare class ReactiveClient {
|
|
|
196
249
|
*/
|
|
197
250
|
onConnectionStateChange(listener: (state: ConnectionState) => void): () => void;
|
|
198
251
|
/**
|
|
199
|
-
* Subscribe to a procedure
|
|
252
|
+
* Subscribe to a procedure via HTTP.
|
|
253
|
+
* Snapshot is returned via HTTP, patches come via WebSocket.
|
|
200
254
|
*/
|
|
201
255
|
subscribe<TData>(path: string, options?: SubscriptionOptions<unknown>): Subscription<TData>;
|
|
202
256
|
/**
|
|
203
|
-
*
|
|
257
|
+
* Send subscribe request via HTTP
|
|
204
258
|
*/
|
|
205
|
-
|
|
259
|
+
private sendSubscribeRequest;
|
|
260
|
+
/**
|
|
261
|
+
* Call a mutation procedure via HTTP with optimistic update support.
|
|
262
|
+
* All optimistic updates are applied INSTANTLY (before HTTP request).
|
|
263
|
+
*/
|
|
264
|
+
call<TInput, TOutput>(path: string, input: TInput, options?: MutationOptions<TInput, TOutput>): Promise<TOutput>;
|
|
206
265
|
/**
|
|
207
266
|
* Unsubscribe from a subscription
|
|
208
267
|
*/
|
|
209
268
|
private unsubscribe;
|
|
210
269
|
/**
|
|
211
|
-
* Refetch a subscription
|
|
270
|
+
* Refetch a subscription via HTTP
|
|
212
271
|
*/
|
|
213
272
|
private refetch;
|
|
214
273
|
/**
|
|
215
|
-
* Handle incoming server messages
|
|
274
|
+
* Handle incoming server messages.
|
|
275
|
+
* Note: 'connected' messages are handled by WebSocketManager before reaching here.
|
|
216
276
|
*/
|
|
217
277
|
private handleMessage;
|
|
218
278
|
/**
|
|
@@ -232,7 +292,7 @@ declare class ReactiveClient {
|
|
|
232
292
|
*/
|
|
233
293
|
private handleError;
|
|
234
294
|
/**
|
|
235
|
-
* Resubscribe to all subscriptions after reconnect
|
|
295
|
+
* Resubscribe to all subscriptions after reconnect via HTTP
|
|
236
296
|
*/
|
|
237
297
|
private resubscribeAll;
|
|
238
298
|
/**
|
|
@@ -253,6 +313,22 @@ declare class ReactiveClient {
|
|
|
253
313
|
* Used internally by React hooks
|
|
254
314
|
*/
|
|
255
315
|
getSubscriptionState<TData>(id: string): SubscriptionState<TData> | undefined;
|
|
316
|
+
/**
|
|
317
|
+
* Find subscription by path
|
|
318
|
+
*/
|
|
319
|
+
private findSubscriptionByPath;
|
|
320
|
+
/**
|
|
321
|
+
* Apply an optimistic update to a subscription's data
|
|
322
|
+
*/
|
|
323
|
+
private applyOptimisticUpdate;
|
|
324
|
+
/**
|
|
325
|
+
* Rollback an optimistic update
|
|
326
|
+
*/
|
|
327
|
+
private rollbackOptimisticUpdate;
|
|
328
|
+
/**
|
|
329
|
+
* Clear optimistic state (called when real data arrives)
|
|
330
|
+
*/
|
|
331
|
+
private clearOptimisticUpdate;
|
|
256
332
|
}
|
|
257
333
|
/**
|
|
258
334
|
* Create a type-safe reactive client
|
|
@@ -314,10 +390,15 @@ declare function useSubscription<TData>(path: string, options?: SubscriptionOpti
|
|
|
314
390
|
refetch: () => Promise<void>;
|
|
315
391
|
};
|
|
316
392
|
/**
|
|
317
|
-
* Hook for mutations
|
|
318
|
-
*
|
|
393
|
+
* Hook for mutations with automatic optimistic updates
|
|
394
|
+
*
|
|
395
|
+
* Auto-detection:
|
|
396
|
+
* - `invalidates`: Auto-detected from path (e.g., 'todos.toggle' -> 'todos.list')
|
|
397
|
+
* - `optimisticUpdate`: Auto-detected from path ('*.delete' -> 'remove', others -> 'merge')
|
|
398
|
+
*
|
|
399
|
+
* You can override these with explicit options.
|
|
319
400
|
*/
|
|
320
|
-
declare function useMutation<TInput, TOutput>(path: string): {
|
|
401
|
+
declare function useMutation<TInput, TOutput>(path: string, options?: MutationOptions<TInput, TOutput>): {
|
|
321
402
|
mutate: (input: TInput) => Promise<TOutput>;
|
|
322
403
|
data: TOutput | undefined;
|
|
323
404
|
loading: boolean;
|
|
@@ -326,7 +407,93 @@ declare function useMutation<TInput, TOutput>(path: string): {
|
|
|
326
407
|
reset: () => void;
|
|
327
408
|
};
|
|
328
409
|
/**
|
|
329
|
-
*
|
|
410
|
+
* Type helper for extracting procedure input type from ProcedureDefinition
|
|
411
|
+
* Uses Zod's internal _output property to extract the parsed type
|
|
412
|
+
* (Zod schemas have _input/_output as actual properties for type inference)
|
|
413
|
+
*/
|
|
414
|
+
type ProcedureInput<T> = T extends {
|
|
415
|
+
inputSchema?: {
|
|
416
|
+
_output: infer I;
|
|
417
|
+
};
|
|
418
|
+
} ? I : undefined;
|
|
419
|
+
/**
|
|
420
|
+
* Type helper for extracting procedure output type from ProcedureDefinition
|
|
421
|
+
* Extracts from resolver return type using any[] for flexible argument matching
|
|
422
|
+
*/
|
|
423
|
+
type ProcedureOutput<T> = T extends {
|
|
424
|
+
resolver: (...args: any[]) => infer TReturn;
|
|
425
|
+
} ? Awaited<TReturn> : unknown;
|
|
426
|
+
/**
|
|
427
|
+
* Typed client hooks for a router procedure
|
|
428
|
+
* Provides both useQuery and useMutation - use whichever matches your procedure type
|
|
429
|
+
*/
|
|
430
|
+
type TypedProcedureHooks<TInput, TOutput> = {
|
|
431
|
+
useQuery: (input: TInput, options?: Omit<SubscriptionOptions<TInput>, 'input'>) => {
|
|
432
|
+
data: TOutput | undefined;
|
|
433
|
+
loading: boolean;
|
|
434
|
+
error: Error | undefined;
|
|
435
|
+
disabled: boolean;
|
|
436
|
+
refetch: () => Promise<void>;
|
|
437
|
+
};
|
|
438
|
+
useMutation: (options?: MutationOptions<TInput, TOutput>) => {
|
|
439
|
+
mutate: (input: TInput) => Promise<TOutput>;
|
|
440
|
+
data: TOutput | undefined;
|
|
441
|
+
loading: boolean;
|
|
442
|
+
error: Error | undefined;
|
|
443
|
+
disabled: boolean;
|
|
444
|
+
reset: () => void;
|
|
445
|
+
};
|
|
446
|
+
};
|
|
447
|
+
/**
|
|
448
|
+
* Extract router definition from Router class or use T directly
|
|
449
|
+
*/
|
|
450
|
+
type ExtractRouterDefinition<T> = T extends {
|
|
451
|
+
definition: infer TDef;
|
|
452
|
+
} ? TDef : T;
|
|
453
|
+
/**
|
|
454
|
+
* Recursive type for typed client proxy
|
|
455
|
+
* Checks for 'resolver' property to identify procedures (all procedures have it)
|
|
456
|
+
*/
|
|
457
|
+
type TypedClientProxyRecursive<T> = T extends {
|
|
458
|
+
resolver: unknown;
|
|
459
|
+
} ? TypedProcedureHooks<ProcedureInput<T>, ProcedureOutput<T>> : {
|
|
460
|
+
[K in keyof T]: TypedClientProxyRecursive<T[K]>;
|
|
461
|
+
};
|
|
462
|
+
/**
|
|
463
|
+
* Client utilities available at the root level
|
|
464
|
+
*/
|
|
465
|
+
interface ClientUtilities {
|
|
466
|
+
/**
|
|
467
|
+
* Set custom headers to be sent with HTTP requests.
|
|
468
|
+
* Useful for auth context (e.g., user tokens, session info).
|
|
469
|
+
*/
|
|
470
|
+
setHeaders: (headers: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>)) => void;
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Typed client proxy combining router procedures and client utilities
|
|
474
|
+
*/
|
|
475
|
+
type TypedClientProxy<T> = TypedClientProxyRecursive<ExtractRouterDefinition<T>> & ClientUtilities;
|
|
476
|
+
/**
|
|
477
|
+
* Create a typed client proxy for use in React components.
|
|
478
|
+
*
|
|
479
|
+
* @example
|
|
480
|
+
* ```tsx
|
|
481
|
+
* const client = useClient<AppRouter>();
|
|
482
|
+
*
|
|
483
|
+
* // Queries (subscriptions)
|
|
484
|
+
* const { data, loading } = client.todos.list.useQuery({});
|
|
485
|
+
*
|
|
486
|
+
* // Mutations
|
|
487
|
+
* const { mutate, loading } = client.todos.toggle.useMutation();
|
|
488
|
+
* await mutate({ id: '123' });
|
|
489
|
+
*
|
|
490
|
+
* // Set custom headers for HTTP requests (e.g., for auth context)
|
|
491
|
+
* client.setHeaders({ 'Authorization': 'Bearer token' });
|
|
492
|
+
* ```
|
|
493
|
+
*/
|
|
494
|
+
declare function useClient<TRouter>(): TypedClientProxy<TRouter>;
|
|
495
|
+
/**
|
|
496
|
+
* @deprecated Use useClient<TRouter>() instead for typed proxy access
|
|
330
497
|
*/
|
|
331
498
|
declare function createReactiveHooks(): {
|
|
332
499
|
useSubscription: <TPath extends string, TData = unknown>(path: TPath, options?: SubscriptionOptions<unknown>) => {
|
|
@@ -336,7 +503,7 @@ declare function createReactiveHooks(): {
|
|
|
336
503
|
disabled: boolean;
|
|
337
504
|
refetch: () => Promise<void>;
|
|
338
505
|
};
|
|
339
|
-
useMutation: <TPath extends string, TInput = unknown, TOutput = unknown>(path: TPath) => {
|
|
506
|
+
useMutation: <TPath extends string, TInput = unknown, TOutput = unknown>(path: TPath, options?: MutationOptions<TInput, TOutput>) => {
|
|
340
507
|
mutate: (input: TInput) => Promise<TOutput>;
|
|
341
508
|
data: TOutput | undefined;
|
|
342
509
|
loading: boolean;
|
|
@@ -348,7 +515,7 @@ declare function createReactiveHooks(): {
|
|
|
348
515
|
useReactiveClient: typeof useReactiveClient;
|
|
349
516
|
};
|
|
350
517
|
/**
|
|
351
|
-
*
|
|
518
|
+
* @deprecated Use useClient<TRouter>() instead for typed proxy access
|
|
352
519
|
*/
|
|
353
520
|
declare function createProcedureHooks<TInput, TOutput>(path: string): {
|
|
354
521
|
useSubscription: (input?: TInput, options?: Omit<SubscriptionOptions<TInput>, "input">) => {
|
|
@@ -358,7 +525,7 @@ declare function createProcedureHooks<TInput, TOutput>(path: string): {
|
|
|
358
525
|
disabled: boolean;
|
|
359
526
|
refetch: () => Promise<void>;
|
|
360
527
|
};
|
|
361
|
-
useMutation: () => {
|
|
528
|
+
useMutation: (options?: MutationOptions<TInput, TOutput>) => {
|
|
362
529
|
mutate: (input: TInput) => Promise<TOutput>;
|
|
363
530
|
data: TOutput | undefined;
|
|
364
531
|
loading: boolean;
|
|
@@ -368,4 +535,4 @@ declare function createProcedureHooks<TInput, TOutput>(path: string): {
|
|
|
368
535
|
};
|
|
369
536
|
};
|
|
370
537
|
|
|
371
|
-
export { type ConnectionState as C, type ErrorMessage as E, type InferInput as I, type JsonPatch as J, type PatchMessage as P, type ReactiveClientConfig as R, type ServerMessage as S, type TypedReactiveClient as T, type UnsubscribeMessage as U, type ClientMessage as a, ReactiveClient as b, createReactiveClient as c, type CallMessage as d, type InferOutput as e, type ResultMessage as f, type SnapshotMessage as g, type SubscribeMessage as h, type Subscription as i, type SubscriptionOptions as j, createProcedureHooks as k, createReactiveHooks as l, ReactiveClientProvider as m,
|
|
538
|
+
export { type ConnectionState as C, type ErrorMessage as E, type InferInput as I, type JsonPatch as J, type MutationOptions as M, type PatchMessage as P, type ReactiveClientConfig as R, type ServerMessage as S, type TypedReactiveClient as T, type UnsubscribeMessage as U, type ClientMessage as a, ReactiveClient as b, createReactiveClient as c, type CallMessage as d, type InferOutput as e, type ResultMessage as f, type SnapshotMessage as g, type SubscribeMessage as h, type Subscription as i, type SubscriptionOptions as j, createProcedureHooks as k, createReactiveHooks as l, ReactiveClientProvider as m, useConnectionState as n, useMutation as o, useReactiveClient as p, useReactiveClientOrThrow as q, useSubscription as r, useClient as u };
|
package/dist/react.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { m as ReactiveClientProvider, k as createProcedureHooks, l as createReactiveHooks, u as
|
|
1
|
+
export { m as ReactiveClientProvider, k as createProcedureHooks, l as createReactiveHooks, u as useClient, n as useConnectionState, o as useMutation, p as useReactiveClient, q as useReactiveClientOrThrow, r as useSubscription } from './react-B8Q_XoCk.js';
|
|
2
2
|
import 'react/jsx-runtime';
|
|
3
3
|
import 'react';
|
package/dist/react.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { ReactiveClientProvider, createProcedureHooks, createReactiveHooks, useConnectionState, useMutation, useReactiveClient, useReactiveClientOrThrow, useSubscription } from './chunk-
|
|
1
|
+
export { ReactiveClientProvider, createProcedureHooks, createReactiveHooks, useClient, useConnectionState, useMutation, useReactiveClient, useReactiveClientOrThrow, useSubscription } from './chunk-MI2ZLLB2.js';
|
|
2
2
|
//# sourceMappingURL=react.js.map
|
|
3
3
|
//# sourceMappingURL=react.js.map
|
package/dist/server.d.ts
CHANGED
|
@@ -18,10 +18,18 @@ interface ProcedureContext<TContext = unknown> {
|
|
|
18
18
|
*/
|
|
19
19
|
interface DatabaseContext {
|
|
20
20
|
query<TTable extends AnyDynamoTable>(table: TTable): QueryBuilder<TTable>;
|
|
21
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Get a single item by key
|
|
23
|
+
* @template TItem - Optional explicit item type (defaults to TableItem<TTable>)
|
|
24
|
+
*/
|
|
25
|
+
get<TItem = never, TTable extends AnyDynamoTable = AnyDynamoTable>(table: TTable, key: TableKeyInput<TTable>): Promise<([TItem] extends [never] ? TableItem<TTable> : TItem) | null>;
|
|
22
26
|
put<TTable extends AnyDynamoTable>(table: TTable, item: TableItem<TTable>): Promise<void>;
|
|
23
27
|
delete<TTable extends AnyDynamoTable>(table: TTable, key: TableKeyInput<TTable>): Promise<void>;
|
|
24
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Update an item and return the updated item
|
|
30
|
+
* @template TItem - Optional explicit item type (defaults to TableItem<TTable>)
|
|
31
|
+
*/
|
|
32
|
+
update<TItem = never, TTable extends AnyDynamoTable = AnyDynamoTable>(table: TTable, key: TableKeyInput<TTable>, updates: Partial<[TItem] extends [never] ? TableItem<TTable> : TItem>): Promise<[TItem] extends [never] ? TableItem<TTable> : TItem>;
|
|
25
33
|
}
|
|
26
34
|
/**
|
|
27
35
|
* Extract the item type from a DynamoTable
|
|
@@ -111,6 +119,12 @@ interface QueryDependency {
|
|
|
111
119
|
fieldValue: string;
|
|
112
120
|
indexName?: string;
|
|
113
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* Transform a router definition into a callable structure (for queries only)
|
|
124
|
+
*/
|
|
125
|
+
type RouterCaller<TRouter> = {
|
|
126
|
+
[K in keyof TRouter]: TRouter[K] extends ProcedureDefinition<any, infer TInput, infer TOutput> ? TInput extends z.ZodTypeAny ? (input: z.infer<TInput>) => Promise<TOutput> : () => Promise<TOutput> : TRouter[K] extends Record<string, unknown> ? RouterCaller<TRouter[K]> : never;
|
|
127
|
+
};
|
|
114
128
|
/**
|
|
115
129
|
* Tracked query operation (for dependency extraction and stream processing)
|
|
116
130
|
*/
|
|
@@ -435,20 +449,24 @@ declare function optimizePatches(patches: JsonPatch[]): JsonPatch[];
|
|
|
435
449
|
declare function batchPatches(patchSets: JsonPatch[][]): JsonPatch[];
|
|
436
450
|
|
|
437
451
|
/**
|
|
438
|
-
* Request types for the reactive handler
|
|
452
|
+
* Request types for the reactive handler.
|
|
453
|
+
* All requests include connectionId from the WebSocket connection.
|
|
439
454
|
*/
|
|
440
455
|
interface SubscribeRequest {
|
|
441
456
|
type: 'subscribe';
|
|
457
|
+
connectionId: string;
|
|
442
458
|
subscriptionId: string;
|
|
443
459
|
path: string;
|
|
444
460
|
input: unknown;
|
|
445
461
|
}
|
|
446
462
|
interface UnsubscribeRequest {
|
|
447
463
|
type: 'unsubscribe';
|
|
464
|
+
connectionId: string;
|
|
448
465
|
subscriptionId: string;
|
|
449
466
|
}
|
|
450
467
|
interface CallRequest {
|
|
451
468
|
type: 'call';
|
|
469
|
+
connectionId: string;
|
|
452
470
|
path: string;
|
|
453
471
|
input: unknown;
|
|
454
472
|
}
|
|
@@ -473,31 +491,41 @@ interface ResultResponse {
|
|
|
473
491
|
interface ErrorResponse {
|
|
474
492
|
type: 'error';
|
|
475
493
|
message: string;
|
|
494
|
+
/** HTTP status code (e.g., 400, 403, 404, 500) */
|
|
495
|
+
code?: number;
|
|
476
496
|
subscriptionId?: string;
|
|
477
497
|
}
|
|
478
498
|
type ReactiveResponse = SnapshotResponse | PatchResponse | ResultResponse | ErrorResponse;
|
|
499
|
+
/**
|
|
500
|
+
* Request info passed to getContext for extracting user context
|
|
501
|
+
*/
|
|
502
|
+
interface RequestInfo {
|
|
503
|
+
connectionId: string;
|
|
504
|
+
/** Headers from the HTTP request (for auth, user info, etc.) */
|
|
505
|
+
headers?: Record<string, string>;
|
|
506
|
+
}
|
|
479
507
|
/**
|
|
480
508
|
* Configuration for the reactive handler
|
|
481
509
|
*/
|
|
482
|
-
interface ReactiveHandlerConfig<TContext> {
|
|
483
|
-
router: Router<TContext,
|
|
510
|
+
interface ReactiveHandlerConfig<TContext, TRouter extends RouterDefinition<TContext> = RouterDefinition<TContext>> {
|
|
511
|
+
router: Router<TContext, TRouter>;
|
|
484
512
|
dbConfig?: DbContextConfig;
|
|
485
|
-
getContext: (
|
|
513
|
+
getContext: (requestInfo: RequestInfo) => Promise<TContext>;
|
|
486
514
|
ttlSeconds?: number;
|
|
487
|
-
/**
|
|
488
|
-
|
|
489
|
-
dependenciesTableName?: string;
|
|
490
|
-
queriesTableName?: string;
|
|
515
|
+
/** Prefix for system table names (e.g., 'TodoApp' creates 'TodoApp-ReactiveConnections') */
|
|
516
|
+
tablePrefix?: string;
|
|
491
517
|
}
|
|
492
518
|
/**
|
|
493
519
|
* Create a reactive handler for Next.js API routes or other HTTP servers.
|
|
494
520
|
* This handler is used for subscribe/call requests and stores queryMetadata
|
|
495
521
|
* for the stream handler to use later.
|
|
496
522
|
*/
|
|
497
|
-
declare function createReactiveHandler<TContext
|
|
498
|
-
handleRequest: (
|
|
523
|
+
declare function createReactiveHandler<TContext, TRouter extends RouterDefinition<TContext>>(config: ReactiveHandlerConfig<TContext, TRouter>): {
|
|
524
|
+
handleRequest: (request: ReactiveRequest, headers?: Record<string, string>) => Promise<ReactiveResponse>;
|
|
499
525
|
registerConnection: (connectionId: string, context?: Record<string, unknown>) => Promise<void>;
|
|
500
526
|
unregisterConnection: (connectionId: string) => Promise<void>;
|
|
527
|
+
query: <T = unknown>(path: string, input?: unknown, headers?: Record<string, string>) => Promise<T>;
|
|
528
|
+
createCaller: () => RouterCaller<TRouter>;
|
|
501
529
|
};
|
|
502
530
|
|
|
503
531
|
/**
|
|
@@ -628,4 +656,4 @@ declare function sortRecords(records: Record<string, unknown>[], sortField?: str
|
|
|
628
656
|
*/
|
|
629
657
|
declare function getRecordKey(record: Record<string, unknown>, pkField: string, skField?: string): string;
|
|
630
658
|
|
|
631
|
-
export { AnyDynamoTable, type AnyProcedureDefinition, type CallRequest, type DatabaseContext, type DbContextConfig, DependencyTracker, type ErrorResponse, type FilterBuilder, FilterCondition, type InferProcedureInput, type InferProcedureOutput, type InferRouterType, type PartiQLStatement, type PatchResponse, ProcedureBuilder, type ProcedureContext, type ProcedureDefinition, type ProcedureType, type QueryBuilder, QueryBuilderImpl, type QueryDependency, type ReactiveBuilder, type ReactiveHandlerConfig, type ReactiveHarness, type ReactiveHarnessConfig, type ReactiveRequest, type ReactiveResponse, type ResultResponse, Router, type RouterDefinition, type SnapshotResponse, type StreamHandlerConfig, type SubscribeRequest, type TableItem, type TableKeyInput, type TrackedQueryOperation, type UnsubscribeRequest, applyPatches, batchPatches, buildDeleteStatement, buildGetStatement, buildInsertStatement, buildSelectStatement, buildUpdateStatement, createConnectHandler, createDbContext, createDependencyKey, createDisconnectHandler, createFilterBuilder, createLambdaHandlers, createReactiveHandler, createReactiveHarness, createRouter, createStreamHandler, evaluateFilter, evaluateFilters, executeProcedure, extractAffectedKeys, extractDependencies, generatePatches, getRecordKey, hasChanges, initReactive, isProcedure, mergeRouters, operationToQueryMetadata, optimizePatches, parseDependencyKey, sortRecords };
|
|
659
|
+
export { AnyDynamoTable, type AnyProcedureDefinition, type CallRequest, type DatabaseContext, type DbContextConfig, DependencyTracker, type ErrorResponse, type FilterBuilder, FilterCondition, type InferProcedureInput, type InferProcedureOutput, type InferRouterType, type PartiQLStatement, type PatchResponse, ProcedureBuilder, type ProcedureContext, type ProcedureDefinition, type ProcedureType, type QueryBuilder, QueryBuilderImpl, type QueryDependency, type ReactiveBuilder, type ReactiveHandlerConfig, type ReactiveHarness, type ReactiveHarnessConfig, type ReactiveRequest, type ReactiveResponse, type RequestInfo, type ResultResponse, Router, type RouterDefinition, type SnapshotResponse, type StreamHandlerConfig, type SubscribeRequest, type TableItem, type TableKeyInput, type TrackedQueryOperation, type UnsubscribeRequest, applyPatches, batchPatches, buildDeleteStatement, buildGetStatement, buildInsertStatement, buildSelectStatement, buildUpdateStatement, createConnectHandler, createDbContext, createDependencyKey, createDisconnectHandler, createFilterBuilder, createLambdaHandlers, createReactiveHandler, createReactiveHarness, createRouter, createStreamHandler, evaluateFilter, evaluateFilters, executeProcedure, extractAffectedKeys, extractDependencies, generatePatches, getRecordKey, hasChanges, initReactive, isProcedure, mergeRouters, operationToQueryMetadata, optimizePatches, parseDependencyKey, sortRecords };
|
package/dist/server.js
CHANGED
|
@@ -742,31 +742,35 @@ function batchPatches(patchSets) {
|
|
|
742
742
|
}
|
|
743
743
|
function createReactiveHandler(config) {
|
|
744
744
|
const ttlSeconds = config.ttlSeconds ?? 3600;
|
|
745
|
-
const
|
|
746
|
-
const
|
|
747
|
-
const
|
|
745
|
+
const prefix = config.tablePrefix ? `${config.tablePrefix}-` : "";
|
|
746
|
+
const connectionsTable = `${prefix}${SystemTableNames.connections}`;
|
|
747
|
+
const dependenciesTable = `${prefix}${SystemTableNames.dependencies}`;
|
|
748
|
+
const queriesTable = `${prefix}${SystemTableNames.queries}`;
|
|
748
749
|
const ddbClient = new DynamoDBClient({
|
|
749
750
|
region: config.dbConfig?.region ?? process.env.AWS_REGION
|
|
750
751
|
});
|
|
751
|
-
const docClient = DynamoDBDocumentClient.from(ddbClient
|
|
752
|
-
|
|
752
|
+
const docClient = DynamoDBDocumentClient.from(ddbClient, {
|
|
753
|
+
marshallOptions: { removeUndefinedValues: true }
|
|
754
|
+
});
|
|
755
|
+
async function handleRequest(request, headers) {
|
|
753
756
|
try {
|
|
754
|
-
const
|
|
757
|
+
const connectionId = request.connectionId;
|
|
758
|
+
const ctx = await config.getContext({ connectionId, headers });
|
|
755
759
|
const dependencyTracker = new DependencyTracker();
|
|
756
760
|
const db = createDbContext(config.dbConfig ?? {}, dependencyTracker);
|
|
757
761
|
const fullCtx = { ...ctx, db };
|
|
758
762
|
switch (request.type) {
|
|
759
763
|
case "subscribe":
|
|
760
|
-
return handleSubscribe(
|
|
764
|
+
return await handleSubscribe(
|
|
761
765
|
connectionId,
|
|
762
766
|
request,
|
|
763
767
|
fullCtx,
|
|
764
768
|
dependencyTracker
|
|
765
769
|
);
|
|
766
770
|
case "unsubscribe":
|
|
767
|
-
return handleUnsubscribe(connectionId, request);
|
|
771
|
+
return await handleUnsubscribe(connectionId, request);
|
|
768
772
|
case "call":
|
|
769
|
-
return handleCall(request, fullCtx);
|
|
773
|
+
return await handleCall(request, fullCtx);
|
|
770
774
|
default:
|
|
771
775
|
return {
|
|
772
776
|
type: "error",
|
|
@@ -777,6 +781,7 @@ function createReactiveHandler(config) {
|
|
|
777
781
|
return {
|
|
778
782
|
type: "error",
|
|
779
783
|
message: error instanceof Error ? error.message : "Unknown error",
|
|
784
|
+
code: error.code,
|
|
780
785
|
subscriptionId: "subscriptionId" in request ? request.subscriptionId : void 0
|
|
781
786
|
};
|
|
782
787
|
}
|
|
@@ -911,13 +916,38 @@ function createReactiveHandler(config) {
|
|
|
911
916
|
);
|
|
912
917
|
console.log("Connection unregistered:", connectionId);
|
|
913
918
|
}
|
|
919
|
+
async function query(path, input, headers) {
|
|
920
|
+
const ctx = await config.getContext({ connectionId: "query", headers });
|
|
921
|
+
const dependencyTracker = new DependencyTracker();
|
|
922
|
+
const db = createDbContext(config.dbConfig ?? {}, dependencyTracker);
|
|
923
|
+
const fullCtx = { ...ctx, db };
|
|
924
|
+
return config.router.execute(path, fullCtx, input);
|
|
925
|
+
}
|
|
926
|
+
function createCaller() {
|
|
927
|
+
function buildProxy(path = []) {
|
|
928
|
+
return new Proxy(() => {
|
|
929
|
+
}, {
|
|
930
|
+
get(_target, prop) {
|
|
931
|
+
return buildProxy([...path, prop]);
|
|
932
|
+
},
|
|
933
|
+
apply(_target, _thisArg, args) {
|
|
934
|
+
const procedurePath = path.join(".");
|
|
935
|
+
return query(procedurePath, args[0]);
|
|
936
|
+
}
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
return buildProxy();
|
|
940
|
+
}
|
|
914
941
|
return {
|
|
915
942
|
handleRequest,
|
|
916
943
|
registerConnection,
|
|
917
|
-
unregisterConnection
|
|
944
|
+
unregisterConnection,
|
|
945
|
+
query,
|
|
946
|
+
createCaller
|
|
918
947
|
};
|
|
919
948
|
}
|
|
920
949
|
function createStreamHandler(config) {
|
|
950
|
+
const connectionsTable = config.connectionsTableName ?? SystemTableNames.connections;
|
|
921
951
|
const dependenciesTable = config.dependenciesTableName ?? SystemTableNames.dependencies;
|
|
922
952
|
const queriesTable = config.queriesTableName ?? SystemTableNames.queries;
|
|
923
953
|
const ddbClient = new DynamoDBClient({
|
|
@@ -1172,6 +1202,54 @@ function createStreamHandler(config) {
|
|
|
1172
1202
|
}
|
|
1173
1203
|
async function cleanupConnection(connectionId) {
|
|
1174
1204
|
console.log("Cleaning up disconnected connection:", connectionId);
|
|
1205
|
+
try {
|
|
1206
|
+
const queriesResponse = await docClient.send(
|
|
1207
|
+
new QueryCommand({
|
|
1208
|
+
TableName: queriesTable,
|
|
1209
|
+
KeyConditionExpression: "pk = :pk",
|
|
1210
|
+
ExpressionAttributeValues: {
|
|
1211
|
+
":pk": connectionId
|
|
1212
|
+
}
|
|
1213
|
+
})
|
|
1214
|
+
);
|
|
1215
|
+
const subscriptions = queriesResponse.Items ?? [];
|
|
1216
|
+
for (const sub of subscriptions) {
|
|
1217
|
+
const deps = sub.dependencies ?? [];
|
|
1218
|
+
for (const depKey of deps) {
|
|
1219
|
+
await docClient.send(
|
|
1220
|
+
new DeleteCommand({
|
|
1221
|
+
TableName: dependenciesTable,
|
|
1222
|
+
Key: {
|
|
1223
|
+
pk: depKey,
|
|
1224
|
+
sk: `${connectionId}#${sub.sk}`
|
|
1225
|
+
}
|
|
1226
|
+
})
|
|
1227
|
+
);
|
|
1228
|
+
}
|
|
1229
|
+
await docClient.send(
|
|
1230
|
+
new DeleteCommand({
|
|
1231
|
+
TableName: queriesTable,
|
|
1232
|
+
Key: {
|
|
1233
|
+
pk: connectionId,
|
|
1234
|
+
sk: sub.sk
|
|
1235
|
+
}
|
|
1236
|
+
})
|
|
1237
|
+
);
|
|
1238
|
+
}
|
|
1239
|
+
await docClient.send(
|
|
1240
|
+
new DeleteCommand({
|
|
1241
|
+
TableName: connectionsTable,
|
|
1242
|
+
Key: {
|
|
1243
|
+
connectionId
|
|
1244
|
+
}
|
|
1245
|
+
})
|
|
1246
|
+
);
|
|
1247
|
+
console.log(
|
|
1248
|
+
`Cleaned up connection ${connectionId}: ${subscriptions.length} subscriptions removed`
|
|
1249
|
+
);
|
|
1250
|
+
} catch (error) {
|
|
1251
|
+
console.error("Error cleaning up connection:", error);
|
|
1252
|
+
}
|
|
1175
1253
|
}
|
|
1176
1254
|
return { handler };
|
|
1177
1255
|
}
|
|
@@ -1291,6 +1369,13 @@ function createLambdaHandlers() {
|
|
|
1291
1369
|
const { type, subscriptionId } = body;
|
|
1292
1370
|
let response;
|
|
1293
1371
|
switch (type) {
|
|
1372
|
+
case "init": {
|
|
1373
|
+
response = {
|
|
1374
|
+
type: "connected",
|
|
1375
|
+
connectionId
|
|
1376
|
+
};
|
|
1377
|
+
break;
|
|
1378
|
+
}
|
|
1294
1379
|
case "unsubscribe": {
|
|
1295
1380
|
const subResponse = await docClient.send(
|
|
1296
1381
|
new GetCommand({
|