@void-snippets/react 0.2.2 → 0.6.0

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