@void-snippets/react 0.3.0 → 0.6.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/index.mjs CHANGED
@@ -6,19 +6,212 @@ import {
6
6
  useQueryClient
7
7
  } from "@tanstack/react-query";
8
8
  import { createDefaultAdapters } from "@void-snippets/core";
9
- var DEFAULT_PAGINATION = {
10
- page: 1,
11
- limit: 10
12
- };
9
+ var DEFAULT_PAGINATION = { page: 1, limit: 10 };
10
+ function toOperation(op) {
11
+ if (op.kind === "create") return { kind: "create", payload: op.payload, tempId: op.tempId };
12
+ if (op.kind === "update") return { kind: "update", _id: op._id, payload: op.payload };
13
+ return { kind: "remove", _id: op._id };
14
+ }
15
+ var _stacks = /* @__PURE__ */ new WeakMap();
16
+ function getStack(client, prefix) {
17
+ if (!_stacks.has(client)) _stacks.set(client, /* @__PURE__ */ new Map());
18
+ const map = _stacks.get(client);
19
+ if (!map.has(prefix)) {
20
+ map.set(prefix, {
21
+ pendingOps: [],
22
+ effectiveBaseListSnapshots: [],
23
+ effectiveBaseInfiniteSnapshots: [],
24
+ effectiveBaseGet: /* @__PURE__ */ new Map()
25
+ });
26
+ }
27
+ return map.get(prefix);
28
+ }
29
+ function isListQueryKey(query) {
30
+ const key = query.queryKey;
31
+ return key.length === 2 && key[1] !== null && typeof key[1] === "object" && !Array.isArray(key[1]);
32
+ }
33
+ function generateTempId() {
34
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
35
+ return crypto.randomUUID();
36
+ }
37
+ return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
38
+ }
39
+ function patchPagination(pagination, delta) {
40
+ if (delta === 0) return pagination;
41
+ const newTotal = Math.max(0, pagination.totalDocuments + delta);
42
+ return {
43
+ ...pagination,
44
+ totalDocuments: newTotal,
45
+ totalPages: Math.ceil(newTotal / pagination.limit)
46
+ };
47
+ }
48
+ function applyUpdateToAllCaches(client, prefix, _id, payload, handler) {
49
+ client.setQueriesData(
50
+ { queryKey: [prefix], predicate: isListQueryKey },
51
+ (old) => old ? { ...old, items: handler(old.items, { _id, payload }) } : old
52
+ );
53
+ client.setQueriesData(
54
+ { queryKey: [prefix, "INFINITE"] },
55
+ (old) => old ? { ...old, pages: old.pages.map((p) => ({ ...p, items: handler(p.items, { _id, payload }) })) } : old
56
+ );
57
+ }
58
+ function applyRemoveFromAllCaches(client, prefix, _id, handler) {
59
+ client.setQueriesData(
60
+ { queryKey: [prefix], predicate: isListQueryKey },
61
+ (old) => {
62
+ if (!old) return old;
63
+ const newItems = handler(old.items, _id);
64
+ return { ...old, items: newItems, pagination: patchPagination(old.pagination, newItems.length - old.items.length) };
65
+ }
66
+ );
67
+ client.setQueriesData(
68
+ { queryKey: [prefix, "INFINITE"] },
69
+ (old) => old ? {
70
+ ...old,
71
+ pages: old.pages.map((p) => {
72
+ const newItems = handler(p.items, _id);
73
+ return { ...p, items: newItems, pagination: patchPagination(p.pagination, newItems.length - p.items.length) };
74
+ })
75
+ } : old
76
+ );
77
+ }
78
+ function applyCreateToAllCaches(client, prefix, payload, tempId, handler) {
79
+ client.setQueriesData(
80
+ { queryKey: [prefix], predicate: isListQueryKey },
81
+ (old) => {
82
+ if (!old) return old;
83
+ const newItems = handler(old.items, { payload, tempId });
84
+ return { ...old, items: newItems, pagination: patchPagination(old.pagination, newItems.length - old.items.length) };
85
+ }
86
+ );
87
+ client.setQueriesData(
88
+ { queryKey: [prefix, "INFINITE"] },
89
+ (old) => {
90
+ if (!old) return old;
91
+ return {
92
+ ...old,
93
+ pages: old.pages.map((p, i) => {
94
+ if (i !== 0) return p;
95
+ const newItems = handler(p.items, { payload, tempId });
96
+ return { ...p, items: newItems, pagination: patchPagination(p.pagination, newItems.length - p.items.length) };
97
+ })
98
+ };
99
+ }
100
+ );
101
+ }
102
+ function restoreEffectiveBase(client, prefix, stack) {
103
+ stack.effectiveBaseListSnapshots.forEach(([key, data]) => client.setQueryData(key, data));
104
+ stack.effectiveBaseInfiniteSnapshots.forEach(([key, data]) => client.setQueryData(key, data));
105
+ stack.effectiveBaseGet.forEach((data, idStr) => client.setQueryData([prefix, idStr], data));
106
+ }
107
+ function advanceEffectiveBase(stack, op, optimistic) {
108
+ if (op.kind === "update" && optimistic.update) {
109
+ stack.effectiveBaseListSnapshots = stack.effectiveBaseListSnapshots.map(
110
+ ([key, data]) => data ? [key, { ...data, items: optimistic.update(data.items, { _id: op._id, payload: op.payload }) }] : [key, data]
111
+ );
112
+ stack.effectiveBaseInfiniteSnapshots = stack.effectiveBaseInfiniteSnapshots.map(
113
+ ([key, data]) => data ? [key, { ...data, pages: data.pages.map((p) => ({ ...p, items: optimistic.update(p.items, { _id: op._id, payload: op.payload }) })) }] : [key, data]
114
+ );
115
+ const baseEntry = stack.effectiveBaseGet.get(String(op._id));
116
+ if (baseEntry !== void 0) {
117
+ const advanced = optimistic.updateSingle ? optimistic.updateSingle(baseEntry, op.payload) : { ...baseEntry, ...op.payload };
118
+ stack.effectiveBaseGet.set(String(op._id), advanced);
119
+ }
120
+ return;
121
+ }
122
+ if (op.kind === "remove" && optimistic.remove) {
123
+ stack.effectiveBaseListSnapshots = stack.effectiveBaseListSnapshots.map(([key, data]) => {
124
+ if (!data) return [key, data];
125
+ const newItems = optimistic.remove(data.items, op._id);
126
+ return [key, { ...data, items: newItems, pagination: patchPagination(data.pagination, newItems.length - data.items.length) }];
127
+ });
128
+ stack.effectiveBaseInfiniteSnapshots = stack.effectiveBaseInfiniteSnapshots.map(
129
+ ([key, data]) => data ? [
130
+ key,
131
+ {
132
+ ...data,
133
+ pages: data.pages.map((p) => {
134
+ const newItems = optimistic.remove(p.items, op._id);
135
+ return { ...p, items: newItems, pagination: patchPagination(p.pagination, newItems.length - p.items.length) };
136
+ })
137
+ }
138
+ ] : [key, data]
139
+ );
140
+ stack.effectiveBaseGet.delete(String(op._id));
141
+ return;
142
+ }
143
+ }
144
+ function replayPendingOps(client, prefix, stack, optimistic) {
145
+ for (const op of stack.pendingOps) {
146
+ if (op.kind === "update" && optimistic.update) {
147
+ applyUpdateToAllCaches(client, prefix, op._id, op.payload, optimistic.update);
148
+ const current = client.getQueryData([prefix, String(op._id)]);
149
+ if (current !== void 0) {
150
+ const updated = optimistic.updateSingle ? optimistic.updateSingle(current, op.payload) : { ...current, ...op.payload };
151
+ client.setQueryData([prefix, String(op._id)], updated);
152
+ }
153
+ } else if (op.kind === "remove" && optimistic.remove) {
154
+ applyRemoveFromAllCaches(client, prefix, op._id, optimistic.remove);
155
+ client.invalidateQueries({ queryKey: [prefix, String(op._id)], refetchType: "none" });
156
+ } else if (op.kind === "create" && optimistic.create) {
157
+ applyCreateToAllCaches(client, prefix, op.payload, op.tempId, optimistic.create);
158
+ }
159
+ }
160
+ }
161
+ function flushStack(client, prefix, stack) {
162
+ stack.pendingOps = [];
163
+ stack.effectiveBaseListSnapshots = [];
164
+ stack.effectiveBaseInfiniteSnapshots = [];
165
+ stack.effectiveBaseGet.clear();
166
+ client.invalidateQueries({ queryKey: [prefix] });
167
+ }
168
+ function handleOptimisticError(client, prefix, error, context, optimistic) {
169
+ if (!context) return;
170
+ const stack = getStack(client, prefix);
171
+ const failedOp = stack.pendingOps.find((op) => op.id === context.operationId);
172
+ stack.pendingOps = stack.pendingOps.filter((op) => op.id !== context.operationId);
173
+ restoreEffectiveBase(client, prefix, stack);
174
+ replayPendingOps(client, prefix, stack, optimistic);
175
+ if (failedOp && optimistic.onError) {
176
+ try {
177
+ optimistic.onError(error, toOperation(failedOp));
178
+ } catch {
179
+ }
180
+ }
181
+ }
182
+ function handleOptimisticSettled(client, prefix, error, context, optimistic) {
183
+ if (!context) {
184
+ client.invalidateQueries({ queryKey: [prefix] });
185
+ return;
186
+ }
187
+ const stack = getStack(client, prefix);
188
+ if (!error) {
189
+ const settledOp = stack.pendingOps.find((op) => op.id === context.operationId);
190
+ if (settledOp) {
191
+ advanceEffectiveBase(stack, settledOp, optimistic);
192
+ if (optimistic.onSuccess) {
193
+ try {
194
+ optimistic.onSuccess(toOperation(settledOp));
195
+ } catch {
196
+ }
197
+ }
198
+ }
199
+ }
200
+ stack.pendingOps = stack.pendingOps.filter((op) => op.id !== context.operationId);
201
+ if (stack.pendingOps.length === 0) {
202
+ flushStack(client, prefix, stack);
203
+ }
204
+ }
13
205
  function createResourceHooks(queryKeyPrefix, apiService, options = {}) {
14
206
  const service = apiService;
15
207
  const {
16
208
  adapters = createDefaultAdapters(),
17
- defaultParams = DEFAULT_PAGINATION
209
+ defaultParams = DEFAULT_PAGINATION,
210
+ optimistic
18
211
  } = options;
19
212
  return {
20
213
  // -------------------------------------------------------------------------
21
- // useList — fixed generic shape: { list, isLoading, pagination, error, invalidate }
214
+ // useList
22
215
  // -------------------------------------------------------------------------
23
216
  useList: (params = defaultParams) => {
24
217
  var _a, _b, _c, _d, _e;
@@ -38,13 +231,17 @@ function createResourceHooks(queryKeyPrefix, apiService, options = {}) {
38
231
  totalPages: 0,
39
232
  totalDocuments: 0
40
233
  },
41
- isLoading: query.isLoading || query.isFetching,
234
+ isLoading: query.isLoading,
235
+ isFetching: query.isFetching,
236
+ isRefetching: query.isRefetching,
237
+ isError: query.isError,
42
238
  error: query.error,
239
+ refetch: query.refetch,
43
240
  invalidate: () => queryClient.invalidateQueries({ queryKey: [queryKeyPrefix] })
44
241
  };
45
242
  },
46
243
  // -------------------------------------------------------------------------
47
- // useGet — { item, isLoading, error, refetch }
244
+ // useGet
48
245
  // -------------------------------------------------------------------------
49
246
  useGet: (id, staleTime = 3e4) => {
50
247
  const query = useQuery({
@@ -58,46 +255,173 @@ function createResourceHooks(queryKeyPrefix, apiService, options = {}) {
58
255
  });
59
256
  return {
60
257
  item: query.data,
61
- isLoading: query.isLoading || query.isFetching,
258
+ isLoading: query.isLoading,
259
+ isFetching: query.isFetching,
260
+ isRefetching: query.isRefetching,
261
+ isError: query.isError,
62
262
  error: query.error,
63
263
  refetch: query.refetch
64
264
  };
65
265
  },
66
266
  // -------------------------------------------------------------------------
67
- // useMutations — create / update / remove (not delete — reserved keyword)
267
+ // useMutations
68
268
  // -------------------------------------------------------------------------
69
269
  useMutations: () => {
70
270
  const queryClient = useQueryClient();
71
271
  const invalidate = () => queryClient.invalidateQueries({ queryKey: [queryKeyPrefix] });
272
+ function captureBaseIfFirstOp(stack) {
273
+ if (stack.pendingOps.length === 0) {
274
+ stack.effectiveBaseListSnapshots = queryClient.getQueriesData({
275
+ queryKey: [queryKeyPrefix],
276
+ predicate: isListQueryKey
277
+ });
278
+ stack.effectiveBaseInfiniteSnapshots = queryClient.getQueriesData({
279
+ queryKey: [queryKeyPrefix, "INFINITE"]
280
+ });
281
+ }
282
+ }
72
283
  const createMutation = useMutation({
73
284
  mutationFn: async (payload) => {
74
285
  const raw = await service.create(payload);
75
286
  return adapters.fromSingle(raw);
76
287
  },
77
- onSuccess: invalidate
288
+ onMutate: async (payload) => {
289
+ if (!(optimistic == null ? void 0 : optimistic.create)) return void 0;
290
+ await queryClient.cancelQueries({ queryKey: [queryKeyPrefix] });
291
+ const stack = getStack(
292
+ queryClient,
293
+ queryKeyPrefix
294
+ );
295
+ captureBaseIfFirstOp(stack);
296
+ const tempId = generateTempId();
297
+ const op = {
298
+ id: /* @__PURE__ */ Symbol(),
299
+ kind: "create",
300
+ payload,
301
+ tempId
302
+ };
303
+ stack.pendingOps.push(op);
304
+ try {
305
+ applyCreateToAllCaches(queryClient, queryKeyPrefix, payload, tempId, optimistic.create);
306
+ } catch {
307
+ stack.pendingOps = stack.pendingOps.filter((o) => o.id !== op.id);
308
+ throw new Error("[@void-snippets/react] Optimistic create setup failed.");
309
+ }
310
+ return { operationId: op.id };
311
+ },
312
+ onError: (_err, _vars, context) => {
313
+ if (!(optimistic == null ? void 0 : optimistic.create)) return;
314
+ handleOptimisticError(queryClient, queryKeyPrefix, _err, context, optimistic);
315
+ },
316
+ onSettled: (_data, error, _vars, context) => {
317
+ if (!(optimistic == null ? void 0 : optimistic.create)) {
318
+ invalidate();
319
+ return;
320
+ }
321
+ handleOptimisticSettled(queryClient, queryKeyPrefix, error != null ? error : null, context, optimistic);
322
+ }
78
323
  });
79
324
  const updateMutation = useMutation({
80
325
  mutationFn: async ({ _id, payload }) => {
81
326
  const raw = await service.update(_id, payload);
82
327
  return adapters.fromSingle(raw);
83
328
  },
84
- onSuccess: invalidate
329
+ onMutate: async ({ _id, payload }) => {
330
+ if (!(optimistic == null ? void 0 : optimistic.update)) return void 0;
331
+ await queryClient.cancelQueries({ queryKey: [queryKeyPrefix] });
332
+ const stack = getStack(
333
+ queryClient,
334
+ queryKeyPrefix
335
+ );
336
+ captureBaseIfFirstOp(stack);
337
+ const currentSingle = queryClient.getQueryData([queryKeyPrefix, String(_id)]);
338
+ if (currentSingle !== void 0) {
339
+ stack.effectiveBaseGet.set(String(_id), currentSingle);
340
+ }
341
+ const op = {
342
+ id: /* @__PURE__ */ Symbol(),
343
+ kind: "update",
344
+ _id,
345
+ payload
346
+ };
347
+ stack.pendingOps.push(op);
348
+ try {
349
+ applyUpdateToAllCaches(queryClient, queryKeyPrefix, _id, payload, optimistic.update);
350
+ if (currentSingle !== void 0) {
351
+ const updated = optimistic.updateSingle ? optimistic.updateSingle(currentSingle, payload) : { ...currentSingle, ...payload };
352
+ queryClient.setQueryData([queryKeyPrefix, String(_id)], updated);
353
+ }
354
+ } catch {
355
+ stack.pendingOps = stack.pendingOps.filter((o) => o.id !== op.id);
356
+ throw new Error("[@void-snippets/react] Optimistic update setup failed.");
357
+ }
358
+ return { operationId: op.id };
359
+ },
360
+ onError: (_err, _vars, context) => {
361
+ if (!(optimistic == null ? void 0 : optimistic.update)) return;
362
+ handleOptimisticError(queryClient, queryKeyPrefix, _err, context, optimistic);
363
+ },
364
+ onSettled: (_data, error, _vars, context) => {
365
+ if (!(optimistic == null ? void 0 : optimistic.update)) {
366
+ invalidate();
367
+ return;
368
+ }
369
+ handleOptimisticSettled(queryClient, queryKeyPrefix, error != null ? error : null, context, optimistic);
370
+ }
85
371
  });
86
372
  const removeMutation = useMutation({
87
373
  mutationFn: async (_id) => {
88
374
  const raw = await service.delete(_id);
89
375
  return adapters.fromSingle(raw);
90
376
  },
91
- onSuccess: invalidate
377
+ onMutate: async (_id) => {
378
+ if (!(optimistic == null ? void 0 : optimistic.remove)) return void 0;
379
+ await queryClient.cancelQueries({ queryKey: [queryKeyPrefix] });
380
+ const stack = getStack(
381
+ queryClient,
382
+ queryKeyPrefix
383
+ );
384
+ captureBaseIfFirstOp(stack);
385
+ const currentSingle = queryClient.getQueryData([queryKeyPrefix, String(_id)]);
386
+ if (currentSingle !== void 0) {
387
+ stack.effectiveBaseGet.set(String(_id), currentSingle);
388
+ }
389
+ const op = {
390
+ id: /* @__PURE__ */ Symbol(),
391
+ kind: "remove",
392
+ _id
393
+ };
394
+ stack.pendingOps.push(op);
395
+ try {
396
+ applyRemoveFromAllCaches(queryClient, queryKeyPrefix, _id, optimistic.remove);
397
+ if (currentSingle !== void 0) {
398
+ queryClient.invalidateQueries({
399
+ queryKey: [queryKeyPrefix, String(_id)],
400
+ refetchType: "none"
401
+ });
402
+ }
403
+ } catch {
404
+ stack.pendingOps = stack.pendingOps.filter((o) => o.id !== op.id);
405
+ throw new Error("[@void-snippets/react] Optimistic remove setup failed.");
406
+ }
407
+ return { operationId: op.id };
408
+ },
409
+ onError: (_err, _vars, context) => {
410
+ if (!(optimistic == null ? void 0 : optimistic.remove)) return;
411
+ handleOptimisticError(queryClient, queryKeyPrefix, _err, context, optimistic);
412
+ },
413
+ onSettled: (_data, error, _vars, context) => {
414
+ if (!(optimistic == null ? void 0 : optimistic.remove)) {
415
+ invalidate();
416
+ return;
417
+ }
418
+ handleOptimisticSettled(queryClient, queryKeyPrefix, error != null ? error : null, context, optimistic);
419
+ }
92
420
  });
93
- return {
94
- create: createMutation,
95
- update: updateMutation,
96
- remove: removeMutation
97
- };
421
+ return { create: createMutation, update: updateMutation, remove: removeMutation };
98
422
  },
99
423
  // -------------------------------------------------------------------------
100
- // useInfinite — infinite scroll / load more
424
+ // useInfinite
101
425
  // -------------------------------------------------------------------------
102
426
  useInfinite: (params = defaultParams) => {
103
427
  return useInfiniteQuery({
@@ -121,15 +445,224 @@ function createResourceHooks(queryKeyPrefix, apiService, options = {}) {
121
445
  };
122
446
  }
123
447
 
448
+ // src/socket/createSocketHooks.ts
449
+ import { useCallback, useEffect, useRef, useState } from "react";
450
+ function createSocketHooks(socket) {
451
+ function useSocketEmit() {
452
+ const emit = useCallback(
453
+ (event, ...args) => {
454
+ if (!socket.connected) {
455
+ throw new Error(
456
+ `[@void-snippets/react] Cannot emit "${String(event)}" \u2014 socket is not connected.`
457
+ );
458
+ }
459
+ socket.emit(event, ...args);
460
+ },
461
+ []
462
+ );
463
+ const emitWithAck = useCallback(
464
+ (event, ...args) => {
465
+ if (!socket.connected) {
466
+ return Promise.reject(
467
+ new Error(
468
+ `[@void-snippets/react] Cannot emit "${String(event)}" \u2014 socket is not connected.`
469
+ )
470
+ );
471
+ }
472
+ return socket.emitWithAck(
473
+ event,
474
+ ...args
475
+ );
476
+ },
477
+ []
478
+ );
479
+ return { emit, emitWithAck };
480
+ }
481
+ function useSocketListener(event, handler, options) {
482
+ var _a;
483
+ const enabled = (_a = options == null ? void 0 : options.enabled) != null ? _a : true;
484
+ const savedHandler = useRef(handler);
485
+ useEffect(() => {
486
+ savedHandler.current = handler;
487
+ }, [handler]);
488
+ useEffect(() => {
489
+ if (!enabled) return;
490
+ const listener = ((...args) => {
491
+ savedHandler.current(...args);
492
+ });
493
+ socket.on(event, listener);
494
+ return () => {
495
+ socket.off(event, listener);
496
+ };
497
+ }, [event, enabled]);
498
+ }
499
+ function useSocketConnection() {
500
+ const [isConnected, setIsConnected] = useState(socket.connected);
501
+ const [isConnecting, setIsConnecting] = useState(false);
502
+ const [socketId, setSocketId] = useState(socket.id);
503
+ const [error, setError] = useState(null);
504
+ useEffect(() => {
505
+ function onConnect() {
506
+ setIsConnected(true);
507
+ setIsConnecting(false);
508
+ setSocketId(socket.id);
509
+ setError(null);
510
+ }
511
+ function onDisconnect() {
512
+ setIsConnected(false);
513
+ setIsConnecting(false);
514
+ setSocketId(void 0);
515
+ }
516
+ function onConnectError(err) {
517
+ setIsConnected(false);
518
+ setIsConnecting(false);
519
+ setError(err);
520
+ }
521
+ function onReconnectAttempt() {
522
+ setIsConnecting(true);
523
+ }
524
+ function onReconnectFailed() {
525
+ setIsConnecting(false);
526
+ setError(
527
+ new Error(
528
+ "[@void-snippets/react] Socket reconnection failed \u2014 maximum attempts exceeded."
529
+ )
530
+ );
531
+ }
532
+ socket.on("connect", onConnect);
533
+ socket.on("disconnect", onDisconnect);
534
+ socket.on("connect_error", onConnectError);
535
+ socket.io.on("reconnect_attempt", onReconnectAttempt);
536
+ socket.io.on("reconnect_failed", onReconnectFailed);
537
+ return () => {
538
+ socket.off("connect", onConnect);
539
+ socket.off("disconnect", onDisconnect);
540
+ socket.off("connect_error", onConnectError);
541
+ socket.io.off("reconnect_attempt", onReconnectAttempt);
542
+ socket.io.off("reconnect_failed", onReconnectFailed);
543
+ };
544
+ }, []);
545
+ const connect = useCallback(() => {
546
+ if (!socket.connected) {
547
+ setIsConnecting(true);
548
+ socket.connect();
549
+ }
550
+ }, []);
551
+ const disconnect = useCallback(() => {
552
+ socket.disconnect();
553
+ }, []);
554
+ return {
555
+ isConnected,
556
+ isConnecting,
557
+ socketId,
558
+ error,
559
+ connect,
560
+ disconnect
561
+ };
562
+ }
563
+ return {
564
+ useSocketEmit,
565
+ useSocketListener,
566
+ useSocketConnection
567
+ };
568
+ }
569
+
570
+ // src/routing/createRouteContract.ts
571
+ import { generatePath } from "react-router";
572
+ import { useSearchParams } from "react-router";
573
+ function isRouteDefinition(node) {
574
+ if (node === null || typeof node !== "object") return false;
575
+ const n = node;
576
+ return typeof n["path"] === "string" && "_search" in n && typeof n["search"] === "function";
577
+ }
578
+ function defineRoute(path, config) {
579
+ const definition = {
580
+ ...config,
581
+ path,
582
+ // Phantom anchor — undefined at runtime, typed as `never` for the base definition.
583
+ // The search<S>() method changes this to `S` at the TypeScript level only.
584
+ _search: void 0,
585
+ search() {
586
+ return definition;
587
+ }
588
+ };
589
+ return definition;
590
+ }
591
+ function createRouteContract(tree) {
592
+ const result = {};
593
+ for (const key in tree) {
594
+ const node = tree[key];
595
+ if (isRouteDefinition(node)) {
596
+ const {
597
+ _search: _phantom,
598
+ search: _searchFn,
599
+ ...metadata
600
+ } = node;
601
+ result[key] = {
602
+ ...metadata,
603
+ // Preserve the phantom anchor on the ProcessedRoute for useTypedSearchParams.
604
+ _search: void 0,
605
+ build(options = {}) {
606
+ const { params, search } = options;
607
+ const pathname = params ? generatePath(
608
+ node.path,
609
+ Object.fromEntries(
610
+ Object.entries(params).map(([k, v]) => [k, String(v)])
611
+ )
612
+ ) : node.path;
613
+ if (search) {
614
+ const defined = Object.entries(search).filter(
615
+ ([, v]) => v !== void 0 && v !== null
616
+ );
617
+ if (defined.length > 0) {
618
+ const qs = new URLSearchParams(
619
+ defined.map(([k, v]) => [k, String(v)])
620
+ ).toString();
621
+ return `${pathname}?${qs}`;
622
+ }
623
+ }
624
+ return pathname;
625
+ }
626
+ };
627
+ } else {
628
+ result[key] = createRouteContract(node);
629
+ }
630
+ }
631
+ return result;
632
+ }
633
+ function useTypedSearchParams(_route) {
634
+ const [searchParams, setSearchParams] = useSearchParams();
635
+ const search = Object.fromEntries(searchParams.entries());
636
+ function setSearch(update) {
637
+ setSearchParams((prev) => {
638
+ const next = Object.fromEntries(prev.entries());
639
+ for (const [k, v] of Object.entries(
640
+ update
641
+ )) {
642
+ if (v === void 0 || v === null) {
643
+ delete next[k];
644
+ } else {
645
+ next[k] = String(v);
646
+ }
647
+ }
648
+ return next;
649
+ });
650
+ }
651
+ function clearSearch() {
652
+ setSearchParams({});
653
+ }
654
+ return { search, setSearch, clearSearch };
655
+ }
656
+
124
657
  // src/hooks/useAlertMessage.ts
125
- import { useCallback, useState } from "react";
658
+ import { useCallback as useCallback2, useState as useState2 } from "react";
126
659
  function useAlertMessage(autoHideDuration = 3e3) {
127
- const [alert, setAlert] = useState({
660
+ const [alert, setAlert] = useState2({
128
661
  message: null,
129
662
  type: "info",
130
663
  isVisible: false
131
664
  });
132
- const showAlert = useCallback(
665
+ const showAlert = useCallback2(
133
666
  (message, type = "info") => {
134
667
  setAlert({ message, type, isVisible: true });
135
668
  if (autoHideDuration) {
@@ -140,31 +673,31 @@ function useAlertMessage(autoHideDuration = 3e3) {
140
673
  },
141
674
  [autoHideDuration]
142
675
  );
143
- const hideAlert = useCallback(() => {
676
+ const hideAlert = useCallback2(() => {
144
677
  setAlert((prev) => ({ ...prev, isVisible: false }));
145
678
  }, []);
146
679
  return { alert, showAlert, hideAlert };
147
680
  }
148
681
 
149
682
  // src/hooks/useAsyncState.ts
150
- import { useCallback as useCallback2, useMemo, useState as useState2 } from "react";
683
+ import { useCallback as useCallback3, useMemo, useState as useState3 } from "react";
151
684
  import { catchError } from "@void-snippets/core";
152
685
  function useAsyncState(initialData = null) {
153
- const [state, setState] = useState2({
686
+ const [state, setState] = useState3({
154
687
  data: initialData,
155
688
  status: "idle",
156
689
  error: null
157
690
  });
158
- const setData = useCallback2((data) => {
691
+ const setData = useCallback3((data) => {
159
692
  setState({ data, status: "success", error: null });
160
693
  }, []);
161
- const setError = useCallback2((error) => {
694
+ const setError = useCallback3((error) => {
162
695
  setState((prev) => ({ ...prev, error, status: "error" }));
163
696
  }, []);
164
- const reset = useCallback2(() => {
697
+ const reset = useCallback3(() => {
165
698
  setState({ data: initialData, status: "idle", error: null });
166
699
  }, [initialData]);
167
- const execute = useCallback2(
700
+ const execute = useCallback3(
168
701
  async (asyncFn, options) => {
169
702
  var _a, _b;
170
703
  setState((prev) => ({ ...prev, status: "pending", error: null }));
@@ -192,10 +725,10 @@ function useAsyncState(initialData = null) {
192
725
  }
193
726
 
194
727
  // src/hooks/useCallTimer.ts
195
- import { useEffect, useState as useState3 } from "react";
728
+ import { useEffect as useEffect2, useState as useState4 } from "react";
196
729
  function useCallTimer(startedAt) {
197
- const [duration, setDuration] = useState3("00:00");
198
- useEffect(() => {
730
+ const [duration, setDuration] = useState4("00:00");
731
+ useEffect2(() => {
199
732
  if (!startedAt) {
200
733
  setDuration("00:00");
201
734
  return;
@@ -212,27 +745,27 @@ function useCallTimer(startedAt) {
212
745
  }
213
746
 
214
747
  // src/hooks/useModal.ts
215
- import { useCallback as useCallback3, useState as useState4 } from "react";
748
+ import { useCallback as useCallback4, useState as useState5 } from "react";
216
749
  function useModal() {
217
- const [isOpen, setIsOpen] = useState4(false);
218
- const [data, setData] = useState4(null);
219
- const [isLoading, setIsLoading] = useState4(false);
220
- const openCreateModal = useCallback3(() => {
750
+ const [isOpen, setIsOpen] = useState5(false);
751
+ const [data, setData] = useState5(null);
752
+ const [isLoading, setIsLoading] = useState5(false);
753
+ const openCreateModal = useCallback4(() => {
221
754
  setIsOpen(true);
222
755
  setData(null);
223
756
  }, []);
224
- const openEditModal = useCallback3((editData) => {
757
+ const openEditModal = useCallback4((editData) => {
225
758
  setIsOpen(true);
226
759
  setData(editData);
227
760
  }, []);
228
- const setLoading = useCallback3((loading) => {
761
+ const setLoading = useCallback4((loading) => {
229
762
  setIsLoading(loading);
230
763
  }, []);
231
- const closeModal = useCallback3(() => {
764
+ const closeModal = useCallback4(() => {
232
765
  setIsOpen(false);
233
766
  setData(null);
234
767
  }, []);
235
- const setModal = useCallback3((open, editData) => {
768
+ const setModal = useCallback4((open, editData) => {
236
769
  setIsOpen(open);
237
770
  setData(editData != null ? editData : null);
238
771
  }, []);
@@ -249,18 +782,18 @@ function useModal() {
249
782
  }
250
783
 
251
784
  // src/hooks/usePagination.ts
252
- import { useCallback as useCallback4, useState as useState5 } from "react";
785
+ import { useCallback as useCallback5, useState as useState6 } from "react";
253
786
  function usePagination(initialPage = 1, initialLimit = 10) {
254
- const [page, setPage] = useState5(initialPage);
255
- const [limit, setLimit] = useState5(initialLimit);
256
- const onPaginationChange = useCallback4(
787
+ const [page, setPage] = useState6(initialPage);
788
+ const [limit, setLimit] = useState6(initialLimit);
789
+ const onPaginationChange = useCallback5(
257
790
  (newPage, newLimit) => {
258
791
  setPage(newPage);
259
792
  setLimit(newLimit);
260
793
  },
261
794
  []
262
795
  );
263
- const resetPagination = useCallback4(() => {
796
+ const resetPagination = useCallback5(() => {
264
797
  setPage(1);
265
798
  }, []);
266
799
  return {
@@ -275,10 +808,14 @@ function usePagination(initialPage = 1, initialLimit = 10) {
275
808
  }
276
809
  export {
277
810
  createResourceHooks,
811
+ createRouteContract,
812
+ createSocketHooks,
813
+ defineRoute,
278
814
  useAlertMessage,
279
815
  useAsyncState,
280
816
  useCallTimer,
281
817
  useModal,
282
- usePagination
818
+ usePagination,
819
+ useTypedSearchParams
283
820
  };
284
821
  //# sourceMappingURL=index.mjs.map