@classytic/arc-next 0.9.0 → 0.10.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/cache.d.ts +15 -1
- package/dist/cache.js +21 -1
- package/dist/field-encryption.d.ts +86 -0
- package/dist/field-encryption.js +159 -0
- package/dist/hooks.d.ts +8 -0
- package/dist/hooks.js +20 -28
- package/dist/prefetch.d.ts +23 -62
- package/dist/prefetch.js +25 -103
- package/dist/presets/tree.js +1 -5
- package/dist/query-options.d.ts +165 -0
- package/dist/query-options.js +188 -0
- package/package.json +173 -165
package/dist/prefetch.js
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createEntityQueries } from "./query-options.js";
|
|
2
2
|
import { HydrationBoundary, dehydrate } from "@tanstack/react-query";
|
|
3
3
|
|
|
4
4
|
//#region src/prefetch.ts
|
|
5
|
+
/** Extract the queryFn context (auth + fetch caching) from prefetch options. */
|
|
6
|
+
function toCtx(options) {
|
|
7
|
+
const { staleTime: _staleTime, ...ctx } = options;
|
|
8
|
+
return ctx;
|
|
9
|
+
}
|
|
5
10
|
/**
|
|
6
11
|
* Create server-safe prefetch helpers for CRUD queries.
|
|
7
12
|
* Use in Next.js server components to pre-populate the query cache before rendering.
|
|
8
13
|
*
|
|
14
|
+
* Since 0.10 this is a thin layer over `createEntityQueries`
|
|
15
|
+
* (@classytic/arc-next/query-options) — the queryOptions factories are the
|
|
16
|
+
* single source of key + queryFn, shared with the client CRUD hooks, so
|
|
17
|
+
* prefetch keys can never drift from hook keys. Prefer the factories directly
|
|
18
|
+
* for new code that also needs `ensureQueryData` / router-loader integration:
|
|
19
|
+
*
|
|
20
|
+
* const products = createEntityQueries(productApi, 'products');
|
|
21
|
+
* await queryClient.prefetchQuery({ ...products.list({ limit: 20 }, { token }), staleTime: 60_000 });
|
|
22
|
+
*
|
|
9
23
|
* @example
|
|
10
24
|
* // products-prefetch.ts
|
|
11
25
|
* import { productsApi } from '@/api/products-api';
|
|
@@ -28,146 +42,54 @@ import { HydrationBoundary, dehydrate } from "@tanstack/react-query";
|
|
|
28
42
|
* }
|
|
29
43
|
*/
|
|
30
44
|
function createCrudPrefetcher(api, entityKey) {
|
|
31
|
-
const
|
|
32
|
-
const apiOptions = (o) => {
|
|
33
|
-
const opt = {};
|
|
34
|
-
if (o.headers) opt.headerOptions = o.headers;
|
|
35
|
-
if (o.cache !== void 0) opt.cache = o.cache;
|
|
36
|
-
if (o.revalidate !== void 0) opt.revalidate = o.revalidate;
|
|
37
|
-
if (o.tags !== void 0) opt.tags = o.tags;
|
|
38
|
-
return Object.keys(opt).length ? { options: opt } : {};
|
|
39
|
-
};
|
|
45
|
+
const queries = createEntityQueries(api, entityKey);
|
|
40
46
|
return {
|
|
41
47
|
async prefetchList(queryClient, params = {}, options = {}) {
|
|
42
|
-
const { organizationId: paramOrgId, ...restParams } = params;
|
|
43
|
-
const orgId = paramOrgId ?? options.organizationId ?? null;
|
|
44
|
-
const scope = orgId ? "tenant" : "super-admin";
|
|
45
|
-
const queryKey = KEYS.scopedList(scope, {
|
|
46
|
-
...orgId ? { organizationId: orgId } : {},
|
|
47
|
-
...restParams
|
|
48
|
-
});
|
|
49
48
|
await queryClient.prefetchQuery({
|
|
50
|
-
|
|
51
|
-
queryFn: () => api.getAll({
|
|
52
|
-
params: restParams,
|
|
53
|
-
token: options.token ?? null,
|
|
54
|
-
organizationId: orgId,
|
|
55
|
-
...apiOptions(options)
|
|
56
|
-
}),
|
|
49
|
+
...queries.list(params, toCtx(options)),
|
|
57
50
|
staleTime: options.staleTime
|
|
58
51
|
});
|
|
59
52
|
},
|
|
60
53
|
async prefetchDetail(queryClient, id, options = {}) {
|
|
61
|
-
const {
|
|
62
|
-
const baseKey = KEYS.detail(id);
|
|
63
|
-
const queryKey = params ? [...baseKey, params] : baseKey;
|
|
54
|
+
const { staleTime, ...detailOpts } = options;
|
|
64
55
|
await queryClient.prefetchQuery({
|
|
65
|
-
|
|
66
|
-
queryFn: () => api.getById({
|
|
67
|
-
id,
|
|
68
|
-
token: token ?? null,
|
|
69
|
-
organizationId: organizationId ?? null,
|
|
70
|
-
...params ? { params } : {},
|
|
71
|
-
...apiOptions(options)
|
|
72
|
-
}),
|
|
56
|
+
...queries.detail(id, detailOpts),
|
|
73
57
|
staleTime
|
|
74
58
|
});
|
|
75
59
|
},
|
|
76
60
|
async prefetchBySlug(queryClient, slug, options = {}) {
|
|
77
61
|
if (!api.getBySlug) throw new Error(`[arc-next] prefetchBySlug requires an api with getBySlug (slugLookup preset)`);
|
|
78
|
-
const {
|
|
79
|
-
const queryKey = params ? KEYS.custom("slug", slug, params) : KEYS.custom("slug", slug);
|
|
62
|
+
const { staleTime, ...detailOpts } = options;
|
|
80
63
|
await queryClient.prefetchQuery({
|
|
81
|
-
|
|
82
|
-
queryFn: () => api.getBySlug({
|
|
83
|
-
slug,
|
|
84
|
-
token: token ?? null,
|
|
85
|
-
organizationId: organizationId ?? null,
|
|
86
|
-
...params ? { params } : {},
|
|
87
|
-
...apiOptions(options)
|
|
88
|
-
}),
|
|
64
|
+
...queries.bySlug(slug, detailOpts),
|
|
89
65
|
staleTime
|
|
90
66
|
});
|
|
91
67
|
},
|
|
92
68
|
async prefetchDeleted(queryClient, params = {}, options = {}) {
|
|
93
69
|
if (!api.getDeleted) throw new Error(`[arc-next] prefetchDeleted requires an api with getDeleted (softDelete preset)`);
|
|
94
|
-
const { organizationId: paramOrgId, ...restParams } = params;
|
|
95
|
-
const orgId = paramOrgId ?? options.organizationId ?? null;
|
|
96
|
-
const queryKey = KEYS.custom("deleted", {
|
|
97
|
-
...orgId ? { organizationId: orgId } : {},
|
|
98
|
-
...restParams
|
|
99
|
-
});
|
|
100
70
|
await queryClient.prefetchQuery({
|
|
101
|
-
|
|
102
|
-
queryFn: () => api.getDeleted({
|
|
103
|
-
params: restParams,
|
|
104
|
-
token: options.token ?? null,
|
|
105
|
-
organizationId: orgId,
|
|
106
|
-
...apiOptions(options)
|
|
107
|
-
}),
|
|
71
|
+
...queries.deleted(params, toCtx(options)),
|
|
108
72
|
staleTime: options.staleTime
|
|
109
73
|
});
|
|
110
74
|
},
|
|
111
75
|
async prefetchAggregation(queryClient, name, filter, options = {}) {
|
|
112
76
|
if (!api.aggregate) throw new Error(`[arc-next] prefetchAggregation requires an api with aggregate (arc 2.13+)`);
|
|
113
77
|
if (!name) throw new Error("[arc-next] prefetchAggregation: aggregation name is required");
|
|
114
|
-
const orgId = options.organizationId ?? null;
|
|
115
|
-
const filterKey = orgId ? {
|
|
116
|
-
_org: orgId,
|
|
117
|
-
...filter ?? {}
|
|
118
|
-
} : filter ?? {};
|
|
119
78
|
await queryClient.prefetchQuery({
|
|
120
|
-
|
|
121
|
-
queryFn: () => api.aggregate({
|
|
122
|
-
name,
|
|
123
|
-
filter,
|
|
124
|
-
token: options.token ?? null,
|
|
125
|
-
organizationId: orgId,
|
|
126
|
-
...apiOptions(options)
|
|
127
|
-
}),
|
|
79
|
+
...queries.aggregation(name, filter, toCtx(options)),
|
|
128
80
|
staleTime: options.staleTime
|
|
129
81
|
});
|
|
130
82
|
},
|
|
131
83
|
async prefetchTree(queryClient, params = {}, options = {}) {
|
|
132
84
|
if (!api.getTree) throw new Error(`[arc-next] prefetchTree requires an api with getTree (tree preset)`);
|
|
133
|
-
const { organizationId: paramOrgId, ...restParams } = params;
|
|
134
|
-
const orgId = paramOrgId ?? options.organizationId ?? null;
|
|
135
|
-
const queryKey = KEYS.custom("tree", {
|
|
136
|
-
...orgId ? { organizationId: orgId } : {},
|
|
137
|
-
...restParams
|
|
138
|
-
});
|
|
139
85
|
await queryClient.prefetchQuery({
|
|
140
|
-
|
|
141
|
-
queryFn: () => api.getTree({
|
|
142
|
-
params: restParams,
|
|
143
|
-
token: options.token ?? null,
|
|
144
|
-
organizationId: orgId,
|
|
145
|
-
...apiOptions(options)
|
|
146
|
-
}),
|
|
86
|
+
...queries.tree(params, toCtx(options)),
|
|
147
87
|
staleTime: options.staleTime
|
|
148
88
|
});
|
|
149
89
|
},
|
|
150
90
|
async prefetchInfiniteList(queryClient, params = {}, options = {}) {
|
|
151
|
-
const { organizationId: paramOrgId, ...restParams } = params;
|
|
152
|
-
const orgId = paramOrgId ?? options.organizationId ?? null;
|
|
153
|
-
const scope = orgId ? "tenant" : "super-admin";
|
|
154
|
-
const queryKey = [...KEYS.scopedList(scope, {
|
|
155
|
-
...orgId ? { organizationId: orgId } : {},
|
|
156
|
-
...restParams
|
|
157
|
-
}), "infinite"];
|
|
158
91
|
await queryClient.prefetchInfiniteQuery({
|
|
159
|
-
|
|
160
|
-
queryFn: ({ pageParam }) => api.getAll({
|
|
161
|
-
params: {
|
|
162
|
-
...restParams,
|
|
163
|
-
...pageParam ? { page: pageParam } : {}
|
|
164
|
-
},
|
|
165
|
-
token: options.token ?? null,
|
|
166
|
-
organizationId: orgId,
|
|
167
|
-
...apiOptions(options)
|
|
168
|
-
}),
|
|
169
|
-
initialPageParam: 1,
|
|
170
|
-
getNextPageParam: () => void 0,
|
|
92
|
+
...queries.infiniteList(params, toCtx(options)),
|
|
171
93
|
staleTime: options.staleTime
|
|
172
94
|
});
|
|
173
95
|
}
|
package/dist/presets/tree.js
CHANGED
|
@@ -16,14 +16,10 @@
|
|
|
16
16
|
function withTree(api) {
|
|
17
17
|
return Object.assign(api, {
|
|
18
18
|
async getTree({ token = null, organizationId = null, params = {}, options = {} } = {}) {
|
|
19
|
-
const merged = {
|
|
20
|
-
...api.config.defaultParams,
|
|
21
|
-
...params
|
|
22
|
-
};
|
|
23
19
|
return api.request("GET", `${api.baseUrl}/tree`, {
|
|
24
20
|
token,
|
|
25
21
|
organizationId,
|
|
26
|
-
params
|
|
22
|
+
params,
|
|
27
23
|
options
|
|
28
24
|
});
|
|
29
25
|
},
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { QueryKeys } from "./cache.js";
|
|
2
|
+
import * as _$_tanstack_react_query0 from "@tanstack/react-query";
|
|
3
|
+
|
|
4
|
+
//#region src/query-options.d.ts
|
|
5
|
+
/** Per-call request context: auth + Next.js fetch caching passthrough. */
|
|
6
|
+
interface QueryFnContext {
|
|
7
|
+
/** Bearer token for protected endpoints (required server-side for non-public reads). */
|
|
8
|
+
token?: string | null;
|
|
9
|
+
/** Organization ID for multi-tenant reads. Also becomes part of the cache key. */
|
|
10
|
+
organizationId?: string | null;
|
|
11
|
+
/** Extra headers (e.g. x-api-key). */
|
|
12
|
+
headers?: Record<string, string>;
|
|
13
|
+
/** Next.js fetch caching forwarded to the API call (ISR-friendly prefetch). */
|
|
14
|
+
cache?: RequestCache;
|
|
15
|
+
revalidate?: number | false;
|
|
16
|
+
tags?: string[];
|
|
17
|
+
}
|
|
18
|
+
type ForwardedApiOptions = {
|
|
19
|
+
headerOptions?: Record<string, string>;
|
|
20
|
+
cache?: RequestCache;
|
|
21
|
+
revalidate?: number | false;
|
|
22
|
+
tags?: string[];
|
|
23
|
+
signal?: AbortSignal;
|
|
24
|
+
};
|
|
25
|
+
/** Structural read-API contract (BaseApi satisfies this; presets add the optionals). */
|
|
26
|
+
interface EntityReadApi {
|
|
27
|
+
getAll: (opts: {
|
|
28
|
+
params?: Record<string, unknown>;
|
|
29
|
+
token?: string | null;
|
|
30
|
+
organizationId?: string | null;
|
|
31
|
+
options?: ForwardedApiOptions;
|
|
32
|
+
}) => Promise<unknown>;
|
|
33
|
+
getById: (opts: {
|
|
34
|
+
id: string;
|
|
35
|
+
token?: string | null;
|
|
36
|
+
organizationId?: string | null;
|
|
37
|
+
params?: Record<string, unknown>;
|
|
38
|
+
options?: ForwardedApiOptions;
|
|
39
|
+
}) => Promise<unknown>;
|
|
40
|
+
getBySlug?: (opts: {
|
|
41
|
+
slug: string;
|
|
42
|
+
token?: string | null;
|
|
43
|
+
organizationId?: string | null;
|
|
44
|
+
params?: Record<string, unknown>;
|
|
45
|
+
options?: ForwardedApiOptions;
|
|
46
|
+
}) => Promise<unknown>;
|
|
47
|
+
getDeleted?: (opts: {
|
|
48
|
+
params?: Record<string, unknown>;
|
|
49
|
+
token?: string | null;
|
|
50
|
+
organizationId?: string | null;
|
|
51
|
+
options?: ForwardedApiOptions;
|
|
52
|
+
}) => Promise<unknown>;
|
|
53
|
+
getTree?: (opts: {
|
|
54
|
+
params?: Record<string, unknown>;
|
|
55
|
+
token?: string | null;
|
|
56
|
+
organizationId?: string | null;
|
|
57
|
+
options?: ForwardedApiOptions;
|
|
58
|
+
}) => Promise<unknown>;
|
|
59
|
+
getChildren?: (opts: {
|
|
60
|
+
parentId: string;
|
|
61
|
+
params?: Record<string, unknown>;
|
|
62
|
+
token?: string | null;
|
|
63
|
+
organizationId?: string | null;
|
|
64
|
+
options?: ForwardedApiOptions;
|
|
65
|
+
}) => Promise<unknown>;
|
|
66
|
+
aggregate?: (opts: {
|
|
67
|
+
name: string;
|
|
68
|
+
filter?: Record<string, unknown>;
|
|
69
|
+
token?: string | null;
|
|
70
|
+
organizationId?: string | null;
|
|
71
|
+
options?: ForwardedApiOptions;
|
|
72
|
+
}) => Promise<unknown>;
|
|
73
|
+
}
|
|
74
|
+
interface DetailQueryOpts extends QueryFnContext {
|
|
75
|
+
/** Query params (select, populate) — becomes part of the key, matching useDetail. */
|
|
76
|
+
params?: {
|
|
77
|
+
select?: string;
|
|
78
|
+
populate?: string | string[];
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
type EntityQueries = ReturnType<typeof createEntityQueries>;
|
|
82
|
+
/**
|
|
83
|
+
* Build queryOptions factories for one entity. Server-safe; keys are
|
|
84
|
+
* hash-identical to the corresponding `createCrudHooks` hooks.
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* // queries/products.ts — colocate next to the api definition
|
|
88
|
+
* export const productQueries = createEntityQueries(productApi, 'products');
|
|
89
|
+
*
|
|
90
|
+
* // RSC / route loader
|
|
91
|
+
* await queryClient.ensureQueryData(productQueries.detail(id, { token }));
|
|
92
|
+
*/
|
|
93
|
+
declare function createEntityQueries(api: EntityReadApi, entityKey: string): {
|
|
94
|
+
/** The entity's key factory — for invalidation / setQueryData at call sites. */keys: QueryKeys; /** GET /:resource — mirrors `useList`'s key (scoped, org-normalized). */
|
|
95
|
+
list(params?: Record<string, unknown>, ctx?: QueryFnContext): _$_tanstack_react_query0.OmitKeyof<_$_tanstack_react_query0.UseQueryOptions<unknown, Error, unknown, readonly unknown[]>, "queryFn"> & {
|
|
96
|
+
queryFn?: _$_tanstack_react_query0.QueryFunction<unknown, readonly unknown[], never> | undefined;
|
|
97
|
+
} & {
|
|
98
|
+
queryKey: readonly unknown[] & {
|
|
99
|
+
[dataTagSymbol]: unknown;
|
|
100
|
+
[dataTagErrorSymbol]: Error;
|
|
101
|
+
};
|
|
102
|
+
}; /** GET /:resource/:id — mirrors `useDetail`'s scoped key (+ params variant). */
|
|
103
|
+
detail(id: string, opts?: DetailQueryOpts): _$_tanstack_react_query0.OmitKeyof<_$_tanstack_react_query0.UseQueryOptions<unknown, Error, unknown, readonly unknown[]>, "queryFn"> & {
|
|
104
|
+
queryFn?: _$_tanstack_react_query0.QueryFunction<unknown, readonly unknown[], never> | undefined;
|
|
105
|
+
} & {
|
|
106
|
+
queryKey: readonly unknown[] & {
|
|
107
|
+
[dataTagSymbol]: unknown;
|
|
108
|
+
[dataTagErrorSymbol]: Error;
|
|
109
|
+
};
|
|
110
|
+
}; /** GET /:resource/slug/:slug — mirrors `useDetailBySlug`'s key. */
|
|
111
|
+
bySlug(slug: string, opts?: DetailQueryOpts): _$_tanstack_react_query0.OmitKeyof<_$_tanstack_react_query0.UseQueryOptions<unknown, Error, unknown, readonly unknown[]>, "queryFn"> & {
|
|
112
|
+
queryFn?: _$_tanstack_react_query0.QueryFunction<unknown, readonly unknown[], never> | undefined;
|
|
113
|
+
} & {
|
|
114
|
+
queryKey: readonly unknown[] & {
|
|
115
|
+
[dataTagSymbol]: unknown;
|
|
116
|
+
[dataTagErrorSymbol]: Error;
|
|
117
|
+
};
|
|
118
|
+
}; /** GET /:resource/deleted — mirrors `useDeleted`'s key. */
|
|
119
|
+
deleted(params?: Record<string, unknown>, ctx?: QueryFnContext): _$_tanstack_react_query0.OmitKeyof<_$_tanstack_react_query0.UseQueryOptions<unknown, Error, unknown, readonly unknown[]>, "queryFn"> & {
|
|
120
|
+
queryFn?: _$_tanstack_react_query0.QueryFunction<unknown, readonly unknown[], never> | undefined;
|
|
121
|
+
} & {
|
|
122
|
+
queryKey: readonly unknown[] & {
|
|
123
|
+
[dataTagSymbol]: unknown;
|
|
124
|
+
[dataTagErrorSymbol]: Error;
|
|
125
|
+
};
|
|
126
|
+
}; /** GET /:resource/tree — mirrors `useTree`'s key. */
|
|
127
|
+
tree(params?: Record<string, unknown>, ctx?: QueryFnContext): _$_tanstack_react_query0.OmitKeyof<_$_tanstack_react_query0.UseQueryOptions<unknown, Error, unknown, readonly unknown[]>, "queryFn"> & {
|
|
128
|
+
queryFn?: _$_tanstack_react_query0.QueryFunction<unknown, readonly unknown[], never> | undefined;
|
|
129
|
+
} & {
|
|
130
|
+
queryKey: readonly unknown[] & {
|
|
131
|
+
[dataTagSymbol]: unknown;
|
|
132
|
+
[dataTagErrorSymbol]: Error;
|
|
133
|
+
};
|
|
134
|
+
}; /** GET /:resource/:parentId/children — mirrors `useChildren`'s key. */
|
|
135
|
+
children(parentId: string, params?: Record<string, unknown>, ctx?: QueryFnContext): _$_tanstack_react_query0.OmitKeyof<_$_tanstack_react_query0.UseQueryOptions<unknown, Error, unknown, readonly unknown[]>, "queryFn"> & {
|
|
136
|
+
queryFn?: _$_tanstack_react_query0.QueryFunction<unknown, readonly unknown[], never> | undefined;
|
|
137
|
+
} & {
|
|
138
|
+
queryKey: readonly unknown[] & {
|
|
139
|
+
[dataTagSymbol]: unknown;
|
|
140
|
+
[dataTagErrorSymbol]: Error;
|
|
141
|
+
};
|
|
142
|
+
}; /** GET /:resource/aggregations/:name — mirrors `useAggregation`'s tenant-scoped key. */
|
|
143
|
+
aggregation(name: string, filter?: Record<string, unknown>, ctx?: QueryFnContext): _$_tanstack_react_query0.OmitKeyof<_$_tanstack_react_query0.UseQueryOptions<unknown, Error, unknown, readonly unknown[]>, "queryFn"> & {
|
|
144
|
+
queryFn?: _$_tanstack_react_query0.QueryFunction<unknown, readonly unknown[], never> | undefined;
|
|
145
|
+
} & {
|
|
146
|
+
queryKey: readonly unknown[] & {
|
|
147
|
+
[dataTagSymbol]: unknown;
|
|
148
|
+
[dataTagErrorSymbol]: Error;
|
|
149
|
+
};
|
|
150
|
+
};
|
|
151
|
+
/**
|
|
152
|
+
* Infinite list — mirrors `useInfiniteList`'s key (`scopedList + 'infinite'`)
|
|
153
|
+
* and its page-param semantics (keyset cursor or offset page + 1).
|
|
154
|
+
*/
|
|
155
|
+
infiniteList(params?: Record<string, unknown>, ctx?: QueryFnContext): _$_tanstack_react_query0.OmitKeyof<_$_tanstack_react_query0.UseInfiniteQueryOptions<unknown, Error, _$_tanstack_react_query0.InfiniteData<unknown, unknown>, unknown[], unknown>, "queryFn"> & {
|
|
156
|
+
queryFn?: _$_tanstack_react_query0.QueryFunction<unknown, unknown[], unknown> | undefined;
|
|
157
|
+
} & {
|
|
158
|
+
queryKey: unknown[] & {
|
|
159
|
+
[dataTagSymbol]: _$_tanstack_react_query0.InfiniteData<unknown, unknown>;
|
|
160
|
+
[dataTagErrorSymbol]: Error;
|
|
161
|
+
};
|
|
162
|
+
};
|
|
163
|
+
};
|
|
164
|
+
//#endregion
|
|
165
|
+
export { DetailQueryOpts, EntityQueries, EntityReadApi, QueryFnContext, createEntityQueries };
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { isKeysetPagination, isOffsetPagination } from "./api.js";
|
|
2
|
+
import { createQueryKeys, withOrgParams } from "./cache.js";
|
|
3
|
+
import { infiniteQueryOptions, queryOptions } from "@tanstack/react-query";
|
|
4
|
+
|
|
5
|
+
//#region src/query-options.ts
|
|
6
|
+
/**
|
|
7
|
+
* Build queryOptions factories for one entity. Server-safe; keys are
|
|
8
|
+
* hash-identical to the corresponding `createCrudHooks` hooks.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* // queries/products.ts — colocate next to the api definition
|
|
12
|
+
* export const productQueries = createEntityQueries(productApi, 'products');
|
|
13
|
+
*
|
|
14
|
+
* // RSC / route loader
|
|
15
|
+
* await queryClient.ensureQueryData(productQueries.detail(id, { token }));
|
|
16
|
+
*/
|
|
17
|
+
function createEntityQueries(api, entityKey) {
|
|
18
|
+
const KEYS = createQueryKeys(entityKey);
|
|
19
|
+
const fwd = (ctx, signal) => {
|
|
20
|
+
const opt = {};
|
|
21
|
+
if (ctx.headers) opt.headerOptions = ctx.headers;
|
|
22
|
+
if (ctx.cache !== void 0) opt.cache = ctx.cache;
|
|
23
|
+
if (ctx.revalidate !== void 0) opt.revalidate = ctx.revalidate;
|
|
24
|
+
if (ctx.tags !== void 0) opt.tags = ctx.tags;
|
|
25
|
+
if (signal) opt.signal = signal;
|
|
26
|
+
return Object.keys(opt).length ? { options: opt } : {};
|
|
27
|
+
};
|
|
28
|
+
const resolveOrg = (params, ctx) => {
|
|
29
|
+
const { organizationId: paramOrg, ...rest } = params;
|
|
30
|
+
return {
|
|
31
|
+
org: paramOrg ?? ctx.organizationId ?? null,
|
|
32
|
+
rest
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
return {
|
|
36
|
+
/** The entity's key factory — for invalidation / setQueryData at call sites. */
|
|
37
|
+
keys: KEYS,
|
|
38
|
+
/** GET /:resource — mirrors `useList`'s key (scoped, org-normalized). */
|
|
39
|
+
list(params = {}, ctx = {}) {
|
|
40
|
+
const { org, rest } = resolveOrg(params, ctx);
|
|
41
|
+
const scope = org ? "tenant" : "super-admin";
|
|
42
|
+
return queryOptions({
|
|
43
|
+
queryKey: KEYS.scopedList(scope, withOrgParams(org, rest)),
|
|
44
|
+
queryFn: ({ signal }) => api.getAll({
|
|
45
|
+
params: rest,
|
|
46
|
+
token: ctx.token ?? null,
|
|
47
|
+
organizationId: org,
|
|
48
|
+
...fwd(ctx, signal)
|
|
49
|
+
})
|
|
50
|
+
});
|
|
51
|
+
},
|
|
52
|
+
/** GET /:resource/:id — mirrors `useDetail`'s scoped key (+ params variant). */
|
|
53
|
+
detail(id, opts = {}) {
|
|
54
|
+
const { params, ...ctx } = opts;
|
|
55
|
+
const baseKey = KEYS.scopedDetail(id, ctx.organizationId ?? null);
|
|
56
|
+
return queryOptions({
|
|
57
|
+
queryKey: params ? [...baseKey, params] : baseKey,
|
|
58
|
+
queryFn: ({ signal }) => api.getById({
|
|
59
|
+
id,
|
|
60
|
+
token: ctx.token ?? null,
|
|
61
|
+
organizationId: ctx.organizationId ?? null,
|
|
62
|
+
...params ? { params } : {},
|
|
63
|
+
...fwd(ctx, signal)
|
|
64
|
+
})
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
/** GET /:resource/slug/:slug — mirrors `useDetailBySlug`'s key. */
|
|
68
|
+
bySlug(slug, opts = {}) {
|
|
69
|
+
const { params, ...ctx } = opts;
|
|
70
|
+
return queryOptions({
|
|
71
|
+
queryKey: params ? KEYS.custom("slug", slug, params) : KEYS.custom("slug", slug),
|
|
72
|
+
queryFn: ({ signal }) => {
|
|
73
|
+
if (!api.getBySlug) return Promise.reject(/* @__PURE__ */ new Error(`[arc-next] "${entityKey}" api does not define getBySlug (slugLookup preset)`));
|
|
74
|
+
return api.getBySlug({
|
|
75
|
+
slug,
|
|
76
|
+
token: ctx.token ?? null,
|
|
77
|
+
organizationId: ctx.organizationId ?? null,
|
|
78
|
+
...params ? { params } : {},
|
|
79
|
+
...fwd(ctx, signal)
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
/** GET /:resource/deleted — mirrors `useDeleted`'s key. */
|
|
85
|
+
deleted(params = {}, ctx = {}) {
|
|
86
|
+
const { org, rest } = resolveOrg(params, ctx);
|
|
87
|
+
return queryOptions({
|
|
88
|
+
queryKey: KEYS.custom("deleted", withOrgParams(org, rest)),
|
|
89
|
+
queryFn: ({ signal }) => {
|
|
90
|
+
if (!api.getDeleted) return Promise.reject(/* @__PURE__ */ new Error(`[arc-next] "${entityKey}" api does not define getDeleted (softDelete preset)`));
|
|
91
|
+
return api.getDeleted({
|
|
92
|
+
params: rest,
|
|
93
|
+
token: ctx.token ?? null,
|
|
94
|
+
organizationId: org,
|
|
95
|
+
...fwd(ctx, signal)
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
},
|
|
100
|
+
/** GET /:resource/tree — mirrors `useTree`'s key. */
|
|
101
|
+
tree(params = {}, ctx = {}) {
|
|
102
|
+
const { org, rest } = resolveOrg(params, ctx);
|
|
103
|
+
return queryOptions({
|
|
104
|
+
queryKey: KEYS.custom("tree", withOrgParams(org, rest)),
|
|
105
|
+
queryFn: ({ signal }) => {
|
|
106
|
+
if (!api.getTree) return Promise.reject(/* @__PURE__ */ new Error(`[arc-next] "${entityKey}" api does not define getTree (tree preset)`));
|
|
107
|
+
return api.getTree({
|
|
108
|
+
params: rest,
|
|
109
|
+
token: ctx.token ?? null,
|
|
110
|
+
organizationId: org,
|
|
111
|
+
...fwd(ctx, signal)
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
},
|
|
116
|
+
/** GET /:resource/:parentId/children — mirrors `useChildren`'s key. */
|
|
117
|
+
children(parentId, params = {}, ctx = {}) {
|
|
118
|
+
const { org, rest } = resolveOrg(params, ctx);
|
|
119
|
+
return queryOptions({
|
|
120
|
+
queryKey: KEYS.custom("children", parentId, withOrgParams(org, rest)),
|
|
121
|
+
queryFn: ({ signal }) => {
|
|
122
|
+
if (!api.getChildren) return Promise.reject(/* @__PURE__ */ new Error(`[arc-next] "${entityKey}" api does not define getChildren (tree preset)`));
|
|
123
|
+
return api.getChildren({
|
|
124
|
+
parentId,
|
|
125
|
+
params: rest,
|
|
126
|
+
token: ctx.token ?? null,
|
|
127
|
+
organizationId: org,
|
|
128
|
+
...fwd(ctx, signal)
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
},
|
|
133
|
+
/** GET /:resource/aggregations/:name — mirrors `useAggregation`'s tenant-scoped key. */
|
|
134
|
+
aggregation(name, filter, ctx = {}) {
|
|
135
|
+
const org = ctx.organizationId ?? null;
|
|
136
|
+
const filterKey = org ? {
|
|
137
|
+
_org: org,
|
|
138
|
+
...filter ?? {}
|
|
139
|
+
} : filter ?? {};
|
|
140
|
+
return queryOptions({
|
|
141
|
+
queryKey: KEYS.aggregation(name, filterKey),
|
|
142
|
+
queryFn: ({ signal }) => {
|
|
143
|
+
if (!api.aggregate) return Promise.reject(/* @__PURE__ */ new Error(`[arc-next] "${entityKey}" api does not define aggregate (arc 2.13+)`));
|
|
144
|
+
return api.aggregate({
|
|
145
|
+
name,
|
|
146
|
+
filter,
|
|
147
|
+
token: ctx.token ?? null,
|
|
148
|
+
organizationId: org,
|
|
149
|
+
...fwd(ctx, signal)
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
},
|
|
154
|
+
/**
|
|
155
|
+
* Infinite list — mirrors `useInfiniteList`'s key (`scopedList + 'infinite'`)
|
|
156
|
+
* and its page-param semantics (keyset cursor or offset page + 1).
|
|
157
|
+
*/
|
|
158
|
+
infiniteList(params = {}, ctx = {}) {
|
|
159
|
+
const { org, rest } = resolveOrg(params, ctx);
|
|
160
|
+
const scope = org ? "tenant" : "super-admin";
|
|
161
|
+
return infiniteQueryOptions({
|
|
162
|
+
queryKey: [...KEYS.scopedList(scope, withOrgParams(org, rest)), "infinite"],
|
|
163
|
+
queryFn: ({ pageParam, signal }) => api.getAll({
|
|
164
|
+
params: {
|
|
165
|
+
...rest,
|
|
166
|
+
...pageParam ? { page: pageParam } : {}
|
|
167
|
+
},
|
|
168
|
+
token: ctx.token ?? null,
|
|
169
|
+
organizationId: org,
|
|
170
|
+
...fwd(ctx, signal)
|
|
171
|
+
}),
|
|
172
|
+
initialPageParam: 1,
|
|
173
|
+
getNextPageParam: (lastPage) => {
|
|
174
|
+
if (isKeysetPagination(lastPage)) return lastPage.hasMore ? lastPage.next : void 0;
|
|
175
|
+
if (isOffsetPagination(lastPage)) {
|
|
176
|
+
const p = lastPage;
|
|
177
|
+
return p.hasNext ? p.page + 1 : void 0;
|
|
178
|
+
}
|
|
179
|
+
const p = lastPage;
|
|
180
|
+
if (p && typeof p.hasNext === "boolean" && typeof p.page === "number") return p.hasNext ? p.page + 1 : void 0;
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
//#endregion
|
|
188
|
+
export { createEntityQueries };
|