@classytic/arc-next 0.4.1 → 0.5.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/api.d.ts CHANGED
@@ -67,7 +67,7 @@ interface BulkDeleteResponse {
67
67
  }
68
68
  type SortDirection = 1 | -1 | 'asc' | 'desc';
69
69
  type SortSpec = Record<string, SortDirection> | string;
70
- type FilterOperator = 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'nin' | 'contains' | 'startsWith' | 'endsWith' | 'regex' | 'like' | 'exists' | 'size' | 'type';
70
+ type FilterOperator = 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'nin' | 'contains' | 'startsWith' | 'endsWith' | 'regex' | 'like' | 'exists' | 'size' | 'type' | 'between' | 'near' | 'nearSphere' | 'geoWithin' | 'withinRadius';
71
71
  interface QueryParams {
72
72
  page?: number;
73
73
  limit?: number;
@@ -97,6 +97,18 @@ interface RequestOptions {
97
97
  responseType?: 'json' | 'blob' | 'text';
98
98
  signal?: AbortSignal;
99
99
  }
100
+ /**
101
+ * Common args every BaseApi-style method accepts: `token`, `organizationId`,
102
+ * and `options` (per-request RequestOptions minus the auth fields).
103
+ *
104
+ * Preset method signatures extend this so the auth-injection contract stays
105
+ * uniform across every call (BaseApi method, preset method, custom user wrapper).
106
+ */
107
+ interface ScopedArgs {
108
+ token?: string | null;
109
+ organizationId?: string | null;
110
+ options?: Omit<RequestOptions, 'token' | 'organizationId'>;
111
+ }
100
112
  interface BaseApiConfig {
101
113
  basePath?: string;
102
114
  defaultParams?: {
@@ -187,154 +199,79 @@ declare class BaseApi<TDoc = Record<string, unknown>, TCreate = Partial<TDoc>, T
187
199
  id,
188
200
  path,
189
201
  options
190
- }: {
191
- token?: string | null;
192
- organizationId?: string | null;
202
+ }: ScopedArgs & {
193
203
  data: FormData; /** Resource ID — shorthand for path, appended as `baseUrl/{id}/upload` */
194
204
  id?: string; /** Custom sub-path appended as `baseUrl/{path}`. Takes precedence over `id`. */
195
205
  path?: string;
196
- options?: Omit<RequestOptions, 'token' | 'organizationId'>;
197
206
  }): Promise<ApiResponse<TDoc>>;
198
- search({
199
- token,
200
- organizationId,
201
- searchParams,
202
- params,
203
- options
204
- }?: {
205
- token?: string | null;
206
- organizationId?: string | null;
207
- searchParams?: Record<string, unknown>;
208
- params?: QueryParams;
209
- options?: Omit<RequestOptions, 'token' | 'organizationId'>;
210
- }): Promise<PaginatedResponse<TDoc>>;
211
- findBy({
212
- token,
213
- organizationId,
214
- field,
215
- value,
216
- operator,
217
- params,
218
- options
219
- }: {
220
- token?: string | null;
221
- organizationId?: string | null;
222
- field: string;
223
- value: unknown;
224
- operator?: FilterOperator;
225
- params?: QueryParams;
226
- options?: Omit<RequestOptions, 'token' | 'organizationId'>;
227
- }): Promise<PaginatedResponse<TDoc>>;
228
207
  request<TResponse = unknown>(method: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE', endpoint: string, {
229
208
  token,
230
209
  organizationId,
231
210
  data,
232
211
  params,
233
212
  options
234
- }?: {
235
- token?: string | null;
236
- organizationId?: string | null;
213
+ }?: ScopedArgs & {
237
214
  data?: unknown;
238
215
  params?: QueryParams;
239
- options?: Omit<RequestOptions, 'token' | 'organizationId'>;
240
216
  }): Promise<TResponse>;
241
- getDeleted({
217
+ /**
218
+ * Invoke a custom route mounted on this resource (e.g. `/todos/stats`,
219
+ * `/todos/recent`). Resource-relative wrapper around {@link request} that
220
+ * prepends `this.baseUrl` so callers don't have to remember the prefix.
221
+ *
222
+ * Use this for `defineResource({ routes: [{ method, path, handler }] })` —
223
+ * Arc's escape hatch for endpoints that don't fit CRUD or actions.
224
+ *
225
+ * For aggregates / reports, prefer the response-aware {@link useApiQuery} hook
226
+ * (which auto-unwraps `{ success, data }`) and pass `invokeRoute` as the queryFn.
227
+ *
228
+ * @example
229
+ * // GET /todos/stats → { success, data: { total, byStatus } }
230
+ * const stats = await api.invokeRoute<{ total: number; byStatus: Record<string, number> }>({
231
+ * method: 'GET',
232
+ * path: '/stats',
233
+ * });
234
+ *
235
+ * // GET /todos/recent?limit=5 → paginated shape spread to root
236
+ * const recent = await api.invokeRoute<PaginatedResponse<Todo>>({
237
+ * method: 'GET',
238
+ * path: '/recent',
239
+ * params: { limit: 5 },
240
+ * });
241
+ *
242
+ * // POST /products/import — body + path
243
+ * await api.invokeRoute({
244
+ * method: 'POST',
245
+ * path: '/import',
246
+ * data: { source: 'csv', items: [...] },
247
+ * });
248
+ */
249
+ invokeRoute<TResponse = unknown>({
242
250
  token,
243
251
  organizationId,
252
+ method,
253
+ path,
254
+ data,
244
255
  params,
245
256
  options
246
- }?: {
247
- token?: string | null;
248
- organizationId?: string | null;
257
+ }: ScopedArgs & {
258
+ method?: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE'; /** Path relative to the resource baseUrl. Leading slash optional. */
259
+ path: string;
260
+ data?: unknown;
249
261
  params?: QueryParams;
250
- options?: Omit<RequestOptions, 'token' | 'organizationId'>;
251
- }): Promise<PaginatedResponse<TDoc>>;
252
- restore({
262
+ }): Promise<TResponse>;
263
+ dispatchAction<TResult = unknown, TBody extends Record<string, unknown> = Record<string, unknown>>({
253
264
  token,
254
265
  organizationId,
255
266
  id,
256
- options
257
- }: {
258
- token?: string | null;
259
- organizationId?: string | null;
260
- id: string;
261
- options?: Omit<RequestOptions, 'token' | 'organizationId'>;
262
- }): Promise<ApiResponse<TDoc>>;
263
- bulkCreate({
264
- token,
265
- organizationId,
266
- data,
267
- options
268
- }: {
269
- token?: string | null;
270
- organizationId?: string | null;
271
- data: TCreate[];
272
- options?: Omit<RequestOptions, 'token' | 'organizationId'>;
273
- }): Promise<BulkCreateResponse<TDoc>>;
274
- bulkUpdate({
275
- token,
276
- organizationId,
277
- filter,
267
+ action,
278
268
  data,
279
269
  options
280
- }: {
281
- token?: string | null;
282
- organizationId?: string | null;
283
- filter: Record<string, unknown>;
284
- data: TUpdate;
285
- options?: Omit<RequestOptions, 'token' | 'organizationId'>;
286
- }): Promise<BulkUpdateResponse>;
287
- bulkDelete({
288
- token,
289
- organizationId,
290
- filter,
291
- options
292
- }: {
293
- token?: string | null;
294
- organizationId?: string | null;
295
- filter: Record<string, unknown>;
296
- options?: Omit<RequestOptions, 'token' | 'organizationId'>;
297
- }): Promise<BulkDeleteResponse>;
298
- getBySlug({
299
- token,
300
- organizationId,
301
- slug,
302
- params,
303
- options
304
- }: {
305
- token?: string | null;
306
- organizationId?: string | null;
307
- slug: string;
308
- params?: {
309
- select?: string;
310
- populate?: string | string[];
311
- };
312
- options?: Omit<RequestOptions, 'token' | 'organizationId'>;
313
- }): Promise<ApiResponse<TDoc>>;
314
- getTree({
315
- token,
316
- organizationId,
317
- params,
318
- options
319
- }?: {
320
- token?: string | null;
321
- organizationId?: string | null;
322
- params?: QueryParams;
323
- options?: Omit<RequestOptions, 'token' | 'organizationId'>;
324
- }): Promise<ApiResponse<TDoc[]>>;
325
- getChildren({
326
- token,
327
- organizationId,
328
- parentId,
329
- params,
330
- options
331
- }: {
332
- token?: string | null;
333
- organizationId?: string | null;
334
- parentId: string;
335
- params?: QueryParams;
336
- options?: Omit<RequestOptions, 'token' | 'organizationId'>;
337
- }): Promise<PaginatedResponse<TDoc>>;
270
+ }: ScopedArgs & {
271
+ id: string;
272
+ action: string;
273
+ data?: TBody;
274
+ }): Promise<ApiResponse<TResult>>;
338
275
  }
339
276
  declare function createCrudApi<TDoc = Record<string, unknown>, TCreate = Partial<TDoc>, TUpdate = Partial<TDoc>>(entity: string, config?: BaseApiConfig): BaseApi<TDoc, TCreate, TUpdate>;
340
277
  type ExtractDoc<T> = T extends PaginatedResponse<infer D> ? D : never;
@@ -342,4 +279,4 @@ declare function isOffsetPagination<T>(response: PaginatedResponse<T>): response
342
279
  declare function isKeysetPagination<T>(response: PaginatedResponse<T>): response is KeysetPaginationResponse<T>;
343
280
  declare function isAggregatePagination<T>(response: PaginatedResponse<T>): response is AggregatePaginationResponse<T>;
344
281
  //#endregion
345
- export { AggregatePaginationResponse, ApiResponse, BaseApi, BaseApiConfig, BulkCreateResponse, BulkDeleteResponse, BulkUpdateResponse, DeleteResponse, ExtractDoc, FilterOperator, KeysetPaginationResponse, OffsetPaginationResponse, PaginatedResponse, PopulateOption, QueryParams, RequestOptions, SortDirection, SortSpec, createCrudApi, isAggregatePagination, isKeysetPagination, isOffsetPagination };
282
+ export { AggregatePaginationResponse, ApiResponse, BaseApi, BaseApiConfig, BulkCreateResponse, BulkDeleteResponse, BulkUpdateResponse, DeleteResponse, ExtractDoc, FilterOperator, KeysetPaginationResponse, OffsetPaginationResponse, PaginatedResponse, PopulateOption, QueryParams, RequestOptions, ScopedArgs, SortDirection, SortSpec, createCrudApi, isAggregatePagination, isKeysetPagination, isOffsetPagination };
package/dist/api.js CHANGED
@@ -63,7 +63,8 @@ var BaseApi = class {
63
63
  }
64
64
  if (value !== void 0 && value !== "") if (["page", "limit"].includes(key)) result[key] = parseInt(String(value)) || (key === "page" ? 1 : 10);
65
65
  else if (Array.isArray(value)) {
66
- if (value.length > 1) result[`${key}[in]`] = value.join(",");
66
+ if (/\[([^\]]+)\]$/.test(key)) result[key] = value.join(",");
67
+ else if (value.length > 1) result[`${key}[in]`] = value.join(",");
67
68
  else if (value.length === 1) result[key] = value[0];
68
69
  } else result[key] = value;
69
70
  });
@@ -133,40 +134,6 @@ var BaseApi = class {
133
134
  if (organizationId) requestOptions.organizationId = organizationId;
134
135
  return this.requestFn("POST", url, this.withHeaders(requestOptions));
135
136
  }
136
- async search({ token = null, organizationId = null, searchParams = {}, params = {}, options = {} } = {}) {
137
- const queryParams = {
138
- ...this.config.defaultParams,
139
- ...params,
140
- ...searchParams
141
- };
142
- const processedParams = this.prepareParams(queryParams);
143
- const queryString = this.createQueryString(processedParams);
144
- const requestOptions = {
145
- cache: this.config.cache,
146
- ...options
147
- };
148
- if (token) requestOptions.token = token;
149
- if (organizationId) requestOptions.organizationId = organizationId;
150
- return this.requestFn("GET", `${this.baseUrl}?${queryString}`, this.withHeaders(requestOptions));
151
- }
152
- async findBy({ token = null, organizationId = null, field, value, operator, params = {}, options = {} }) {
153
- if (!field || value === void 0) throw new Error("Field and value are required");
154
- const queryParams = {
155
- ...this.config.defaultParams,
156
- ...params
157
- };
158
- if (operator) queryParams[`${field}[${operator}]`] = Array.isArray(value) ? value.join(",") : value;
159
- else queryParams[field] = value;
160
- const processedParams = this.prepareParams(queryParams);
161
- const queryString = this.createQueryString(processedParams);
162
- const requestOptions = {
163
- cache: this.config.cache,
164
- ...options
165
- };
166
- if (token) requestOptions.token = token;
167
- if (organizationId) requestOptions.organizationId = organizationId;
168
- return this.requestFn("GET", `${this.baseUrl}?${queryString}`, this.withHeaders(requestOptions));
169
- }
170
137
  async request(method, endpoint, { token = null, organizationId = null, data, params, options = {} } = {}) {
171
138
  let url = endpoint;
172
139
  if (params) {
@@ -182,96 +149,63 @@ var BaseApi = class {
182
149
  if (organizationId) requestOptions.organizationId = organizationId;
183
150
  return this.requestFn(method, url, this.withHeaders(requestOptions));
184
151
  }
185
- async getDeleted({ token = null, organizationId = null, params = {}, options = {} } = {}) {
186
- const mergedParams = {
187
- ...this.config.defaultParams,
188
- ...params
189
- };
190
- const processedParams = this.prepareParams(mergedParams);
191
- const queryString = this.createQueryString(processedParams);
192
- const requestOptions = {
193
- cache: this.config.cache,
194
- ...options
195
- };
196
- if (token) requestOptions.token = token;
197
- if (organizationId) requestOptions.organizationId = organizationId;
198
- return this.requestFn("GET", `${this.baseUrl}/deleted?${queryString}`, this.withHeaders(requestOptions));
152
+ /**
153
+ * Invoke a custom route mounted on this resource (e.g. `/todos/stats`,
154
+ * `/todos/recent`). Resource-relative wrapper around {@link request} that
155
+ * prepends `this.baseUrl` so callers don't have to remember the prefix.
156
+ *
157
+ * Use this for `defineResource({ routes: [{ method, path, handler }] })` —
158
+ * Arc's escape hatch for endpoints that don't fit CRUD or actions.
159
+ *
160
+ * For aggregates / reports, prefer the response-aware {@link useApiQuery} hook
161
+ * (which auto-unwraps `{ success, data }`) and pass `invokeRoute` as the queryFn.
162
+ *
163
+ * @example
164
+ * // GET /todos/stats → { success, data: { total, byStatus } }
165
+ * const stats = await api.invokeRoute<{ total: number; byStatus: Record<string, number> }>({
166
+ * method: 'GET',
167
+ * path: '/stats',
168
+ * });
169
+ *
170
+ * // GET /todos/recent?limit=5 → paginated shape spread to root
171
+ * const recent = await api.invokeRoute<PaginatedResponse<Todo>>({
172
+ * method: 'GET',
173
+ * path: '/recent',
174
+ * params: { limit: 5 },
175
+ * });
176
+ *
177
+ * // POST /products/import — body + path
178
+ * await api.invokeRoute({
179
+ * method: 'POST',
180
+ * path: '/import',
181
+ * data: { source: 'csv', items: [...] },
182
+ * });
183
+ */
184
+ async invokeRoute({ token = null, organizationId = null, method = "GET", path, data, params, options = {} }) {
185
+ if (!path) throw new Error("path is required");
186
+ const normalized = path.startsWith("/") ? path : `/${path}`;
187
+ const endpoint = `${this.baseUrl}${normalized}`;
188
+ return this.request(method, endpoint, {
189
+ token,
190
+ organizationId,
191
+ data,
192
+ params,
193
+ options
194
+ });
199
195
  }
200
- async restore({ token = null, organizationId = null, id, options = {} }) {
196
+ async dispatchAction({ token = null, organizationId = null, id, action, data, options = {} }) {
201
197
  if (!id) throw new Error("ID is required");
202
- const requestOptions = { ...options };
203
- if (token) requestOptions.token = token;
204
- if (organizationId) requestOptions.organizationId = organizationId;
205
- return this.requestFn("POST", `${this.baseUrl}/${id}/restore`, this.withHeaders(requestOptions));
206
- }
207
- async bulkCreate({ token = null, organizationId = null, data, options = {} }) {
208
- const requestOptions = {
209
- body: data,
210
- ...options
211
- };
212
- if (token) requestOptions.token = token;
213
- if (organizationId) requestOptions.organizationId = organizationId;
214
- return this.requestFn("POST", `${this.baseUrl}/bulk`, this.withHeaders(requestOptions));
215
- }
216
- async bulkUpdate({ token = null, organizationId = null, filter, data, options = {} }) {
198
+ if (!action) throw new Error("Action name is required");
217
199
  const requestOptions = {
218
200
  body: {
219
- filter,
220
- data
201
+ action,
202
+ ...data ?? {}
221
203
  },
222
204
  ...options
223
205
  };
224
206
  if (token) requestOptions.token = token;
225
207
  if (organizationId) requestOptions.organizationId = organizationId;
226
- return this.requestFn("PATCH", `${this.baseUrl}/bulk`, this.withHeaders(requestOptions));
227
- }
228
- async bulkDelete({ token = null, organizationId = null, filter, options = {} }) {
229
- const requestOptions = {
230
- body: { filter },
231
- ...options
232
- };
233
- if (token) requestOptions.token = token;
234
- if (organizationId) requestOptions.organizationId = organizationId;
235
- return this.requestFn("DELETE", `${this.baseUrl}/bulk`, this.withHeaders(requestOptions));
236
- }
237
- async getBySlug({ token = null, organizationId = null, slug, params = {}, options = {} }) {
238
- if (!slug) throw new Error("Slug is required");
239
- const queryString = this.createQueryString(params);
240
- const url = queryString ? `${this.baseUrl}/slug/${slug}?${queryString}` : `${this.baseUrl}/slug/${slug}`;
241
- const requestOptions = {
242
- cache: this.config.cache,
243
- ...options
244
- };
245
- if (token) requestOptions.token = token;
246
- if (organizationId) requestOptions.organizationId = organizationId;
247
- return this.requestFn("GET", url, this.withHeaders(requestOptions));
248
- }
249
- async getTree({ token = null, organizationId = null, params = {}, options = {} } = {}) {
250
- const processedParams = this.prepareParams(params);
251
- const queryString = this.createQueryString(processedParams);
252
- const requestOptions = {
253
- cache: this.config.cache,
254
- ...options
255
- };
256
- if (token) requestOptions.token = token;
257
- if (organizationId) requestOptions.organizationId = organizationId;
258
- return this.requestFn("GET", `${this.baseUrl}/tree?${queryString}`, this.withHeaders(requestOptions));
259
- }
260
- async getChildren({ token = null, organizationId = null, parentId, params = {}, options = {} }) {
261
- if (!parentId) throw new Error("Parent ID is required");
262
- const mergedParams = {
263
- ...this.config.defaultParams,
264
- ...params
265
- };
266
- const processedParams = this.prepareParams(mergedParams);
267
- const queryString = this.createQueryString(processedParams);
268
- const requestOptions = {
269
- cache: this.config.cache,
270
- ...options
271
- };
272
- if (token) requestOptions.token = token;
273
- if (organizationId) requestOptions.organizationId = organizationId;
274
- return this.requestFn("GET", `${this.baseUrl}/${parentId}/children?${queryString}`, this.withHeaders(requestOptions));
208
+ return this.requestFn("POST", `${this.baseUrl}/${id}/action`, this.withHeaders(requestOptions));
275
209
  }
276
210
  };
277
211
  function createCrudApi(entity, config = {}) {
@@ -0,0 +1,108 @@
1
+ import { QueryClient, QueryKey } from "@tanstack/react-query";
2
+
3
+ //#region src/cache.d.ts
4
+ interface PaginationData {
5
+ /** Pagination method detected from response (offset | keyset | aggregate) */
6
+ method: 'offset' | 'keyset' | 'aggregate' | null;
7
+ total: number;
8
+ pages: number;
9
+ page: number;
10
+ limit: number;
11
+ hasNext: boolean;
12
+ hasPrev: boolean;
13
+ /** Keyset cursor for next page (keyset pagination only) */
14
+ next?: string | null;
15
+ }
16
+ declare const DEFAULT_QUERY_CONFIG: {
17
+ readonly staleTime: number;
18
+ readonly gcTime: number;
19
+ readonly refetchOnWindowFocus: false;
20
+ readonly retry: 0;
21
+ };
22
+ /** Pre-built query config presets for common data freshness patterns. */
23
+ declare const QUERY_CONFIGS: {
24
+ /** Live data: 20s stale, 30s polling */readonly realtime: {
25
+ readonly staleTime: 20000;
26
+ readonly refetchInterval: 30000;
27
+ }; /** Frequently updated: 60s stale */
28
+ readonly frequent: {
29
+ readonly staleTime: 60000;
30
+ }; /** Stable data: 5min stale (same as default) */
31
+ readonly stable: {
32
+ readonly staleTime: 300000;
33
+ }; /** Rarely changes: 10min stale */
34
+ readonly static: {
35
+ readonly staleTime: 600000;
36
+ };
37
+ };
38
+ /**
39
+ * Extract `_id` or `id` from any item. Returns `null` if neither exists.
40
+ * Coerces numeric IDs to strings so cache keys stay consistent.
41
+ */
42
+ declare function getItemId(item: unknown): string | null;
43
+ /**
44
+ * Normalize any pagination response shape (offset / keyset / aggregate) to a
45
+ * uniform `PaginationData` object. Returns `null` when no pagination signal
46
+ * is present.
47
+ */
48
+ declare function normalizePagination(data: unknown): PaginationData | null;
49
+ /**
50
+ * Permissive list extractor. Checks well-known keys (`docs`, `data`, `items`,
51
+ * `results`) then falls back to *any* top-level array — so `{ products: [...] }`
52
+ * and `{ users: [...] }` work without per-resource configuration.
53
+ */
54
+ declare function extractItems<T>(data: unknown): T[];
55
+ /**
56
+ * Strict detail extractor. Checks well-known keys (`data`, `doc`, `item`,
57
+ * `result`); falls back to returning the response as-is. Primitive responses
58
+ * (string/number/boolean) pass through.
59
+ */
60
+ declare function extractItem<T>(data: unknown): T | null;
61
+ /**
62
+ * Optimistic-update helper that mutates the items array of a list cache
63
+ * regardless of which key holds it. Auto-adjusts `total`/`totalDocs` when
64
+ * the array length changes.
65
+ */
66
+ declare function updateListCache<T>(listData: unknown, updater: (items: T[]) => T[]): unknown;
67
+ interface QueryKeys {
68
+ all: string[];
69
+ lists: () => QueryKey;
70
+ list: (params?: unknown) => QueryKey;
71
+ details: () => QueryKey;
72
+ detail: (id: string) => QueryKey;
73
+ /** Tenant-scoped detail key. Use when IDs are only unique within an org. */
74
+ scopedDetail: (id: string, organizationId: string | null) => QueryKey;
75
+ custom: (key: string, ...args: unknown[]) => QueryKey;
76
+ scopedList: (scope: string, params?: unknown) => QueryKey;
77
+ }
78
+ /**
79
+ * Build a hierarchical query-key factory for a resource. The returned shape
80
+ * is identical between server (prefetch) and client (hooks), so RSC SSR
81
+ * hydration matches what client-side `useList`/`useDetail` produce.
82
+ */
83
+ declare function createQueryKeys(entityKey: string): QueryKeys;
84
+ interface CacheUtils<T> {
85
+ invalidateAll: (client: QueryClient) => Promise<void>;
86
+ invalidateLists: (client: QueryClient) => Promise<void>;
87
+ /** Invalidate detail by ID (prefix-matches all scoped/parameterized variants). */
88
+ invalidateDetail: (client: QueryClient, id: string) => Promise<void>;
89
+ setDetail: (client: QueryClient, id: string, data: T) => void;
90
+ getDetail: (client: QueryClient, id: string) => T | undefined;
91
+ removeDetail: (client: QueryClient, id: string) => void;
92
+ /** Invalidate tenant-scoped detail (prefix-matches parameterized variants within org). */
93
+ invalidateScopedDetail: (client: QueryClient, id: string, organizationId: string | null) => Promise<void>;
94
+ /** Set tenant-scoped detail cache. */
95
+ setScopedDetail: (client: QueryClient, id: string, organizationId: string | null, data: T) => void;
96
+ /** Get tenant-scoped detail from cache. */
97
+ getScopedDetail: (client: QueryClient, id: string, organizationId: string | null) => T | undefined;
98
+ /** Remove tenant-scoped detail from cache. */
99
+ removeScopedDetail: (client: QueryClient, id: string, organizationId: string | null) => void;
100
+ }
101
+ /**
102
+ * Build cache read/write/invalidate helpers bound to the given key factory.
103
+ * Server-safe — operates on a `QueryClient` instance which can be a per-request
104
+ * server client (during prefetch) or the browser singleton.
105
+ */
106
+ declare function createCacheUtils<T>(KEYS: QueryKeys): CacheUtils<T>;
107
+ //#endregion
108
+ export { CacheUtils, DEFAULT_QUERY_CONFIG, PaginationData, QUERY_CONFIGS, QueryKeys, createCacheUtils, createQueryKeys, extractItem, extractItems, getItemId, normalizePagination, updateListCache };