@classytic/arc-next 0.3.1 → 0.4.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/README.md +74 -13
- package/dist/api.d.ts +119 -2
- package/dist/api.js +104 -0
- package/dist/client.d.ts +21 -11
- package/dist/client.js +14 -3
- package/dist/hooks.d.ts +57 -3
- package/dist/hooks.js +316 -20
- package/dist/mutation.d.ts +5 -19
- package/dist/mutation.js +14 -12
- package/dist/prefetch.d.ts +31 -0
- package/dist/prefetch.js +50 -19
- package/dist/query.d.ts +30 -2
- package/dist/query.js +16 -4
- package/dist/sse.d.ts +62 -0
- package/dist/sse.js +144 -0
- package/package.json +22 -13
package/dist/hooks.js
CHANGED
|
@@ -4,8 +4,8 @@ import { getAuthContext, getAuthMode } from "./client.js";
|
|
|
4
4
|
import { isKeysetPagination, isOffsetPagination } from "./api.js";
|
|
5
5
|
import { DEFAULT_QUERY_CONFIG, createCacheUtils, createQueryKeys, extractItem, getItemId, updateListCache, useDetailQuery, useInfiniteListQuery, useListQuery } from "./query.js";
|
|
6
6
|
import { useMutationWithTransition, useOptimisticMutation } from "./mutation.js";
|
|
7
|
-
import {
|
|
8
|
-
import { useCallback,
|
|
7
|
+
import { useQueryClient } from "@tanstack/react-query";
|
|
8
|
+
import { useCallback, useRef } from "react";
|
|
9
9
|
|
|
10
10
|
//#region src/hooks.ts
|
|
11
11
|
let useRouterHook = null;
|
|
@@ -25,7 +25,18 @@ function createEnabledRule(token, options, authMode = getAuthMode()) {
|
|
|
25
25
|
if (authMode === "cookie" || options.public) return options.enabled ?? true;
|
|
26
26
|
return options.enabled !== void 0 ? options.enabled && !!token : !!token;
|
|
27
27
|
}
|
|
28
|
-
function createCrudHooks({ api, entityKey, singular, defaults = {}, callbacks = {}, client }) {
|
|
28
|
+
function createCrudHooks({ api, entityKey, singular, plural, idField, defaults = {}, callbacks = {}, client }) {
|
|
29
|
+
const pluralName = plural ?? `${singular}s`;
|
|
30
|
+
/** Extract ID from an item using configured idField, falling back to _id → id */
|
|
31
|
+
function resolveItemId(item) {
|
|
32
|
+
if (!item || typeof item !== "object") return null;
|
|
33
|
+
const obj = item;
|
|
34
|
+
if (idField) {
|
|
35
|
+
const val = obj[idField];
|
|
36
|
+
if (val != null) return String(val);
|
|
37
|
+
}
|
|
38
|
+
return getItemId(item);
|
|
39
|
+
}
|
|
29
40
|
const KEYS = createQueryKeys(entityKey);
|
|
30
41
|
const cache = createCacheUtils(KEYS);
|
|
31
42
|
const instanceToast = client?.toast;
|
|
@@ -91,6 +102,7 @@ function createCrudHooks({ api, entityKey, singular, defaults = {}, callbacks =
|
|
|
91
102
|
},
|
|
92
103
|
prefillDetailCache: queryOpts.prefillDetailCache ?? true,
|
|
93
104
|
detailKeyBuilder: (id) => KEYS.detail(id),
|
|
105
|
+
itemIdResolver: resolveItemId,
|
|
94
106
|
select: queryOpts.select
|
|
95
107
|
});
|
|
96
108
|
}
|
|
@@ -151,7 +163,7 @@ function createCrudHooks({ api, entityKey, singular, defaults = {}, callbacks =
|
|
|
151
163
|
const optimisticItem = {
|
|
152
164
|
...data,
|
|
153
165
|
_optimistic: true,
|
|
154
|
-
[
|
|
166
|
+
[idField ?? (resolveItemId(data) ? "id" : "_id")]: resolveItemId(data) ?? `temp-${Date.now()}`
|
|
155
167
|
};
|
|
156
168
|
return updateListCache(oldData, (arr) => [optimisticItem, ...arr || []]);
|
|
157
169
|
},
|
|
@@ -181,7 +193,7 @@ function createCrudHooks({ api, entityKey, singular, defaults = {}, callbacks =
|
|
|
181
193
|
queryKeys: [KEYS.lists(), KEYS.details()],
|
|
182
194
|
shouldToast,
|
|
183
195
|
optimisticUpdate: (oldData, { id, data }) => {
|
|
184
|
-
const updated = updateListCache(oldData, (arr) => (arr || []).map((item) =>
|
|
196
|
+
const updated = updateListCache(oldData, (arr) => (arr || []).map((item) => resolveItemId(item) === id ? {
|
|
185
197
|
...item,
|
|
186
198
|
...data
|
|
187
199
|
} : item));
|
|
@@ -229,7 +241,7 @@ function createCrudHooks({ api, entityKey, singular, defaults = {}, callbacks =
|
|
|
229
241
|
queryKeys: [KEYS.lists()],
|
|
230
242
|
shouldToast,
|
|
231
243
|
optimisticUpdate: (oldData, { id }) => {
|
|
232
|
-
return updateListCache(oldData, (arr) => (arr || []).filter((item) =>
|
|
244
|
+
return updateListCache(oldData, (arr) => (arr || []).filter((item) => resolveItemId(item) !== id));
|
|
233
245
|
},
|
|
234
246
|
onSuccess: (data, { id }) => {
|
|
235
247
|
queryClient.removeQueries({ queryKey: KEYS.detail(id) });
|
|
@@ -247,6 +259,23 @@ function createCrudHooks({ api, entityKey, singular, defaults = {}, callbacks =
|
|
|
247
259
|
},
|
|
248
260
|
toastHandler: instanceToast
|
|
249
261
|
});
|
|
262
|
+
const restoreMutation = useMutationWithTransition({
|
|
263
|
+
mutationFn: ({ token, organizationId, id }) => {
|
|
264
|
+
if (!api.restore) return Promise.reject(/* @__PURE__ */ new Error(`[arc-next] "${entityKey}" api does not define a restore method`));
|
|
265
|
+
return api.restore({
|
|
266
|
+
token,
|
|
267
|
+
organizationId,
|
|
268
|
+
id
|
|
269
|
+
});
|
|
270
|
+
},
|
|
271
|
+
invalidateQueries: [KEYS.lists(), KEYS.custom("deleted")],
|
|
272
|
+
shouldToast,
|
|
273
|
+
messages: {
|
|
274
|
+
success: `${singular} restored successfully`,
|
|
275
|
+
error: `Failed to restore ${singular.toLowerCase()}`
|
|
276
|
+
},
|
|
277
|
+
toastHandler: instanceToast
|
|
278
|
+
});
|
|
250
279
|
const resolveAuth = useCallback((params) => {
|
|
251
280
|
const auth = getAuthContext();
|
|
252
281
|
return {
|
|
@@ -301,10 +330,26 @@ function createCrudHooks({ api, entityKey, singular, defaults = {}, callbacks =
|
|
|
301
330
|
silentRef.current = false;
|
|
302
331
|
}
|
|
303
332
|
}, [deleteMutation, resolveAuth]),
|
|
333
|
+
restore: useCallback(async (params, options) => {
|
|
334
|
+
silentRef.current = options?.silent ?? false;
|
|
335
|
+
try {
|
|
336
|
+
const entity = extractItem(await restoreMutation.mutateAsync(resolveAuth(params)));
|
|
337
|
+
options?.onSuccess?.(entity);
|
|
338
|
+
options?.onSettled?.(entity, null);
|
|
339
|
+
return entity;
|
|
340
|
+
} catch (error) {
|
|
341
|
+
options?.onError?.(error);
|
|
342
|
+
options?.onSettled?.(void 0, error);
|
|
343
|
+
throw error;
|
|
344
|
+
} finally {
|
|
345
|
+
silentRef.current = false;
|
|
346
|
+
}
|
|
347
|
+
}, [restoreMutation, resolveAuth]),
|
|
304
348
|
isCreating: createMutation.isPending,
|
|
305
349
|
isUpdating: updateMutation.isPending,
|
|
306
350
|
isDeleting: deleteMutation.isPending,
|
|
307
|
-
|
|
351
|
+
isRestoring: restoreMutation.isPending,
|
|
352
|
+
isMutating: createMutation.isPending || updateMutation.isPending || deleteMutation.isPending || restoreMutation.isPending
|
|
308
353
|
};
|
|
309
354
|
}
|
|
310
355
|
function useInfiniteList(tokenOrParams, paramsOrOptions, maybeOptions) {
|
|
@@ -357,6 +402,13 @@ function createCrudHooks({ api, entityKey, singular, defaults = {}, callbacks =
|
|
|
357
402
|
const p = page;
|
|
358
403
|
if (typeof p.hasNext === "boolean" && typeof p.page === "number") return p.hasNext ? p.page + 1 : void 0;
|
|
359
404
|
},
|
|
405
|
+
getPreviousPageParam: queryOpts.maxPages != null ? (firstPage) => {
|
|
406
|
+
const page = firstPage;
|
|
407
|
+
if (isOffsetPagination(page)) return page.hasPrev ? page.page - 1 : void 0;
|
|
408
|
+
const p = page;
|
|
409
|
+
if (typeof p.hasPrev === "boolean" && typeof p.page === "number") return p.hasPrev ? p.page - 1 : void 0;
|
|
410
|
+
} : void 0,
|
|
411
|
+
maxPages: queryOpts.maxPages,
|
|
360
412
|
options: {
|
|
361
413
|
staleTime: queryOpts.staleTime ?? config.staleTime,
|
|
362
414
|
gcTime: queryOpts.gcTime ?? config.gcTime,
|
|
@@ -392,8 +444,6 @@ function createCrudHooks({ api, entityKey, singular, defaults = {}, callbacks =
|
|
|
392
444
|
});
|
|
393
445
|
}
|
|
394
446
|
function useSearch(query, params, options) {
|
|
395
|
-
if (!api.search) throw new Error(`[arc-next] "${entityKey}" api does not define a search method`);
|
|
396
|
-
const searchApi = api.search;
|
|
397
447
|
const auth = getAuthContext();
|
|
398
448
|
const token = params?.token ?? auth.token;
|
|
399
449
|
const organizationId = params?.organizationId ?? auth.organizationId;
|
|
@@ -413,16 +463,19 @@ function createCrudHooks({ api, entityKey, singular, defaults = {}, callbacks =
|
|
|
413
463
|
"search",
|
|
414
464
|
searchKeyParams
|
|
415
465
|
],
|
|
416
|
-
queryFn: ({ signal }) =>
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
466
|
+
queryFn: ({ signal }) => {
|
|
467
|
+
if (!api.search) return Promise.reject(/* @__PURE__ */ new Error(`[arc-next] "${entityKey}" api does not define a search method`));
|
|
468
|
+
return api.search({
|
|
469
|
+
token,
|
|
470
|
+
organizationId,
|
|
471
|
+
params: searchParams,
|
|
472
|
+
options: {
|
|
473
|
+
signal,
|
|
474
|
+
...requestOpts
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
},
|
|
478
|
+
enabled: !!api.search && query.length > 0 && createEnabledRule(token, queryOpts, resolveAuthMode()),
|
|
426
479
|
options: {
|
|
427
480
|
staleTime: queryOpts.staleTime ?? config.staleTime,
|
|
428
481
|
gcTime: queryOpts.gcTime ?? config.gcTime
|
|
@@ -441,6 +494,243 @@ function createCrudHooks({ api, entityKey, singular, defaults = {}, callbacks =
|
|
|
441
494
|
toastHandler: instanceToast
|
|
442
495
|
});
|
|
443
496
|
}
|
|
497
|
+
function useDeleted(params, options) {
|
|
498
|
+
const auth = getAuthContext();
|
|
499
|
+
const token = auth.token;
|
|
500
|
+
const mergedParams = params ?? {};
|
|
501
|
+
const organizationId = mergedParams.organizationId ?? auth.organizationId;
|
|
502
|
+
const { organizationId: _, ...restParams } = mergedParams;
|
|
503
|
+
const { request: requestOpts, ...queryOpts } = options ?? {};
|
|
504
|
+
return useListQuery({
|
|
505
|
+
queryKey: KEYS.custom("deleted", {
|
|
506
|
+
organizationId,
|
|
507
|
+
...restParams
|
|
508
|
+
}),
|
|
509
|
+
queryFn: ({ signal }) => {
|
|
510
|
+
if (!api.getDeleted) return Promise.reject(/* @__PURE__ */ new Error(`[arc-next] "${entityKey}" api does not define a getDeleted method`));
|
|
511
|
+
return api.getDeleted({
|
|
512
|
+
token,
|
|
513
|
+
organizationId,
|
|
514
|
+
params: restParams,
|
|
515
|
+
options: {
|
|
516
|
+
signal,
|
|
517
|
+
...requestOpts
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
},
|
|
521
|
+
enabled: !!api.getDeleted && createEnabledRule(token, queryOpts, resolveAuthMode()),
|
|
522
|
+
options: {
|
|
523
|
+
staleTime: queryOpts.staleTime ?? config.staleTime,
|
|
524
|
+
gcTime: queryOpts.gcTime ?? config.gcTime
|
|
525
|
+
},
|
|
526
|
+
select: queryOpts.select
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
function useDetailBySlug(slug, options) {
|
|
530
|
+
const auth = getAuthContext();
|
|
531
|
+
const token = auth.token;
|
|
532
|
+
const resolvedOptions = options ?? {};
|
|
533
|
+
const organizationId = resolvedOptions.organizationId ?? auth.organizationId;
|
|
534
|
+
const { params: queryParams, request: requestOpts, ...restOptions } = resolvedOptions;
|
|
535
|
+
return useDetailQuery({
|
|
536
|
+
queryKey: queryParams ? KEYS.custom("slug", slug, queryParams) : KEYS.custom("slug", slug),
|
|
537
|
+
queryFn: ({ signal }) => {
|
|
538
|
+
if (!api.getBySlug) return Promise.reject(/* @__PURE__ */ new Error(`[arc-next] "${entityKey}" api does not define a getBySlug method`));
|
|
539
|
+
return api.getBySlug({
|
|
540
|
+
slug,
|
|
541
|
+
token,
|
|
542
|
+
organizationId,
|
|
543
|
+
params: queryParams,
|
|
544
|
+
options: {
|
|
545
|
+
signal,
|
|
546
|
+
...requestOpts
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
},
|
|
550
|
+
enabled: !!api.getBySlug && !!slug && createEnabledRule(token, restOptions, resolveAuthMode()),
|
|
551
|
+
options: {
|
|
552
|
+
staleTime: restOptions.staleTime ?? config.staleTime,
|
|
553
|
+
gcTime: restOptions.gcTime ?? config.gcTime,
|
|
554
|
+
refetchOnWindowFocus: restOptions.refetchOnWindowFocus ?? config.refetchOnWindowFocus,
|
|
555
|
+
structuralSharing: restOptions.structuralSharing ?? config.structuralSharing
|
|
556
|
+
},
|
|
557
|
+
select: restOptions.select
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
function useTree(params, options) {
|
|
561
|
+
const auth = getAuthContext();
|
|
562
|
+
const token = auth.token;
|
|
563
|
+
const mergedParams = params ?? {};
|
|
564
|
+
const organizationId = mergedParams.organizationId ?? auth.organizationId;
|
|
565
|
+
const { organizationId: _, ...restParams } = mergedParams;
|
|
566
|
+
const { request: requestOpts, ...queryOpts } = options ?? {};
|
|
567
|
+
return useListQuery({
|
|
568
|
+
queryKey: KEYS.custom("tree", {
|
|
569
|
+
organizationId,
|
|
570
|
+
...restParams
|
|
571
|
+
}),
|
|
572
|
+
queryFn: ({ signal }) => {
|
|
573
|
+
if (!api.getTree) return Promise.reject(/* @__PURE__ */ new Error(`[arc-next] "${entityKey}" api does not define a getTree method`));
|
|
574
|
+
return api.getTree({
|
|
575
|
+
token,
|
|
576
|
+
organizationId,
|
|
577
|
+
params: restParams,
|
|
578
|
+
options: {
|
|
579
|
+
signal,
|
|
580
|
+
...requestOpts
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
},
|
|
584
|
+
enabled: !!api.getTree && createEnabledRule(token, queryOpts, resolveAuthMode()),
|
|
585
|
+
options: {
|
|
586
|
+
staleTime: queryOpts.staleTime ?? config.staleTime,
|
|
587
|
+
gcTime: queryOpts.gcTime ?? config.gcTime
|
|
588
|
+
},
|
|
589
|
+
select: queryOpts.select
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
function useChildren(parentId, params, options) {
|
|
593
|
+
const auth = getAuthContext();
|
|
594
|
+
const token = auth.token;
|
|
595
|
+
const mergedParams = params ?? {};
|
|
596
|
+
const organizationId = mergedParams.organizationId ?? auth.organizationId;
|
|
597
|
+
const { organizationId: _, ...restParams } = mergedParams;
|
|
598
|
+
const { request: requestOpts, ...queryOpts } = options ?? {};
|
|
599
|
+
return useListQuery({
|
|
600
|
+
queryKey: KEYS.custom("children", parentId, {
|
|
601
|
+
organizationId,
|
|
602
|
+
...restParams
|
|
603
|
+
}),
|
|
604
|
+
queryFn: ({ signal }) => {
|
|
605
|
+
if (!api.getChildren) return Promise.reject(/* @__PURE__ */ new Error(`[arc-next] "${entityKey}" api does not define a getChildren method`));
|
|
606
|
+
return api.getChildren({
|
|
607
|
+
token,
|
|
608
|
+
organizationId,
|
|
609
|
+
parentId,
|
|
610
|
+
params: restParams,
|
|
611
|
+
options: {
|
|
612
|
+
signal,
|
|
613
|
+
...requestOpts
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
},
|
|
617
|
+
enabled: !!api.getChildren && !!parentId && createEnabledRule(token, queryOpts, resolveAuthMode()),
|
|
618
|
+
options: {
|
|
619
|
+
staleTime: queryOpts.staleTime ?? config.staleTime,
|
|
620
|
+
gcTime: queryOpts.gcTime ?? config.gcTime
|
|
621
|
+
},
|
|
622
|
+
prefillDetailCache: queryOpts.prefillDetailCache ?? true,
|
|
623
|
+
detailKeyBuilder: (id) => KEYS.detail(id),
|
|
624
|
+
itemIdResolver: resolveItemId,
|
|
625
|
+
select: queryOpts.select
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
function useFindBy(field, value, options) {
|
|
629
|
+
const auth = getAuthContext();
|
|
630
|
+
const token = auth.token;
|
|
631
|
+
const organizationId = auth.organizationId;
|
|
632
|
+
const { operator, request: requestOpts, ...queryOpts } = options ?? {};
|
|
633
|
+
return useListQuery({
|
|
634
|
+
queryKey: KEYS.custom("findBy", field, value, operator),
|
|
635
|
+
queryFn: ({ signal }) => {
|
|
636
|
+
if (!api.findBy) return Promise.reject(/* @__PURE__ */ new Error(`[arc-next] "${entityKey}" api does not define a findBy method`));
|
|
637
|
+
return api.findBy({
|
|
638
|
+
token,
|
|
639
|
+
organizationId,
|
|
640
|
+
field,
|
|
641
|
+
value,
|
|
642
|
+
operator,
|
|
643
|
+
options: {
|
|
644
|
+
signal,
|
|
645
|
+
...requestOpts
|
|
646
|
+
}
|
|
647
|
+
});
|
|
648
|
+
},
|
|
649
|
+
enabled: !!api.findBy && value !== void 0 && value !== null && createEnabledRule(token, queryOpts, resolveAuthMode()),
|
|
650
|
+
options: {
|
|
651
|
+
staleTime: queryOpts.staleTime ?? config.staleTime,
|
|
652
|
+
gcTime: queryOpts.gcTime ?? config.gcTime
|
|
653
|
+
},
|
|
654
|
+
prefillDetailCache: queryOpts.prefillDetailCache ?? true,
|
|
655
|
+
detailKeyBuilder: (id) => KEYS.detail(id),
|
|
656
|
+
itemIdResolver: resolveItemId,
|
|
657
|
+
select: queryOpts.select
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
function useBulkActions() {
|
|
661
|
+
const bulkCreateMutation = useMutationWithTransition({
|
|
662
|
+
mutationFn: (vars) => {
|
|
663
|
+
if (!api.bulkCreate) return Promise.reject(/* @__PURE__ */ new Error(`[arc-next] "${entityKey}" api does not define a bulkCreate method`));
|
|
664
|
+
const auth = getAuthContext();
|
|
665
|
+
return api.bulkCreate({
|
|
666
|
+
token: vars.token ?? auth.token,
|
|
667
|
+
organizationId: vars.organizationId ?? auth.organizationId,
|
|
668
|
+
data: vars.data
|
|
669
|
+
});
|
|
670
|
+
},
|
|
671
|
+
invalidateQueries: [KEYS.lists()],
|
|
672
|
+
messages: {
|
|
673
|
+
success: `${pluralName} created successfully`,
|
|
674
|
+
error: `Failed to create ${pluralName.toLowerCase()}`
|
|
675
|
+
},
|
|
676
|
+
toastHandler: instanceToast
|
|
677
|
+
});
|
|
678
|
+
const bulkUpdateMutation = useMutationWithTransition({
|
|
679
|
+
mutationFn: (vars) => {
|
|
680
|
+
if (!api.bulkUpdate) return Promise.reject(/* @__PURE__ */ new Error(`[arc-next] "${entityKey}" api does not define a bulkUpdate method`));
|
|
681
|
+
const auth = getAuthContext();
|
|
682
|
+
return api.bulkUpdate({
|
|
683
|
+
token: vars.token ?? auth.token,
|
|
684
|
+
organizationId: vars.organizationId ?? auth.organizationId,
|
|
685
|
+
filter: vars.filter,
|
|
686
|
+
data: vars.data
|
|
687
|
+
});
|
|
688
|
+
},
|
|
689
|
+
invalidateQueries: [KEYS.lists(), KEYS.details()],
|
|
690
|
+
messages: {
|
|
691
|
+
success: `${pluralName} updated successfully`,
|
|
692
|
+
error: `Failed to update ${pluralName.toLowerCase()}`
|
|
693
|
+
},
|
|
694
|
+
toastHandler: instanceToast
|
|
695
|
+
});
|
|
696
|
+
const bulkDeleteMutation = useMutationWithTransition({
|
|
697
|
+
mutationFn: (vars) => {
|
|
698
|
+
if (!api.bulkDelete) return Promise.reject(/* @__PURE__ */ new Error(`[arc-next] "${entityKey}" api does not define a bulkDelete method`));
|
|
699
|
+
const auth = getAuthContext();
|
|
700
|
+
return api.bulkDelete({
|
|
701
|
+
token: vars.token ?? auth.token,
|
|
702
|
+
organizationId: vars.organizationId ?? auth.organizationId,
|
|
703
|
+
filter: vars.filter
|
|
704
|
+
});
|
|
705
|
+
},
|
|
706
|
+
invalidateQueries: [KEYS.lists()],
|
|
707
|
+
messages: {
|
|
708
|
+
success: `${pluralName} deleted successfully`,
|
|
709
|
+
error: `Failed to delete ${pluralName.toLowerCase()}`
|
|
710
|
+
},
|
|
711
|
+
toastHandler: instanceToast
|
|
712
|
+
});
|
|
713
|
+
return {
|
|
714
|
+
bulkCreate: async (params, options) => {
|
|
715
|
+
const items = (await bulkCreateMutation.mutateAsync(params))?.data ?? [];
|
|
716
|
+
options?.onSuccess?.(items);
|
|
717
|
+
return items;
|
|
718
|
+
},
|
|
719
|
+
bulkUpdate: async (params, options) => {
|
|
720
|
+
const result = await bulkUpdateMutation.mutateAsync(params);
|
|
721
|
+
options?.onSuccess?.(result);
|
|
722
|
+
return result;
|
|
723
|
+
},
|
|
724
|
+
bulkRemove: async (params, options) => {
|
|
725
|
+
const result = await bulkDeleteMutation.mutateAsync(params);
|
|
726
|
+
options?.onSuccess?.(result);
|
|
727
|
+
return result;
|
|
728
|
+
},
|
|
729
|
+
isBulkCreating: bulkCreateMutation.isPending,
|
|
730
|
+
isBulkUpdating: bulkUpdateMutation.isPending,
|
|
731
|
+
isBulkDeleting: bulkDeleteMutation.isPending
|
|
732
|
+
};
|
|
733
|
+
}
|
|
444
734
|
const resolvedRouterHook = instanceNavigation ?? useRouterHook ?? (() => ({
|
|
445
735
|
push: () => {},
|
|
446
736
|
replace: () => {}
|
|
@@ -449,7 +739,7 @@ function createCrudHooks({ api, entityKey, singular, defaults = {}, callbacks =
|
|
|
449
739
|
const queryClient = useQueryClient();
|
|
450
740
|
const router = resolvedRouterHook();
|
|
451
741
|
return useCallback((href, item, options = {}) => {
|
|
452
|
-
const id =
|
|
742
|
+
const id = resolveItemId(item);
|
|
453
743
|
if (id) queryClient.setQueryData(KEYS.detail(id), { data: item });
|
|
454
744
|
if (!router) return;
|
|
455
745
|
const { scroll = true, replace = false } = options;
|
|
@@ -464,6 +754,12 @@ function createCrudHooks({ api, entityKey, singular, defaults = {}, callbacks =
|
|
|
464
754
|
useDetail,
|
|
465
755
|
useInfiniteList,
|
|
466
756
|
useActions,
|
|
757
|
+
useBulkActions,
|
|
758
|
+
useDeleted,
|
|
759
|
+
useDetailBySlug,
|
|
760
|
+
useTree,
|
|
761
|
+
useChildren,
|
|
762
|
+
useFindBy,
|
|
467
763
|
useUpload,
|
|
468
764
|
useSearch,
|
|
469
765
|
useCustomMutation,
|
package/dist/mutation.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ToastHandler } from "./client.js";
|
|
2
|
-
import
|
|
2
|
+
import { QUERY_CONFIGS } from "./query.js";
|
|
3
|
+
import * as _$_tanstack_react_query0 from "@tanstack/react-query";
|
|
3
4
|
import { QueryClient, QueryKey, UseMutateAsyncFunction, UseMutateFunction } from "@tanstack/react-query";
|
|
4
5
|
|
|
5
6
|
//#region src/mutation.d.ts
|
|
@@ -47,8 +48,8 @@ interface TransitionMutationConfig<TData, TVariables> {
|
|
|
47
48
|
toastHandler?: ToastHandler;
|
|
48
49
|
}
|
|
49
50
|
declare function useMutationWithTransition<TData, TVariables>(config: TransitionMutationConfig<TData, TVariables>): {
|
|
50
|
-
mutate: UseMutateFunction<TData, Error, TVariables,
|
|
51
|
-
mutateAsync: UseMutateAsyncFunction<TData, Error, TVariables,
|
|
51
|
+
mutate: UseMutateFunction<TData, Error, TVariables, void>;
|
|
52
|
+
mutateAsync: UseMutateAsyncFunction<TData, Error, TVariables, void>;
|
|
52
53
|
isPending: boolean;
|
|
53
54
|
isSuccess: boolean;
|
|
54
55
|
isError: boolean;
|
|
@@ -100,27 +101,12 @@ interface CreateOptimisticMutationConfig<TData, TVariables> {
|
|
|
100
101
|
shouldToast?: () => boolean;
|
|
101
102
|
toastHandler?: ToastHandler;
|
|
102
103
|
}
|
|
103
|
-
declare function useOptimisticMutation<TData, TVariables>(config: CreateOptimisticMutationConfig<TData, TVariables>): _tanstack_react_query0.UseMutationResult<TData, Error, TVariables, {
|
|
104
|
+
declare function useOptimisticMutation<TData, TVariables>(config: CreateOptimisticMutationConfig<TData, TVariables>): _$_tanstack_react_query0.UseMutationResult<TData, Error, TVariables, {
|
|
104
105
|
previous: {
|
|
105
106
|
key: readonly unknown[];
|
|
106
107
|
data: [readonly unknown[], unknown][];
|
|
107
108
|
}[];
|
|
108
109
|
}>;
|
|
109
|
-
declare const QUERY_CONFIGS: {
|
|
110
|
-
readonly realtime: {
|
|
111
|
-
readonly staleTime: 20000;
|
|
112
|
-
readonly refetchInterval: 30000;
|
|
113
|
-
};
|
|
114
|
-
readonly frequent: {
|
|
115
|
-
readonly staleTime: 60000;
|
|
116
|
-
};
|
|
117
|
-
readonly stable: {
|
|
118
|
-
readonly staleTime: 300000;
|
|
119
|
-
};
|
|
120
|
-
readonly static: {
|
|
121
|
-
readonly staleTime: 600000;
|
|
122
|
-
};
|
|
123
|
-
};
|
|
124
110
|
/** @deprecated Use `useOptimisticMutation` */
|
|
125
111
|
declare const createOptimisticMutation: typeof useOptimisticMutation;
|
|
126
112
|
//#endregion
|
package/dist/mutation.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { isArcApiError } from "./client.js";
|
|
3
|
+
import { isArcApiError, isAutoIdempotency } from "./client.js";
|
|
4
|
+
import { QUERY_CONFIGS } from "./query.js";
|
|
4
5
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
5
|
-
import { useTransition } from "react";
|
|
6
|
+
import { useCallback, useRef, useTransition } from "react";
|
|
6
7
|
|
|
7
8
|
//#region src/mutation.ts
|
|
8
9
|
let toastHandler = {
|
|
@@ -43,8 +44,18 @@ function useMutationWithTransition(config) {
|
|
|
43
44
|
const { mutationFn, invalidateQueries = [], onSuccess, onError, onSettled, messages, useTransition: withTransition = true, showToast: toast = true, toastHandler: instanceToast } = config;
|
|
44
45
|
const queryClient = useQueryClient();
|
|
45
46
|
const [isTransitioning, startTransition] = useTransition();
|
|
47
|
+
const idempotencyKeyRef = useRef(null);
|
|
46
48
|
const mutation = useMutation({
|
|
47
|
-
mutationFn
|
|
49
|
+
mutationFn: (variables) => {
|
|
50
|
+
if (idempotencyKeyRef.current && typeof variables === "object" && variables !== null) {
|
|
51
|
+
const vars = variables;
|
|
52
|
+
if (vars.options && typeof vars.options === "object") vars.options.idempotencyKey ??= idempotencyKeyRef.current;
|
|
53
|
+
}
|
|
54
|
+
return mutationFn(variables);
|
|
55
|
+
},
|
|
56
|
+
onMutate: () => {
|
|
57
|
+
idempotencyKeyRef.current = isAutoIdempotency() ? globalThis.crypto.randomUUID() : null;
|
|
58
|
+
},
|
|
48
59
|
onSuccess: (data, variables) => {
|
|
49
60
|
const invalidate = () => {
|
|
50
61
|
invalidateQueries.forEach((key) => queryClient.invalidateQueries({ queryKey: key }));
|
|
@@ -151,15 +162,6 @@ function useOptimisticMutation(config) {
|
|
|
151
162
|
}
|
|
152
163
|
});
|
|
153
164
|
}
|
|
154
|
-
const QUERY_CONFIGS = {
|
|
155
|
-
realtime: {
|
|
156
|
-
staleTime: 2e4,
|
|
157
|
-
refetchInterval: 3e4
|
|
158
|
-
},
|
|
159
|
-
frequent: { staleTime: 6e4 },
|
|
160
|
-
stable: { staleTime: 3e5 },
|
|
161
|
-
static: { staleTime: 6e5 }
|
|
162
|
-
};
|
|
163
165
|
/** @deprecated Use `useOptimisticMutation` */
|
|
164
166
|
const createOptimisticMutation = useOptimisticMutation;
|
|
165
167
|
|
package/dist/prefetch.d.ts
CHANGED
|
@@ -28,6 +28,21 @@ interface CrudPrefetcher {
|
|
|
28
28
|
* await productsPrefetcher.prefetchDetail(queryClient, productId);
|
|
29
29
|
*/
|
|
30
30
|
prefetchDetail: (queryClient: QueryClient, id: string, options?: PrefetchDetailOptions) => Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Prefetch a detail-by-slug query. Uses the same query keys as useDetailBySlug.
|
|
33
|
+
* Only available when the API has a `getBySlug` method (slugLookup preset).
|
|
34
|
+
*/
|
|
35
|
+
prefetchBySlug: (queryClient: QueryClient, slug: string, options?: PrefetchDetailOptions) => Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Prefetch soft-deleted items. Uses the same query keys as useDeleted.
|
|
38
|
+
* Only available when the API has a `getDeleted` method (softDelete preset).
|
|
39
|
+
*/
|
|
40
|
+
prefetchDeleted: (queryClient: QueryClient, params?: Record<string, unknown>, options?: PrefetchOptions) => Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Prefetch a tree query. Uses the same query keys as useTree.
|
|
43
|
+
* Only available when the API has a `getTree` method (tree preset).
|
|
44
|
+
*/
|
|
45
|
+
prefetchTree: (queryClient: QueryClient, params?: Record<string, unknown>, options?: PrefetchOptions) => Promise<void>;
|
|
31
46
|
}
|
|
32
47
|
/**
|
|
33
48
|
* Create server-safe prefetch helpers for CRUD queries.
|
|
@@ -65,6 +80,22 @@ declare function createCrudPrefetcher(api: {
|
|
|
65
80
|
token?: string | null;
|
|
66
81
|
organizationId?: string | null;
|
|
67
82
|
}) => Promise<unknown>;
|
|
83
|
+
getBySlug?: (opts: {
|
|
84
|
+
slug: string;
|
|
85
|
+
token?: string | null;
|
|
86
|
+
organizationId?: string | null;
|
|
87
|
+
params?: Record<string, unknown>;
|
|
88
|
+
}) => Promise<unknown>;
|
|
89
|
+
getDeleted?: (opts: {
|
|
90
|
+
params?: Record<string, unknown>;
|
|
91
|
+
token?: string | null;
|
|
92
|
+
organizationId?: string | null;
|
|
93
|
+
}) => Promise<unknown>;
|
|
94
|
+
getTree?: (opts: {
|
|
95
|
+
params?: Record<string, unknown>;
|
|
96
|
+
token?: string | null;
|
|
97
|
+
organizationId?: string | null;
|
|
98
|
+
}) => Promise<unknown>;
|
|
68
99
|
}, entityKey: string): CrudPrefetcher;
|
|
69
100
|
//#endregion
|
|
70
101
|
export { CrudPrefetcher, PrefetchDetailOptions, PrefetchOptions, createCrudPrefetcher, dehydrate };
|
package/dist/prefetch.js
CHANGED
|
@@ -1,23 +1,7 @@
|
|
|
1
|
+
import { createQueryKeys } from "./query.js";
|
|
1
2
|
import { dehydrate } from "@tanstack/react-query";
|
|
2
3
|
|
|
3
4
|
//#region src/prefetch.ts
|
|
4
|
-
function scopedListKey(entityKey, scope, params) {
|
|
5
|
-
return [
|
|
6
|
-
entityKey,
|
|
7
|
-
"list",
|
|
8
|
-
{
|
|
9
|
-
_scope: scope,
|
|
10
|
-
...params || {}
|
|
11
|
-
}
|
|
12
|
-
];
|
|
13
|
-
}
|
|
14
|
-
function detailKey(entityKey, id) {
|
|
15
|
-
return [
|
|
16
|
-
entityKey,
|
|
17
|
-
"detail",
|
|
18
|
-
id
|
|
19
|
-
];
|
|
20
|
-
}
|
|
21
5
|
/**
|
|
22
6
|
* Create server-safe prefetch helpers for CRUD queries.
|
|
23
7
|
* Use in Next.js server components to pre-populate the query cache before rendering.
|
|
@@ -44,10 +28,12 @@ function detailKey(entityKey, id) {
|
|
|
44
28
|
* }
|
|
45
29
|
*/
|
|
46
30
|
function createCrudPrefetcher(api, entityKey) {
|
|
31
|
+
const KEYS = createQueryKeys(entityKey);
|
|
47
32
|
return {
|
|
48
33
|
async prefetchList(queryClient, params = {}, options = {}) {
|
|
49
34
|
const { organizationId, ...restParams } = params;
|
|
50
|
-
const
|
|
35
|
+
const scope = organizationId ? "tenant" : "super-admin";
|
|
36
|
+
const queryKey = KEYS.scopedList(scope, {
|
|
51
37
|
organizationId,
|
|
52
38
|
...restParams
|
|
53
39
|
});
|
|
@@ -62,7 +48,7 @@ function createCrudPrefetcher(api, entityKey) {
|
|
|
62
48
|
},
|
|
63
49
|
async prefetchDetail(queryClient, id, options = {}) {
|
|
64
50
|
const { params, staleTime } = options;
|
|
65
|
-
const baseKey =
|
|
51
|
+
const baseKey = KEYS.detail(id);
|
|
66
52
|
const queryKey = params ? [...baseKey, params] : baseKey;
|
|
67
53
|
await queryClient.prefetchQuery({
|
|
68
54
|
queryKey,
|
|
@@ -72,6 +58,51 @@ function createCrudPrefetcher(api, entityKey) {
|
|
|
72
58
|
}),
|
|
73
59
|
staleTime
|
|
74
60
|
});
|
|
61
|
+
},
|
|
62
|
+
async prefetchBySlug(queryClient, slug, options = {}) {
|
|
63
|
+
if (!api.getBySlug) throw new Error(`[arc-next] prefetchBySlug requires an api with getBySlug (slugLookup preset)`);
|
|
64
|
+
const { params, staleTime } = options;
|
|
65
|
+
const queryKey = params ? KEYS.custom("slug", slug, params) : KEYS.custom("slug", slug);
|
|
66
|
+
await queryClient.prefetchQuery({
|
|
67
|
+
queryKey,
|
|
68
|
+
queryFn: () => api.getBySlug({
|
|
69
|
+
slug,
|
|
70
|
+
...params ? { params } : {}
|
|
71
|
+
}),
|
|
72
|
+
staleTime
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
async prefetchDeleted(queryClient, params = {}, options = {}) {
|
|
76
|
+
if (!api.getDeleted) throw new Error(`[arc-next] prefetchDeleted requires an api with getDeleted (softDelete preset)`);
|
|
77
|
+
const { organizationId, ...restParams } = params;
|
|
78
|
+
const queryKey = KEYS.custom("deleted", {
|
|
79
|
+
organizationId,
|
|
80
|
+
...restParams
|
|
81
|
+
});
|
|
82
|
+
await queryClient.prefetchQuery({
|
|
83
|
+
queryKey,
|
|
84
|
+
queryFn: () => api.getDeleted({
|
|
85
|
+
params: restParams,
|
|
86
|
+
organizationId: organizationId ?? null
|
|
87
|
+
}),
|
|
88
|
+
staleTime: options.staleTime
|
|
89
|
+
});
|
|
90
|
+
},
|
|
91
|
+
async prefetchTree(queryClient, params = {}, options = {}) {
|
|
92
|
+
if (!api.getTree) throw new Error(`[arc-next] prefetchTree requires an api with getTree (tree preset)`);
|
|
93
|
+
const { organizationId, ...restParams } = params;
|
|
94
|
+
const queryKey = KEYS.custom("tree", {
|
|
95
|
+
organizationId,
|
|
96
|
+
...restParams
|
|
97
|
+
});
|
|
98
|
+
await queryClient.prefetchQuery({
|
|
99
|
+
queryKey,
|
|
100
|
+
queryFn: () => api.getTree({
|
|
101
|
+
params: restParams,
|
|
102
|
+
organizationId: organizationId ?? null
|
|
103
|
+
}),
|
|
104
|
+
staleTime: options.staleTime
|
|
105
|
+
});
|
|
75
106
|
}
|
|
76
107
|
};
|
|
77
108
|
}
|