@classytic/arc-next 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +93 -27
- package/dist/api.d.ts +120 -33
- package/dist/api.js +116 -13
- package/dist/client.d.ts +38 -12
- package/dist/client.js +24 -7
- package/dist/hooks.d.ts +66 -70
- package/dist/hooks.js +410 -110
- package/dist/mutation.d.ts +10 -21
- package/dist/mutation.js +20 -15
- package/dist/prefetch.d.ts +40 -3
- package/dist/prefetch.js +57 -22
- package/dist/query-client.d.ts +1 -2
- package/dist/query-client.js +2 -2
- package/dist/query.d.ts +47 -6
- package/dist/query.js +67 -20
- package/dist/sse.d.ts +62 -0
- package/dist/sse.js +144 -0
- package/package.json +28 -13
- package/dist/api.d.ts.map +0 -1
- package/dist/api.js.map +0 -1
- package/dist/client.d.ts.map +0 -1
- package/dist/client.js.map +0 -1
- package/dist/hooks.d.ts.map +0 -1
- package/dist/hooks.js.map +0 -1
- package/dist/mutation.d.ts.map +0 -1
- package/dist/mutation.js.map +0 -1
- package/dist/prefetch.d.ts.map +0 -1
- package/dist/prefetch.js.map +0 -1
- package/dist/query-client.d.ts.map +0 -1
- package/dist/query-client.js.map +0 -1
- package/dist/query.d.ts.map +0 -1
- package/dist/query.js.map +0 -1
package/README.md
CHANGED
|
@@ -30,13 +30,14 @@ import { useRouter } from "next/navigation";
|
|
|
30
30
|
// Required — sets the API base URL and auth mode
|
|
31
31
|
configureClient({
|
|
32
32
|
baseUrl: process.env.NEXT_PUBLIC_API_URL!,
|
|
33
|
-
authMode: "cookie",
|
|
34
|
-
|
|
33
|
+
authMode: "cookie", // 'cookie' | 'bearer' (default) | 'header'
|
|
34
|
+
// apiVersion: '2', // sends Accept-Version header
|
|
35
|
+
// autoIdempotency: true, // auto Idempotency-Key on mutations (retry-safe)
|
|
35
36
|
});
|
|
36
37
|
|
|
37
|
-
// Optional — auto-inject
|
|
38
|
+
// Optional — auto-inject tenant context into queries/mutations
|
|
38
39
|
configureAuth({
|
|
39
|
-
getOrgId: () =>
|
|
40
|
+
getOrgId: () => activeTenantId, // return current tenant/org/workspace ID
|
|
40
41
|
getToken: () => null, // null for cookie auth (token only for bearer)
|
|
41
42
|
});
|
|
42
43
|
|
|
@@ -51,13 +52,15 @@ configureNavigation(useRouter);
|
|
|
51
52
|
|
|
52
53
|
| Import | Purpose | `"use client"` |
|
|
53
54
|
| ----------------------------------- | ---------------------------------------------------------------------------- | :-------------: |
|
|
55
|
+
| `@classytic/arc-next` | Root — same as `/hooks` (`createCrudHooks`, `configureNavigation`) | Yes |
|
|
54
56
|
| `@classytic/arc-next/client` | `configureClient`, `configureAuth`, `createClient`, `handleApiRequest`, `createQueryString`, `ArcApiError`, `isArcApiError`, `getAuthMode`, `getAuthContext` | No |
|
|
55
57
|
| `@classytic/arc-next/api` | `BaseApi`, `createCrudApi`, response types, type guards | No |
|
|
56
|
-
| `@classytic/arc-next/query` | `createQueryKeys`, `createCacheUtils`, `
|
|
57
|
-
| `@classytic/arc-next/mutation` | `configureToast`, `useMutationWithTransition`, `
|
|
58
|
+
| `@classytic/arc-next/query` | `createQueryKeys`, `createCacheUtils`, `useListQuery`, `useDetailQuery` | Yes |
|
|
59
|
+
| `@classytic/arc-next/mutation` | `configureToast`, `useMutationWithTransition`, `useOptimisticMutation` | Yes |
|
|
58
60
|
| `@classytic/arc-next/hooks` | `createCrudHooks`, `configureNavigation` | Yes |
|
|
59
61
|
| `@classytic/arc-next/query-client` | `getQueryClient` (SSR-safe singleton) | No |
|
|
60
62
|
| `@classytic/arc-next/prefetch` | `createCrudPrefetcher`, `dehydrate` (SSR prefetch) | No |
|
|
63
|
+
| `@classytic/arc-next/sse` | `useEventStream` (Server-Sent Events for real-time cache invalidation) | Yes |
|
|
61
64
|
|
|
62
65
|
No barrel index — every file is its own entry point. Tree-shakeable (`sideEffects: false`).
|
|
63
66
|
|
|
@@ -147,27 +150,32 @@ export function ProductsPage() {
|
|
|
147
150
|
```ts
|
|
148
151
|
configureClient({
|
|
149
152
|
baseUrl: string; // Required — API base URL
|
|
150
|
-
authMode?: 'cookie' | '
|
|
151
|
-
|
|
152
|
-
|
|
153
|
+
authMode?: 'bearer' | 'cookie' | 'header'; // Default: 'bearer'
|
|
154
|
+
credentials?: RequestCredentials; // Default: derived from authMode
|
|
155
|
+
internalApiKey?: string; // Sent as x-internal-api-key header
|
|
156
|
+
defaultHeaders?: Record<string, string>; // Merged into every request
|
|
157
|
+
apiVersion?: string; // Sent as Accept-Version header
|
|
158
|
+
autoIdempotency?: boolean; // Auto Idempotency-Key on mutations (retry-safe)
|
|
153
159
|
});
|
|
154
160
|
```
|
|
155
161
|
|
|
156
|
-
- `authMode: 'bearer'` (default) — requires
|
|
157
|
-
- `authMode: 'cookie'` —
|
|
162
|
+
- `authMode: 'bearer'` (default) — requires token; queries disabled until provided
|
|
163
|
+
- `authMode: 'cookie'` — HTTP-only cookies (e.g. Better Auth); queries always enabled
|
|
164
|
+
- `authMode: 'header'` — custom header auth (e.g. `x-api-key`); uses `headerName` from `configureAuth`
|
|
158
165
|
|
|
159
|
-
Must be called before any API requests.
|
|
166
|
+
Must be called before any API requests. Warns if called on the server (SSR safety).
|
|
160
167
|
|
|
161
168
|
### `configureAuth(config)`
|
|
162
169
|
|
|
163
170
|
```ts
|
|
164
171
|
configureAuth({
|
|
165
|
-
getToken?: () => string | null; // For bearer auth — return access token
|
|
172
|
+
getToken?: () => string | null; // For bearer/header auth — return access token or API key
|
|
166
173
|
getOrgId?: () => string | null; // Return active organization ID
|
|
174
|
+
headerName?: string; // Custom header name for authMode: 'header' (default: 'x-api-key')
|
|
167
175
|
});
|
|
168
176
|
```
|
|
169
177
|
|
|
170
|
-
Auto-injects `token` and `
|
|
178
|
+
Auto-injects `token` and tenant ID (sent as `x-organization-id` header) into queries/mutations. The header name is a convention — your backend controls how it's read and which field it maps to (`organizationId`, `workspaceId`, `teamId`, etc.). Hooks use the new signature (no explicit token param) — legacy signature still works.
|
|
171
179
|
|
|
172
180
|
### `handleApiRequest<T>(method, endpoint, options?)`
|
|
173
181
|
|
|
@@ -226,18 +234,21 @@ const api = createCrudApi<Product, CreateProduct>("products", {
|
|
|
226
234
|
|
|
227
235
|
### `createCrudHooks<T, TCreate, TUpdate>(config)`
|
|
228
236
|
|
|
229
|
-
Factory that returns everything you need:
|
|
237
|
+
Factory that returns everything you need. The `api` parameter accepts any `createCrudApi()` result directly — no casts needed. Types are derived from `BaseApi` via `Pick`, so generics thread through automatically:
|
|
230
238
|
|
|
231
239
|
```ts
|
|
232
240
|
const {
|
|
233
241
|
KEYS, cache,
|
|
234
242
|
useList, useDetail, useInfiniteList,
|
|
235
|
-
useActions,
|
|
243
|
+
useActions, useBulkActions,
|
|
244
|
+
useDeleted, useDetailBySlug, useTree, useChildren, useFindBy,
|
|
245
|
+
useUpload, useSearch, useCustomMutation,
|
|
236
246
|
useNavigation,
|
|
237
247
|
} = createCrudHooks<Product, CreateProduct>({
|
|
238
|
-
api: productsApi, // from createCrudApi()
|
|
248
|
+
api: productsApi, // from createCrudApi() — types inferred, no cast
|
|
239
249
|
entityKey: "products", // TanStack Query key prefix
|
|
240
250
|
singular: "Product", // for toast messages
|
|
251
|
+
idField: "sku", // optional — custom ID field for cache keys (default: _id → id)
|
|
241
252
|
defaults: { // optional
|
|
242
253
|
staleTime: 60_000,
|
|
243
254
|
messages: { createSuccess: "Product added!" },
|
|
@@ -263,7 +274,7 @@ const { items, pagination, isLoading, isFetching, refetch } = useList(
|
|
|
263
274
|
);
|
|
264
275
|
```
|
|
265
276
|
|
|
266
|
-
- Auto-scopes query keys by
|
|
277
|
+
- Auto-scopes list query keys by tenant context (when present → `tenant` scope, otherwise → `super-admin`)
|
|
267
278
|
- Normalizes pagination from `docs`/`data`/`items`/`results` formats
|
|
268
279
|
- Prefills detail cache from list results (skips re-fetch on navigate)
|
|
269
280
|
- `options.public: true` — enables query without token
|
|
@@ -390,6 +401,52 @@ const { mutateAsync: publish, isPending } = useCustomMutation({
|
|
|
390
401
|
});
|
|
391
402
|
```
|
|
392
403
|
|
|
404
|
+
#### `useDeleted(params?, options?)`
|
|
405
|
+
|
|
406
|
+
List soft-deleted items. Requires `softDelete` preset on the Arc resource.
|
|
407
|
+
|
|
408
|
+
#### `useDetailBySlug(slug, options?)`
|
|
409
|
+
|
|
410
|
+
Fetch a single item by slug (`GET /slug/:slug`). Requires `slugLookup` preset.
|
|
411
|
+
|
|
412
|
+
#### `useTree(params?, options?)`
|
|
413
|
+
|
|
414
|
+
Fetch hierarchical tree data (`GET /tree`). Requires `tree` preset.
|
|
415
|
+
|
|
416
|
+
#### `useChildren(parentId, params?, options?)`
|
|
417
|
+
|
|
418
|
+
Fetch children of a parent node (`GET /:parentId/children`). Requires `tree` preset.
|
|
419
|
+
|
|
420
|
+
#### `useFindBy(field, value, options?)`
|
|
421
|
+
|
|
422
|
+
Query by a single field with optional operator:
|
|
423
|
+
|
|
424
|
+
```ts
|
|
425
|
+
const { items } = useFindBy("status", "active");
|
|
426
|
+
const { items } = useFindBy("price", 50, { operator: "gte" });
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
#### `useBulkActions()`
|
|
430
|
+
|
|
431
|
+
```ts
|
|
432
|
+
const { bulkCreate, bulkUpdate, bulkRemove } = useBulkActions();
|
|
433
|
+
await bulkCreate({ data: [{ name: "A" }, { name: "B" }] });
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
#### `useEventStream(options)` (from `./sse`)
|
|
437
|
+
|
|
438
|
+
Subscribe to Arc SSE events with auto-reconnect and query invalidation:
|
|
439
|
+
|
|
440
|
+
```ts
|
|
441
|
+
import { useEventStream } from "@classytic/arc-next/sse";
|
|
442
|
+
|
|
443
|
+
const { isConnected, lastEvent } = useEventStream({
|
|
444
|
+
resource: "agents",
|
|
445
|
+
patterns: ["agents.created", "agents.updated"],
|
|
446
|
+
invalidateQueries: [agentKeys.lists()],
|
|
447
|
+
});
|
|
448
|
+
```
|
|
449
|
+
|
|
393
450
|
### Query Keys (`KEYS`)
|
|
394
451
|
|
|
395
452
|
```ts
|
|
@@ -534,7 +591,7 @@ useProducts(token, {}, { ...QUERY_CONFIGS.realtime });
|
|
|
534
591
|
|
|
535
592
|
### `updateListCache(listData, updater)`
|
|
536
593
|
|
|
537
|
-
Transforms list cache regardless of format (`docs[]`, `data[]`, `items[]`, `results[]`, or raw
|
|
594
|
+
Transforms list cache regardless of format — well-known keys (`docs[]`, `data[]`, `items[]`, `results[]`), custom keys (`products[]`, `users[]`, etc.), or raw arrays.
|
|
538
595
|
Automatically adjusts `total`/`totalDocs` counts when items are added or removed (optimistic add/delete).
|
|
539
596
|
|
|
540
597
|
```ts
|
|
@@ -551,11 +608,11 @@ Extracts `_id` or `id` from any item. Returns `string | null`.
|
|
|
551
608
|
|
|
552
609
|
### `normalizePagination(data)`
|
|
553
610
|
|
|
554
|
-
Converts any pagination response format to a normalized `PaginationData` object.
|
|
611
|
+
Converts any pagination response format to a normalized `PaginationData` object. Detects pagination method (`offset`, `keyset`, `aggregate`) and normalizes all fields: `total`/`totalDocs`, `pages`/`totalPages`, `page`/`currentPage`, `hasNext`/`hasNextPage`/`hasMore`, `hasPrev`/`hasPrevPage`, `next` (keyset cursor).
|
|
555
612
|
|
|
556
613
|
### `extractItems<T>(data)`
|
|
557
614
|
|
|
558
|
-
Extracts the items array from any response format
|
|
615
|
+
Extracts the items array from any response format. Checks well-known keys first (`docs`, `data`, `items`, `results`), then falls back to finding the first top-level array — so `{ products: [...] }` or `{ users: [...] }` works without configuration.
|
|
559
616
|
|
|
560
617
|
## Multi-Client (Multiple APIs)
|
|
561
618
|
|
|
@@ -654,8 +711,10 @@ try {
|
|
|
654
711
|
### Multi-tenant data fetching
|
|
655
712
|
|
|
656
713
|
```ts
|
|
657
|
-
//
|
|
658
|
-
|
|
714
|
+
// Tenant ID in params → scoped query key → isolated cache per tenant
|
|
715
|
+
// The param name is up to you — arc-next sends it as x-organization-id header,
|
|
716
|
+
// your backend maps it to whatever tenant field your schema uses.
|
|
717
|
+
const { items } = useProducts(token, { organizationId: currentTenantId });
|
|
659
718
|
```
|
|
660
719
|
|
|
661
720
|
### Public endpoints (no auth)
|
|
@@ -704,12 +763,19 @@ const adminApi = createCrudApi("users", {
|
|
|
704
763
|
|
|
705
764
|
- **CRUD Factory** — `createCrudApi` + `createCrudHooks` generates typed API clients and React Query hooks
|
|
706
765
|
- **Optimistic Updates** — Create, update, delete with instant UI feedback and automatic rollback
|
|
707
|
-
- **Multi-Tenant Scoping** — `
|
|
708
|
-
- **Pagination Normalization** — Handles `docs`/`data`/`items`/`results`
|
|
766
|
+
- **Multi-Tenant Scoping** — Tenant ID sent via `x-organization-id` header + scoped list query keys. Backend controls the tenant field name and access enforcement.
|
|
767
|
+
- **Pagination Normalization** — Handles `docs`/`data`/`items`/`results` + any custom key, offset/keyset/aggregate pagination
|
|
709
768
|
- **Detail Cache Prefilling** — List results auto-populate detail query cache
|
|
710
769
|
- **React 19 Transitions** — `useMutationWithTransition` wraps invalidation in `startTransition`
|
|
711
|
-
- **Cookie &
|
|
712
|
-
- **
|
|
770
|
+
- **Cookie, Bearer & Header Auth** — `authMode: 'cookie'` / `'bearer'` / `'header'` (custom header like `x-api-key`)
|
|
771
|
+
- **Custom ID Fields** — `idField` on `createCrudHooks` for resources keyed by `sku`, `slug`, `code`, etc.
|
|
772
|
+
- **Preset Hooks** — `useDeleted`, `useBulkActions`, `useDetailBySlug`, `useTree`, `useChildren`, `useFindBy`
|
|
773
|
+
- **SSE Real-Time** — `useEventStream` with auto-reconnect, pattern filtering, query invalidation
|
|
774
|
+
- **Infinite Scroll** — `maxPages` for memory management with automatic scroll-back support
|
|
775
|
+
- **Idempotency** — `autoIdempotency` generates retry-safe keys at mutation level
|
|
776
|
+
- **API Versioning** — `apiVersion` sends `Accept-Version` header
|
|
777
|
+
- **SSR Prefetch** — `createCrudPrefetcher` + `prefetchBySlug` / `prefetchDeleted` / `prefetchTree`
|
|
778
|
+
- **SSR Safety** — warns when `configureClient`/`configureAuth` called on the server
|
|
713
779
|
- **Multi-Client** — `createClient()` for multiple API backends side by side
|
|
714
780
|
- **Pluggable Toast** — `configureToast()` — use sonner, react-hot-toast, or anything
|
|
715
781
|
- **Pluggable Navigation** — `configureNavigation()` — use Next.js, React Router, or any router
|
package/dist/api.d.ts
CHANGED
|
@@ -52,9 +52,22 @@ interface DeleteResponse {
|
|
|
52
52
|
soft?: boolean;
|
|
53
53
|
};
|
|
54
54
|
}
|
|
55
|
+
interface BulkCreateResponse<T = unknown> {
|
|
56
|
+
success: boolean;
|
|
57
|
+
data?: T[];
|
|
58
|
+
count?: number;
|
|
59
|
+
}
|
|
60
|
+
interface BulkUpdateResponse {
|
|
61
|
+
success: boolean;
|
|
62
|
+
modifiedCount?: number;
|
|
63
|
+
}
|
|
64
|
+
interface BulkDeleteResponse {
|
|
65
|
+
success: boolean;
|
|
66
|
+
deletedCount?: number;
|
|
67
|
+
}
|
|
55
68
|
type SortDirection = 1 | -1 | 'asc' | 'desc';
|
|
56
69
|
type SortSpec = Record<string, SortDirection> | string;
|
|
57
|
-
type FilterOperator = 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'nin' | 'contains' | 'startsWith' | 'endsWith' | 'regex';
|
|
70
|
+
type FilterOperator = 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'nin' | 'contains' | 'startsWith' | 'endsWith' | 'regex' | 'like' | 'exists' | 'size' | 'type';
|
|
58
71
|
interface QueryParams {
|
|
59
72
|
page?: number;
|
|
60
73
|
limit?: number;
|
|
@@ -65,6 +78,13 @@ interface QueryParams {
|
|
|
65
78
|
populate?: string | string[];
|
|
66
79
|
populateOptions?: PopulateOption[];
|
|
67
80
|
lean?: boolean | 'true' | 'false';
|
|
81
|
+
/** Database-agnostic joins. Maps alias → collection or full lookup config. */
|
|
82
|
+
lookup?: Record<string, string | {
|
|
83
|
+
from: string;
|
|
84
|
+
localField: string;
|
|
85
|
+
foreignField: string;
|
|
86
|
+
select?: string;
|
|
87
|
+
}>;
|
|
68
88
|
[key: string]: unknown;
|
|
69
89
|
}
|
|
70
90
|
interface RequestOptions {
|
|
@@ -77,16 +97,6 @@ interface RequestOptions {
|
|
|
77
97
|
responseType?: 'json' | 'blob' | 'text';
|
|
78
98
|
signal?: AbortSignal;
|
|
79
99
|
}
|
|
80
|
-
/**
|
|
81
|
-
* Arc scope for multi-tenant APIs.
|
|
82
|
-
*
|
|
83
|
-
* - `'tenant'` (default) — Org-scoped. Hooks auto-inject `organizationId` from auth context.
|
|
84
|
-
* - `'platform'` — Platform admin scope. Skips org injection, sets `x-arc-scope: platform`
|
|
85
|
-
* header so Arc's elevation plugin grants cross-org access for superadmins.
|
|
86
|
-
*/
|
|
87
|
-
type ArcScope = 'tenant' | 'platform';
|
|
88
|
-
/** Header name used by Arc's elevation plugin */
|
|
89
|
-
declare const ARC_SCOPE_HEADER = "x-arc-scope";
|
|
90
100
|
interface BaseApiConfig {
|
|
91
101
|
basePath?: string;
|
|
92
102
|
defaultParams?: {
|
|
@@ -96,32 +106,13 @@ interface BaseApiConfig {
|
|
|
96
106
|
};
|
|
97
107
|
cache?: RequestCache;
|
|
98
108
|
headers?: Record<string, string>;
|
|
99
|
-
/**
|
|
100
|
-
* API scope — controls org context injection and Arc scope headers.
|
|
101
|
-
*
|
|
102
|
-
* @default 'tenant'
|
|
103
|
-
*
|
|
104
|
-
* @example
|
|
105
|
-
* ```ts
|
|
106
|
-
* // Org-scoped API (default) — auto-injects organizationId
|
|
107
|
-
* const postsApi = createCrudApi('posts', { basePath: '/api' });
|
|
108
|
-
*
|
|
109
|
-
* // Platform admin API — skips org injection, adds x-arc-scope header
|
|
110
|
-
* const adminApi = createCrudApi('subscriptions', {
|
|
111
|
-
* basePath: '/api',
|
|
112
|
-
* scope: 'platform',
|
|
113
|
-
* });
|
|
114
|
-
* ```
|
|
115
|
-
*/
|
|
116
|
-
scope?: ArcScope;
|
|
117
109
|
client?: ArcClient;
|
|
118
110
|
}
|
|
119
111
|
declare class BaseApi<TDoc = Record<string, unknown>, TCreate = Partial<TDoc>, TUpdate = Partial<TDoc>> {
|
|
120
112
|
readonly entity: string;
|
|
121
|
-
readonly config: Required<Omit<BaseApiConfig, 'client'
|
|
113
|
+
readonly config: Required<Omit<BaseApiConfig, 'client'>>;
|
|
122
114
|
readonly baseUrl: string;
|
|
123
115
|
private readonly requestFn;
|
|
124
|
-
readonly scope: ArcScope;
|
|
125
116
|
constructor(entity: string, config?: BaseApiConfig);
|
|
126
117
|
/** Merge per-instance headers into request options */
|
|
127
118
|
private withHeaders;
|
|
@@ -247,6 +238,103 @@ declare class BaseApi<TDoc = Record<string, unknown>, TCreate = Partial<TDoc>, T
|
|
|
247
238
|
params?: QueryParams;
|
|
248
239
|
options?: Omit<RequestOptions, 'token' | 'organizationId'>;
|
|
249
240
|
}): Promise<TResponse>;
|
|
241
|
+
getDeleted({
|
|
242
|
+
token,
|
|
243
|
+
organizationId,
|
|
244
|
+
params,
|
|
245
|
+
options
|
|
246
|
+
}?: {
|
|
247
|
+
token?: string | null;
|
|
248
|
+
organizationId?: string | null;
|
|
249
|
+
params?: QueryParams;
|
|
250
|
+
options?: Omit<RequestOptions, 'token' | 'organizationId'>;
|
|
251
|
+
}): Promise<PaginatedResponse<TDoc>>;
|
|
252
|
+
restore({
|
|
253
|
+
token,
|
|
254
|
+
organizationId,
|
|
255
|
+
id,
|
|
256
|
+
options
|
|
257
|
+
}: {
|
|
258
|
+
token?: string | null;
|
|
259
|
+
organizationId?: string | null;
|
|
260
|
+
id: string;
|
|
261
|
+
options?: Omit<RequestOptions, 'token' | 'organizationId'>;
|
|
262
|
+
}): Promise<ApiResponse<TDoc>>;
|
|
263
|
+
bulkCreate({
|
|
264
|
+
token,
|
|
265
|
+
organizationId,
|
|
266
|
+
data,
|
|
267
|
+
options
|
|
268
|
+
}: {
|
|
269
|
+
token?: string | null;
|
|
270
|
+
organizationId?: string | null;
|
|
271
|
+
data: TCreate[];
|
|
272
|
+
options?: Omit<RequestOptions, 'token' | 'organizationId'>;
|
|
273
|
+
}): Promise<BulkCreateResponse<TDoc>>;
|
|
274
|
+
bulkUpdate({
|
|
275
|
+
token,
|
|
276
|
+
organizationId,
|
|
277
|
+
filter,
|
|
278
|
+
data,
|
|
279
|
+
options
|
|
280
|
+
}: {
|
|
281
|
+
token?: string | null;
|
|
282
|
+
organizationId?: string | null;
|
|
283
|
+
filter: Record<string, unknown>;
|
|
284
|
+
data: TUpdate;
|
|
285
|
+
options?: Omit<RequestOptions, 'token' | 'organizationId'>;
|
|
286
|
+
}): Promise<BulkUpdateResponse>;
|
|
287
|
+
bulkDelete({
|
|
288
|
+
token,
|
|
289
|
+
organizationId,
|
|
290
|
+
filter,
|
|
291
|
+
options
|
|
292
|
+
}: {
|
|
293
|
+
token?: string | null;
|
|
294
|
+
organizationId?: string | null;
|
|
295
|
+
filter: Record<string, unknown>;
|
|
296
|
+
options?: Omit<RequestOptions, 'token' | 'organizationId'>;
|
|
297
|
+
}): Promise<BulkDeleteResponse>;
|
|
298
|
+
getBySlug({
|
|
299
|
+
token,
|
|
300
|
+
organizationId,
|
|
301
|
+
slug,
|
|
302
|
+
params,
|
|
303
|
+
options
|
|
304
|
+
}: {
|
|
305
|
+
token?: string | null;
|
|
306
|
+
organizationId?: string | null;
|
|
307
|
+
slug: string;
|
|
308
|
+
params?: {
|
|
309
|
+
select?: string;
|
|
310
|
+
populate?: string | string[];
|
|
311
|
+
};
|
|
312
|
+
options?: Omit<RequestOptions, 'token' | 'organizationId'>;
|
|
313
|
+
}): Promise<ApiResponse<TDoc>>;
|
|
314
|
+
getTree({
|
|
315
|
+
token,
|
|
316
|
+
organizationId,
|
|
317
|
+
params,
|
|
318
|
+
options
|
|
319
|
+
}?: {
|
|
320
|
+
token?: string | null;
|
|
321
|
+
organizationId?: string | null;
|
|
322
|
+
params?: QueryParams;
|
|
323
|
+
options?: Omit<RequestOptions, 'token' | 'organizationId'>;
|
|
324
|
+
}): Promise<ApiResponse<TDoc[]>>;
|
|
325
|
+
getChildren({
|
|
326
|
+
token,
|
|
327
|
+
organizationId,
|
|
328
|
+
parentId,
|
|
329
|
+
params,
|
|
330
|
+
options
|
|
331
|
+
}: {
|
|
332
|
+
token?: string | null;
|
|
333
|
+
organizationId?: string | null;
|
|
334
|
+
parentId: string;
|
|
335
|
+
params?: QueryParams;
|
|
336
|
+
options?: Omit<RequestOptions, 'token' | 'organizationId'>;
|
|
337
|
+
}): Promise<PaginatedResponse<TDoc>>;
|
|
250
338
|
}
|
|
251
339
|
declare function createCrudApi<TDoc = Record<string, unknown>, TCreate = Partial<TDoc>, TUpdate = Partial<TDoc>>(entity: string, config?: BaseApiConfig): BaseApi<TDoc, TCreate, TUpdate>;
|
|
252
340
|
type ExtractDoc<T> = T extends PaginatedResponse<infer D> ? D : never;
|
|
@@ -254,5 +342,4 @@ declare function isOffsetPagination<T>(response: PaginatedResponse<T>): response
|
|
|
254
342
|
declare function isKeysetPagination<T>(response: PaginatedResponse<T>): response is KeysetPaginationResponse<T>;
|
|
255
343
|
declare function isAggregatePagination<T>(response: PaginatedResponse<T>): response is AggregatePaginationResponse<T>;
|
|
256
344
|
//#endregion
|
|
257
|
-
export {
|
|
258
|
-
//# sourceMappingURL=api.d.ts.map
|
|
345
|
+
export { AggregatePaginationResponse, ApiResponse, BaseApi, BaseApiConfig, BulkCreateResponse, BulkDeleteResponse, BulkUpdateResponse, DeleteResponse, ExtractDoc, FilterOperator, KeysetPaginationResponse, OffsetPaginationResponse, PaginatedResponse, PopulateOption, QueryParams, RequestOptions, SortDirection, SortSpec, createCrudApi, isAggregatePagination, isKeysetPagination, isOffsetPagination };
|
package/dist/api.js
CHANGED
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
import { createQueryString, handleApiRequest } from "./client.js";
|
|
2
2
|
|
|
3
3
|
//#region src/api.ts
|
|
4
|
-
/** Header name used by Arc's elevation plugin */
|
|
5
|
-
const ARC_SCOPE_HEADER = "x-arc-scope";
|
|
6
4
|
var BaseApi = class {
|
|
7
5
|
entity;
|
|
8
6
|
config;
|
|
9
7
|
baseUrl;
|
|
10
8
|
requestFn;
|
|
11
|
-
scope;
|
|
12
9
|
constructor(entity, config = {}) {
|
|
13
10
|
this.entity = entity;
|
|
14
|
-
this.scope = config.scope ?? "tenant";
|
|
15
11
|
this.requestFn = config.client?.request ?? handleApiRequest;
|
|
16
|
-
const scopeHeaders = this.scope === "platform" ? { [ARC_SCOPE_HEADER]: "platform" } : {};
|
|
17
12
|
this.config = {
|
|
18
13
|
basePath: config.basePath ?? "/api/v1",
|
|
19
14
|
defaultParams: {
|
|
@@ -22,10 +17,7 @@ var BaseApi = class {
|
|
|
22
17
|
...config.defaultParams || {}
|
|
23
18
|
},
|
|
24
19
|
cache: config.cache ?? "no-store",
|
|
25
|
-
headers: {
|
|
26
|
-
...scopeHeaders,
|
|
27
|
-
...config.headers || {}
|
|
28
|
-
}
|
|
20
|
+
headers: { ...config.headers || {} }
|
|
29
21
|
};
|
|
30
22
|
this.baseUrl = `${this.config.basePath}/${this.entity}`;
|
|
31
23
|
}
|
|
@@ -56,6 +48,19 @@ var BaseApi = class {
|
|
|
56
48
|
if (Array.isArray(value) && value.length > 0) result[key] = value;
|
|
57
49
|
return;
|
|
58
50
|
}
|
|
51
|
+
if (key === "lookup") {
|
|
52
|
+
if (typeof value === "object" && value !== null) Object.entries(value).forEach(([alias, lv]) => {
|
|
53
|
+
if (typeof lv === "string") result[`lookup[${alias}]`] = lv;
|
|
54
|
+
else if (typeof lv === "object" && lv !== null) {
|
|
55
|
+
const cfg = lv;
|
|
56
|
+
result[`lookup[${alias}][from]`] = cfg.from;
|
|
57
|
+
result[`lookup[${alias}][localField]`] = cfg.localField;
|
|
58
|
+
result[`lookup[${alias}][foreignField]`] = cfg.foreignField;
|
|
59
|
+
if (cfg.select) result[`lookup[${alias}][select]`] = cfg.select;
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
59
64
|
if (value !== void 0 && value !== "") if (["page", "limit"].includes(key)) result[key] = parseInt(String(value)) || (key === "page" ? 1 : 10);
|
|
60
65
|
else if (Array.isArray(value)) {
|
|
61
66
|
if (value.length > 1) result[`${key}[in]`] = value.join(",");
|
|
@@ -65,7 +70,11 @@ var BaseApi = class {
|
|
|
65
70
|
return result;
|
|
66
71
|
}
|
|
67
72
|
async getAll({ token = null, organizationId = null, params = {}, options = {} } = {}) {
|
|
68
|
-
const
|
|
73
|
+
const mergedParams = {
|
|
74
|
+
...this.config.defaultParams,
|
|
75
|
+
...params
|
|
76
|
+
};
|
|
77
|
+
const processedParams = this.prepareParams(mergedParams);
|
|
69
78
|
const queryString = this.createQueryString(processedParams);
|
|
70
79
|
const requestOptions = {
|
|
71
80
|
cache: this.config.cache,
|
|
@@ -126,6 +135,7 @@ var BaseApi = class {
|
|
|
126
135
|
}
|
|
127
136
|
async search({ token = null, organizationId = null, searchParams = {}, params = {}, options = {} } = {}) {
|
|
128
137
|
const queryParams = {
|
|
138
|
+
...this.config.defaultParams,
|
|
129
139
|
...params,
|
|
130
140
|
...searchParams
|
|
131
141
|
};
|
|
@@ -141,7 +151,10 @@ var BaseApi = class {
|
|
|
141
151
|
}
|
|
142
152
|
async findBy({ token = null, organizationId = null, field, value, operator, params = {}, options = {} }) {
|
|
143
153
|
if (!field || value === void 0) throw new Error("Field and value are required");
|
|
144
|
-
const queryParams = {
|
|
154
|
+
const queryParams = {
|
|
155
|
+
...this.config.defaultParams,
|
|
156
|
+
...params
|
|
157
|
+
};
|
|
145
158
|
if (operator) queryParams[`${field}[${operator}]`] = Array.isArray(value) ? value.join(",") : value;
|
|
146
159
|
else queryParams[field] = value;
|
|
147
160
|
const processedParams = this.prepareParams(queryParams);
|
|
@@ -169,6 +182,97 @@ var BaseApi = class {
|
|
|
169
182
|
if (organizationId) requestOptions.organizationId = organizationId;
|
|
170
183
|
return this.requestFn(method, url, this.withHeaders(requestOptions));
|
|
171
184
|
}
|
|
185
|
+
async getDeleted({ token = null, organizationId = null, params = {}, options = {} } = {}) {
|
|
186
|
+
const mergedParams = {
|
|
187
|
+
...this.config.defaultParams,
|
|
188
|
+
...params
|
|
189
|
+
};
|
|
190
|
+
const processedParams = this.prepareParams(mergedParams);
|
|
191
|
+
const queryString = this.createQueryString(processedParams);
|
|
192
|
+
const requestOptions = {
|
|
193
|
+
cache: this.config.cache,
|
|
194
|
+
...options
|
|
195
|
+
};
|
|
196
|
+
if (token) requestOptions.token = token;
|
|
197
|
+
if (organizationId) requestOptions.organizationId = organizationId;
|
|
198
|
+
return this.requestFn("GET", `${this.baseUrl}/deleted?${queryString}`, this.withHeaders(requestOptions));
|
|
199
|
+
}
|
|
200
|
+
async restore({ token, organizationId = null, id, options = {} }) {
|
|
201
|
+
if (!id) throw new Error("ID is required");
|
|
202
|
+
const requestOptions = { ...options };
|
|
203
|
+
if (token) requestOptions.token = token;
|
|
204
|
+
if (organizationId) requestOptions.organizationId = organizationId;
|
|
205
|
+
return this.requestFn("POST", `${this.baseUrl}/${id}/restore`, this.withHeaders(requestOptions));
|
|
206
|
+
}
|
|
207
|
+
async bulkCreate({ token, organizationId = null, data, options = {} }) {
|
|
208
|
+
const requestOptions = {
|
|
209
|
+
body: data,
|
|
210
|
+
...options
|
|
211
|
+
};
|
|
212
|
+
if (token) requestOptions.token = token;
|
|
213
|
+
if (organizationId) requestOptions.organizationId = organizationId;
|
|
214
|
+
return this.requestFn("POST", `${this.baseUrl}/bulk`, this.withHeaders(requestOptions));
|
|
215
|
+
}
|
|
216
|
+
async bulkUpdate({ token, organizationId = null, filter, data, options = {} }) {
|
|
217
|
+
const requestOptions = {
|
|
218
|
+
body: {
|
|
219
|
+
filter,
|
|
220
|
+
data
|
|
221
|
+
},
|
|
222
|
+
...options
|
|
223
|
+
};
|
|
224
|
+
if (token) requestOptions.token = token;
|
|
225
|
+
if (organizationId) requestOptions.organizationId = organizationId;
|
|
226
|
+
return this.requestFn("PATCH", `${this.baseUrl}/bulk`, this.withHeaders(requestOptions));
|
|
227
|
+
}
|
|
228
|
+
async bulkDelete({ token, organizationId = null, filter, options = {} }) {
|
|
229
|
+
const requestOptions = {
|
|
230
|
+
body: { filter },
|
|
231
|
+
...options
|
|
232
|
+
};
|
|
233
|
+
if (token) requestOptions.token = token;
|
|
234
|
+
if (organizationId) requestOptions.organizationId = organizationId;
|
|
235
|
+
return this.requestFn("DELETE", `${this.baseUrl}/bulk`, this.withHeaders(requestOptions));
|
|
236
|
+
}
|
|
237
|
+
async getBySlug({ token = null, organizationId = null, slug, params = {}, options = {} }) {
|
|
238
|
+
if (!slug) throw new Error("Slug is required");
|
|
239
|
+
const queryString = this.createQueryString(params);
|
|
240
|
+
const url = queryString ? `${this.baseUrl}/slug/${slug}?${queryString}` : `${this.baseUrl}/slug/${slug}`;
|
|
241
|
+
const requestOptions = {
|
|
242
|
+
cache: this.config.cache,
|
|
243
|
+
...options
|
|
244
|
+
};
|
|
245
|
+
if (token) requestOptions.token = token;
|
|
246
|
+
if (organizationId) requestOptions.organizationId = organizationId;
|
|
247
|
+
return this.requestFn("GET", url, this.withHeaders(requestOptions));
|
|
248
|
+
}
|
|
249
|
+
async getTree({ token = null, organizationId = null, params = {}, options = {} } = {}) {
|
|
250
|
+
const processedParams = this.prepareParams(params);
|
|
251
|
+
const queryString = this.createQueryString(processedParams);
|
|
252
|
+
const requestOptions = {
|
|
253
|
+
cache: this.config.cache,
|
|
254
|
+
...options
|
|
255
|
+
};
|
|
256
|
+
if (token) requestOptions.token = token;
|
|
257
|
+
if (organizationId) requestOptions.organizationId = organizationId;
|
|
258
|
+
return this.requestFn("GET", `${this.baseUrl}/tree?${queryString}`, this.withHeaders(requestOptions));
|
|
259
|
+
}
|
|
260
|
+
async getChildren({ token = null, organizationId = null, parentId, params = {}, options = {} }) {
|
|
261
|
+
if (!parentId) throw new Error("Parent ID is required");
|
|
262
|
+
const mergedParams = {
|
|
263
|
+
...this.config.defaultParams,
|
|
264
|
+
...params
|
|
265
|
+
};
|
|
266
|
+
const processedParams = this.prepareParams(mergedParams);
|
|
267
|
+
const queryString = this.createQueryString(processedParams);
|
|
268
|
+
const requestOptions = {
|
|
269
|
+
cache: this.config.cache,
|
|
270
|
+
...options
|
|
271
|
+
};
|
|
272
|
+
if (token) requestOptions.token = token;
|
|
273
|
+
if (organizationId) requestOptions.organizationId = organizationId;
|
|
274
|
+
return this.requestFn("GET", `${this.baseUrl}/${parentId}/children?${queryString}`, this.withHeaders(requestOptions));
|
|
275
|
+
}
|
|
172
276
|
};
|
|
173
277
|
function createCrudApi(entity, config = {}) {
|
|
174
278
|
return new BaseApi(entity, config);
|
|
@@ -184,5 +288,4 @@ function isAggregatePagination(response) {
|
|
|
184
288
|
}
|
|
185
289
|
|
|
186
290
|
//#endregion
|
|
187
|
-
export {
|
|
188
|
-
//# sourceMappingURL=api.js.map
|
|
291
|
+
export { BaseApi, createCrudApi, isAggregatePagination, isKeysetPagination, isOffsetPagination };
|