@classytic/arc-next 0.1.1 → 0.1.3
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 +488 -51
- package/dist/api.d.ts +2 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +21 -9
- package/dist/api.js.map +1 -1
- package/dist/hooks.d.ts +4 -0
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +3 -2
- package/dist/hooks.js.map +1 -1
- package/dist/query.d.ts +5 -0
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +15 -17
- package/dist/query.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# @classytic/arc-next
|
|
2
2
|
|
|
3
|
-
React + TanStack Query SDK for Arc resources.
|
|
3
|
+
React + TanStack Query SDK for Arc resources. Typed CRUD hooks with optimistic updates, automatic rollback, multi-tenant scoping, pagination normalization, and detail cache prefilling. No separate state management library needed.
|
|
4
|
+
|
|
5
|
+
**Requires:** React 19+, TanStack React Query 5+
|
|
4
6
|
|
|
5
7
|
## Install
|
|
6
8
|
|
|
@@ -19,31 +21,49 @@ npm install react@^19 @tanstack/react-query@^5
|
|
|
19
21
|
Call the configuration functions once at app init (e.g., in your root providers):
|
|
20
22
|
|
|
21
23
|
```ts
|
|
22
|
-
import { configureClient } from "@classytic/arc-next/client";
|
|
24
|
+
import { configureClient, configureAuth } from "@classytic/arc-next/client";
|
|
23
25
|
import { configureToast } from "@classytic/arc-next/mutation";
|
|
24
26
|
import { configureNavigation } from "@classytic/arc-next/hooks";
|
|
25
27
|
import { toast } from "sonner";
|
|
26
28
|
import { useRouter } from "next/navigation";
|
|
27
29
|
|
|
28
|
-
// Required — sets the API base URL
|
|
30
|
+
// Required — sets the API base URL and auth mode
|
|
29
31
|
configureClient({
|
|
30
32
|
baseUrl: process.env.NEXT_PUBLIC_API_URL!,
|
|
33
|
+
authMode: "cookie", // 'cookie' for Better Auth, 'bearer' for token auth (default)
|
|
31
34
|
internalApiKey: process.env.NEXT_PUBLIC_INTERNAL_API_KEY, // optional
|
|
32
35
|
});
|
|
33
36
|
|
|
34
|
-
// Optional —
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
// Optional — auto-inject org context into queries/mutations
|
|
38
|
+
configureAuth({
|
|
39
|
+
getOrgId: () => activeOrgId, // return current org ID
|
|
40
|
+
getToken: () => null, // null for cookie auth (token only for bearer)
|
|
38
41
|
});
|
|
39
42
|
|
|
43
|
+
// Optional — pluggable toast (defaults to console)
|
|
44
|
+
configureToast({ success: toast.success, error: toast.error });
|
|
45
|
+
|
|
40
46
|
// Optional — enables useNavigation() routing (defaults to cache-only)
|
|
41
47
|
configureNavigation(useRouter);
|
|
42
48
|
```
|
|
43
49
|
|
|
44
|
-
##
|
|
50
|
+
## Subpath Exports
|
|
51
|
+
|
|
52
|
+
| Import | Purpose | `"use client"` |
|
|
53
|
+
| ----------------------------------- | ---------------------------------------------------------------------------- | :-------------: |
|
|
54
|
+
| `@classytic/arc-next/client` | `configureClient`, `configureAuth`, `createClient`, `handleApiRequest` | No |
|
|
55
|
+
| `@classytic/arc-next/api` | `BaseApi`, `createCrudApi`, response types, type guards | No |
|
|
56
|
+
| `@classytic/arc-next/query` | `createQueryKeys`, `createCacheUtils`, `createListQuery`, `createDetailQuery`| Yes |
|
|
57
|
+
| `@classytic/arc-next/mutation` | `configureToast`, `useMutationWithTransition`, `createOptimisticMutation` | Yes |
|
|
58
|
+
| `@classytic/arc-next/hooks` | `createCrudHooks`, `configureNavigation` | Yes |
|
|
59
|
+
| `@classytic/arc-next/query-client` | `getQueryClient` (SSR-safe singleton) | No |
|
|
60
|
+
| `@classytic/arc-next/prefetch` | `createCrudPrefetcher`, `dehydrate` (SSR prefetch) | No |
|
|
61
|
+
|
|
62
|
+
No barrel index — every file is its own entry point. Tree-shakeable (`sideEffects: false`).
|
|
45
63
|
|
|
46
|
-
|
|
64
|
+
## Quick Start
|
|
65
|
+
|
|
66
|
+
### 1. Define API
|
|
47
67
|
|
|
48
68
|
```ts
|
|
49
69
|
import { createCrudApi } from "@classytic/arc-next/api";
|
|
@@ -98,21 +118,20 @@ export function ProductsPage() {
|
|
|
98
118
|
|
|
99
119
|
const { create, remove, isCreating } = useProductActions();
|
|
100
120
|
|
|
101
|
-
const handleCreate = async () => {
|
|
102
|
-
await create({ data: { name: "New Product", price: 29.99 } });
|
|
103
|
-
};
|
|
104
|
-
|
|
105
121
|
if (isLoading) return <div>Loading...</div>;
|
|
106
122
|
|
|
107
123
|
return (
|
|
108
124
|
<div>
|
|
109
|
-
<button
|
|
125
|
+
<button
|
|
126
|
+
onClick={() => create({ data: { name: "Widget", price: 9.99 } })}
|
|
127
|
+
disabled={isCreating}
|
|
128
|
+
>
|
|
110
129
|
Add Product
|
|
111
130
|
</button>
|
|
112
|
-
{items.map((
|
|
113
|
-
<div key={
|
|
114
|
-
{
|
|
115
|
-
<button onClick={() => remove({ id:
|
|
131
|
+
{items.map((p) => (
|
|
132
|
+
<div key={p._id}>
|
|
133
|
+
{p.name} — ${p.price}
|
|
134
|
+
<button onClick={() => remove({ id: p._id })}>Delete</button>
|
|
116
135
|
</div>
|
|
117
136
|
))}
|
|
118
137
|
{pagination && <span>{pagination.total} total</span>}
|
|
@@ -121,50 +140,193 @@ export function ProductsPage() {
|
|
|
121
140
|
}
|
|
122
141
|
```
|
|
123
142
|
|
|
124
|
-
##
|
|
143
|
+
## API Reference
|
|
125
144
|
|
|
126
|
-
|
|
127
|
-
|---|---|:-:|
|
|
128
|
-
| `@classytic/arc-next/client` | `configureClient`, `handleApiRequest`, `createQueryString` | No |
|
|
129
|
-
| `@classytic/arc-next/api` | `BaseApi`, `createCrudApi`, response types, type guards | No |
|
|
130
|
-
| `@classytic/arc-next/query` | `createQueryKeys`, `createCacheUtils`, `createListQuery`, `createDetailQuery` | Yes |
|
|
131
|
-
| `@classytic/arc-next/mutation` | `configureToast`, `useMutationWithTransition`, `createOptimisticMutation` | Yes |
|
|
132
|
-
| `@classytic/arc-next/hooks` | `createCrudHooks`, `configureNavigation` | Yes |
|
|
133
|
-
| `@classytic/arc-next/query-client` | `getQueryClient` (SSR-safe singleton) | No |
|
|
145
|
+
### `configureClient(config)`
|
|
134
146
|
|
|
135
|
-
|
|
147
|
+
```ts
|
|
148
|
+
configureClient({
|
|
149
|
+
baseUrl: string; // Required — API base URL
|
|
150
|
+
authMode?: 'cookie' | 'bearer'; // Default: 'bearer'
|
|
151
|
+
internalApiKey?: string; // Optional — sent as x-internal-api-key header
|
|
152
|
+
defaultHeaders?: Record<string, string>; // Optional — merged into every request
|
|
153
|
+
});
|
|
154
|
+
```
|
|
136
155
|
|
|
137
|
-
|
|
138
|
-
- **Optimistic Updates** — Create, update, delete with instant UI feedback and automatic rollback
|
|
139
|
-
- **Multi-Tenant Scoping** — `organizationId` in headers + scoped query keys
|
|
140
|
-
- **Pagination Normalization** — Handles `docs`/`data`/`items` response formats, offset/keyset/aggregate pagination
|
|
141
|
-
- **Detail Cache Prefilling** — List results auto-populate detail query cache
|
|
142
|
-
- **React 19 Transitions** — `useMutationWithTransition` wraps invalidation in `startTransition`
|
|
143
|
-
- **Pluggable Toast** — `configureToast()` — use sonner, react-hot-toast, or anything
|
|
144
|
-
- **Pluggable Navigation** — `configureNavigation()` — use Next.js, React Router, or any router
|
|
145
|
-
- **SSR-Safe QueryClient** — `getQueryClient()` — singleton in browser, new per request on server
|
|
146
|
-
- **Framework-Agnostic** — No hard dependency on Next.js
|
|
147
|
-
- **Tree-Shakeable** — `sideEffects: false`, flat files, no barrels
|
|
156
|
+
Must be called before any API requests. Throws if not configured.
|
|
148
157
|
|
|
149
|
-
|
|
158
|
+
### `configureAuth(config)`
|
|
150
159
|
|
|
151
|
-
|
|
160
|
+
```ts
|
|
161
|
+
configureAuth({
|
|
162
|
+
getToken?: () => string | null; // For bearer auth — return access token
|
|
163
|
+
getOrgId?: () => string | null; // Return active organization ID
|
|
164
|
+
});
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Auto-injects `token` and `organizationId` into queries/mutations. Hooks use the new signature (no explicit token param) — legacy signature still works.
|
|
168
|
+
|
|
169
|
+
### `handleApiRequest<T>(method, endpoint, options?)`
|
|
170
|
+
|
|
171
|
+
Universal fetch wrapper. Handles JSON, PDF, image, CSV, and text responses.
|
|
152
172
|
|
|
153
173
|
```ts
|
|
154
|
-
|
|
155
|
-
|
|
174
|
+
const result = await handleApiRequest<ApiResponse<User>>("GET", "/api/users/me");
|
|
175
|
+
const list = await handleApiRequest<PaginatedResponse<Product>>("GET", "/api/products?page=1");
|
|
176
|
+
```
|
|
156
177
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
178
|
+
**Options:**
|
|
179
|
+
- `body` — request body (auto-serializes JSON, passes FormData as-is)
|
|
180
|
+
- `token` — Bearer token
|
|
181
|
+
- `organizationId` — sent as `x-organization-id` header
|
|
182
|
+
- `headerOptions` — additional headers merged into request
|
|
183
|
+
- `revalidate` / `tags` / `cache` — Next.js fetch extensions
|
|
184
|
+
|
|
185
|
+
### `createQueryString(params)`
|
|
186
|
+
|
|
187
|
+
MongoKit-compatible query string builder:
|
|
188
|
+
- Arrays → `field[in]=a,b,c`
|
|
189
|
+
- `populateOptions` → `populate[path][select]=field1,field2`
|
|
190
|
+
- `null` → `field=null`
|
|
191
|
+
|
|
192
|
+
### `createCrudApi<TDoc, TCreate, TUpdate>(entity, config?)`
|
|
193
|
+
|
|
194
|
+
Creates a typed API client instance.
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
const api = createCrudApi<Product, CreateProduct>("products", {
|
|
198
|
+
basePath: "/api", // default: "/api/v1"
|
|
199
|
+
defaultParams: { limit: 20 },
|
|
200
|
+
cache: "no-store", // default
|
|
201
|
+
headers: { // optional — sent with every request from this instance
|
|
202
|
+
"x-arc-scope": "platform", // e.g. for superadmin elevation
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**Methods:**
|
|
208
|
+
|
|
209
|
+
| Method | Signature |
|
|
210
|
+
|---|---|
|
|
211
|
+
| `getAll` | `({ token?, organizationId?, params? }) → PaginatedResponse<T>` |
|
|
212
|
+
| `getById` | `({ id, token?, organizationId?, params? }) → ApiResponse<T>` |
|
|
213
|
+
| `create` | `({ data, token?, organizationId? }) → ApiResponse<T>` |
|
|
214
|
+
| `update` | `({ id, data, token?, organizationId? }) → ApiResponse<T>` |
|
|
215
|
+
| `delete` | `({ id, token?, organizationId? }) → DeleteResponse` |
|
|
216
|
+
| `search` | `({ searchParams?, params?, token?, organizationId? }) → PaginatedResponse<T>` |
|
|
217
|
+
| `findBy` | `({ field, value, operator?, token?, organizationId? }) → PaginatedResponse<T>` |
|
|
218
|
+
| `request` | `(method, endpoint, { data?, params?, token? }) → T` |
|
|
219
|
+
|
|
220
|
+
**`prepareParams(params)`** — processes query params: critical filters (`organizationId`, `ownerId`) preserved as null, arrays → `field[in]`, pagination parsed to int.
|
|
221
|
+
|
|
222
|
+
### `createCrudHooks<T, TCreate, TUpdate>(config)`
|
|
223
|
+
|
|
224
|
+
Factory that returns everything you need:
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
const { KEYS, cache, useList, useDetail, useActions, useNavigation } =
|
|
228
|
+
createCrudHooks<Product, CreateProduct>({
|
|
229
|
+
api: productsApi, // from createCrudApi()
|
|
230
|
+
entityKey: "products", // TanStack Query key prefix
|
|
231
|
+
singular: "Product", // for toast messages
|
|
232
|
+
defaults: { // optional
|
|
233
|
+
staleTime: 60_000,
|
|
234
|
+
messages: { createSuccess: "Product added!" },
|
|
235
|
+
},
|
|
236
|
+
callbacks: { // optional
|
|
237
|
+
onCreate: { onSuccess: (data) => console.log("Created:", data) },
|
|
238
|
+
},
|
|
163
239
|
});
|
|
164
|
-
}
|
|
165
240
|
```
|
|
166
241
|
|
|
167
|
-
|
|
242
|
+
**Returned hooks:**
|
|
243
|
+
|
|
244
|
+
#### `useList(token, params?, options?)`
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
const { items, pagination, isLoading, isFetching, refetch } = useList(
|
|
248
|
+
token,
|
|
249
|
+
{ organizationId: "org-123", status: "active" },
|
|
250
|
+
{ public: true, staleTime: 30_000, prefillDetailCache: true }
|
|
251
|
+
);
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
- Auto-scopes query keys by `organizationId` (tenant vs super-admin)
|
|
255
|
+
- Normalizes pagination from `docs`/`data`/`items`/`results` formats
|
|
256
|
+
- Prefills detail cache from list results (skips re-fetch on navigate)
|
|
257
|
+
- `options.public: true` — enables query without token
|
|
258
|
+
|
|
259
|
+
#### `useDetail(id, token, options?)`
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
const { item, isLoading } = useDetail(productId, token, {
|
|
263
|
+
organizationId: "org-123",
|
|
264
|
+
});
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
- Disabled when `id` is null (conditional fetching)
|
|
268
|
+
- Extracts item from `{ data: T }` wrapper
|
|
269
|
+
|
|
270
|
+
#### `useActions()`
|
|
271
|
+
|
|
272
|
+
```ts
|
|
273
|
+
const { create, update, remove, isCreating, isUpdating, isDeleting, isMutating } =
|
|
274
|
+
useActions();
|
|
275
|
+
|
|
276
|
+
// All mutations have optimistic updates + automatic rollback on error
|
|
277
|
+
await create({ data: { name: "New" }, organizationId: "org-123" });
|
|
278
|
+
await update({ id: "123", data: { name: "Updated" } });
|
|
279
|
+
await remove({ id: "123" });
|
|
280
|
+
|
|
281
|
+
// Per-call callbacks
|
|
282
|
+
await create(
|
|
283
|
+
{ data: { name: "New" } },
|
|
284
|
+
{ onSuccess: (item) => navigate(`/products/${item._id}`) }
|
|
285
|
+
);
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
- **Create** — optimistic: prepends to list with temp ID
|
|
289
|
+
- **Update** — optimistic: patches item in list + detail cache
|
|
290
|
+
- **Delete** — optimistic: removes from list + detail cache
|
|
291
|
+
- All roll back automatically on error
|
|
292
|
+
|
|
293
|
+
#### `useNavigation()`
|
|
294
|
+
|
|
295
|
+
```ts
|
|
296
|
+
const navigate = useNavigation();
|
|
297
|
+
navigate(`/products/${id}`, product); // push + cache prefill
|
|
298
|
+
navigate(`/products/${id}`, product, { replace: true }); // replace
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Sets detail cache before navigation (instant page load, no loading spinner).
|
|
302
|
+
Requires `configureNavigation(useRouter)` — without it, only sets cache (no routing).
|
|
303
|
+
|
|
304
|
+
### Query Keys (`KEYS`)
|
|
305
|
+
|
|
306
|
+
```ts
|
|
307
|
+
KEYS.all // ["products"]
|
|
308
|
+
KEYS.lists() // ["products", "list"]
|
|
309
|
+
KEYS.list(params) // ["products", "list", params]
|
|
310
|
+
KEYS.details() // ["products", "detail"]
|
|
311
|
+
KEYS.detail(id) // ["products", "detail", id]
|
|
312
|
+
KEYS.custom("stats", orgId) // ["products", "stats", orgId]
|
|
313
|
+
KEYS.scopedList("tenant", params) // ["products", "list", { _scope: "tenant", ...params }]
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Cache Utilities (`cache`)
|
|
317
|
+
|
|
318
|
+
```ts
|
|
319
|
+
await cache.invalidateAll(queryClient);
|
|
320
|
+
await cache.invalidateLists(queryClient);
|
|
321
|
+
await cache.invalidateDetail(queryClient, id);
|
|
322
|
+
cache.setDetail(queryClient, id, data);
|
|
323
|
+
cache.getDetail(queryClient, id); // T | undefined
|
|
324
|
+
cache.removeDetail(queryClient, id);
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### `getQueryClient(overrides?)`
|
|
328
|
+
|
|
329
|
+
SSR-safe singleton. Server: new per request. Browser: reuses singleton.
|
|
168
330
|
|
|
169
331
|
```ts
|
|
170
332
|
import { getQueryClient } from "@classytic/arc-next/query-client";
|
|
@@ -180,6 +342,281 @@ function Providers({ children }) {
|
|
|
180
342
|
}
|
|
181
343
|
```
|
|
182
344
|
|
|
345
|
+
Defaults: `staleTime: 5min`, `gcTime: 30min`, `retry: 0`, `refetchOnWindowFocus: false`.
|
|
346
|
+
|
|
347
|
+
## SSR Prefetch (Server Components)
|
|
348
|
+
|
|
349
|
+
Pre-populate the query cache on the server to avoid loading spinners:
|
|
350
|
+
|
|
351
|
+
```ts
|
|
352
|
+
// products-prefetch.ts
|
|
353
|
+
import { createCrudPrefetcher } from "@classytic/arc-next/prefetch";
|
|
354
|
+
import { productsApi } from "@/api/products-api";
|
|
355
|
+
|
|
356
|
+
export const productsPrefetcher = createCrudPrefetcher(productsApi, "products");
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
```tsx
|
|
360
|
+
// app/products/page.tsx (server component)
|
|
361
|
+
import { getQueryClient } from "@classytic/arc-next/query-client";
|
|
362
|
+
import { dehydrate } from "@classytic/arc-next/prefetch";
|
|
363
|
+
import { HydrationBoundary } from "@tanstack/react-query";
|
|
364
|
+
import { productsPrefetcher } from "@/prefetch/products-prefetch";
|
|
365
|
+
|
|
366
|
+
export default async function ProductsPage() {
|
|
367
|
+
const queryClient = getQueryClient();
|
|
368
|
+
await productsPrefetcher.prefetchList(queryClient, { limit: 20 });
|
|
369
|
+
|
|
370
|
+
return (
|
|
371
|
+
<HydrationBoundary state={dehydrate(queryClient)}>
|
|
372
|
+
<ProductsList />
|
|
373
|
+
</HydrationBoundary>
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
**Methods:** `prefetchList(queryClient, params?, options?)`, `prefetchDetail(queryClient, id, options?)`
|
|
379
|
+
|
|
380
|
+
## Custom Mutations
|
|
381
|
+
|
|
382
|
+
For operations beyond CRUD (publish, schedule, upload):
|
|
383
|
+
|
|
384
|
+
### `useMutationWithTransition(config)`
|
|
385
|
+
|
|
386
|
+
Mutation + React 19 `useTransition` for smooth cache invalidation:
|
|
387
|
+
|
|
388
|
+
```ts
|
|
389
|
+
import { useMutationWithTransition } from "@classytic/arc-next/mutation";
|
|
390
|
+
|
|
391
|
+
export function usePublishPost() {
|
|
392
|
+
return useMutationWithTransition({
|
|
393
|
+
mutationFn: (id: string) =>
|
|
394
|
+
postsApi.request("POST", `${postsApi.baseUrl}/${id}/publish`),
|
|
395
|
+
invalidateQueries: [postKeys.all],
|
|
396
|
+
messages: { success: "Published!", error: "Failed to publish" },
|
|
397
|
+
useTransition: true, // default
|
|
398
|
+
showToast: true, // default
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
Returns: `{ mutate, mutateAsync, isPending, isSuccess, isError, error, data, reset }`
|
|
404
|
+
|
|
405
|
+
### `useMutationWithOptimistic(config)`
|
|
406
|
+
|
|
407
|
+
Mutation + optimistic updates + automatic rollback:
|
|
408
|
+
|
|
409
|
+
```ts
|
|
410
|
+
import { useMutationWithOptimistic } from "@classytic/arc-next/mutation";
|
|
411
|
+
|
|
412
|
+
export function useToggleFavorite() {
|
|
413
|
+
return useMutationWithOptimistic({
|
|
414
|
+
mutationFn: ({ id, isFav }) =>
|
|
415
|
+
api.request("PATCH", `/api/products/${id}`, {
|
|
416
|
+
data: { favorite: !isFav },
|
|
417
|
+
}),
|
|
418
|
+
queryKeys: [productKeys.lists()],
|
|
419
|
+
optimisticUpdate: (old, { id, isFav }) =>
|
|
420
|
+
updateListCache(old, (items) =>
|
|
421
|
+
items.map((i) => (getItemId(i) === id ? { ...i, favorite: !isFav } : i))
|
|
422
|
+
),
|
|
423
|
+
messages: { success: "Updated!" },
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
### Query Config Presets
|
|
429
|
+
|
|
430
|
+
```ts
|
|
431
|
+
import { QUERY_CONFIGS } from "@classytic/arc-next/mutation";
|
|
432
|
+
|
|
433
|
+
// Use in useList options:
|
|
434
|
+
useProducts(token, {}, { ...QUERY_CONFIGS.realtime });
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
| Preset | `staleTime` | `refetchInterval` |
|
|
438
|
+
| ---------- | ----------- | ------------------ |
|
|
439
|
+
| `realtime` | 20s | 30s |
|
|
440
|
+
| `frequent` | 1min | — |
|
|
441
|
+
| `stable` | 5min | — |
|
|
442
|
+
| `static` | 10min | — |
|
|
443
|
+
|
|
444
|
+
## Low-Level Utilities
|
|
445
|
+
|
|
446
|
+
### `updateListCache(listData, updater)`
|
|
447
|
+
|
|
448
|
+
Transforms list cache regardless of format (`docs[]`, `data[]`, `items[]`, `results[]`, or raw array):
|
|
449
|
+
|
|
450
|
+
```ts
|
|
451
|
+
import { updateListCache } from "@classytic/arc-next/query";
|
|
452
|
+
|
|
453
|
+
queryClient.setQueryData(KEYS.lists(), (old) =>
|
|
454
|
+
updateListCache(old, (items) => items.filter((i) => i.status !== "archived"))
|
|
455
|
+
);
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
### `getItemId(item)`
|
|
459
|
+
|
|
460
|
+
Extracts `_id` or `id` from any item. Returns `string | null`.
|
|
461
|
+
|
|
462
|
+
## Multi-Client (Multiple APIs)
|
|
463
|
+
|
|
464
|
+
By default, `configureClient()` sets a single global `baseUrl`. Use `createClient()` when your app talks to multiple backends.
|
|
465
|
+
|
|
466
|
+
### Create isolated clients
|
|
467
|
+
|
|
468
|
+
```ts
|
|
469
|
+
import { createClient } from "@classytic/arc-next/client";
|
|
470
|
+
import { toast } from "sonner";
|
|
471
|
+
import { useRouter } from "next/navigation";
|
|
472
|
+
|
|
473
|
+
const analyticsClient = createClient({
|
|
474
|
+
baseUrl: "https://analytics.example.com",
|
|
475
|
+
internalApiKey: "analytics-key",
|
|
476
|
+
toast: { success: toast.success, error: toast.error },
|
|
477
|
+
navigation: useRouter,
|
|
478
|
+
});
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
### Use with createCrudApi
|
|
482
|
+
|
|
483
|
+
Pass `client` in the config — requests go through the client's `baseUrl` instead of the global one:
|
|
484
|
+
|
|
485
|
+
```ts
|
|
486
|
+
const eventsApi = createCrudApi("events", {
|
|
487
|
+
basePath: "/api",
|
|
488
|
+
client: analyticsClient,
|
|
489
|
+
});
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
### Use with createCrudHooks
|
|
493
|
+
|
|
494
|
+
Pass `client` — toast and navigation use the client's handlers instead of globals:
|
|
495
|
+
|
|
496
|
+
```ts
|
|
497
|
+
const { useList, useActions } = createCrudHooks({
|
|
498
|
+
api: eventsApi,
|
|
499
|
+
entityKey: "events",
|
|
500
|
+
singular: "Event",
|
|
501
|
+
client: analyticsClient,
|
|
502
|
+
});
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
### Direct requests
|
|
506
|
+
|
|
507
|
+
```ts
|
|
508
|
+
const data = await analyticsClient.request("GET", "/api/stats");
|
|
509
|
+
const result = await analyticsClient.request("POST", "/api/events", {
|
|
510
|
+
body: { type: "page_view" },
|
|
511
|
+
});
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
## Response Types
|
|
515
|
+
|
|
516
|
+
```ts
|
|
517
|
+
import type {
|
|
518
|
+
ApiResponse, // { success, data?, message? }
|
|
519
|
+
PaginatedResponse, // OffsetPaginationResponse | KeysetPaginationResponse | AggregatePaginationResponse
|
|
520
|
+
OffsetPaginationResponse, // { docs[], page, limit, total, pages, hasNext, hasPrev }
|
|
521
|
+
KeysetPaginationResponse, // { docs[], limit, hasMore, next }
|
|
522
|
+
AggregatePaginationResponse, // same shape as offset
|
|
523
|
+
DeleteResponse, // { success, deleted, id?, soft?, message? }
|
|
524
|
+
} from "@classytic/arc-next/api";
|
|
525
|
+
|
|
526
|
+
// Type guards
|
|
527
|
+
import {
|
|
528
|
+
isOffsetPagination,
|
|
529
|
+
isKeysetPagination,
|
|
530
|
+
isAggregatePagination,
|
|
531
|
+
} from "@classytic/arc-next/api";
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
## Error Handling
|
|
535
|
+
|
|
536
|
+
All API errors throw `ArcApiError`:
|
|
537
|
+
|
|
538
|
+
```ts
|
|
539
|
+
import { ArcApiError } from "@classytic/arc-next/client";
|
|
540
|
+
|
|
541
|
+
try {
|
|
542
|
+
await productsApi.create({ data: { name: "" } });
|
|
543
|
+
} catch (err) {
|
|
544
|
+
if (err instanceof ArcApiError) {
|
|
545
|
+
console.log(err.status); // HTTP status code
|
|
546
|
+
console.log(err.message); // Error message from server
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
## Common Patterns
|
|
552
|
+
|
|
553
|
+
### Multi-tenant data fetching
|
|
554
|
+
|
|
555
|
+
```ts
|
|
556
|
+
// organizationId in params → scoped query key → isolated cache per tenant
|
|
557
|
+
const { items } = useProducts(token, { organizationId: currentOrg });
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
### Public endpoints (no auth)
|
|
561
|
+
|
|
562
|
+
```ts
|
|
563
|
+
const { items } = useProducts(null, {}, { public: true });
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
### Conditional fetching
|
|
567
|
+
|
|
568
|
+
```ts
|
|
569
|
+
const { item } = useProduct(selectedId, token); // disabled when selectedId is null
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
### Per-call callbacks
|
|
573
|
+
|
|
574
|
+
```ts
|
|
575
|
+
await create(
|
|
576
|
+
{ data: formData, organizationId: org },
|
|
577
|
+
{
|
|
578
|
+
onSuccess: (product) => router.push(`/products/${product._id}`),
|
|
579
|
+
onError: (err) => setFieldErrors(err),
|
|
580
|
+
}
|
|
581
|
+
);
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
### Navigate with cache prefill
|
|
585
|
+
|
|
586
|
+
```ts
|
|
587
|
+
const navigate = useProductNavigation();
|
|
588
|
+
// Prefills detail cache → no loading spinner on detail page
|
|
589
|
+
navigate(`/products/${product._id}`, product);
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
### Per-instance headers
|
|
593
|
+
|
|
594
|
+
```ts
|
|
595
|
+
// All requests from this API include x-arc-scope header
|
|
596
|
+
const adminApi = createCrudApi("users", {
|
|
597
|
+
headers: { "x-arc-scope": "platform" },
|
|
598
|
+
});
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
## Features
|
|
602
|
+
|
|
603
|
+
- **CRUD Factory** — `createCrudApi` + `createCrudHooks` generates typed API clients and React Query hooks
|
|
604
|
+
- **Optimistic Updates** — Create, update, delete with instant UI feedback and automatic rollback
|
|
605
|
+
- **Multi-Tenant Scoping** — `organizationId` in headers + scoped query keys
|
|
606
|
+
- **Pagination Normalization** — Handles `docs`/`data`/`items`/`results` response formats, offset/keyset/aggregate pagination
|
|
607
|
+
- **Detail Cache Prefilling** — List results auto-populate detail query cache
|
|
608
|
+
- **React 19 Transitions** — `useMutationWithTransition` wraps invalidation in `startTransition`
|
|
609
|
+
- **Cookie & Bearer Auth** — `authMode: 'cookie'` for Better Auth, `'bearer'` for token auth
|
|
610
|
+
- **SSR Prefetch** — `createCrudPrefetcher` + `dehydrate` for server component data loading
|
|
611
|
+
- **Multi-Client** — `createClient()` for multiple API backends side by side
|
|
612
|
+
- **Pluggable Toast** — `configureToast()` — use sonner, react-hot-toast, or anything
|
|
613
|
+
- **Pluggable Navigation** — `configureNavigation()` — use Next.js, React Router, or any router
|
|
614
|
+
- **SSR-Safe QueryClient** — `getQueryClient()` — singleton in browser, new per request on server
|
|
615
|
+
- **Per-Instance Headers** — `config.headers` on `createCrudApi` merged into every request
|
|
616
|
+
- **Query Config Presets** — `QUERY_CONFIGS.realtime/frequent/stable/static`
|
|
617
|
+
- **Framework-Agnostic** — No hard dependency on Next.js
|
|
618
|
+
- **Tree-Shakeable** — `sideEffects: false`, flat files, no barrels
|
|
619
|
+
|
|
183
620
|
## License
|
|
184
621
|
|
|
185
622
|
MIT
|
package/dist/api.d.ts
CHANGED
|
@@ -94,6 +94,8 @@ declare class BaseApi<TDoc = Record<string, unknown>, TCreate = Partial<TDoc>, T
|
|
|
94
94
|
readonly baseUrl: string;
|
|
95
95
|
private readonly requestFn;
|
|
96
96
|
constructor(entity: string, config?: BaseApiConfig);
|
|
97
|
+
/** Merge per-instance headers into request options */
|
|
98
|
+
private withHeaders;
|
|
97
99
|
createQueryString(params?: Record<string, unknown>): string;
|
|
98
100
|
prepareParams(params?: QueryParams): Record<string, unknown>;
|
|
99
101
|
getAll({
|
package/dist/api.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","names":[],"sources":["../src/api.ts"],"mappings":";;;UAOiB,cAAA;EACf,IAAA;EACA,MAAA;EACA,KAAA,GAAQ,MAAA;AAAA;AAAA,UAOO,WAAA;EACf,OAAA;EACA,IAAA,GAAO,CAAA;EACP,OAAA;AAAA;AAAA,UAGe,wBAAA;EACf,OAAA;EACA,MAAA;EACA,IAAA,EAAM,CAAA;EACN,IAAA;EACA,KAAA;EACA,KAAA;EACA,KAAA;EACA,OAAA;EACA,OAAA;EACA,OAAA;AAAA;AAAA,UAGe,wBAAA;EACf,OAAA;EACA,MAAA;EACA,IAAA,EAAM,CAAA;EACN,KAAA;EACA,OAAA;EACA,IAAA;AAAA;AAAA,UAGe,2BAAA;EACf,OAAA;EACA,MAAA;EACA,IAAA,EAAM,CAAA;EACN,IAAA;EACA,KAAA;EACA,KAAA;EACA,KAAA;EACA,OAAA;EACA,OAAA;EACA,OAAA;AAAA;AAAA,KAGU,iBAAA,gBACR,wBAAA,CAAyB,CAAA,IACzB,wBAAA,CAAyB,CAAA,IACzB,2BAAA,CAA4B,CAAA;AAAA,UAEf,cAAA;EACf,OAAA;EACA,OAAA;EACA,EAAA;EACA,IAAA;EACA,OAAA;EACA,KAAA;AAAA;AAAA,KAOU,aAAA;AAAA,KACA,QAAA,GAAW,MAAA,SAAe,aAAA;AAAA,KAE1B,cAAA;AAAA,UAKK,WAAA;EACf,IAAA;EACA,KAAA;EACA,KAAA;EACA,MAAA;EACA,IAAA;EACA,MAAA;EACA,QAAA;EACA,eAAA,GAAkB,cAAA;EAClB,IAAA;EAAA,CACC,GAAA;AAAA;AAAA,UAGc,cAAA;EACf,KAAA;EACA,cAAA;EACA,KAAA,GAAQ,YAAA;EACR,UAAA;EACA,IAAA;EACA,aAAA,GAAgB,MAAA;EAChB,YAAA;EACA,MAAA,GAAS,WAAA;AAAA;AAAA,UAGM,aAAA;EACf,QAAA;EACA,aAAA;IACE,KAAA;IACA,IAAA;IAAA,CACC,GAAA;EAAA;EAEH,KAAA,GAAQ,YAAA;EACR,OAAA,GAAU,MAAA;EACV,MAAA,GAAS,SAAA;AAAA;AAAA,cAiBE,OAAA,QACJ,MAAA,6BACG,OAAA,CAAQ,IAAA,aACR,OAAA,CAAQ,IAAA;EAAA,SAET,MAAA;EAAA,SACA,MAAA,EAAQ,QAAA,CAAS,IAAA,CAAK,aAAA;EAAA,SACtB,OAAA;EAAA,iBACQ,SAAA;cAEL,MAAA,UAAgB,MAAA,GAAQ,aAAA;
|
|
1
|
+
{"version":3,"file":"api.d.ts","names":[],"sources":["../src/api.ts"],"mappings":"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"}
|
package/dist/api.js
CHANGED
|
@@ -21,6 +21,18 @@ var BaseApi = class {
|
|
|
21
21
|
};
|
|
22
22
|
this.baseUrl = `${this.config.basePath}/${this.entity}`;
|
|
23
23
|
}
|
|
24
|
+
/** Merge per-instance headers into request options */
|
|
25
|
+
withHeaders(options) {
|
|
26
|
+
const instanceHeaders = this.config.headers;
|
|
27
|
+
if (!instanceHeaders || Object.keys(instanceHeaders).length === 0) return options;
|
|
28
|
+
return {
|
|
29
|
+
...options,
|
|
30
|
+
headerOptions: {
|
|
31
|
+
...instanceHeaders,
|
|
32
|
+
...options.headerOptions ?? {}
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
24
36
|
createQueryString(params = {}) {
|
|
25
37
|
return createQueryString(params);
|
|
26
38
|
}
|
|
@@ -53,7 +65,7 @@ var BaseApi = class {
|
|
|
53
65
|
};
|
|
54
66
|
if (token) requestOptions.token = token;
|
|
55
67
|
if (organizationId) requestOptions.organizationId = organizationId;
|
|
56
|
-
return this.requestFn("GET", `${this.baseUrl}?${queryString}`, requestOptions);
|
|
68
|
+
return this.requestFn("GET", `${this.baseUrl}?${queryString}`, this.withHeaders(requestOptions));
|
|
57
69
|
}
|
|
58
70
|
async getById({ token = null, organizationId = null, id, params = {}, options = {} }) {
|
|
59
71
|
if (!id) throw new Error("ID is required");
|
|
@@ -65,7 +77,7 @@ var BaseApi = class {
|
|
|
65
77
|
};
|
|
66
78
|
if (token) requestOptions.token = token;
|
|
67
79
|
if (organizationId) requestOptions.organizationId = organizationId;
|
|
68
|
-
return this.requestFn("GET", url, requestOptions);
|
|
80
|
+
return this.requestFn("GET", url, this.withHeaders(requestOptions));
|
|
69
81
|
}
|
|
70
82
|
async create({ token, organizationId = null, data, options = {} }) {
|
|
71
83
|
const requestOptions = {
|
|
@@ -74,7 +86,7 @@ var BaseApi = class {
|
|
|
74
86
|
};
|
|
75
87
|
if (token) requestOptions.token = token;
|
|
76
88
|
if (organizationId) requestOptions.organizationId = organizationId;
|
|
77
|
-
return this.requestFn("POST", this.baseUrl, requestOptions);
|
|
89
|
+
return this.requestFn("POST", this.baseUrl, this.withHeaders(requestOptions));
|
|
78
90
|
}
|
|
79
91
|
async update({ token, organizationId = null, id, data, options = {} }) {
|
|
80
92
|
if (!id) throw new Error("ID is required");
|
|
@@ -84,14 +96,14 @@ var BaseApi = class {
|
|
|
84
96
|
};
|
|
85
97
|
if (token) requestOptions.token = token;
|
|
86
98
|
if (organizationId) requestOptions.organizationId = organizationId;
|
|
87
|
-
return this.requestFn("PATCH", `${this.baseUrl}/${id}`, requestOptions);
|
|
99
|
+
return this.requestFn("PATCH", `${this.baseUrl}/${id}`, this.withHeaders(requestOptions));
|
|
88
100
|
}
|
|
89
101
|
async delete({ token, organizationId = null, id, options = {} }) {
|
|
90
102
|
if (!id) throw new Error("ID is required");
|
|
91
103
|
const requestOptions = { ...options };
|
|
92
104
|
if (token) requestOptions.token = token;
|
|
93
105
|
if (organizationId) requestOptions.organizationId = organizationId;
|
|
94
|
-
return this.requestFn("DELETE", `${this.baseUrl}/${id}`, requestOptions);
|
|
106
|
+
return this.requestFn("DELETE", `${this.baseUrl}/${id}`, this.withHeaders(requestOptions));
|
|
95
107
|
}
|
|
96
108
|
async upload({ token, organizationId = null, data, path, options = {} }) {
|
|
97
109
|
const url = path ? `${this.baseUrl}/${path}` : this.baseUrl;
|
|
@@ -101,7 +113,7 @@ var BaseApi = class {
|
|
|
101
113
|
};
|
|
102
114
|
if (token) requestOptions.token = token;
|
|
103
115
|
if (organizationId) requestOptions.organizationId = organizationId;
|
|
104
|
-
return this.requestFn("POST", url, requestOptions);
|
|
116
|
+
return this.requestFn("POST", url, this.withHeaders(requestOptions));
|
|
105
117
|
}
|
|
106
118
|
async search({ token = null, organizationId = null, searchParams = {}, params = {}, options = {} } = {}) {
|
|
107
119
|
const queryParams = {
|
|
@@ -116,7 +128,7 @@ var BaseApi = class {
|
|
|
116
128
|
};
|
|
117
129
|
if (token) requestOptions.token = token;
|
|
118
130
|
if (organizationId) requestOptions.organizationId = organizationId;
|
|
119
|
-
return this.requestFn("GET", `${this.baseUrl}?${queryString}`, requestOptions);
|
|
131
|
+
return this.requestFn("GET", `${this.baseUrl}?${queryString}`, this.withHeaders(requestOptions));
|
|
120
132
|
}
|
|
121
133
|
async findBy({ token = null, organizationId = null, field, value, operator, params = {}, options = {} }) {
|
|
122
134
|
if (!field || value === void 0) throw new Error("Field and value are required");
|
|
@@ -131,7 +143,7 @@ var BaseApi = class {
|
|
|
131
143
|
};
|
|
132
144
|
if (token) requestOptions.token = token;
|
|
133
145
|
if (organizationId) requestOptions.organizationId = organizationId;
|
|
134
|
-
return this.requestFn("GET", `${this.baseUrl}?${queryString}`, requestOptions);
|
|
146
|
+
return this.requestFn("GET", `${this.baseUrl}?${queryString}`, this.withHeaders(requestOptions));
|
|
135
147
|
}
|
|
136
148
|
async request(method, endpoint, { token, organizationId = null, data, params, options = {} } = {}) {
|
|
137
149
|
let url = endpoint;
|
|
@@ -146,7 +158,7 @@ var BaseApi = class {
|
|
|
146
158
|
};
|
|
147
159
|
if (token) requestOptions.token = token;
|
|
148
160
|
if (organizationId) requestOptions.organizationId = organizationId;
|
|
149
|
-
return this.requestFn(method, url, requestOptions);
|
|
161
|
+
return this.requestFn(method, url, this.withHeaders(requestOptions));
|
|
150
162
|
}
|
|
151
163
|
};
|
|
152
164
|
function createCrudApi(entity, config = {}) {
|
package/dist/api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.js","names":[],"sources":["../src/api.ts"],"sourcesContent":["import { handleApiRequest, createQueryString } from './client.js';\nimport type { ApiRequestOptions, ArcClient } from './client.js';\n\n// ============================================================================\n// Populate Types\n// ============================================================================\n\nexport interface PopulateOption {\n path: string;\n select?: string;\n match?: Record<string, unknown>;\n}\n\n// ============================================================================\n// Response Types\n// ============================================================================\n\nexport interface ApiResponse<T = unknown> {\n success: boolean;\n data?: T;\n message?: string;\n}\n\nexport interface OffsetPaginationResponse<T = unknown> {\n success: boolean;\n method: 'offset';\n docs: T[];\n page: number;\n limit: number;\n total: number;\n pages: number;\n hasNext: boolean;\n hasPrev: boolean;\n warning?: string;\n}\n\nexport interface KeysetPaginationResponse<T = unknown> {\n success: boolean;\n method: 'keyset';\n docs: T[];\n limit: number;\n hasMore: boolean;\n next: string | null;\n}\n\nexport interface AggregatePaginationResponse<T = unknown> {\n success: boolean;\n method: 'aggregate';\n docs: T[];\n page: number;\n limit: number;\n total: number;\n pages: number;\n hasNext: boolean;\n hasPrev: boolean;\n warning?: string;\n}\n\nexport type PaginatedResponse<T = unknown> =\n | OffsetPaginationResponse<T>\n | KeysetPaginationResponse<T>\n | AggregatePaginationResponse<T>;\n\nexport interface DeleteResponse {\n success: boolean;\n deleted: boolean;\n id?: string;\n soft?: boolean;\n message?: string;\n count?: number;\n}\n\n// ============================================================================\n// Request Types\n// ============================================================================\n\nexport type SortDirection = 1 | -1 | 'asc' | 'desc';\nexport type SortSpec = Record<string, SortDirection> | string;\n\nexport type FilterOperator =\n | 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte'\n | 'in' | 'nin'\n | 'contains' | 'startsWith' | 'endsWith' | 'regex';\n\nexport interface QueryParams {\n page?: number;\n limit?: number;\n after?: string;\n cursor?: string;\n sort?: string;\n select?: string;\n populate?: string | string[];\n populateOptions?: PopulateOption[];\n lean?: boolean | 'true' | 'false';\n [key: string]: unknown;\n}\n\nexport interface RequestOptions {\n token?: string | null;\n organizationId?: string | null;\n cache?: RequestCache;\n revalidate?: number;\n tags?: string[];\n headerOptions?: Record<string, string>;\n responseType?: 'json' | 'blob' | 'text';\n signal?: AbortSignal;\n}\n\nexport interface BaseApiConfig {\n basePath?: string;\n defaultParams?: {\n limit?: number;\n page?: number;\n [key: string]: unknown;\n };\n cache?: RequestCache;\n headers?: Record<string, string>;\n client?: ArcClient;\n}\n\n// ============================================================================\n// Request Function Type\n// ============================================================================\n\ntype RequestFn = <T = unknown>(\n method: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE',\n endpoint: string,\n options?: ApiRequestOptions,\n) => Promise<T>;\n\n// ============================================================================\n// Base API Class\n// ============================================================================\n\nexport class BaseApi<\n TDoc = Record<string, unknown>,\n TCreate = Partial<TDoc>,\n TUpdate = Partial<TDoc>\n> {\n readonly entity: string;\n readonly config: Required<Omit<BaseApiConfig, 'client'>>;\n readonly baseUrl: string;\n private readonly requestFn: RequestFn;\n\n constructor(entity: string, config: BaseApiConfig = {}) {\n this.entity = entity;\n this.requestFn = config.client?.request ?? handleApiRequest;\n this.config = {\n basePath: config.basePath ?? '/api/v1',\n defaultParams: {\n limit: 10,\n page: 1,\n ...(config.defaultParams || {}),\n },\n cache: config.cache ?? 'no-store',\n headers: {\n ...(config.headers || {}),\n },\n };\n\n this.baseUrl = `${this.config.basePath}/${this.entity}`;\n }\n\n createQueryString(params: Record<string, unknown> = {}): string {\n return createQueryString(params);\n }\n\n prepareParams(params: QueryParams = {}): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n const CRITICAL_FILTERS = ['organizationId', 'ownerId'];\n\n Object.entries(params).forEach(([key, value]) => {\n if (CRITICAL_FILTERS.includes(key)) {\n result[key] = value || null;\n return;\n }\n\n if (key === 'populateOptions') {\n if (Array.isArray(value) && value.length > 0) {\n result[key] = value;\n }\n return;\n }\n\n if (value !== undefined && value !== '') {\n if (['page', 'limit'].includes(key)) {\n result[key] = parseInt(String(value)) || (key === 'page' ? 1 : 10);\n } else if (Array.isArray(value)) {\n if (value.length > 1) {\n result[`${key}[in]`] = value.join(',');\n } else if (value.length === 1) {\n result[key] = value[0];\n }\n } else {\n result[key] = value;\n }\n }\n });\n\n return result;\n }\n\n async getAll({\n token = null,\n organizationId = null,\n params = {},\n options = {},\n }: {\n token?: string | null;\n organizationId?: string | null;\n params?: QueryParams;\n options?: Omit<RequestOptions, 'token' | 'organizationId'>;\n } = {}): Promise<PaginatedResponse<TDoc>> {\n const processedParams = this.prepareParams(params);\n const queryString = this.createQueryString(processedParams);\n\n const requestOptions: ApiRequestOptions = {\n cache: this.config.cache,\n ...options,\n };\n\n if (token) requestOptions.token = token;\n if (organizationId) requestOptions.organizationId = organizationId;\n\n return this.requestFn('GET', `${this.baseUrl}?${queryString}`, requestOptions);\n }\n\n async getById({\n token = null,\n organizationId = null,\n id,\n params = {},\n options = {},\n }: {\n token?: string | null;\n organizationId?: string | null;\n id: string;\n params?: { select?: string; populate?: string | string[] };\n options?: Omit<RequestOptions, 'token' | 'organizationId'>;\n }): Promise<ApiResponse<TDoc>> {\n if (!id) throw new Error('ID is required');\n\n const queryString = this.createQueryString(params);\n const url = queryString ? `${this.baseUrl}/${id}?${queryString}` : `${this.baseUrl}/${id}`;\n\n const requestOptions: ApiRequestOptions = {\n cache: this.config.cache,\n ...options,\n };\n\n if (token) requestOptions.token = token;\n if (organizationId) requestOptions.organizationId = organizationId;\n\n return this.requestFn('GET', url, requestOptions);\n }\n\n async create({\n token,\n organizationId = null,\n data,\n options = {},\n }: {\n token?: string | null;\n organizationId?: string | null;\n data: TCreate;\n options?: Omit<RequestOptions, 'token' | 'organizationId'>;\n }): Promise<ApiResponse<TDoc>> {\n const requestOptions: ApiRequestOptions = {\n body: data,\n ...options,\n };\n\n if (token) requestOptions.token = token;\n if (organizationId) requestOptions.organizationId = organizationId;\n\n return this.requestFn('POST', this.baseUrl, requestOptions);\n }\n\n async update({\n token,\n organizationId = null,\n id,\n data,\n options = {},\n }: {\n token?: string | null;\n organizationId?: string | null;\n id: string;\n data: TUpdate;\n options?: Omit<RequestOptions, 'token' | 'organizationId'>;\n }): Promise<ApiResponse<TDoc>> {\n if (!id) throw new Error('ID is required');\n\n const requestOptions: ApiRequestOptions = {\n body: data,\n ...options,\n };\n\n if (token) requestOptions.token = token;\n if (organizationId) requestOptions.organizationId = organizationId;\n\n return this.requestFn('PATCH', `${this.baseUrl}/${id}`, requestOptions);\n }\n\n async delete({\n token,\n organizationId = null,\n id,\n options = {},\n }: {\n token?: string | null;\n organizationId?: string | null;\n id: string;\n options?: Omit<RequestOptions, 'token' | 'organizationId'>;\n }): Promise<DeleteResponse> {\n if (!id) throw new Error('ID is required');\n\n const requestOptions: ApiRequestOptions = { ...options };\n\n if (token) requestOptions.token = token;\n if (organizationId) requestOptions.organizationId = organizationId;\n\n return this.requestFn('DELETE', `${this.baseUrl}/${id}`, requestOptions);\n }\n\n async upload({\n token,\n organizationId = null,\n data,\n path,\n options = {},\n }: {\n token?: string | null;\n organizationId?: string | null;\n data: FormData;\n path?: string;\n options?: Omit<RequestOptions, 'token' | 'organizationId'>;\n }): Promise<ApiResponse<TDoc>> {\n const url = path ? `${this.baseUrl}/${path}` : this.baseUrl;\n\n const requestOptions: ApiRequestOptions = {\n body: data,\n ...options,\n };\n\n if (token) requestOptions.token = token;\n if (organizationId) requestOptions.organizationId = organizationId;\n\n return this.requestFn('POST', url, requestOptions);\n }\n\n async search({\n token = null,\n organizationId = null,\n searchParams = {},\n params = {},\n options = {},\n }: {\n token?: string | null;\n organizationId?: string | null;\n searchParams?: Record<string, unknown>;\n params?: QueryParams;\n options?: Omit<RequestOptions, 'token' | 'organizationId'>;\n } = {}): Promise<PaginatedResponse<TDoc>> {\n const queryParams = { ...params, ...searchParams };\n const processedParams = this.prepareParams(queryParams);\n const queryString = this.createQueryString(processedParams);\n\n const requestOptions: ApiRequestOptions = {\n cache: this.config.cache,\n ...options,\n };\n\n if (token) requestOptions.token = token;\n if (organizationId) requestOptions.organizationId = organizationId;\n\n return this.requestFn('GET', `${this.baseUrl}?${queryString}`, requestOptions);\n }\n\n async findBy({\n token = null,\n organizationId = null,\n field,\n value,\n operator,\n params = {},\n options = {},\n }: {\n token?: string | null;\n organizationId?: string | null;\n field: string;\n value: unknown;\n operator?: FilterOperator;\n params?: QueryParams;\n options?: Omit<RequestOptions, 'token' | 'organizationId'>;\n }): Promise<PaginatedResponse<TDoc>> {\n if (!field || value === undefined) {\n throw new Error('Field and value are required');\n }\n\n const queryParams: QueryParams = { ...params };\n\n if (operator) {\n queryParams[`${field}[${operator}]`] = Array.isArray(value) ? value.join(',') : value;\n } else {\n queryParams[field] = value;\n }\n\n const processedParams = this.prepareParams(queryParams);\n const queryString = this.createQueryString(processedParams);\n\n const requestOptions: ApiRequestOptions = {\n cache: this.config.cache,\n ...options,\n };\n\n if (token) requestOptions.token = token;\n if (organizationId) requestOptions.organizationId = organizationId;\n\n return this.requestFn('GET', `${this.baseUrl}?${queryString}`, requestOptions);\n }\n\n async request<TResponse = unknown>(\n method: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE',\n endpoint: string,\n {\n token,\n organizationId = null,\n data,\n params,\n options = {},\n }: {\n token?: string;\n organizationId?: string | null;\n data?: unknown;\n params?: QueryParams;\n options?: Omit<RequestOptions, 'token' | 'organizationId'>;\n } = {}\n ): Promise<TResponse> {\n let url = endpoint;\n\n if (params) {\n const processedParams = this.prepareParams(params);\n const queryString = this.createQueryString(processedParams);\n url = `${endpoint}?${queryString}`;\n }\n\n const requestOptions: ApiRequestOptions = {\n body: data,\n cache: this.config.cache,\n ...options,\n };\n\n if (token) requestOptions.token = token;\n if (organizationId) requestOptions.organizationId = organizationId;\n\n return this.requestFn(method, url, requestOptions);\n }\n}\n\n// ============================================================================\n// Factory\n// ============================================================================\n\nexport function createCrudApi<\n TDoc = Record<string, unknown>,\n TCreate = Partial<TDoc>,\n TUpdate = Partial<TDoc>\n>(entity: string, config: BaseApiConfig = {}): BaseApi<TDoc, TCreate, TUpdate> {\n return new BaseApi<TDoc, TCreate, TUpdate>(entity, config);\n}\n\n// ============================================================================\n// Type Helpers\n// ============================================================================\n\nexport type ExtractDoc<T> = T extends PaginatedResponse<infer D> ? D : never;\n\nexport function isOffsetPagination<T>(\n response: PaginatedResponse<T>\n): response is OffsetPaginationResponse<T> {\n return response.method === 'offset';\n}\n\nexport function isKeysetPagination<T>(\n response: PaginatedResponse<T>\n): response is KeysetPaginationResponse<T> {\n return response.method === 'keyset';\n}\n\nexport function isAggregatePagination<T>(\n response: PaginatedResponse<T>\n): response is AggregatePaginationResponse<T> {\n return response.method === 'aggregate';\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"api.js","names":[],"sources":["../src/api.ts"],"sourcesContent":["import { handleApiRequest, createQueryString } from './client.js';\nimport type { ApiRequestOptions, ArcClient } from './client.js';\n\n// ============================================================================\n// Populate Types\n// ============================================================================\n\nexport interface PopulateOption {\n path: string;\n select?: string;\n match?: Record<string, unknown>;\n}\n\n// ============================================================================\n// Response Types\n// ============================================================================\n\nexport interface ApiResponse<T = unknown> {\n success: boolean;\n data?: T;\n message?: string;\n}\n\nexport interface OffsetPaginationResponse<T = unknown> {\n success: boolean;\n method: 'offset';\n docs: T[];\n page: number;\n limit: number;\n total: number;\n pages: number;\n hasNext: boolean;\n hasPrev: boolean;\n warning?: string;\n}\n\nexport interface KeysetPaginationResponse<T = unknown> {\n success: boolean;\n method: 'keyset';\n docs: T[];\n limit: number;\n hasMore: boolean;\n next: string | null;\n}\n\nexport interface AggregatePaginationResponse<T = unknown> {\n success: boolean;\n method: 'aggregate';\n docs: T[];\n page: number;\n limit: number;\n total: number;\n pages: number;\n hasNext: boolean;\n hasPrev: boolean;\n warning?: string;\n}\n\nexport type PaginatedResponse<T = unknown> =\n | OffsetPaginationResponse<T>\n | KeysetPaginationResponse<T>\n | AggregatePaginationResponse<T>;\n\nexport interface DeleteResponse {\n success: boolean;\n deleted: boolean;\n id?: string;\n soft?: boolean;\n message?: string;\n count?: number;\n}\n\n// ============================================================================\n// Request Types\n// ============================================================================\n\nexport type SortDirection = 1 | -1 | 'asc' | 'desc';\nexport type SortSpec = Record<string, SortDirection> | string;\n\nexport type FilterOperator =\n | 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte'\n | 'in' | 'nin'\n | 'contains' | 'startsWith' | 'endsWith' | 'regex';\n\nexport interface QueryParams {\n page?: number;\n limit?: number;\n after?: string;\n cursor?: string;\n sort?: string;\n select?: string;\n populate?: string | string[];\n populateOptions?: PopulateOption[];\n lean?: boolean | 'true' | 'false';\n [key: string]: unknown;\n}\n\nexport interface RequestOptions {\n token?: string | null;\n organizationId?: string | null;\n cache?: RequestCache;\n revalidate?: number;\n tags?: string[];\n headerOptions?: Record<string, string>;\n responseType?: 'json' | 'blob' | 'text';\n signal?: AbortSignal;\n}\n\nexport interface BaseApiConfig {\n basePath?: string;\n defaultParams?: {\n limit?: number;\n page?: number;\n [key: string]: unknown;\n };\n cache?: RequestCache;\n headers?: Record<string, string>;\n client?: ArcClient;\n}\n\n// ============================================================================\n// Request Function Type\n// ============================================================================\n\ntype RequestFn = <T = unknown>(\n method: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE',\n endpoint: string,\n options?: ApiRequestOptions,\n) => Promise<T>;\n\n// ============================================================================\n// Base API Class\n// ============================================================================\n\nexport class BaseApi<\n TDoc = Record<string, unknown>,\n TCreate = Partial<TDoc>,\n TUpdate = Partial<TDoc>\n> {\n readonly entity: string;\n readonly config: Required<Omit<BaseApiConfig, 'client'>>;\n readonly baseUrl: string;\n private readonly requestFn: RequestFn;\n\n constructor(entity: string, config: BaseApiConfig = {}) {\n this.entity = entity;\n this.requestFn = config.client?.request ?? handleApiRequest;\n this.config = {\n basePath: config.basePath ?? '/api/v1',\n defaultParams: {\n limit: 10,\n page: 1,\n ...(config.defaultParams || {}),\n },\n cache: config.cache ?? 'no-store',\n headers: {\n ...(config.headers || {}),\n },\n };\n\n this.baseUrl = `${this.config.basePath}/${this.entity}`;\n }\n\n /** Merge per-instance headers into request options */\n private withHeaders(options: ApiRequestOptions): ApiRequestOptions {\n const instanceHeaders = this.config.headers;\n if (!instanceHeaders || Object.keys(instanceHeaders).length === 0) return options;\n return {\n ...options,\n headerOptions: { ...instanceHeaders, ...(options.headerOptions ?? {}) },\n };\n }\n\n createQueryString(params: Record<string, unknown> = {}): string {\n return createQueryString(params);\n }\n\n prepareParams(params: QueryParams = {}): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n const CRITICAL_FILTERS = ['organizationId', 'ownerId'];\n\n Object.entries(params).forEach(([key, value]) => {\n if (CRITICAL_FILTERS.includes(key)) {\n result[key] = value || null;\n return;\n }\n\n if (key === 'populateOptions') {\n if (Array.isArray(value) && value.length > 0) {\n result[key] = value;\n }\n return;\n }\n\n if (value !== undefined && value !== '') {\n if (['page', 'limit'].includes(key)) {\n result[key] = parseInt(String(value)) || (key === 'page' ? 1 : 10);\n } else if (Array.isArray(value)) {\n if (value.length > 1) {\n result[`${key}[in]`] = value.join(',');\n } else if (value.length === 1) {\n result[key] = value[0];\n }\n } else {\n result[key] = value;\n }\n }\n });\n\n return result;\n }\n\n async getAll({\n token = null,\n organizationId = null,\n params = {},\n options = {},\n }: {\n token?: string | null;\n organizationId?: string | null;\n params?: QueryParams;\n options?: Omit<RequestOptions, 'token' | 'organizationId'>;\n } = {}): Promise<PaginatedResponse<TDoc>> {\n const processedParams = this.prepareParams(params);\n const queryString = this.createQueryString(processedParams);\n\n const requestOptions: ApiRequestOptions = {\n cache: this.config.cache,\n ...options,\n };\n\n if (token) requestOptions.token = token;\n if (organizationId) requestOptions.organizationId = organizationId;\n\n return this.requestFn('GET', `${this.baseUrl}?${queryString}`, this.withHeaders(requestOptions));\n }\n\n async getById({\n token = null,\n organizationId = null,\n id,\n params = {},\n options = {},\n }: {\n token?: string | null;\n organizationId?: string | null;\n id: string;\n params?: { select?: string; populate?: string | string[] };\n options?: Omit<RequestOptions, 'token' | 'organizationId'>;\n }): Promise<ApiResponse<TDoc>> {\n if (!id) throw new Error('ID is required');\n\n const queryString = this.createQueryString(params);\n const url = queryString ? `${this.baseUrl}/${id}?${queryString}` : `${this.baseUrl}/${id}`;\n\n const requestOptions: ApiRequestOptions = {\n cache: this.config.cache,\n ...options,\n };\n\n if (token) requestOptions.token = token;\n if (organizationId) requestOptions.organizationId = organizationId;\n\n return this.requestFn('GET', url, this.withHeaders(requestOptions));\n }\n\n async create({\n token,\n organizationId = null,\n data,\n options = {},\n }: {\n token?: string | null;\n organizationId?: string | null;\n data: TCreate;\n options?: Omit<RequestOptions, 'token' | 'organizationId'>;\n }): Promise<ApiResponse<TDoc>> {\n const requestOptions: ApiRequestOptions = {\n body: data,\n ...options,\n };\n\n if (token) requestOptions.token = token;\n if (organizationId) requestOptions.organizationId = organizationId;\n\n return this.requestFn('POST', this.baseUrl, this.withHeaders(requestOptions));\n }\n\n async update({\n token,\n organizationId = null,\n id,\n data,\n options = {},\n }: {\n token?: string | null;\n organizationId?: string | null;\n id: string;\n data: TUpdate;\n options?: Omit<RequestOptions, 'token' | 'organizationId'>;\n }): Promise<ApiResponse<TDoc>> {\n if (!id) throw new Error('ID is required');\n\n const requestOptions: ApiRequestOptions = {\n body: data,\n ...options,\n };\n\n if (token) requestOptions.token = token;\n if (organizationId) requestOptions.organizationId = organizationId;\n\n return this.requestFn('PATCH', `${this.baseUrl}/${id}`, this.withHeaders(requestOptions));\n }\n\n async delete({\n token,\n organizationId = null,\n id,\n options = {},\n }: {\n token?: string | null;\n organizationId?: string | null;\n id: string;\n options?: Omit<RequestOptions, 'token' | 'organizationId'>;\n }): Promise<DeleteResponse> {\n if (!id) throw new Error('ID is required');\n\n const requestOptions: ApiRequestOptions = { ...options };\n\n if (token) requestOptions.token = token;\n if (organizationId) requestOptions.organizationId = organizationId;\n\n return this.requestFn('DELETE', `${this.baseUrl}/${id}`, this.withHeaders(requestOptions));\n }\n\n async upload({\n token,\n organizationId = null,\n data,\n path,\n options = {},\n }: {\n token?: string | null;\n organizationId?: string | null;\n data: FormData;\n path?: string;\n options?: Omit<RequestOptions, 'token' | 'organizationId'>;\n }): Promise<ApiResponse<TDoc>> {\n const url = path ? `${this.baseUrl}/${path}` : this.baseUrl;\n\n const requestOptions: ApiRequestOptions = {\n body: data,\n ...options,\n };\n\n if (token) requestOptions.token = token;\n if (organizationId) requestOptions.organizationId = organizationId;\n\n return this.requestFn('POST', url, this.withHeaders(requestOptions));\n }\n\n async search({\n token = null,\n organizationId = null,\n searchParams = {},\n params = {},\n options = {},\n }: {\n token?: string | null;\n organizationId?: string | null;\n searchParams?: Record<string, unknown>;\n params?: QueryParams;\n options?: Omit<RequestOptions, 'token' | 'organizationId'>;\n } = {}): Promise<PaginatedResponse<TDoc>> {\n const queryParams = { ...params, ...searchParams };\n const processedParams = this.prepareParams(queryParams);\n const queryString = this.createQueryString(processedParams);\n\n const requestOptions: ApiRequestOptions = {\n cache: this.config.cache,\n ...options,\n };\n\n if (token) requestOptions.token = token;\n if (organizationId) requestOptions.organizationId = organizationId;\n\n return this.requestFn('GET', `${this.baseUrl}?${queryString}`, this.withHeaders(requestOptions));\n }\n\n async findBy({\n token = null,\n organizationId = null,\n field,\n value,\n operator,\n params = {},\n options = {},\n }: {\n token?: string | null;\n organizationId?: string | null;\n field: string;\n value: unknown;\n operator?: FilterOperator;\n params?: QueryParams;\n options?: Omit<RequestOptions, 'token' | 'organizationId'>;\n }): Promise<PaginatedResponse<TDoc>> {\n if (!field || value === undefined) {\n throw new Error('Field and value are required');\n }\n\n const queryParams: QueryParams = { ...params };\n\n if (operator) {\n queryParams[`${field}[${operator}]`] = Array.isArray(value) ? value.join(',') : value;\n } else {\n queryParams[field] = value;\n }\n\n const processedParams = this.prepareParams(queryParams);\n const queryString = this.createQueryString(processedParams);\n\n const requestOptions: ApiRequestOptions = {\n cache: this.config.cache,\n ...options,\n };\n\n if (token) requestOptions.token = token;\n if (organizationId) requestOptions.organizationId = organizationId;\n\n return this.requestFn('GET', `${this.baseUrl}?${queryString}`, this.withHeaders(requestOptions));\n }\n\n async request<TResponse = unknown>(\n method: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE',\n endpoint: string,\n {\n token,\n organizationId = null,\n data,\n params,\n options = {},\n }: {\n token?: string;\n organizationId?: string | null;\n data?: unknown;\n params?: QueryParams;\n options?: Omit<RequestOptions, 'token' | 'organizationId'>;\n } = {}\n ): Promise<TResponse> {\n let url = endpoint;\n\n if (params) {\n const processedParams = this.prepareParams(params);\n const queryString = this.createQueryString(processedParams);\n url = `${endpoint}?${queryString}`;\n }\n\n const requestOptions: ApiRequestOptions = {\n body: data,\n cache: this.config.cache,\n ...options,\n };\n\n if (token) requestOptions.token = token;\n if (organizationId) requestOptions.organizationId = organizationId;\n\n return this.requestFn(method, url, this.withHeaders(requestOptions));\n }\n}\n\n// ============================================================================\n// Factory\n// ============================================================================\n\nexport function createCrudApi<\n TDoc = Record<string, unknown>,\n TCreate = Partial<TDoc>,\n TUpdate = Partial<TDoc>\n>(entity: string, config: BaseApiConfig = {}): BaseApi<TDoc, TCreate, TUpdate> {\n return new BaseApi<TDoc, TCreate, TUpdate>(entity, config);\n}\n\n// ============================================================================\n// Type Helpers\n// ============================================================================\n\nexport type ExtractDoc<T> = T extends PaginatedResponse<infer D> ? D : never;\n\nexport function isOffsetPagination<T>(\n response: PaginatedResponse<T>\n): response is OffsetPaginationResponse<T> {\n return response.method === 'offset';\n}\n\nexport function isKeysetPagination<T>(\n response: PaginatedResponse<T>\n): response is KeysetPaginationResponse<T> {\n return response.method === 'keyset';\n}\n\nexport function isAggregatePagination<T>(\n response: PaginatedResponse<T>\n): response is AggregatePaginationResponse<T> {\n return response.method === 'aggregate';\n}\n"],"mappings":"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"}
|
package/dist/hooks.d.ts
CHANGED
|
@@ -17,6 +17,10 @@ interface CrudApi<T, TCreate = Partial<T>, TUpdate = Partial<T>> {
|
|
|
17
17
|
id: string;
|
|
18
18
|
token?: string | null;
|
|
19
19
|
organizationId?: string | null;
|
|
20
|
+
params?: {
|
|
21
|
+
select?: string;
|
|
22
|
+
populate?: string | string[];
|
|
23
|
+
};
|
|
20
24
|
options?: {
|
|
21
25
|
signal?: AbortSignal;
|
|
22
26
|
[key: string]: unknown;
|
package/dist/hooks.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.d.ts","names":[],"sources":["../src/hooks.ts"],"mappings":";;;;;UAyCiB,OAAA,cAAqB,OAAA,CAAQ,CAAA,aAAc,OAAA,CAAQ,CAAA;EAClE,MAAA,GAAS,OAAA;IACP,KAAA;IACA,cAAA;IACA,MAAA,GAAS,MAAA;IACT,OAAA;MAAY,MAAA,GAAS,WAAA;MAAA,CAAc,GAAA;IAAA;EAAA,MAC/B,OAAA;EAEN,OAAA,GAAU,OAAA;IACR,EAAA;IACA,KAAA;IACA,cAAA;IACA,OAAA;MAAY,MAAA,GAAS,WAAA;MAAA,CAAc,GAAA;IAAA;EAAA,MAC/B,OAAA;EAEN,MAAA,GAAS,OAAA;IACP,KAAA;IACA,cAAA;IACA,IAAA,EAAM,OAAA;EAAA,MACF,OAAA;EAEN,MAAA,GAAS,OAAA;IACP,KAAA;IACA,cAAA;IACA,EAAA;IACA,IAAA,EAAM,OAAA;EAAA,MACF,OAAA;EAEN,MAAA,GAAS,OAAA;IACP,KAAA;IACA,cAAA;IACA,EAAA;EAAA,MACI,OAAA;AAAA;AAAA,UAGS,eAAA,cAA6B,OAAA,CAAQ,CAAA,aAAc,OAAA,CAAQ,CAAA;EAC1E,GAAA,EAAK,OAAA,CAAQ,CAAA,EAAG,OAAA,EAAS,OAAA;EACzB,SAAA;EACA,QAAA;EACA,MAAA;EACA,QAAA;IACE,SAAA;IACA,MAAA;IACA,oBAAA;IACA,iBAAA;IACA,QAAA;MACE,aAAA;MACA,WAAA;MACA,aAAA;MACA,WAAA;MACA,aAAA;MACA,WAAA;IAAA;EAAA;EAGJ,SAAA;IACE,QAAA,GAAW,iBAAA,CAAkB,CAAA;MAAK,IAAA,EAAM,OAAA;IAAA;IACxC,QAAA,GAAW,iBAAA,CAAkB,CAAA;MAAK,EAAA;MAAY,IAAA,EAAM,OAAA;IAAA;IACpD,QAAA,GAAW,iBAAA;MAA6B,EAAA;IAAA;EAAA;EAE1C,MAAA,GAAS,SAAA;AAAA;AAAA,UAGM,cAAA;EACf,KAAA;EACA,cAAA;EACA,IAAA,EAAM,KAAA;AAAA;AAAA,UAGS,YAAA,gBAA4B,cAAA,CAAe,KAAA;EAC1D,EAAA;AAAA;AAAA,UAGe,YAAA;EACf,KAAA;EACA,cAAA;EACA,EAAA;AAAA;AAAA,UAGe,WAAA;EACf,SAAA,IAAa,IAAA,EAAM,KAAA;EACnB,OAAA,IAAW,KAAA,EAAO,KAAA;EAClB,MAAA;AAAA;AAAA,UAGe,WAAA;EACf,MAAA,GAAS,MAAA,EAAQ,cAAA,CAAe,OAAA,GAAU,OAAA,GAAU,WAAA,CAAY,CAAA,MAAO,OAAA,CAAQ,CAAA;EAC/E,MAAA,GAAS,MAAA,EAAQ,YAAA,CAAa,OAAA,GAAU,OAAA,GAAU,WAAA,CAAY,CAAA,MAAO,OAAA,CAAQ,CAAA;EAC7E,MAAA,GAAS,MAAA,EAAQ,YAAA,EAAc,OAAA,GAAU,WAAA,KAAgB,OAAA;EACzD,UAAA;EACA,UAAA;EACA,UAAA;EACA,UAAA;AAAA;AAAA,UAGe,iBAAA;EACf,MAAA;EACA,OAAA;AAAA;AAAA,KAGU,UAAA,OAAiB,IAAA,UAAc,IAAA,EAAM,CAAA,EAAG,OAAA,GAAU,iBAAA;AAAA,UAE7C,eAAA;EACf,IAAA,EAAM,SAAA;EACN,KAAA,EAAO,UAAA,CAAW,CAAA;EAClB,OAAA;
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","names":[],"sources":["../src/hooks.ts"],"mappings":";;;;;UAyCiB,OAAA,cAAqB,OAAA,CAAQ,CAAA,aAAc,OAAA,CAAQ,CAAA;EAClE,MAAA,GAAS,OAAA;IACP,KAAA;IACA,cAAA;IACA,MAAA,GAAS,MAAA;IACT,OAAA;MAAY,MAAA,GAAS,WAAA;MAAA,CAAc,GAAA;IAAA;EAAA,MAC/B,OAAA;EAEN,OAAA,GAAU,OAAA;IACR,EAAA;IACA,KAAA;IACA,cAAA;IACA,MAAA;MAAW,MAAA;MAAiB,QAAA;IAAA;IAC5B,OAAA;MAAY,MAAA,GAAS,WAAA;MAAA,CAAc,GAAA;IAAA;EAAA,MAC/B,OAAA;EAEN,MAAA,GAAS,OAAA;IACP,KAAA;IACA,cAAA;IACA,IAAA,EAAM,OAAA;EAAA,MACF,OAAA;EAEN,MAAA,GAAS,OAAA;IACP,KAAA;IACA,cAAA;IACA,EAAA;IACA,IAAA,EAAM,OAAA;EAAA,MACF,OAAA;EAEN,MAAA,GAAS,OAAA;IACP,KAAA;IACA,cAAA;IACA,EAAA;EAAA,MACI,OAAA;AAAA;AAAA,UAGS,eAAA,cAA6B,OAAA,CAAQ,CAAA,aAAc,OAAA,CAAQ,CAAA;EAC1E,GAAA,EAAK,OAAA,CAAQ,CAAA,EAAG,OAAA,EAAS,OAAA;EACzB,SAAA;EACA,QAAA;EACA,MAAA;EACA,QAAA;IACE,SAAA;IACA,MAAA;IACA,oBAAA;IACA,iBAAA;IACA,QAAA;MACE,aAAA;MACA,WAAA;MACA,aAAA;MACA,WAAA;MACA,aAAA;MACA,WAAA;IAAA;EAAA;EAGJ,SAAA;IACE,QAAA,GAAW,iBAAA,CAAkB,CAAA;MAAK,IAAA,EAAM,OAAA;IAAA;IACxC,QAAA,GAAW,iBAAA,CAAkB,CAAA;MAAK,EAAA;MAAY,IAAA,EAAM,OAAA;IAAA;IACpD,QAAA,GAAW,iBAAA;MAA6B,EAAA;IAAA;EAAA;EAE1C,MAAA,GAAS,SAAA;AAAA;AAAA,UAGM,cAAA;EACf,KAAA;EACA,cAAA;EACA,IAAA,EAAM,KAAA;AAAA;AAAA,UAGS,YAAA,gBAA4B,cAAA,CAAe,KAAA;EAC1D,EAAA;AAAA;AAAA,UAGe,YAAA;EACf,KAAA;EACA,cAAA;EACA,EAAA;AAAA;AAAA,UAGe,WAAA;EACf,SAAA,IAAa,IAAA,EAAM,KAAA;EACnB,OAAA,IAAW,KAAA,EAAO,KAAA;EAClB,MAAA;AAAA;AAAA,UAGe,WAAA;EACf,MAAA,GAAS,MAAA,EAAQ,cAAA,CAAe,OAAA,GAAU,OAAA,GAAU,WAAA,CAAY,CAAA,MAAO,OAAA,CAAQ,CAAA;EAC/E,MAAA,GAAS,MAAA,EAAQ,YAAA,CAAa,OAAA,GAAU,OAAA,GAAU,WAAA,CAAY,CAAA,MAAO,OAAA,CAAQ,CAAA;EAC7E,MAAA,GAAS,MAAA,EAAQ,YAAA,EAAc,OAAA,GAAU,WAAA,KAAgB,OAAA;EACzD,UAAA;EACA,UAAA;EACA,UAAA;EACA,UAAA;AAAA;AAAA,UAGe,iBAAA;EACf,MAAA;EACA,OAAA;AAAA;AAAA,KAGU,UAAA,OAAiB,IAAA,UAAc,IAAA,EAAM,CAAA,EAAG,OAAA,GAAU,iBAAA;AAAA,UAE7C,eAAA;EACf,IAAA,EAAM,SAAA;EACN,KAAA,EAAO,UAAA,CAAW,CAAA;EAClB,OAAA;IAjEA,6EAmEG,MAAA,GAAS,MAAA,mBAAyB,OAAA,GAAU,gBAAA,GAAmB,eAAA,CAAgB,CAAA,GAjEhF;IAAA,CAmEC,KAAA,iBAAsB,MAAA,GAAS,MAAA,mBAAyB,OAAA,GAAU,gBAAA,GAAmB,eAAA,CAAgB,CAAA;EAAA;EAExG,SAAA;IAjEE,uEAmEC,EAAA,iBAAmB,OAAA,GAAU,kBAAA,GAAqB,iBAAA,CAAkB,CAAA,GAjEnE;IAAA,CAmED,EAAA,iBAAmB,KAAA,iBAAsB,OAAA,GAAU,kBAAA,GAAqB,iBAAA,CAAkB,CAAA;EAAA;EAE7F,eAAA;IAjEI,6EAmED,MAAA,GAAS,MAAA,mBAAyB,OAAA,GAAU,wBAAA,GAA2B,uBAAA,CAAwB,CAAA,GA/DhG;IAAA,CAiEC,KAAA,iBAAsB,MAAA,GAAS,MAAA,mBAAyB,OAAA,GAAU,wBAAA,GAA2B,uBAAA,CAAwB,CAAA;EAAA;EAExH,UAAA,QAAkB,WAAA,CAAY,CAAA,EAAG,OAAA,EAAS,OAAA;EAC1C,aAAA,QAAqB,UAAA,CAAW,CAAA;AAAA;;;;;;;;iBAgBlB,mBAAA,CAAoB,IAAA,EAAM,aAAA;AAAA,iBAqB1B,eAAA,cAA6B,OAAA,CAAQ,CAAA,aAAc,OAAA,CAAQ,CAAA,EAAA,CAAA;EACzE,GAAA;EACA,SAAA;EACA,QAAA;EACA,QAAA;EACA,SAAA;EACA;AAAA,GACC,eAAA,CAAgB,CAAA,EAAG,OAAA,EAAS,OAAA,IAAW,eAAA,CAAgB,CAAA,EAAG,OAAA,EAAS,OAAA"}
|
package/dist/hooks.js
CHANGED
|
@@ -102,13 +102,14 @@ function createCrudHooks({ api, entityKey, singular, defaults = {}, callbacks =
|
|
|
102
102
|
organizationId: auth.organizationId
|
|
103
103
|
};
|
|
104
104
|
}
|
|
105
|
-
const { organizationId, request: requestOpts, ...restOptions } = options;
|
|
105
|
+
const { organizationId, params: queryParams, request: requestOpts, ...restOptions } = options;
|
|
106
106
|
return createDetailQuery({
|
|
107
|
-
queryKey: KEYS.detail(id || ""),
|
|
107
|
+
queryKey: queryParams ? [...KEYS.detail(id || ""), queryParams] : KEYS.detail(id || ""),
|
|
108
108
|
queryFn: () => api.getById({
|
|
109
109
|
id,
|
|
110
110
|
token,
|
|
111
111
|
organizationId,
|
|
112
|
+
params: queryParams,
|
|
112
113
|
options: { ...requestOpts }
|
|
113
114
|
}),
|
|
114
115
|
enabled: !!id && createEnabledRule(token, restOptions),
|
package/dist/hooks.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.js","names":[],"sources":["../src/hooks.ts"],"sourcesContent":["\"use client\";\n\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport { useCallback } from \"react\";\nimport { createOptimisticMutation } from \"./mutation.js\";\nimport {\n createListQuery,\n createDetailQuery,\n createInfiniteListQuery,\n updateListCache,\n getItemId,\n createQueryKeys,\n createCacheUtils,\n DEFAULT_QUERY_CONFIG,\n} from \"./query.js\";\nimport type {\n QueryKeys,\n CacheUtils,\n ListQueryOptions,\n DetailQueryOptions,\n ListQueryResult,\n DetailQueryResult,\n InfiniteListQueryOptions,\n InfiniteListQueryResult,\n} from \"./query.js\";\nimport {\n isOffsetPagination,\n isKeysetPagination,\n} from \"./api.js\";\nimport type { PaginatedResponse } from \"./api.js\";\nimport type { MutationCallbacks } from \"./mutation.js\";\nimport { getAuthMode, getAuthContext } from \"./client.js\";\nimport type { ArcClient, ToastHandler, UseRouterHook } from \"./client.js\";\n\n// Re-export UseRouterHook for backward compatibility\nexport type { UseRouterHook } from \"./client.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface CrudApi<T, TCreate = Partial<T>, TUpdate = Partial<T>> {\n getAll: (options: {\n token?: string | null;\n organizationId?: string | null;\n params?: Record<string, unknown>;\n options?: { signal?: AbortSignal; [key: string]: unknown };\n }) => Promise<unknown>;\n\n getById: (options: {\n id: string;\n token?: string | null;\n organizationId?: string | null;\n options?: { signal?: AbortSignal; [key: string]: unknown };\n }) => Promise<unknown>;\n\n create: (options: {\n token?: string | null;\n organizationId?: string | null;\n data: TCreate;\n }) => Promise<unknown>;\n\n update: (options: {\n token?: string | null;\n organizationId?: string | null;\n id: string;\n data: TUpdate;\n }) => Promise<unknown>;\n\n delete: (options: {\n token?: string | null;\n organizationId?: string | null;\n id: string;\n }) => Promise<unknown>;\n}\n\nexport interface CrudHooksConfig<T, TCreate = Partial<T>, TUpdate = Partial<T>> {\n api: CrudApi<T, TCreate, TUpdate>;\n entityKey: string;\n singular: string;\n plural?: string;\n defaults?: {\n staleTime?: number;\n gcTime?: number;\n refetchOnWindowFocus?: boolean;\n structuralSharing?: boolean;\n messages?: {\n createSuccess?: string;\n createError?: string;\n updateSuccess?: string;\n updateError?: string;\n deleteSuccess?: string;\n deleteError?: string;\n };\n };\n callbacks?: {\n onCreate?: MutationCallbacks<T, { data: TCreate }>;\n onUpdate?: MutationCallbacks<T, { id: string; data: TUpdate }>;\n onDelete?: MutationCallbacks<unknown, { id: string }>;\n };\n client?: ArcClient;\n}\n\nexport interface MutationParams<TData> {\n token?: string | null;\n organizationId?: string | null;\n data: TData;\n}\n\nexport interface UpdateParams<TData> extends MutationParams<TData> {\n id: string;\n}\n\nexport interface DeleteParams {\n token?: string | null;\n organizationId?: string | null;\n id: string;\n}\n\nexport interface CallOptions<TData = unknown> {\n onSuccess?: (data: TData) => void;\n onError?: (error: Error) => void;\n silent?: boolean;\n}\n\nexport interface CrudActions<T, TCreate, TUpdate> {\n create: (params: MutationParams<TCreate>, options?: CallOptions<T>) => Promise<T>;\n update: (params: UpdateParams<TUpdate>, options?: CallOptions<T>) => Promise<T>;\n remove: (params: DeleteParams, options?: CallOptions) => Promise<unknown>;\n isCreating: boolean;\n isUpdating: boolean;\n isDeleting: boolean;\n isMutating: boolean;\n}\n\nexport interface NavigationOptions {\n scroll?: boolean;\n replace?: boolean;\n}\n\nexport type NavigateFn<T> = (href: string, item: T, options?: NavigationOptions) => void;\n\nexport interface CrudHooksReturn<T, TCreate, TUpdate> {\n KEYS: QueryKeys;\n cache: CacheUtils<T>;\n useList: {\n /** New signature — auto-injects token/orgId from configureAuth() context */\n (params?: Record<string, unknown>, options?: ListQueryOptions): ListQueryResult<T>;\n /** Legacy signature — explicit token */\n (token: string | null, params?: Record<string, unknown>, options?: ListQueryOptions): ListQueryResult<T>;\n };\n useDetail: {\n /** New signature — auto-injects token from configureAuth() context */\n (id: string | null, options?: DetailQueryOptions): DetailQueryResult<T>;\n /** Legacy signature — explicit token */\n (id: string | null, token: string | null, options?: DetailQueryOptions): DetailQueryResult<T>;\n };\n useInfiniteList: {\n /** New signature — auto-injects token/orgId from configureAuth() context */\n (params?: Record<string, unknown>, options?: InfiniteListQueryOptions): InfiniteListQueryResult<T>;\n /** Legacy signature — explicit token */\n (token: string | null, params?: Record<string, unknown>, options?: InfiniteListQueryOptions): InfiniteListQueryResult<T>;\n };\n useActions: () => CrudActions<T, TCreate, TUpdate>;\n useNavigation: () => NavigateFn<T>;\n}\n\n// ============================================================================\n// Navigation Configuration (Global)\n// ============================================================================\n\nlet useRouterHook: UseRouterHook | null = null;\n\n/**\n * Configure the router hook for useNavigation. Call once at app init.\n *\n * @example\n * import { useRouter } from \"next/navigation\";\n * configureNavigation(useRouter);\n */\nexport function configureNavigation(hook: UseRouterHook): void {\n useRouterHook = hook;\n}\n\n// ============================================================================\n// Enabled Rule\n// ============================================================================\n\nfunction createEnabledRule(token: string | null, options: { public?: boolean; enabled?: boolean }): boolean {\n // Cookie-based auth: no token needed, queries always enabled (cookies sent via credentials: 'include')\n if (getAuthMode() === 'cookie' || options.public) {\n return options.enabled ?? true;\n }\n // Bearer token auth: require token to enable queries\n return options.enabled !== undefined ? options.enabled && !!token : !!token;\n}\n\n// ============================================================================\n// Create CRUD Hooks Factory\n// ============================================================================\n\nexport function createCrudHooks<T, TCreate = Partial<T>, TUpdate = Partial<T>>({\n api,\n entityKey,\n singular,\n defaults = {},\n callbacks = {},\n client,\n}: CrudHooksConfig<T, TCreate, TUpdate>): CrudHooksReturn<T, TCreate, TUpdate> {\n const KEYS = createQueryKeys(entityKey);\n const cache = createCacheUtils<T>(KEYS);\n\n // Resolve toast handler: client instance → global\n const instanceToast: ToastHandler | undefined = client?.toast;\n // Resolve navigation hook: client instance → global\n const instanceNavigation: UseRouterHook | null = client?.navigation ?? null;\n\n const config = {\n ...DEFAULT_QUERY_CONFIG,\n ...defaults,\n structuralSharing: defaults.structuralSharing,\n messages: {\n createSuccess: `${singular} created successfully`,\n createError: `Failed to create ${singular.toLowerCase()}`,\n updateSuccess: `${singular} updated successfully`,\n updateError: `Failed to update ${singular.toLowerCase()}`,\n deleteSuccess: `${singular} deleted successfully`,\n deleteError: `Failed to delete ${singular.toLowerCase()}`,\n ...defaults.messages,\n },\n };\n\n // ========== useList ==========\n\n function useList(\n tokenOrParams?: string | null | Record<string, unknown>,\n paramsOrOptions?: Record<string, unknown> | ListQueryOptions,\n maybeOptions?: ListQueryOptions,\n ): ListQueryResult<T> {\n let token: string | null;\n let params: Record<string, unknown>;\n let options: ListQueryOptions;\n\n // Detect which overload: first arg is string|null = legacy, object/undefined = new\n if (tokenOrParams === null || typeof tokenOrParams === 'string') {\n token = tokenOrParams;\n params = (paramsOrOptions as Record<string, unknown>) ?? {};\n options = maybeOptions ?? {};\n } else {\n const auth = getAuthContext();\n token = auth.token;\n params = (tokenOrParams as Record<string, unknown>) ?? {};\n options = (paramsOrOptions as ListQueryOptions) ?? {};\n if (auth.organizationId && !params.organizationId) {\n params = { ...params, organizationId: auth.organizationId };\n }\n }\n\n const { organizationId, ...restParams } = params;\n const scope = options._scope || (organizationId ? \"tenant\" : \"super-admin\");\n const { request: requestOpts, ...queryOpts } = options;\n\n return createListQuery<T>({\n queryKey: KEYS.scopedList(scope, { organizationId, ...restParams }),\n queryFn: () => api.getAll({ token, organizationId: organizationId as string | null, params: restParams, options: { ...requestOpts } }),\n enabled: createEnabledRule(token, queryOpts),\n options: {\n staleTime: queryOpts.staleTime ?? config.staleTime,\n gcTime: queryOpts.gcTime ?? config.gcTime,\n refetchOnWindowFocus: queryOpts.refetchOnWindowFocus ?? config.refetchOnWindowFocus,\n structuralSharing: queryOpts.structuralSharing ?? config.structuralSharing,\n refetchInterval: queryOpts.refetchInterval,\n refetchIntervalInBackground: queryOpts.refetchIntervalInBackground,\n },\n prefillDetailCache: queryOpts.prefillDetailCache ?? true,\n detailKeyBuilder: (id) => KEYS.detail(id),\n });\n }\n\n // ========== useDetail ==========\n\n function useDetail(\n id: string | null,\n tokenOrOptions?: string | null | DetailQueryOptions,\n maybeOptions?: DetailQueryOptions,\n ): DetailQueryResult<T> {\n let token: string | null;\n let options: DetailQueryOptions;\n\n // Detect which overload: second arg is string|null = legacy, object/undefined = new\n if (tokenOrOptions === null || typeof tokenOrOptions === 'string') {\n token = tokenOrOptions;\n options = maybeOptions ?? {};\n } else {\n const auth = getAuthContext();\n token = auth.token;\n options = (tokenOrOptions as DetailQueryOptions) ?? {};\n if (auth.organizationId && !options.organizationId) {\n options = { ...options, organizationId: auth.organizationId };\n }\n }\n\n const { organizationId, request: requestOpts, ...restOptions } = options;\n\n return createDetailQuery<T>({\n queryKey: KEYS.detail(id || \"\"),\n queryFn: () => api.getById({ id: id!, token, organizationId, options: { ...requestOpts } }),\n enabled: !!id && createEnabledRule(token, restOptions),\n options: {\n staleTime: restOptions.staleTime ?? config.staleTime,\n gcTime: restOptions.gcTime ?? config.gcTime,\n structuralSharing: restOptions.structuralSharing ?? config.structuralSharing,\n refetchInterval: restOptions.refetchInterval,\n refetchIntervalInBackground: restOptions.refetchIntervalInBackground,\n },\n });\n }\n\n // ========== useActions ==========\n\n function useActions(): CrudActions<T, TCreate, TUpdate> {\n const queryClient = useQueryClient();\n\n const createMutation = createOptimisticMutation({\n mutationFn: ({ token, organizationId, data }: MutationParams<TCreate>) =>\n api.create({ token, organizationId, data }),\n queryClient,\n queryKeys: [KEYS.lists()],\n optimisticUpdate: (oldData, { data }) => {\n const optimisticItem = {\n ...(data as object),\n _optimistic: true,\n [getItemId(data) ? \"id\" : \"_id\"]: getItemId(data) ?? `temp-${Date.now()}`,\n };\n return updateListCache(oldData, (arr: unknown[]) => [optimisticItem, ...(arr || [])]);\n },\n onSuccess: (data, variables) => {\n callbacks.onCreate?.onSuccess?.(data as T, { data: variables.data }, undefined);\n },\n onError: (error, variables) => {\n callbacks.onCreate?.onError?.(error, { data: variables.data }, undefined);\n },\n messages: { success: config.messages.createSuccess, error: config.messages.createError },\n toastHandler: instanceToast,\n });\n\n const updateMutation = createOptimisticMutation({\n mutationFn: ({ token, organizationId, id, data }: UpdateParams<TUpdate>) =>\n api.update({ token, organizationId, id, data }),\n queryClient,\n queryKeys: [KEYS.lists()],\n optimisticUpdate: (oldData, { id, data }) => {\n const updated = updateListCache(oldData, (arr: unknown[]) =>\n (arr || []).map((item) => (getItemId(item) === id ? { ...(item as object), ...(data as object) } : item))\n );\n queryClient.setQueryData(KEYS.detail(id), (current: unknown) =>\n current ? { ...(current as object), data: { ...((current as { data?: object }).data || {}), ...(data as object) } } : current\n );\n return updated;\n },\n onSuccess: (data, { id }) => {\n queryClient.invalidateQueries({ queryKey: KEYS.detail(id) });\n callbacks.onUpdate?.onSuccess?.(data as T, { id, data: {} as TUpdate }, undefined);\n },\n onError: (error, { id, data }) => {\n callbacks.onUpdate?.onError?.(error, { id, data }, undefined);\n },\n messages: { success: config.messages.updateSuccess, error: config.messages.updateError },\n toastHandler: instanceToast,\n });\n\n const deleteMutation = createOptimisticMutation({\n mutationFn: ({ token, organizationId, id }: DeleteParams) => api.delete({ token, organizationId, id }),\n queryClient,\n queryKeys: [KEYS.lists()],\n optimisticUpdate: (oldData, { id }) => {\n queryClient.removeQueries({ queryKey: KEYS.detail(id) });\n return updateListCache(oldData, (arr: unknown[]) => (arr || []).filter((item) => getItemId(item) !== id));\n },\n onSuccess: (data, { id }) => {\n callbacks.onDelete?.onSuccess?.(data, { id }, undefined);\n },\n onError: (error, { id }) => {\n callbacks.onDelete?.onError?.(error, { id }, undefined);\n },\n messages: { success: config.messages.deleteSuccess, error: config.messages.deleteError },\n toastHandler: instanceToast,\n });\n\n const resolveAuth = <P extends { token?: string | null; organizationId?: string | null }>(params: P): P => {\n const auth = getAuthContext();\n return {\n ...params,\n token: params.token ?? auth.token,\n organizationId: params.organizationId ?? auth.organizationId,\n };\n };\n\n const create = async (params: MutationParams<TCreate>, options?: CallOptions<T>): Promise<T> => {\n try {\n const result = await createMutation.mutateAsync(resolveAuth(params));\n options?.onSuccess?.(result as T);\n return result as T;\n } catch (error) {\n options?.onError?.(error as Error);\n throw error;\n }\n };\n\n const update = async (params: UpdateParams<TUpdate>, options?: CallOptions<T>): Promise<T> => {\n try {\n const result = await updateMutation.mutateAsync(resolveAuth(params));\n options?.onSuccess?.(result as T);\n return result as T;\n } catch (error) {\n options?.onError?.(error as Error);\n throw error;\n }\n };\n\n const remove = async (params: DeleteParams, options?: CallOptions): Promise<unknown> => {\n try {\n const result = await deleteMutation.mutateAsync(resolveAuth(params));\n options?.onSuccess?.(result);\n return result;\n } catch (error) {\n options?.onError?.(error as Error);\n throw error;\n }\n };\n\n return {\n create,\n update,\n remove,\n isCreating: createMutation.isPending,\n isUpdating: updateMutation.isPending,\n isDeleting: deleteMutation.isPending,\n isMutating: createMutation.isPending || updateMutation.isPending || deleteMutation.isPending,\n };\n }\n\n // ========== useInfiniteList ==========\n\n function useInfiniteList(\n tokenOrParams?: string | null | Record<string, unknown>,\n paramsOrOptions?: Record<string, unknown> | InfiniteListQueryOptions,\n maybeOptions?: InfiniteListQueryOptions,\n ): InfiniteListQueryResult<T> {\n let token: string | null;\n let params: Record<string, unknown>;\n let options: InfiniteListQueryOptions;\n\n if (tokenOrParams === null || typeof tokenOrParams === 'string') {\n token = tokenOrParams;\n params = (paramsOrOptions as Record<string, unknown>) ?? {};\n options = maybeOptions ?? {};\n } else {\n const auth = getAuthContext();\n token = auth.token;\n params = (tokenOrParams as Record<string, unknown>) ?? {};\n options = (paramsOrOptions as InfiniteListQueryOptions) ?? {};\n if (auth.organizationId && !params.organizationId) {\n params = { ...params, organizationId: auth.organizationId };\n }\n }\n\n const { organizationId, ...restParams } = params;\n const scope = options._scope || (organizationId ? \"tenant\" : \"super-admin\");\n const { request: requestOpts, ...queryOpts } = options;\n\n return createInfiniteListQuery<T>({\n queryKey: [...KEYS.scopedList(scope, { organizationId, ...restParams }), 'infinite'],\n queryFn: ({ pageParam }) => {\n const paginationParams = typeof pageParam === 'string'\n ? { after: pageParam }\n : { page: pageParam };\n\n return api.getAll({\n token,\n organizationId: organizationId as string | null,\n params: { ...restParams, ...paginationParams },\n options: { ...requestOpts },\n });\n },\n enabled: createEnabledRule(token, queryOpts),\n initialPageParam: restParams.after ? restParams.after : 1,\n getNextPageParam: (lastPage) => {\n const page = lastPage as PaginatedResponse<T>;\n if (isKeysetPagination(page)) {\n return page.hasMore ? page.next : undefined;\n }\n if (isOffsetPagination(page)) {\n return page.hasNext ? page.page + 1 : undefined;\n }\n // Fallback: check common pagination fields\n const p = page as unknown as Record<string, unknown>;\n if (typeof p.hasNext === 'boolean' && typeof p.page === 'number') {\n return p.hasNext ? (p.page as number) + 1 : undefined;\n }\n return undefined;\n },\n options: {\n staleTime: queryOpts.staleTime ?? config.staleTime,\n gcTime: queryOpts.gcTime ?? config.gcTime,\n refetchOnWindowFocus: queryOpts.refetchOnWindowFocus ?? config.refetchOnWindowFocus,\n structuralSharing: queryOpts.structuralSharing ?? config.structuralSharing,\n },\n });\n }\n\n // ========== useNavigation ==========\n\n function useNavigation(): NavigateFn<T> {\n const queryClient = useQueryClient();\n const activeRouterHook = instanceNavigation ?? useRouterHook;\n const router = activeRouterHook?.();\n\n return useCallback(\n (href: string, item: T, options: NavigationOptions = {}) => {\n const id = getItemId(item);\n if (id) {\n queryClient.setQueryData(KEYS.detail(id), { data: item });\n }\n\n if (!router) return;\n\n const { scroll = true, replace = false } = options;\n if (replace) {\n router.replace(href, { scroll });\n } else {\n router.push(href, { scroll });\n }\n },\n [queryClient, router]\n );\n }\n\n return {\n KEYS,\n cache,\n useList,\n useDetail,\n useInfiniteList,\n useActions,\n useNavigation,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"hooks.js","names":[],"sources":["../src/hooks.ts"],"sourcesContent":["\"use client\";\n\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport { useCallback } from \"react\";\nimport { createOptimisticMutation } from \"./mutation.js\";\nimport {\n createListQuery,\n createDetailQuery,\n createInfiniteListQuery,\n updateListCache,\n getItemId,\n createQueryKeys,\n createCacheUtils,\n DEFAULT_QUERY_CONFIG,\n} from \"./query.js\";\nimport type {\n QueryKeys,\n CacheUtils,\n ListQueryOptions,\n DetailQueryOptions,\n ListQueryResult,\n DetailQueryResult,\n InfiniteListQueryOptions,\n InfiniteListQueryResult,\n} from \"./query.js\";\nimport {\n isOffsetPagination,\n isKeysetPagination,\n} from \"./api.js\";\nimport type { PaginatedResponse } from \"./api.js\";\nimport type { MutationCallbacks } from \"./mutation.js\";\nimport { getAuthMode, getAuthContext } from \"./client.js\";\nimport type { ArcClient, ToastHandler, UseRouterHook } from \"./client.js\";\n\n// Re-export UseRouterHook for backward compatibility\nexport type { UseRouterHook } from \"./client.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface CrudApi<T, TCreate = Partial<T>, TUpdate = Partial<T>> {\n getAll: (options: {\n token?: string | null;\n organizationId?: string | null;\n params?: Record<string, unknown>;\n options?: { signal?: AbortSignal; [key: string]: unknown };\n }) => Promise<unknown>;\n\n getById: (options: {\n id: string;\n token?: string | null;\n organizationId?: string | null;\n params?: { select?: string; populate?: string | string[] };\n options?: { signal?: AbortSignal; [key: string]: unknown };\n }) => Promise<unknown>;\n\n create: (options: {\n token?: string | null;\n organizationId?: string | null;\n data: TCreate;\n }) => Promise<unknown>;\n\n update: (options: {\n token?: string | null;\n organizationId?: string | null;\n id: string;\n data: TUpdate;\n }) => Promise<unknown>;\n\n delete: (options: {\n token?: string | null;\n organizationId?: string | null;\n id: string;\n }) => Promise<unknown>;\n}\n\nexport interface CrudHooksConfig<T, TCreate = Partial<T>, TUpdate = Partial<T>> {\n api: CrudApi<T, TCreate, TUpdate>;\n entityKey: string;\n singular: string;\n plural?: string;\n defaults?: {\n staleTime?: number;\n gcTime?: number;\n refetchOnWindowFocus?: boolean;\n structuralSharing?: boolean;\n messages?: {\n createSuccess?: string;\n createError?: string;\n updateSuccess?: string;\n updateError?: string;\n deleteSuccess?: string;\n deleteError?: string;\n };\n };\n callbacks?: {\n onCreate?: MutationCallbacks<T, { data: TCreate }>;\n onUpdate?: MutationCallbacks<T, { id: string; data: TUpdate }>;\n onDelete?: MutationCallbacks<unknown, { id: string }>;\n };\n client?: ArcClient;\n}\n\nexport interface MutationParams<TData> {\n token?: string | null;\n organizationId?: string | null;\n data: TData;\n}\n\nexport interface UpdateParams<TData> extends MutationParams<TData> {\n id: string;\n}\n\nexport interface DeleteParams {\n token?: string | null;\n organizationId?: string | null;\n id: string;\n}\n\nexport interface CallOptions<TData = unknown> {\n onSuccess?: (data: TData) => void;\n onError?: (error: Error) => void;\n silent?: boolean;\n}\n\nexport interface CrudActions<T, TCreate, TUpdate> {\n create: (params: MutationParams<TCreate>, options?: CallOptions<T>) => Promise<T>;\n update: (params: UpdateParams<TUpdate>, options?: CallOptions<T>) => Promise<T>;\n remove: (params: DeleteParams, options?: CallOptions) => Promise<unknown>;\n isCreating: boolean;\n isUpdating: boolean;\n isDeleting: boolean;\n isMutating: boolean;\n}\n\nexport interface NavigationOptions {\n scroll?: boolean;\n replace?: boolean;\n}\n\nexport type NavigateFn<T> = (href: string, item: T, options?: NavigationOptions) => void;\n\nexport interface CrudHooksReturn<T, TCreate, TUpdate> {\n KEYS: QueryKeys;\n cache: CacheUtils<T>;\n useList: {\n /** New signature — auto-injects token/orgId from configureAuth() context */\n (params?: Record<string, unknown>, options?: ListQueryOptions): ListQueryResult<T>;\n /** Legacy signature — explicit token */\n (token: string | null, params?: Record<string, unknown>, options?: ListQueryOptions): ListQueryResult<T>;\n };\n useDetail: {\n /** New signature — auto-injects token from configureAuth() context */\n (id: string | null, options?: DetailQueryOptions): DetailQueryResult<T>;\n /** Legacy signature — explicit token */\n (id: string | null, token: string | null, options?: DetailQueryOptions): DetailQueryResult<T>;\n };\n useInfiniteList: {\n /** New signature — auto-injects token/orgId from configureAuth() context */\n (params?: Record<string, unknown>, options?: InfiniteListQueryOptions): InfiniteListQueryResult<T>;\n /** Legacy signature — explicit token */\n (token: string | null, params?: Record<string, unknown>, options?: InfiniteListQueryOptions): InfiniteListQueryResult<T>;\n };\n useActions: () => CrudActions<T, TCreate, TUpdate>;\n useNavigation: () => NavigateFn<T>;\n}\n\n// ============================================================================\n// Navigation Configuration (Global)\n// ============================================================================\n\nlet useRouterHook: UseRouterHook | null = null;\n\n/**\n * Configure the router hook for useNavigation. Call once at app init.\n *\n * @example\n * import { useRouter } from \"next/navigation\";\n * configureNavigation(useRouter);\n */\nexport function configureNavigation(hook: UseRouterHook): void {\n useRouterHook = hook;\n}\n\n// ============================================================================\n// Enabled Rule\n// ============================================================================\n\nfunction createEnabledRule(token: string | null, options: { public?: boolean; enabled?: boolean }): boolean {\n // Cookie-based auth: no token needed, queries always enabled (cookies sent via credentials: 'include')\n if (getAuthMode() === 'cookie' || options.public) {\n return options.enabled ?? true;\n }\n // Bearer token auth: require token to enable queries\n return options.enabled !== undefined ? options.enabled && !!token : !!token;\n}\n\n// ============================================================================\n// Create CRUD Hooks Factory\n// ============================================================================\n\nexport function createCrudHooks<T, TCreate = Partial<T>, TUpdate = Partial<T>>({\n api,\n entityKey,\n singular,\n defaults = {},\n callbacks = {},\n client,\n}: CrudHooksConfig<T, TCreate, TUpdate>): CrudHooksReturn<T, TCreate, TUpdate> {\n const KEYS = createQueryKeys(entityKey);\n const cache = createCacheUtils<T>(KEYS);\n\n // Resolve toast handler: client instance → global\n const instanceToast: ToastHandler | undefined = client?.toast;\n // Resolve navigation hook: client instance → global\n const instanceNavigation: UseRouterHook | null = client?.navigation ?? null;\n\n const config = {\n ...DEFAULT_QUERY_CONFIG,\n ...defaults,\n structuralSharing: defaults.structuralSharing,\n messages: {\n createSuccess: `${singular} created successfully`,\n createError: `Failed to create ${singular.toLowerCase()}`,\n updateSuccess: `${singular} updated successfully`,\n updateError: `Failed to update ${singular.toLowerCase()}`,\n deleteSuccess: `${singular} deleted successfully`,\n deleteError: `Failed to delete ${singular.toLowerCase()}`,\n ...defaults.messages,\n },\n };\n\n // ========== useList ==========\n\n function useList(\n tokenOrParams?: string | null | Record<string, unknown>,\n paramsOrOptions?: Record<string, unknown> | ListQueryOptions,\n maybeOptions?: ListQueryOptions,\n ): ListQueryResult<T> {\n let token: string | null;\n let params: Record<string, unknown>;\n let options: ListQueryOptions;\n\n // Detect which overload: first arg is string|null = legacy, object/undefined = new\n if (tokenOrParams === null || typeof tokenOrParams === 'string') {\n token = tokenOrParams;\n params = (paramsOrOptions as Record<string, unknown>) ?? {};\n options = maybeOptions ?? {};\n } else {\n const auth = getAuthContext();\n token = auth.token;\n params = (tokenOrParams as Record<string, unknown>) ?? {};\n options = (paramsOrOptions as ListQueryOptions) ?? {};\n if (auth.organizationId && !params.organizationId) {\n params = { ...params, organizationId: auth.organizationId };\n }\n }\n\n const { organizationId, ...restParams } = params;\n const scope = options._scope || (organizationId ? \"tenant\" : \"super-admin\");\n const { request: requestOpts, ...queryOpts } = options;\n\n return createListQuery<T>({\n queryKey: KEYS.scopedList(scope, { organizationId, ...restParams }),\n queryFn: () => api.getAll({ token, organizationId: organizationId as string | null, params: restParams, options: { ...requestOpts } }),\n enabled: createEnabledRule(token, queryOpts),\n options: {\n staleTime: queryOpts.staleTime ?? config.staleTime,\n gcTime: queryOpts.gcTime ?? config.gcTime,\n refetchOnWindowFocus: queryOpts.refetchOnWindowFocus ?? config.refetchOnWindowFocus,\n structuralSharing: queryOpts.structuralSharing ?? config.structuralSharing,\n refetchInterval: queryOpts.refetchInterval,\n refetchIntervalInBackground: queryOpts.refetchIntervalInBackground,\n },\n prefillDetailCache: queryOpts.prefillDetailCache ?? true,\n detailKeyBuilder: (id) => KEYS.detail(id),\n });\n }\n\n // ========== useDetail ==========\n\n function useDetail(\n id: string | null,\n tokenOrOptions?: string | null | DetailQueryOptions,\n maybeOptions?: DetailQueryOptions,\n ): DetailQueryResult<T> {\n let token: string | null;\n let options: DetailQueryOptions;\n\n // Detect which overload: second arg is string|null = legacy, object/undefined = new\n if (tokenOrOptions === null || typeof tokenOrOptions === 'string') {\n token = tokenOrOptions;\n options = maybeOptions ?? {};\n } else {\n const auth = getAuthContext();\n token = auth.token;\n options = (tokenOrOptions as DetailQueryOptions) ?? {};\n if (auth.organizationId && !options.organizationId) {\n options = { ...options, organizationId: auth.organizationId };\n }\n }\n\n const { organizationId, params: queryParams, request: requestOpts, ...restOptions } = options;\n\n return createDetailQuery<T>({\n queryKey: queryParams ? [...KEYS.detail(id || \"\"), queryParams] : KEYS.detail(id || \"\"),\n queryFn: () => api.getById({ id: id!, token, organizationId, params: queryParams, options: { ...requestOpts } }),\n enabled: !!id && createEnabledRule(token, restOptions),\n options: {\n staleTime: restOptions.staleTime ?? config.staleTime,\n gcTime: restOptions.gcTime ?? config.gcTime,\n structuralSharing: restOptions.structuralSharing ?? config.structuralSharing,\n refetchInterval: restOptions.refetchInterval,\n refetchIntervalInBackground: restOptions.refetchIntervalInBackground,\n },\n });\n }\n\n // ========== useActions ==========\n\n function useActions(): CrudActions<T, TCreate, TUpdate> {\n const queryClient = useQueryClient();\n\n const createMutation = createOptimisticMutation({\n mutationFn: ({ token, organizationId, data }: MutationParams<TCreate>) =>\n api.create({ token, organizationId, data }),\n queryClient,\n queryKeys: [KEYS.lists()],\n optimisticUpdate: (oldData, { data }) => {\n const optimisticItem = {\n ...(data as object),\n _optimistic: true,\n [getItemId(data) ? \"id\" : \"_id\"]: getItemId(data) ?? `temp-${Date.now()}`,\n };\n return updateListCache(oldData, (arr: unknown[]) => [optimisticItem, ...(arr || [])]);\n },\n onSuccess: (data, variables) => {\n callbacks.onCreate?.onSuccess?.(data as T, { data: variables.data }, undefined);\n },\n onError: (error, variables) => {\n callbacks.onCreate?.onError?.(error, { data: variables.data }, undefined);\n },\n messages: { success: config.messages.createSuccess, error: config.messages.createError },\n toastHandler: instanceToast,\n });\n\n const updateMutation = createOptimisticMutation({\n mutationFn: ({ token, organizationId, id, data }: UpdateParams<TUpdate>) =>\n api.update({ token, organizationId, id, data }),\n queryClient,\n queryKeys: [KEYS.lists()],\n optimisticUpdate: (oldData, { id, data }) => {\n const updated = updateListCache(oldData, (arr: unknown[]) =>\n (arr || []).map((item) => (getItemId(item) === id ? { ...(item as object), ...(data as object) } : item))\n );\n queryClient.setQueryData(KEYS.detail(id), (current: unknown) =>\n current ? { ...(current as object), data: { ...((current as { data?: object }).data || {}), ...(data as object) } } : current\n );\n return updated;\n },\n onSuccess: (data, { id }) => {\n queryClient.invalidateQueries({ queryKey: KEYS.detail(id) });\n callbacks.onUpdate?.onSuccess?.(data as T, { id, data: {} as TUpdate }, undefined);\n },\n onError: (error, { id, data }) => {\n callbacks.onUpdate?.onError?.(error, { id, data }, undefined);\n },\n messages: { success: config.messages.updateSuccess, error: config.messages.updateError },\n toastHandler: instanceToast,\n });\n\n const deleteMutation = createOptimisticMutation({\n mutationFn: ({ token, organizationId, id }: DeleteParams) => api.delete({ token, organizationId, id }),\n queryClient,\n queryKeys: [KEYS.lists()],\n optimisticUpdate: (oldData, { id }) => {\n queryClient.removeQueries({ queryKey: KEYS.detail(id) });\n return updateListCache(oldData, (arr: unknown[]) => (arr || []).filter((item) => getItemId(item) !== id));\n },\n onSuccess: (data, { id }) => {\n callbacks.onDelete?.onSuccess?.(data, { id }, undefined);\n },\n onError: (error, { id }) => {\n callbacks.onDelete?.onError?.(error, { id }, undefined);\n },\n messages: { success: config.messages.deleteSuccess, error: config.messages.deleteError },\n toastHandler: instanceToast,\n });\n\n const resolveAuth = <P extends { token?: string | null; organizationId?: string | null }>(params: P): P => {\n const auth = getAuthContext();\n return {\n ...params,\n token: params.token ?? auth.token,\n organizationId: params.organizationId ?? auth.organizationId,\n };\n };\n\n const create = async (params: MutationParams<TCreate>, options?: CallOptions<T>): Promise<T> => {\n try {\n const result = await createMutation.mutateAsync(resolveAuth(params));\n options?.onSuccess?.(result as T);\n return result as T;\n } catch (error) {\n options?.onError?.(error as Error);\n throw error;\n }\n };\n\n const update = async (params: UpdateParams<TUpdate>, options?: CallOptions<T>): Promise<T> => {\n try {\n const result = await updateMutation.mutateAsync(resolveAuth(params));\n options?.onSuccess?.(result as T);\n return result as T;\n } catch (error) {\n options?.onError?.(error as Error);\n throw error;\n }\n };\n\n const remove = async (params: DeleteParams, options?: CallOptions): Promise<unknown> => {\n try {\n const result = await deleteMutation.mutateAsync(resolveAuth(params));\n options?.onSuccess?.(result);\n return result;\n } catch (error) {\n options?.onError?.(error as Error);\n throw error;\n }\n };\n\n return {\n create,\n update,\n remove,\n isCreating: createMutation.isPending,\n isUpdating: updateMutation.isPending,\n isDeleting: deleteMutation.isPending,\n isMutating: createMutation.isPending || updateMutation.isPending || deleteMutation.isPending,\n };\n }\n\n // ========== useInfiniteList ==========\n\n function useInfiniteList(\n tokenOrParams?: string | null | Record<string, unknown>,\n paramsOrOptions?: Record<string, unknown> | InfiniteListQueryOptions,\n maybeOptions?: InfiniteListQueryOptions,\n ): InfiniteListQueryResult<T> {\n let token: string | null;\n let params: Record<string, unknown>;\n let options: InfiniteListQueryOptions;\n\n if (tokenOrParams === null || typeof tokenOrParams === 'string') {\n token = tokenOrParams;\n params = (paramsOrOptions as Record<string, unknown>) ?? {};\n options = maybeOptions ?? {};\n } else {\n const auth = getAuthContext();\n token = auth.token;\n params = (tokenOrParams as Record<string, unknown>) ?? {};\n options = (paramsOrOptions as InfiniteListQueryOptions) ?? {};\n if (auth.organizationId && !params.organizationId) {\n params = { ...params, organizationId: auth.organizationId };\n }\n }\n\n const { organizationId, ...restParams } = params;\n const scope = options._scope || (organizationId ? \"tenant\" : \"super-admin\");\n const { request: requestOpts, ...queryOpts } = options;\n\n return createInfiniteListQuery<T>({\n queryKey: [...KEYS.scopedList(scope, { organizationId, ...restParams }), 'infinite'],\n queryFn: ({ pageParam }) => {\n const paginationParams = typeof pageParam === 'string'\n ? { after: pageParam }\n : { page: pageParam };\n\n return api.getAll({\n token,\n organizationId: organizationId as string | null,\n params: { ...restParams, ...paginationParams },\n options: { ...requestOpts },\n });\n },\n enabled: createEnabledRule(token, queryOpts),\n initialPageParam: restParams.after ? restParams.after : 1,\n getNextPageParam: (lastPage) => {\n const page = lastPage as PaginatedResponse<T>;\n if (isKeysetPagination(page)) {\n return page.hasMore ? page.next : undefined;\n }\n if (isOffsetPagination(page)) {\n return page.hasNext ? page.page + 1 : undefined;\n }\n // Fallback: check common pagination fields\n const p = page as unknown as Record<string, unknown>;\n if (typeof p.hasNext === 'boolean' && typeof p.page === 'number') {\n return p.hasNext ? (p.page as number) + 1 : undefined;\n }\n return undefined;\n },\n options: {\n staleTime: queryOpts.staleTime ?? config.staleTime,\n gcTime: queryOpts.gcTime ?? config.gcTime,\n refetchOnWindowFocus: queryOpts.refetchOnWindowFocus ?? config.refetchOnWindowFocus,\n structuralSharing: queryOpts.structuralSharing ?? config.structuralSharing,\n },\n });\n }\n\n // ========== useNavigation ==========\n\n function useNavigation(): NavigateFn<T> {\n const queryClient = useQueryClient();\n const activeRouterHook = instanceNavigation ?? useRouterHook;\n const router = activeRouterHook?.();\n\n return useCallback(\n (href: string, item: T, options: NavigationOptions = {}) => {\n const id = getItemId(item);\n if (id) {\n queryClient.setQueryData(KEYS.detail(id), { data: item });\n }\n\n if (!router) return;\n\n const { scroll = true, replace = false } = options;\n if (replace) {\n router.replace(href, { scroll });\n } else {\n router.push(href, { scroll });\n }\n },\n [queryClient, router]\n );\n }\n\n return {\n KEYS,\n cache,\n useList,\n useDetail,\n useInfiniteList,\n useActions,\n useNavigation,\n };\n}\n"],"mappings":"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"}
|
package/dist/query.d.ts
CHANGED
|
@@ -40,6 +40,11 @@ interface DetailQueryOptions {
|
|
|
40
40
|
structuralSharing?: boolean;
|
|
41
41
|
refetchInterval?: number | false;
|
|
42
42
|
refetchIntervalInBackground?: boolean;
|
|
43
|
+
/** Query params passed to getById (e.g. select, populate) */
|
|
44
|
+
params?: {
|
|
45
|
+
select?: string;
|
|
46
|
+
populate?: string | string[];
|
|
47
|
+
};
|
|
43
48
|
/** Pass-through options for the underlying fetch request (cache, revalidate, tags, headers) */
|
|
44
49
|
request?: RequestPassthrough;
|
|
45
50
|
}
|
package/dist/query.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query.d.ts","names":[],"sources":["../src/query.ts"],"mappings":";;;UAUiB,cAAA;EACf,KAAA;EACA,KAAA;EACA,IAAA;EACA,KAAA;EACA,OAAA;EACA,OAAA;AAAA;;UAIe,kBAAA;EACf,KAAA,GAAQ,YAAA;EACR,UAAA;EACA,IAAA;EACA,aAAA,GAAgB,MAAA;EAChB,MAAA,GAAS,WAAA;AAAA;AAAA,UAGM,gBAAA;EACf,MAAA;EACA,OAAA;EACA,SAAA;EACA,MAAA;EACA,oBAAA;EACA,iBAAA;EACA,kBAAA;EACA,eAAA;EACA,2BAAA;EACA,MAAA;EAbA;EAeA,OAAA,GAAU,kBAAA;AAAA;AAAA,UAGK,kBAAA;EACf,MAAA;EACA,cAAA;EACA,OAAA;EACA,SAAA;EACA,MAAA;EACA,iBAAA;EACA,eAAA;EACA,2BAAA;EAlBA;EAoBA,OAAA,GAAU,kBAAA;AAAA;AAAA,UAGK,eAAA;EACf,KAAA,EAAO,CAAA;EACP,UAAA,EAAY,cAAA;EACZ,SAAA;EACA,UAAA;EACA,OAAA;EACA,SAAA;EACA,OAAA;EACA,KAAA,EAAO,KAAA;EACP,OAAA,QAAe,OAAA;EACf,IAAA;AAAA;AAAA,UAGe,iBAAA;EACf,IAAA,EAAM,CAAA;EACN,SAAA;EACA,UAAA;EACA,OAAA;EACA,SAAA;EACA,OAAA;EACA,KAAA,EAAO,KAAA;EACP,OAAA,QAAe,OAAA;EACf,IAAA;AAAA;AAAA,UAGe,SAAA;EACf,GAAA;EACA,KAAA,QAAa,QAAA;EACb,IAAA,GAAO,MAAA,eAAqB,QAAA;EAC5B,OAAA,QAAe,QAAA;EACf,MAAA,GAAS,EAAA,aAAe,QAAA;EACxB,MAAA,GAAS,GAAA,aAAgB,IAAA,gBAAoB,QAAA;EAC7C,UAAA,GAAa,KAAA,UAAe,MAAA,eAAqB,QAAA;AAAA;AAAA,UAGlC,UAAA;EACf,aAAA,GAAgB,MAAA,EAAQ,WAAA,KAAgB,OAAA;EACxC,eAAA,GAAkB,MAAA,EAAQ,WAAA,KAAgB,OAAA;EAC1C,gBAAA,GAAmB,MAAA,EAAQ,WAAA,EAAa,EAAA,aAAe,OAAA;EACvD,SAAA,GAAY,MAAA,EAAQ,WAAA,EAAa,EAAA,UAAY,IAAA,EAAM,CAAA;EACnD,SAAA,GAAY,MAAA,EAAQ,WAAA,EAAa,EAAA,aAAe,CAAA;EAChD,YAAA,GAAe,MAAA,EAAQ,WAAA,EAAa,EAAA;AAAA;AAAA,cAOzB,oBAAA;EAAA;;;;;iBAWG,SAAA,CAAU,IAAA;AAAA,iBA2CV,eAAA,GAAA,CAAmB,QAAA,WAAmB,OAAA,GAAU,KAAA,EAAO,CAAA,OAAQ,CAAA;AAAA,
|
|
1
|
+
{"version":3,"file":"query.d.ts","names":[],"sources":["../src/query.ts"],"mappings":"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"}
|
package/dist/query.js
CHANGED
|
@@ -47,25 +47,23 @@ function extractItem(data) {
|
|
|
47
47
|
}
|
|
48
48
|
function updateListCache(listData, updater) {
|
|
49
49
|
if (!listData) return listData;
|
|
50
|
-
if (typeof listData === "object" && "docs" in listData) {
|
|
51
|
-
const d = listData;
|
|
52
|
-
const updatedDocs = updater(d.docs);
|
|
53
|
-
return {
|
|
54
|
-
...d,
|
|
55
|
-
docs: updatedDocs,
|
|
56
|
-
total: updatedDocs.length,
|
|
57
|
-
totalDocs: updatedDocs.length
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
50
|
if (Array.isArray(listData)) return updater(listData);
|
|
61
|
-
if (typeof listData
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
51
|
+
if (typeof listData !== "object") return listData;
|
|
52
|
+
const d = listData;
|
|
53
|
+
const arrayField = "docs" in d ? "docs" : Array.isArray(d.data) ? "data" : "items" in d && Array.isArray(d.items) ? "items" : "results" in d && Array.isArray(d.results) ? "results" : null;
|
|
54
|
+
if (!arrayField) return listData;
|
|
55
|
+
const updated = updater(d[arrayField]);
|
|
56
|
+
const original = d[arrayField];
|
|
57
|
+
const delta = updated.length - original.length;
|
|
58
|
+
const result = {
|
|
59
|
+
...d,
|
|
60
|
+
[arrayField]: updated
|
|
61
|
+
};
|
|
62
|
+
if (delta !== 0) {
|
|
63
|
+
if (d.total != null) result.total = Math.max(0, Number(d.total) + delta);
|
|
64
|
+
if (d.totalDocs != null) result.totalDocs = Math.max(0, Number(d.totalDocs) + delta);
|
|
67
65
|
}
|
|
68
|
-
return
|
|
66
|
+
return result;
|
|
69
67
|
}
|
|
70
68
|
function createQueryKeys(entityKey) {
|
|
71
69
|
return {
|
package/dist/query.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query.js","names":[],"sources":["../src/query.ts"],"sourcesContent":["\"use client\";\n\nimport { useQuery, useInfiniteQuery, useQueryClient, keepPreviousData, type QueryKey, type InfiniteData } from \"@tanstack/react-query\";\nimport { useEffect } from \"react\";\nimport type { QueryClient } from \"@tanstack/react-query\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface PaginationData {\n total: number;\n pages: number;\n page: number;\n limit: number;\n hasNext: boolean;\n hasPrev: boolean;\n}\n\n/** Request-level options passed through to the fetch call */\nexport interface RequestPassthrough {\n cache?: RequestCache;\n revalidate?: number;\n tags?: string[];\n headerOptions?: Record<string, string>;\n signal?: AbortSignal;\n}\n\nexport interface ListQueryOptions {\n public?: boolean;\n enabled?: boolean;\n staleTime?: number;\n gcTime?: number;\n refetchOnWindowFocus?: boolean;\n structuralSharing?: boolean;\n prefillDetailCache?: boolean;\n refetchInterval?: number | false;\n refetchIntervalInBackground?: boolean;\n _scope?: string;\n /** Pass-through options for the underlying fetch request (cache, revalidate, tags, headers) */\n request?: RequestPassthrough;\n}\n\nexport interface DetailQueryOptions {\n public?: boolean;\n organizationId?: string | null;\n enabled?: boolean;\n staleTime?: number;\n gcTime?: number;\n structuralSharing?: boolean;\n refetchInterval?: number | false;\n refetchIntervalInBackground?: boolean;\n /** Pass-through options for the underlying fetch request (cache, revalidate, tags, headers) */\n request?: RequestPassthrough;\n}\n\nexport interface ListQueryResult<T> {\n items: T[];\n pagination: PaginationData | null;\n isLoading: boolean;\n isFetching: boolean;\n isError: boolean;\n isSuccess: boolean;\n isStale: boolean;\n error: Error | null;\n refetch: () => Promise<unknown>;\n data: unknown;\n}\n\nexport interface DetailQueryResult<T> {\n item: T | null;\n isLoading: boolean;\n isFetching: boolean;\n isError: boolean;\n isSuccess: boolean;\n isStale: boolean;\n error: Error | null;\n refetch: () => Promise<unknown>;\n data: unknown;\n}\n\nexport interface QueryKeys {\n all: string[];\n lists: () => QueryKey;\n list: (params?: unknown) => QueryKey;\n details: () => QueryKey;\n detail: (id: string) => QueryKey;\n custom: (key: string, ...args: unknown[]) => QueryKey;\n scopedList: (scope: string, params?: unknown) => QueryKey;\n}\n\nexport interface CacheUtils<T> {\n invalidateAll: (client: QueryClient) => Promise<void>;\n invalidateLists: (client: QueryClient) => Promise<void>;\n invalidateDetail: (client: QueryClient, id: string) => Promise<void>;\n setDetail: (client: QueryClient, id: string, data: T) => void;\n getDetail: (client: QueryClient, id: string) => T | undefined;\n removeDetail: (client: QueryClient, id: string) => void;\n}\n\n// ============================================================================\n// Default Config\n// ============================================================================\n\nexport const DEFAULT_QUERY_CONFIG = {\n staleTime: 5 * 60 * 1000,\n gcTime: 30 * 60 * 1000,\n refetchOnWindowFocus: false,\n retry: 0,\n} as const;\n\n// ============================================================================\n// Utilities\n// ============================================================================\n\nexport function getItemId(item: unknown): string | null {\n if (!item || typeof item !== \"object\") return null;\n const obj = item as Record<string, unknown>;\n const id = obj._id ?? obj.id;\n return typeof id === \"string\" ? id : id ? String(id) : null;\n}\n\nfunction normalizePagination(data: unknown): PaginationData | null {\n if (!data || typeof data !== \"object\") return null;\n const d = data as Record<string, unknown>;\n\n const hasTotal = d.total != null || d.totalDocs != null;\n const hasPages = d.pages != null || d.totalPages != null;\n if (!hasTotal && !hasPages) return null;\n\n return {\n total: Number(d.total ?? d.totalDocs ?? 0),\n pages: Number(d.pages ?? d.totalPages ?? 1),\n page: Number(d.page ?? d.currentPage ?? 1),\n limit: Number(d.limit ?? 10),\n hasNext: Boolean(d.hasNext ?? d.hasNextPage ?? d.hasMore ?? false),\n hasPrev: Boolean(d.hasPrev ?? d.hasPrevPage ?? false),\n };\n}\n\nfunction extractItems<T>(data: unknown): T[] {\n if (!data) return [];\n if (Array.isArray(data)) return data as T[];\n if (typeof data !== \"object\") return [];\n\n const d = data as Record<string, unknown>;\n const items = d.docs ?? d.data ?? d.items ?? d.results;\n return Array.isArray(items) ? (items as T[]) : [];\n}\n\nfunction extractItem<T>(data: unknown): T | null {\n if (!data) return null;\n if (typeof data !== \"object\") return null;\n\n const d = data as Record<string, unknown>;\n return (d.data ?? d) as T | null;\n}\n\nexport function updateListCache<T>(listData: unknown, updater: (items: T[]) => T[]): unknown {\n if (!listData) return listData;\n\n if (typeof listData === \"object\" && \"docs\" in (listData as object)) {\n const d = listData as Record<string, unknown>;\n const updatedDocs = updater(d.docs as T[]);\n return { ...d, docs: updatedDocs, total: updatedDocs.length, totalDocs: updatedDocs.length };\n }\n\n if (Array.isArray(listData)) {\n return updater(listData as T[]);\n }\n\n if (typeof listData === \"object\" && \"data\" in (listData as object)) {\n const d = listData as Record<string, unknown>;\n if (Array.isArray(d.data)) {\n return { ...d, data: updater(d.data as T[]) };\n }\n }\n\n return listData;\n}\n\n// ============================================================================\n// Query Keys Factory\n// ============================================================================\n\nexport function createQueryKeys(entityKey: string): QueryKeys {\n return {\n all: [entityKey],\n lists: () => [entityKey, \"list\"],\n list: (params) => [entityKey, \"list\", params],\n details: () => [entityKey, \"detail\"],\n detail: (id) => [entityKey, \"detail\", id],\n custom: (key, ...args) => [entityKey, key, ...args],\n scopedList: (scope, params) => [entityKey, \"list\", { _scope: scope, ...(params as object) }],\n };\n}\n\n// ============================================================================\n// Cache Utilities Factory\n// ============================================================================\n\nexport function createCacheUtils<T>(KEYS: QueryKeys): CacheUtils<T> {\n return {\n invalidateAll: (client) => client.invalidateQueries({ queryKey: KEYS.all }),\n invalidateLists: (client) => client.invalidateQueries({ queryKey: KEYS.lists() }),\n invalidateDetail: (client, id) => client.invalidateQueries({ queryKey: KEYS.detail(id) }),\n setDetail: (client, id, data) => client.setQueryData(KEYS.detail(id), { data }),\n getDetail: (client, id) => {\n const cached = client.getQueryData(KEYS.detail(id)) as { data?: T } | undefined;\n return cached?.data;\n },\n removeDetail: (client, id) => client.removeQueries({ queryKey: KEYS.detail(id) }),\n };\n}\n\n// ============================================================================\n// List Query Hook\n// ============================================================================\n\nexport interface CreateListQueryConfig {\n queryKey: QueryKey;\n queryFn: (context: { signal?: AbortSignal }) => Promise<unknown>;\n enabled?: boolean;\n options?: Record<string, unknown>;\n prefillDetailCache?: boolean;\n detailKeyBuilder?: (id: string) => QueryKey;\n}\n\nexport function createListQuery<T>({\n queryKey,\n queryFn,\n enabled = true,\n options = {},\n prefillDetailCache = true,\n detailKeyBuilder,\n}: CreateListQueryConfig): ListQueryResult<T> {\n const queryClient = useQueryClient();\n\n const query = useQuery({\n queryKey,\n queryFn: () => queryFn({}),\n enabled,\n ...DEFAULT_QUERY_CONFIG,\n ...options,\n placeholderData: keepPreviousData,\n });\n\n const items = extractItems<T>(query.data);\n const pagination = normalizePagination(query.data);\n\n useEffect(() => {\n if (!prefillDetailCache || !detailKeyBuilder || items.length === 0) return;\n\n items.forEach((item) => {\n const id = getItemId(item);\n if (id) queryClient.setQueryData(detailKeyBuilder(id), { data: item });\n });\n }, [items, prefillDetailCache, detailKeyBuilder, queryClient]);\n\n return {\n items,\n pagination,\n isLoading: query.isLoading,\n isFetching: query.isFetching,\n isError: query.isError,\n isSuccess: query.isSuccess,\n isStale: query.isStale,\n error: query.error,\n refetch: query.refetch,\n data: query.data,\n };\n}\n\n// ============================================================================\n// Detail Query Hook\n// ============================================================================\n\nexport interface CreateDetailQueryConfig {\n queryKey: QueryKey;\n queryFn: (context: { signal?: AbortSignal }) => Promise<unknown>;\n enabled?: boolean;\n options?: Record<string, unknown>;\n}\n\nexport function createDetailQuery<T>({\n queryKey,\n queryFn,\n enabled = true,\n options = {},\n}: CreateDetailQueryConfig): DetailQueryResult<T> {\n const query = useQuery({\n queryKey,\n queryFn: () => queryFn({}),\n enabled,\n ...DEFAULT_QUERY_CONFIG,\n ...options,\n });\n\n const item = extractItem<T>(query.data);\n\n return {\n item,\n isLoading: query.isLoading,\n isFetching: query.isFetching,\n isError: query.isError,\n isSuccess: query.isSuccess,\n isStale: query.isStale,\n error: query.error,\n refetch: query.refetch,\n data: query.data,\n };\n}\n\n// ============================================================================\n// Infinite List Query Hook\n// ============================================================================\n\nexport interface InfiniteListQueryOptions {\n public?: boolean;\n enabled?: boolean;\n staleTime?: number;\n gcTime?: number;\n refetchOnWindowFocus?: boolean;\n structuralSharing?: boolean;\n _scope?: string;\n request?: RequestPassthrough;\n}\n\nexport interface InfiniteListQueryResult<T> {\n items: T[];\n hasNextPage: boolean;\n hasPreviousPage: boolean;\n isFetchingNextPage: boolean;\n isFetchingPreviousPage: boolean;\n fetchNextPage: () => void;\n fetchPreviousPage: () => void;\n isLoading: boolean;\n isFetching: boolean;\n isError: boolean;\n isSuccess: boolean;\n error: Error | null;\n refetch: () => Promise<unknown>;\n data: InfiniteData<unknown> | undefined;\n}\n\nexport interface CreateInfiniteListQueryConfig {\n queryKey: QueryKey;\n queryFn: (context: { signal?: AbortSignal; pageParam: unknown }) => Promise<unknown>;\n enabled?: boolean;\n options?: Record<string, unknown>;\n initialPageParam?: unknown;\n getNextPageParam: (lastPage: unknown) => unknown;\n getPreviousPageParam?: (firstPage: unknown) => unknown;\n}\n\nexport function createInfiniteListQuery<T>({\n queryKey,\n queryFn,\n enabled = true,\n options = {},\n initialPageParam = 1,\n getNextPageParam,\n getPreviousPageParam,\n}: CreateInfiniteListQueryConfig): InfiniteListQueryResult<T> {\n const query = useInfiniteQuery({\n queryKey,\n queryFn: ({ pageParam }) => queryFn({ pageParam }),\n enabled,\n initialPageParam,\n getNextPageParam,\n getPreviousPageParam,\n ...DEFAULT_QUERY_CONFIG,\n ...options,\n });\n\n const items: T[] = query.data?.pages.flatMap((page) => extractItems<T>(page)) ?? [];\n\n return {\n items,\n hasNextPage: query.hasNextPage,\n hasPreviousPage: query.hasPreviousPage,\n isFetchingNextPage: query.isFetchingNextPage,\n isFetchingPreviousPage: query.isFetchingPreviousPage,\n fetchNextPage: query.fetchNextPage,\n fetchPreviousPage: query.fetchPreviousPage,\n isLoading: query.isLoading,\n isFetching: query.isFetching,\n isError: query.isError,\n isSuccess: query.isSuccess,\n error: query.error,\n refetch: query.refetch,\n data: query.data,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"query.js","names":[],"sources":["../src/query.ts"],"sourcesContent":["\"use client\";\n\nimport { useQuery, useInfiniteQuery, useQueryClient, keepPreviousData, type QueryKey, type InfiniteData } from \"@tanstack/react-query\";\nimport { useEffect } from \"react\";\nimport type { QueryClient } from \"@tanstack/react-query\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface PaginationData {\n total: number;\n pages: number;\n page: number;\n limit: number;\n hasNext: boolean;\n hasPrev: boolean;\n}\n\n/** Request-level options passed through to the fetch call */\nexport interface RequestPassthrough {\n cache?: RequestCache;\n revalidate?: number;\n tags?: string[];\n headerOptions?: Record<string, string>;\n signal?: AbortSignal;\n}\n\nexport interface ListQueryOptions {\n public?: boolean;\n enabled?: boolean;\n staleTime?: number;\n gcTime?: number;\n refetchOnWindowFocus?: boolean;\n structuralSharing?: boolean;\n prefillDetailCache?: boolean;\n refetchInterval?: number | false;\n refetchIntervalInBackground?: boolean;\n _scope?: string;\n /** Pass-through options for the underlying fetch request (cache, revalidate, tags, headers) */\n request?: RequestPassthrough;\n}\n\nexport interface DetailQueryOptions {\n public?: boolean;\n organizationId?: string | null;\n enabled?: boolean;\n staleTime?: number;\n gcTime?: number;\n structuralSharing?: boolean;\n refetchInterval?: number | false;\n refetchIntervalInBackground?: boolean;\n /** Query params passed to getById (e.g. select, populate) */\n params?: { select?: string; populate?: string | string[] };\n /** Pass-through options for the underlying fetch request (cache, revalidate, tags, headers) */\n request?: RequestPassthrough;\n}\n\nexport interface ListQueryResult<T> {\n items: T[];\n pagination: PaginationData | null;\n isLoading: boolean;\n isFetching: boolean;\n isError: boolean;\n isSuccess: boolean;\n isStale: boolean;\n error: Error | null;\n refetch: () => Promise<unknown>;\n data: unknown;\n}\n\nexport interface DetailQueryResult<T> {\n item: T | null;\n isLoading: boolean;\n isFetching: boolean;\n isError: boolean;\n isSuccess: boolean;\n isStale: boolean;\n error: Error | null;\n refetch: () => Promise<unknown>;\n data: unknown;\n}\n\nexport interface QueryKeys {\n all: string[];\n lists: () => QueryKey;\n list: (params?: unknown) => QueryKey;\n details: () => QueryKey;\n detail: (id: string) => QueryKey;\n custom: (key: string, ...args: unknown[]) => QueryKey;\n scopedList: (scope: string, params?: unknown) => QueryKey;\n}\n\nexport interface CacheUtils<T> {\n invalidateAll: (client: QueryClient) => Promise<void>;\n invalidateLists: (client: QueryClient) => Promise<void>;\n invalidateDetail: (client: QueryClient, id: string) => Promise<void>;\n setDetail: (client: QueryClient, id: string, data: T) => void;\n getDetail: (client: QueryClient, id: string) => T | undefined;\n removeDetail: (client: QueryClient, id: string) => void;\n}\n\n// ============================================================================\n// Default Config\n// ============================================================================\n\nexport const DEFAULT_QUERY_CONFIG = {\n staleTime: 5 * 60 * 1000,\n gcTime: 30 * 60 * 1000,\n refetchOnWindowFocus: false,\n retry: 0,\n} as const;\n\n// ============================================================================\n// Utilities\n// ============================================================================\n\nexport function getItemId(item: unknown): string | null {\n if (!item || typeof item !== \"object\") return null;\n const obj = item as Record<string, unknown>;\n const id = obj._id ?? obj.id;\n return typeof id === \"string\" ? id : id ? String(id) : null;\n}\n\nfunction normalizePagination(data: unknown): PaginationData | null {\n if (!data || typeof data !== \"object\") return null;\n const d = data as Record<string, unknown>;\n\n const hasTotal = d.total != null || d.totalDocs != null;\n const hasPages = d.pages != null || d.totalPages != null;\n if (!hasTotal && !hasPages) return null;\n\n return {\n total: Number(d.total ?? d.totalDocs ?? 0),\n pages: Number(d.pages ?? d.totalPages ?? 1),\n page: Number(d.page ?? d.currentPage ?? 1),\n limit: Number(d.limit ?? 10),\n hasNext: Boolean(d.hasNext ?? d.hasNextPage ?? d.hasMore ?? false),\n hasPrev: Boolean(d.hasPrev ?? d.hasPrevPage ?? false),\n };\n}\n\nfunction extractItems<T>(data: unknown): T[] {\n if (!data) return [];\n if (Array.isArray(data)) return data as T[];\n if (typeof data !== \"object\") return [];\n\n const d = data as Record<string, unknown>;\n const items = d.docs ?? d.data ?? d.items ?? d.results;\n return Array.isArray(items) ? (items as T[]) : [];\n}\n\nfunction extractItem<T>(data: unknown): T | null {\n if (!data) return null;\n if (typeof data !== \"object\") return null;\n\n const d = data as Record<string, unknown>;\n return (d.data ?? d) as T | null;\n}\n\nexport function updateListCache<T>(listData: unknown, updater: (items: T[]) => T[]): unknown {\n if (!listData) return listData;\n\n if (Array.isArray(listData)) {\n return updater(listData as T[]);\n }\n\n if (typeof listData !== \"object\") return listData;\n const d = listData as Record<string, unknown>;\n\n // Detect the items array field: docs > data > items > results (same order as extractItems)\n const arrayField = \"docs\" in d ? \"docs\"\n : (Array.isArray(d.data) ? \"data\"\n : (\"items\" in d && Array.isArray(d.items) ? \"items\"\n : (\"results\" in d && Array.isArray(d.results) ? \"results\" : null)));\n\n if (!arrayField) return listData;\n\n const updated = updater(d[arrayField] as T[]);\n const original = d[arrayField] as T[];\n const delta = updated.length - original.length;\n\n // Update total/totalDocs when item count changes (optimistic add/delete)\n const result: Record<string, unknown> = { ...d, [arrayField]: updated };\n if (delta !== 0) {\n if (d.total != null) result.total = Math.max(0, Number(d.total) + delta);\n if (d.totalDocs != null) result.totalDocs = Math.max(0, Number(d.totalDocs) + delta);\n }\n\n return result;\n}\n\n// ============================================================================\n// Query Keys Factory\n// ============================================================================\n\nexport function createQueryKeys(entityKey: string): QueryKeys {\n return {\n all: [entityKey],\n lists: () => [entityKey, \"list\"],\n list: (params) => [entityKey, \"list\", params],\n details: () => [entityKey, \"detail\"],\n detail: (id) => [entityKey, \"detail\", id],\n custom: (key, ...args) => [entityKey, key, ...args],\n scopedList: (scope, params) => [entityKey, \"list\", { _scope: scope, ...(params as object) }],\n };\n}\n\n// ============================================================================\n// Cache Utilities Factory\n// ============================================================================\n\nexport function createCacheUtils<T>(KEYS: QueryKeys): CacheUtils<T> {\n return {\n invalidateAll: (client) => client.invalidateQueries({ queryKey: KEYS.all }),\n invalidateLists: (client) => client.invalidateQueries({ queryKey: KEYS.lists() }),\n invalidateDetail: (client, id) => client.invalidateQueries({ queryKey: KEYS.detail(id) }),\n setDetail: (client, id, data) => client.setQueryData(KEYS.detail(id), { data }),\n getDetail: (client, id) => {\n const cached = client.getQueryData(KEYS.detail(id)) as { data?: T } | undefined;\n return cached?.data;\n },\n removeDetail: (client, id) => client.removeQueries({ queryKey: KEYS.detail(id) }),\n };\n}\n\n// ============================================================================\n// List Query Hook\n// ============================================================================\n\nexport interface CreateListQueryConfig {\n queryKey: QueryKey;\n queryFn: (context: { signal?: AbortSignal }) => Promise<unknown>;\n enabled?: boolean;\n options?: Record<string, unknown>;\n prefillDetailCache?: boolean;\n detailKeyBuilder?: (id: string) => QueryKey;\n}\n\nexport function createListQuery<T>({\n queryKey,\n queryFn,\n enabled = true,\n options = {},\n prefillDetailCache = true,\n detailKeyBuilder,\n}: CreateListQueryConfig): ListQueryResult<T> {\n const queryClient = useQueryClient();\n\n const query = useQuery({\n queryKey,\n queryFn: () => queryFn({}),\n enabled,\n ...DEFAULT_QUERY_CONFIG,\n ...options,\n placeholderData: keepPreviousData,\n });\n\n const items = extractItems<T>(query.data);\n const pagination = normalizePagination(query.data);\n\n useEffect(() => {\n if (!prefillDetailCache || !detailKeyBuilder || items.length === 0) return;\n\n items.forEach((item) => {\n const id = getItemId(item);\n if (id) queryClient.setQueryData(detailKeyBuilder(id), { data: item });\n });\n }, [items, prefillDetailCache, detailKeyBuilder, queryClient]);\n\n return {\n items,\n pagination,\n isLoading: query.isLoading,\n isFetching: query.isFetching,\n isError: query.isError,\n isSuccess: query.isSuccess,\n isStale: query.isStale,\n error: query.error,\n refetch: query.refetch,\n data: query.data,\n };\n}\n\n// ============================================================================\n// Detail Query Hook\n// ============================================================================\n\nexport interface CreateDetailQueryConfig {\n queryKey: QueryKey;\n queryFn: (context: { signal?: AbortSignal }) => Promise<unknown>;\n enabled?: boolean;\n options?: Record<string, unknown>;\n}\n\nexport function createDetailQuery<T>({\n queryKey,\n queryFn,\n enabled = true,\n options = {},\n}: CreateDetailQueryConfig): DetailQueryResult<T> {\n const query = useQuery({\n queryKey,\n queryFn: () => queryFn({}),\n enabled,\n ...DEFAULT_QUERY_CONFIG,\n ...options,\n });\n\n const item = extractItem<T>(query.data);\n\n return {\n item,\n isLoading: query.isLoading,\n isFetching: query.isFetching,\n isError: query.isError,\n isSuccess: query.isSuccess,\n isStale: query.isStale,\n error: query.error,\n refetch: query.refetch,\n data: query.data,\n };\n}\n\n// ============================================================================\n// Infinite List Query Hook\n// ============================================================================\n\nexport interface InfiniteListQueryOptions {\n public?: boolean;\n enabled?: boolean;\n staleTime?: number;\n gcTime?: number;\n refetchOnWindowFocus?: boolean;\n structuralSharing?: boolean;\n _scope?: string;\n request?: RequestPassthrough;\n}\n\nexport interface InfiniteListQueryResult<T> {\n items: T[];\n hasNextPage: boolean;\n hasPreviousPage: boolean;\n isFetchingNextPage: boolean;\n isFetchingPreviousPage: boolean;\n fetchNextPage: () => void;\n fetchPreviousPage: () => void;\n isLoading: boolean;\n isFetching: boolean;\n isError: boolean;\n isSuccess: boolean;\n error: Error | null;\n refetch: () => Promise<unknown>;\n data: InfiniteData<unknown> | undefined;\n}\n\nexport interface CreateInfiniteListQueryConfig {\n queryKey: QueryKey;\n queryFn: (context: { signal?: AbortSignal; pageParam: unknown }) => Promise<unknown>;\n enabled?: boolean;\n options?: Record<string, unknown>;\n initialPageParam?: unknown;\n getNextPageParam: (lastPage: unknown) => unknown;\n getPreviousPageParam?: (firstPage: unknown) => unknown;\n}\n\nexport function createInfiniteListQuery<T>({\n queryKey,\n queryFn,\n enabled = true,\n options = {},\n initialPageParam = 1,\n getNextPageParam,\n getPreviousPageParam,\n}: CreateInfiniteListQueryConfig): InfiniteListQueryResult<T> {\n const query = useInfiniteQuery({\n queryKey,\n queryFn: ({ pageParam }) => queryFn({ pageParam }),\n enabled,\n initialPageParam,\n getNextPageParam,\n getPreviousPageParam,\n ...DEFAULT_QUERY_CONFIG,\n ...options,\n });\n\n const items: T[] = query.data?.pages.flatMap((page) => extractItems<T>(page)) ?? [];\n\n return {\n items,\n hasNextPage: query.hasNextPage,\n hasPreviousPage: query.hasPreviousPage,\n isFetchingNextPage: query.isFetchingNextPage,\n isFetchingPreviousPage: query.isFetchingPreviousPage,\n fetchNextPage: query.fetchNextPage,\n fetchPreviousPage: query.fetchPreviousPage,\n isLoading: query.isLoading,\n isFetching: query.isFetching,\n isError: query.isError,\n isSuccess: query.isSuccess,\n error: query.error,\n refetch: query.refetch,\n data: query.data,\n };\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@classytic/arc-next",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "React + TanStack Query SDK for Arc resources",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -88,4 +88,4 @@
|
|
|
88
88
|
"typescript": "^5.7.2",
|
|
89
89
|
"vitest": "^4.0.18"
|
|
90
90
|
}
|
|
91
|
-
}
|
|
91
|
+
}
|