@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.mjs
CHANGED
|
@@ -6,50 +6,242 @@ import {
|
|
|
6
6
|
useQueryClient
|
|
7
7
|
} from "@tanstack/react-query";
|
|
8
8
|
import { createDefaultAdapters } from "@void-snippets/core";
|
|
9
|
-
var DEFAULT_PAGINATION = {
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);
|
|
20
|
-
const capPrefix = capitalize(queryKeyPrefix);
|
|
21
212
|
return {
|
|
22
213
|
// -------------------------------------------------------------------------
|
|
23
|
-
// useList
|
|
214
|
+
// useList
|
|
24
215
|
// -------------------------------------------------------------------------
|
|
25
216
|
useList: (params = defaultParams) => {
|
|
26
217
|
var _a, _b, _c, _d, _e;
|
|
27
218
|
const queryClient = useQueryClient();
|
|
28
|
-
const queryKey = [queryKeyPrefix, params];
|
|
29
219
|
const query = useQuery({
|
|
30
|
-
queryKey,
|
|
220
|
+
queryKey: [queryKeyPrefix, params],
|
|
31
221
|
queryFn: async () => {
|
|
32
222
|
const raw = await service.list(params);
|
|
33
223
|
return adapters.fromList(raw);
|
|
34
224
|
}
|
|
35
225
|
});
|
|
36
|
-
const items = (_b = (_a = query.data) == null ? void 0 : _a.items) != null ? _b : [];
|
|
37
|
-
const pagination = (_e = (_c = query.data) == null ? void 0 : _c.pagination) != null ? _e : {
|
|
38
|
-
page: 1,
|
|
39
|
-
limit: (_d = defaultParams.limit) != null ? _d : 10,
|
|
40
|
-
totalPages: 0,
|
|
41
|
-
totalDocuments: 0
|
|
42
|
-
};
|
|
43
226
|
return {
|
|
44
|
-
|
|
45
|
-
pagination
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
227
|
+
list: (_b = (_a = query.data) == null ? void 0 : _a.items) != null ? _b : [],
|
|
228
|
+
pagination: (_e = (_c = query.data) == null ? void 0 : _c.pagination) != null ? _e : {
|
|
229
|
+
page: 1,
|
|
230
|
+
limit: (_d = defaultParams.limit) != null ? _d : 10,
|
|
231
|
+
totalPages: 0,
|
|
232
|
+
totalDocuments: 0
|
|
233
|
+
},
|
|
234
|
+
isLoading: query.isLoading,
|
|
235
|
+
isFetching: query.isFetching,
|
|
236
|
+
isRefetching: query.isRefetching,
|
|
237
|
+
isError: query.isError,
|
|
238
|
+
error: query.error,
|
|
239
|
+
refetch: query.refetch,
|
|
240
|
+
invalidate: () => queryClient.invalidateQueries({ queryKey: [queryKeyPrefix] })
|
|
49
241
|
};
|
|
50
242
|
},
|
|
51
243
|
// -------------------------------------------------------------------------
|
|
52
|
-
// useGet
|
|
244
|
+
// useGet
|
|
53
245
|
// -------------------------------------------------------------------------
|
|
54
246
|
useGet: (id, staleTime = 3e4) => {
|
|
55
247
|
const query = useQuery({
|
|
@@ -62,47 +254,174 @@ function createResourceHooks(queryKeyPrefix, apiService, options = {}) {
|
|
|
62
254
|
staleTime
|
|
63
255
|
});
|
|
64
256
|
return {
|
|
65
|
-
|
|
66
|
-
isLoading: query.isLoading
|
|
257
|
+
item: query.data,
|
|
258
|
+
isLoading: query.isLoading,
|
|
259
|
+
isFetching: query.isFetching,
|
|
260
|
+
isRefetching: query.isRefetching,
|
|
261
|
+
isError: query.isError,
|
|
67
262
|
error: query.error,
|
|
68
263
|
refetch: query.refetch
|
|
69
264
|
};
|
|
70
265
|
},
|
|
71
266
|
// -------------------------------------------------------------------------
|
|
72
|
-
// useMutations
|
|
267
|
+
// useMutations
|
|
73
268
|
// -------------------------------------------------------------------------
|
|
74
269
|
useMutations: () => {
|
|
75
270
|
const queryClient = useQueryClient();
|
|
76
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
|
+
}
|
|
77
283
|
const createMutation = useMutation({
|
|
78
284
|
mutationFn: async (payload) => {
|
|
79
285
|
const raw = await service.create(payload);
|
|
80
286
|
return adapters.fromSingle(raw);
|
|
81
287
|
},
|
|
82
|
-
|
|
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
|
+
}
|
|
83
323
|
});
|
|
84
324
|
const updateMutation = useMutation({
|
|
85
325
|
mutationFn: async ({ _id, payload }) => {
|
|
86
326
|
const raw = await service.update(_id, payload);
|
|
87
327
|
return adapters.fromSingle(raw);
|
|
88
328
|
},
|
|
89
|
-
|
|
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
|
+
}
|
|
90
371
|
});
|
|
91
|
-
const
|
|
372
|
+
const removeMutation = useMutation({
|
|
92
373
|
mutationFn: async (_id) => {
|
|
93
374
|
const raw = await service.delete(_id);
|
|
94
375
|
return adapters.fromSingle(raw);
|
|
95
376
|
},
|
|
96
|
-
|
|
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
|
+
}
|
|
97
420
|
});
|
|
98
|
-
return {
|
|
99
|
-
create: createMutation,
|
|
100
|
-
update: updateMutation,
|
|
101
|
-
delete: deleteMutation
|
|
102
|
-
};
|
|
421
|
+
return { create: createMutation, update: updateMutation, remove: removeMutation };
|
|
103
422
|
},
|
|
104
423
|
// -------------------------------------------------------------------------
|
|
105
|
-
// useInfinite
|
|
424
|
+
// useInfinite
|
|
106
425
|
// -------------------------------------------------------------------------
|
|
107
426
|
useInfinite: (params = defaultParams) => {
|
|
108
427
|
return useInfiniteQuery({
|
|
@@ -125,7 +444,378 @@ function createResourceHooks(queryKeyPrefix, apiService, options = {}) {
|
|
|
125
444
|
}
|
|
126
445
|
};
|
|
127
446
|
}
|
|
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
|
+
|
|
657
|
+
// src/hooks/useAlertMessage.ts
|
|
658
|
+
import { useCallback as useCallback2, useState as useState2 } from "react";
|
|
659
|
+
function useAlertMessage(autoHideDuration = 3e3) {
|
|
660
|
+
const [alert, setAlert] = useState2({
|
|
661
|
+
message: null,
|
|
662
|
+
type: "info",
|
|
663
|
+
isVisible: false
|
|
664
|
+
});
|
|
665
|
+
const showAlert = useCallback2(
|
|
666
|
+
(message, type = "info") => {
|
|
667
|
+
setAlert({ message, type, isVisible: true });
|
|
668
|
+
if (autoHideDuration) {
|
|
669
|
+
setTimeout(() => {
|
|
670
|
+
setAlert((prev) => ({ ...prev, isVisible: false }));
|
|
671
|
+
}, autoHideDuration);
|
|
672
|
+
}
|
|
673
|
+
},
|
|
674
|
+
[autoHideDuration]
|
|
675
|
+
);
|
|
676
|
+
const hideAlert = useCallback2(() => {
|
|
677
|
+
setAlert((prev) => ({ ...prev, isVisible: false }));
|
|
678
|
+
}, []);
|
|
679
|
+
return { alert, showAlert, hideAlert };
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// src/hooks/useAsyncState.ts
|
|
683
|
+
import { useCallback as useCallback3, useMemo, useState as useState3 } from "react";
|
|
684
|
+
import { catchError } from "@void-snippets/core";
|
|
685
|
+
function useAsyncState(initialData = null) {
|
|
686
|
+
const [state, setState] = useState3({
|
|
687
|
+
data: initialData,
|
|
688
|
+
status: "idle",
|
|
689
|
+
error: null
|
|
690
|
+
});
|
|
691
|
+
const setData = useCallback3((data) => {
|
|
692
|
+
setState({ data, status: "success", error: null });
|
|
693
|
+
}, []);
|
|
694
|
+
const setError = useCallback3((error) => {
|
|
695
|
+
setState((prev) => ({ ...prev, error, status: "error" }));
|
|
696
|
+
}, []);
|
|
697
|
+
const reset = useCallback3(() => {
|
|
698
|
+
setState({ data: initialData, status: "idle", error: null });
|
|
699
|
+
}, [initialData]);
|
|
700
|
+
const execute = useCallback3(
|
|
701
|
+
async (asyncFn, options) => {
|
|
702
|
+
var _a, _b;
|
|
703
|
+
setState((prev) => ({ ...prev, status: "pending", error: null }));
|
|
704
|
+
const [err, res] = await catchError(asyncFn());
|
|
705
|
+
if (err) {
|
|
706
|
+
setState((prev) => ({ ...prev, status: "error", error: err }));
|
|
707
|
+
(_a = options == null ? void 0 : options.onError) == null ? void 0 : _a.call(options, err);
|
|
708
|
+
return [err, null];
|
|
709
|
+
}
|
|
710
|
+
setState({ data: res, status: "success", error: null });
|
|
711
|
+
(_b = options == null ? void 0 : options.onSuccess) == null ? void 0 : _b.call(options, res);
|
|
712
|
+
return [null, res];
|
|
713
|
+
},
|
|
714
|
+
[]
|
|
715
|
+
);
|
|
716
|
+
const flags = useMemo(
|
|
717
|
+
() => ({
|
|
718
|
+
isLoading: state.status === "pending",
|
|
719
|
+
isSuccess: state.status === "success",
|
|
720
|
+
isError: state.status === "error"
|
|
721
|
+
}),
|
|
722
|
+
[state.status]
|
|
723
|
+
);
|
|
724
|
+
return { ...state, ...flags, setData, setError, reset, execute };
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// src/hooks/useCallTimer.ts
|
|
728
|
+
import { useEffect as useEffect2, useState as useState4 } from "react";
|
|
729
|
+
function useCallTimer(startedAt) {
|
|
730
|
+
const [duration, setDuration] = useState4("00:00");
|
|
731
|
+
useEffect2(() => {
|
|
732
|
+
if (!startedAt) {
|
|
733
|
+
setDuration("00:00");
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
const interval = setInterval(() => {
|
|
737
|
+
const diffInSeconds = Math.floor((Date.now() - startedAt) / 1e3);
|
|
738
|
+
const minutes = Math.floor(diffInSeconds / 60).toString().padStart(2, "0");
|
|
739
|
+
const seconds = (diffInSeconds % 60).toString().padStart(2, "0");
|
|
740
|
+
setDuration(`${minutes}:${seconds}`);
|
|
741
|
+
}, 1e3);
|
|
742
|
+
return () => clearInterval(interval);
|
|
743
|
+
}, [startedAt]);
|
|
744
|
+
return duration;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// src/hooks/useModal.ts
|
|
748
|
+
import { useCallback as useCallback4, useState as useState5 } from "react";
|
|
749
|
+
function useModal() {
|
|
750
|
+
const [isOpen, setIsOpen] = useState5(false);
|
|
751
|
+
const [data, setData] = useState5(null);
|
|
752
|
+
const [isLoading, setIsLoading] = useState5(false);
|
|
753
|
+
const openCreateModal = useCallback4(() => {
|
|
754
|
+
setIsOpen(true);
|
|
755
|
+
setData(null);
|
|
756
|
+
}, []);
|
|
757
|
+
const openEditModal = useCallback4((editData) => {
|
|
758
|
+
setIsOpen(true);
|
|
759
|
+
setData(editData);
|
|
760
|
+
}, []);
|
|
761
|
+
const setLoading = useCallback4((loading) => {
|
|
762
|
+
setIsLoading(loading);
|
|
763
|
+
}, []);
|
|
764
|
+
const closeModal = useCallback4(() => {
|
|
765
|
+
setIsOpen(false);
|
|
766
|
+
setData(null);
|
|
767
|
+
}, []);
|
|
768
|
+
const setModal = useCallback4((open, editData) => {
|
|
769
|
+
setIsOpen(open);
|
|
770
|
+
setData(editData != null ? editData : null);
|
|
771
|
+
}, []);
|
|
772
|
+
return {
|
|
773
|
+
isOpen,
|
|
774
|
+
data,
|
|
775
|
+
isLoading,
|
|
776
|
+
openCreateModal,
|
|
777
|
+
openEditModal,
|
|
778
|
+
setLoading,
|
|
779
|
+
closeModal,
|
|
780
|
+
setModal
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// src/hooks/usePagination.ts
|
|
785
|
+
import { useCallback as useCallback5, useState as useState6 } from "react";
|
|
786
|
+
function usePagination(initialPage = 1, initialLimit = 10) {
|
|
787
|
+
const [page, setPage] = useState6(initialPage);
|
|
788
|
+
const [limit, setLimit] = useState6(initialLimit);
|
|
789
|
+
const onPaginationChange = useCallback5(
|
|
790
|
+
(newPage, newLimit) => {
|
|
791
|
+
setPage(newPage);
|
|
792
|
+
setLimit(newLimit);
|
|
793
|
+
},
|
|
794
|
+
[]
|
|
795
|
+
);
|
|
796
|
+
const resetPagination = useCallback5(() => {
|
|
797
|
+
setPage(1);
|
|
798
|
+
}, []);
|
|
799
|
+
return {
|
|
800
|
+
page,
|
|
801
|
+
limit,
|
|
802
|
+
onPaginationChange,
|
|
803
|
+
resetPagination,
|
|
804
|
+
setPage,
|
|
805
|
+
setLimit,
|
|
806
|
+
queryParams: { page, limit }
|
|
807
|
+
};
|
|
808
|
+
}
|
|
128
809
|
export {
|
|
129
|
-
createResourceHooks
|
|
810
|
+
createResourceHooks,
|
|
811
|
+
createRouteContract,
|
|
812
|
+
createSocketHooks,
|
|
813
|
+
defineRoute,
|
|
814
|
+
useAlertMessage,
|
|
815
|
+
useAsyncState,
|
|
816
|
+
useCallTimer,
|
|
817
|
+
useModal,
|
|
818
|
+
usePagination,
|
|
819
|
+
useTypedSearchParams
|
|
130
820
|
};
|
|
131
821
|
//# sourceMappingURL=index.mjs.map
|