@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.d.mts +610 -75
- package/dist/index.d.ts +610 -75
- package/dist/index.js +737 -38
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +727 -37
- package/dist/index.mjs.map +1 -1
- package/package.json +31 -5
- package/README.md +0 -178
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
|
-
|
|
32
|
-
|
|
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
|
|
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
|
-
|
|
66
|
-
pagination
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
|
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
|
-
|
|
87
|
-
isLoading: query.isLoading
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|