@classytic/arc-next 0.3.1 → 0.4.1

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 CHANGED
@@ -30,8 +30,9 @@ 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", // 'cookie' for Better Auth, 'bearer' for token auth (default)
34
- // credentials: 'omit', // override if you don't want cookies sent cross-origin
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
38
  // Optional — auto-inject tenant context into queries/mutations
@@ -59,6 +60,7 @@ configureNavigation(useRouter);
59
60
  | `@classytic/arc-next/hooks` | `createCrudHooks`, `configureNavigation` | Yes |
60
61
  | `@classytic/arc-next/query-client` | `getQueryClient` (SSR-safe singleton) | No |
61
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 |
62
64
 
63
65
  No barrel index — every file is its own entry point. Tree-shakeable (`sideEffects: false`).
64
66
 
@@ -148,25 +150,28 @@ export function ProductsPage() {
148
150
  ```ts
149
151
  configureClient({
150
152
  baseUrl: string; // Required — API base URL
151
- authMode?: 'cookie' | 'bearer'; // Default: 'bearer'
153
+ authMode?: 'bearer' | 'cookie' | 'header'; // Default: 'bearer'
152
154
  credentials?: RequestCredentials; // Default: derived from authMode
153
- internalApiKey?: string; // Optional — sent as x-internal-api-key header
154
- defaultHeaders?: Record<string, string>; // Optional — merged into every request
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)
155
159
  });
156
160
  ```
157
161
 
158
- - `authMode: 'bearer'` (default) — requires a token; queries disabled until token provided; `credentials: 'same-origin'`
159
- - `authMode: 'cookie'` — HTTP-only cookies (e.g. Better Auth); queries always enabled; `credentials: 'include'`
160
- - `credentials` — explicit override: `'include'` (send cookies cross-origin), `'same-origin'` (same-origin only), `'omit'` (never send cookies)
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`
161
165
 
162
- Must be called before any API requests. Throws if not configured.
166
+ Must be called before any API requests. Warns if called on the server (SSR safety).
163
167
 
164
168
  ### `configureAuth(config)`
165
169
 
166
170
  ```ts
167
171
  configureAuth({
168
- getToken?: () => string | null; // For bearer auth — return access token
172
+ getToken?: () => string | null; // For bearer/header auth — return access token or API key
169
173
  getOrgId?: () => string | null; // Return active organization ID
174
+ headerName?: string; // Custom header name for authMode: 'header' (default: 'x-api-key')
170
175
  });
171
176
  ```
172
177
 
@@ -235,12 +240,15 @@ Factory that returns everything you need. The `api` parameter accepts any `creat
235
240
  const {
236
241
  KEYS, cache,
237
242
  useList, useDetail, useInfiniteList,
238
- useActions, useUpload, useSearch, useCustomMutation,
243
+ useActions, useBulkActions,
244
+ useDeleted, useDetailBySlug, useTree, useChildren, useFindBy,
245
+ useUpload, useSearch, useCustomMutation,
239
246
  useNavigation,
240
247
  } = createCrudHooks<Product, CreateProduct>({
241
248
  api: productsApi, // from createCrudApi() — types inferred, no cast
242
249
  entityKey: "products", // TanStack Query key prefix
243
250
  singular: "Product", // for toast messages
251
+ idField: "sku", // optional — custom ID field for cache keys (default: _id → id)
244
252
  defaults: { // optional
245
253
  staleTime: 60_000,
246
254
  messages: { createSuccess: "Product added!" },
@@ -393,27 +401,94 @@ const { mutateAsync: publish, isPending } = useCustomMutation({
393
401
  });
394
402
  ```
395
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
+ // Global stream — all events (matches Arc's /events/stream)
444
+ const { isConnected } = useEventStream({
445
+ invalidateQueries: [agentKeys.lists()],
446
+ });
447
+
448
+ // Filtered by resource — auto-generates patterns: ['agents.*']
449
+ const { lastEvent } = useEventStream({
450
+ resource: "agents",
451
+ invalidateQueries: [agentKeys.lists()],
452
+ });
453
+
454
+ // Custom SSE path or explicit patterns
455
+ const { isConnected } = useEventStream({
456
+ path: "/api/v2/events",
457
+ patterns: ["orders.created", "orders.updated"],
458
+ });
459
+ ```
460
+
396
461
  ### Query Keys (`KEYS`)
397
462
 
398
463
  ```ts
399
- KEYS.all // ["products"]
400
- KEYS.lists() // ["products", "list"]
401
- KEYS.list(params) // ["products", "list", params]
402
- KEYS.details() // ["products", "detail"]
403
- KEYS.detail(id) // ["products", "detail", id]
404
- KEYS.custom("stats", orgId) // ["products", "stats", orgId]
405
- KEYS.scopedList("tenant", params) // ["products", "list", { _scope: "tenant", ...params }]
464
+ KEYS.all // ["products"]
465
+ KEYS.lists() // ["products", "list"]
466
+ KEYS.list(params) // ["products", "list", params]
467
+ KEYS.details() // ["products", "detail"]
468
+ KEYS.detail(id) // ["products", "detail", id]
469
+ KEYS.scopedDetail(id, orgId) // ["products", "detail", id, { _org: orgId }] (or bare when null)
470
+ KEYS.custom("stats", orgId) // ["products", "stats", orgId]
471
+ KEYS.scopedList("tenant", params) // ["products", "list", { _scope: "tenant", ...params }]
406
472
  ```
407
473
 
408
474
  ### Cache Utilities (`cache`)
409
475
 
410
476
  ```ts
411
- await cache.invalidateAll(queryClient);
412
- await cache.invalidateLists(queryClient);
413
- await cache.invalidateDetail(queryClient, id);
477
+ // Bare (single-tenant or public)
414
478
  cache.setDetail(queryClient, id, data);
415
- cache.getDetail(queryClient, id); // T | undefined
479
+ cache.getDetail(queryClient, id);
416
480
  cache.removeDetail(queryClient, id);
481
+ await cache.invalidateDetail(queryClient, id); // prefix-matches ALL scoped variants
482
+
483
+ // Tenant-scoped (multi-tenant — isolated per org)
484
+ cache.setScopedDetail(queryClient, id, orgId, data);
485
+ cache.getScopedDetail(queryClient, id, orgId);
486
+ cache.removeScopedDetail(queryClient, id, orgId);
487
+ await cache.invalidateScopedDetail(queryClient, id, orgId);
488
+
489
+ // Global
490
+ await cache.invalidateAll(queryClient);
491
+ await cache.invalidateLists(queryClient);
417
492
  ```
418
493
 
419
494
  ### `getQueryClient(overrides?)`
@@ -467,7 +542,17 @@ export default async function ProductsPage() {
467
542
  }
468
543
  ```
469
544
 
470
- **Methods:** `prefetchList(queryClient, params?, options?)`, `prefetchDetail(queryClient, id, options?)`
545
+ **Methods:** `prefetchList`, `prefetchDetail`, `prefetchBySlug`, `prefetchDeleted`, `prefetchTree`
546
+
547
+ All accept auth options for protected routes:
548
+
549
+ ```ts
550
+ await prefetcher.prefetchList(queryClient, { limit: 20 }, {
551
+ token: serverToken, // for bearer/header auth
552
+ organizationId: "org-1", // for multi-tenant
553
+ headers: { "x-api-key": apiKey }, // for custom header auth
554
+ });
555
+ ```
471
556
 
472
557
  ## Custom Mutations
473
558
 
@@ -566,16 +651,30 @@ By default, `configureClient()` sets a single global `baseUrl`. Use `createClien
566
651
 
567
652
  ### Create isolated clients
568
653
 
654
+ Each client gets its own `baseUrl`, auth, and headers — fully independent from the global config:
655
+
569
656
  ```ts
570
657
  import { createClient } from "@classytic/arc-next/client";
571
- import { toast } from "sonner";
572
- import { useRouter } from "next/navigation";
573
658
 
659
+ // Bearer auth for main API
660
+ const mainClient = createClient({
661
+ baseUrl: "https://api.example.com",
662
+ getToken: () => session.jwt,
663
+ getOrgId: () => currentOrg.id,
664
+ });
665
+
666
+ // API key auth for analytics (no token needed — hooks auto-enable)
574
667
  const analyticsClient = createClient({
575
- baseUrl: "https://analytics.example.com",
576
- internalApiKey: "analytics-key",
577
- toast: { success: toast.success, error: toast.error },
578
- navigation: useRouter,
668
+ baseUrl: "https://analytics.internal",
669
+ authMode: "header",
670
+ getToken: () => env.ANALYTICS_KEY,
671
+ headerName: "x-analytics-key",
672
+ });
673
+
674
+ // Cookie auth for auth service
675
+ const authClient = createClient({
676
+ baseUrl: "https://auth.example.com",
677
+ authMode: "cookie",
579
678
  });
580
679
  ```
581
680
 
@@ -709,12 +808,20 @@ const adminApi = createCrudApi("users", {
709
808
 
710
809
  - **CRUD Factory** — `createCrudApi` + `createCrudHooks` generates typed API clients and React Query hooks
711
810
  - **Optimistic Updates** — Create, update, delete with instant UI feedback and automatic rollback
712
- - **Multi-Tenant Scoping** — Tenant ID sent via `x-organization-id` header + scoped list query keys. Backend controls the tenant field name and access enforcement.
811
+ - **Multi-Tenant Scoping** — `scopedDetail(id, orgId)` + `scopedList` isolate cache per tenant. Scoped cache utils for reads/writes. Navigation prefill is tenant-aware.
713
812
  - **Pagination Normalization** — Handles `docs`/`data`/`items`/`results` + any custom key, offset/keyset/aggregate pagination
714
813
  - **Detail Cache Prefilling** — List results auto-populate detail query cache
715
814
  - **React 19 Transitions** — `useMutationWithTransition` wraps invalidation in `startTransition`
716
- - **Cookie & Bearer Auth** — `authMode: 'cookie'` for Better Auth, `'bearer'` for token auth, configurable `credentials` policy
717
- - **SSR Prefetch** — `createCrudPrefetcher` + `dehydrate` for server component data loading
815
+ - **Cookie, Bearer & Header Auth** — `authMode: 'cookie'` / `'bearer'` / `'header'` (custom header like `x-api-key`)
816
+ - **Custom ID Fields** — `idField` on `createCrudHooks` for resources keyed by `sku`, `slug`, `code`, etc.
817
+ - **Preset Hooks** — `useDeleted`, `useBulkActions`, `useDetailBySlug`, `useTree`, `useChildren`, `useFindBy`
818
+ - **SSE Real-Time** — `useEventStream` with auto-reconnect, auto-pattern derivation from `resource`, query invalidation. Works with zero config or custom `path`.
819
+ - **Infinite Scroll** — `maxPages` for memory management with automatic scroll-back support
820
+ - **Idempotency** — `autoIdempotency` generates retry-safe keys at mutation level
821
+ - **API Versioning** — `apiVersion` sends `Accept-Version` header
822
+ - **SSR Prefetch** — `createCrudPrefetcher` + `prefetchBySlug` / `prefetchDeleted` / `prefetchTree`
823
+ - **SSR Safety** — warns when `configureClient`/`configureAuth` called on the server
824
+ - **Per-Client Auth** — `createClient({ getToken, getOrgId, headerName })` — each backend gets its own auth, queries auto-enable
718
825
  - **Multi-Client** — `createClient()` for multiple API backends side by side
719
826
  - **Pluggable Toast** — `configureToast()` — use sonner, react-hot-toast, or anything
720
827
  - **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 {
@@ -212,12 +232,109 @@ declare class BaseApi<TDoc = Record<string, unknown>, TCreate = Partial<TDoc>, T
212
232
  params,
213
233
  options
214
234
  }?: {
215
- token?: string;
235
+ token?: string | null;
216
236
  organizationId?: string | null;
217
237
  data?: unknown;
218
238
  params?: QueryParams;
219
239
  options?: Omit<RequestOptions, 'token' | 'organizationId'>;
220
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>>;
221
338
  }
222
339
  declare function createCrudApi<TDoc = Record<string, unknown>, TCreate = Partial<TDoc>, TUpdate = Partial<TDoc>>(entity: string, config?: BaseApiConfig): BaseApi<TDoc, TCreate, TUpdate>;
223
340
  type ExtractDoc<T> = T extends PaginatedResponse<infer D> ? D : never;
@@ -225,4 +342,4 @@ declare function isOffsetPagination<T>(response: PaginatedResponse<T>): response
225
342
  declare function isKeysetPagination<T>(response: PaginatedResponse<T>): response is KeysetPaginationResponse<T>;
226
343
  declare function isAggregatePagination<T>(response: PaginatedResponse<T>): response is AggregatePaginationResponse<T>;
227
344
  //#endregion
228
- export { AggregatePaginationResponse, ApiResponse, BaseApi, BaseApiConfig, DeleteResponse, ExtractDoc, FilterOperator, KeysetPaginationResponse, OffsetPaginationResponse, PaginatedResponse, PopulateOption, QueryParams, RequestOptions, SortDirection, SortSpec, createCrudApi, isAggregatePagination, isKeysetPagination, isOffsetPagination };
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
@@ -48,6 +48,19 @@ var BaseApi = class {
48
48
  if (Array.isArray(value) && value.length > 0) result[key] = value;
49
49
  return;
50
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
+ }
51
64
  if (value !== void 0 && value !== "") if (["page", "limit"].includes(key)) result[key] = parseInt(String(value)) || (key === "page" ? 1 : 10);
52
65
  else if (Array.isArray(value)) {
53
66
  if (value.length > 1) result[`${key}[in]`] = value.join(",");
@@ -83,7 +96,7 @@ var BaseApi = class {
83
96
  if (organizationId) requestOptions.organizationId = organizationId;
84
97
  return this.requestFn("GET", url, this.withHeaders(requestOptions));
85
98
  }
86
- async create({ token, organizationId = null, data, options = {} }) {
99
+ async create({ token = null, organizationId = null, data, options = {} }) {
87
100
  const requestOptions = {
88
101
  body: data,
89
102
  ...options
@@ -92,7 +105,7 @@ var BaseApi = class {
92
105
  if (organizationId) requestOptions.organizationId = organizationId;
93
106
  return this.requestFn("POST", this.baseUrl, this.withHeaders(requestOptions));
94
107
  }
95
- async update({ token, organizationId = null, id, data, options = {} }) {
108
+ async update({ token = null, organizationId = null, id, data, options = {} }) {
96
109
  if (!id) throw new Error("ID is required");
97
110
  const requestOptions = {
98
111
  body: data,
@@ -102,14 +115,14 @@ var BaseApi = class {
102
115
  if (organizationId) requestOptions.organizationId = organizationId;
103
116
  return this.requestFn("PATCH", `${this.baseUrl}/${id}`, this.withHeaders(requestOptions));
104
117
  }
105
- async delete({ token, organizationId = null, id, options = {} }) {
118
+ async delete({ token = null, organizationId = null, id, options = {} }) {
106
119
  if (!id) throw new Error("ID is required");
107
120
  const requestOptions = { ...options };
108
121
  if (token) requestOptions.token = token;
109
122
  if (organizationId) requestOptions.organizationId = organizationId;
110
123
  return this.requestFn("DELETE", `${this.baseUrl}/${id}`, this.withHeaders(requestOptions));
111
124
  }
112
- async upload({ token, organizationId = null, data, id, path, options = {} }) {
125
+ async upload({ token = null, organizationId = null, data, id, path, options = {} }) {
113
126
  const suffix = path ?? (id ? `${id}/upload` : void 0);
114
127
  const url = suffix ? `${this.baseUrl}/${suffix}` : this.baseUrl;
115
128
  const requestOptions = {
@@ -154,7 +167,7 @@ var BaseApi = class {
154
167
  if (organizationId) requestOptions.organizationId = organizationId;
155
168
  return this.requestFn("GET", `${this.baseUrl}?${queryString}`, this.withHeaders(requestOptions));
156
169
  }
157
- async request(method, endpoint, { token, organizationId = null, data, params, options = {} } = {}) {
170
+ async request(method, endpoint, { token = null, organizationId = null, data, params, options = {} } = {}) {
158
171
  let url = endpoint;
159
172
  if (params) {
160
173
  const processedParams = this.prepareParams(params);
@@ -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 = null, 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 = null, organizationId = null, data, options = {} }) {
208
+ const requestOptions = {
209
+ body: data,
210
+ ...options
211
+ };
212
+ if (token) requestOptions.token = token;
213
+ if (organizationId) requestOptions.organizationId = organizationId;
214
+ return this.requestFn("POST", `${this.baseUrl}/bulk`, this.withHeaders(requestOptions));
215
+ }
216
+ async bulkUpdate({ token = null, organizationId = null, filter, data, options = {} }) {
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 = null, organizationId = null, filter, options = {} }) {
229
+ const requestOptions = {
230
+ body: { filter },
231
+ ...options
232
+ };
233
+ if (token) requestOptions.token = token;
234
+ if (organizationId) requestOptions.organizationId = organizationId;
235
+ return this.requestFn("DELETE", `${this.baseUrl}/bulk`, this.withHeaders(requestOptions));
236
+ }
237
+ async getBySlug({ token = null, organizationId = null, slug, params = {}, options = {} }) {
238
+ if (!slug) throw new Error("Slug is required");
239
+ const queryString = this.createQueryString(params);
240
+ const url = queryString ? `${this.baseUrl}/slug/${slug}?${queryString}` : `${this.baseUrl}/slug/${slug}`;
241
+ const requestOptions = {
242
+ cache: this.config.cache,
243
+ ...options
244
+ };
245
+ if (token) requestOptions.token = token;
246
+ if (organizationId) requestOptions.organizationId = organizationId;
247
+ return this.requestFn("GET", url, this.withHeaders(requestOptions));
248
+ }
249
+ async getTree({ token = null, organizationId = null, params = {}, options = {} } = {}) {
250
+ const processedParams = this.prepareParams(params);
251
+ const queryString = this.createQueryString(processedParams);
252
+ const requestOptions = {
253
+ cache: this.config.cache,
254
+ ...options
255
+ };
256
+ if (token) requestOptions.token = token;
257
+ if (organizationId) requestOptions.organizationId = organizationId;
258
+ return this.requestFn("GET", `${this.baseUrl}/tree?${queryString}`, this.withHeaders(requestOptions));
259
+ }
260
+ async getChildren({ token = null, organizationId = null, parentId, params = {}, options = {} }) {
261
+ if (!parentId) throw new Error("Parent ID is required");
262
+ const mergedParams = {
263
+ ...this.config.defaultParams,
264
+ ...params
265
+ };
266
+ const processedParams = this.prepareParams(mergedParams);
267
+ const queryString = this.createQueryString(processedParams);
268
+ const requestOptions = {
269
+ cache: this.config.cache,
270
+ ...options
271
+ };
272
+ if (token) requestOptions.token = token;
273
+ if (organizationId) requestOptions.organizationId = organizationId;
274
+ return this.requestFn("GET", `${this.baseUrl}/${parentId}/children?${queryString}`, this.withHeaders(requestOptions));
275
+ }
172
276
  };
173
277
  function createCrudApi(entity, config = {}) {
174
278
  return new BaseApi(entity, config);