@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 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", // 'cookie' for Better Auth, 'bearer' for token auth (default)
34
- internalApiKey: process.env.NEXT_PUBLIC_INTERNAL_API_KEY, // optional
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 org context into queries/mutations
38
+ // Optional — auto-inject tenant context into queries/mutations
38
39
  configureAuth({
39
- getOrgId: () => activeOrgId, // return current org ID
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`, `createListQuery`, `createDetailQuery`| Yes |
57
- | `@classytic/arc-next/mutation` | `configureToast`, `useMutationWithTransition`, `createOptimisticMutation` | Yes |
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' | '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
+ 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 a token for authenticated requests; queries are disabled until a token is provided
157
- - `authMode: 'cookie'` — auth via HTTP-only cookies (e.g. Better Auth); queries are always enabled, no token needed
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. Throws if not configured.
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 `organizationId` into queries/mutations. Hooks use the new signature (no explicit token param) — legacy signature still works.
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, useUpload, useSearch, useCustomMutation,
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 `organizationId` (tenant vs super-admin)
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 array).
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. Handles `total`/`totalDocs`, `pages`/`totalPages`, `page`/`currentPage`, `hasNext`/`hasNextPage`/`hasMore`, `hasPrev`/`hasPrevPage`.
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 looks for `docs`, `data`, `items`, `results` fields, or returns the data directly if it's already an array.
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
- // organizationId in params → scoped query key → isolated cache per tenant
658
- const { items } = useProducts(token, { organizationId: currentOrg });
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** — `organizationId` in headers + scoped query keys
708
- - **Pagination Normalization** — Handles `docs`/`data`/`items`/`results` response formats, offset/keyset/aggregate pagination
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 & Bearer Auth** — `authMode: 'cookie'` for Better Auth, `'bearer'` for token auth
712
- - **SSR Prefetch** — `createCrudPrefetcher` + `dehydrate` for server component data loading
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' | 'scope'>>;
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 { ARC_SCOPE_HEADER, AggregatePaginationResponse, ApiResponse, ArcScope, BaseApi, BaseApiConfig, DeleteResponse, ExtractDoc, FilterOperator, KeysetPaginationResponse, OffsetPaginationResponse, PaginatedResponse, PopulateOption, QueryParams, RequestOptions, SortDirection, SortSpec, createCrudApi, isAggregatePagination, isKeysetPagination, isOffsetPagination };
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 processedParams = this.prepareParams(params);
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 = { ...params };
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 { ARC_SCOPE_HEADER, BaseApi, createCrudApi, isAggregatePagination, isKeysetPagination, isOffsetPagination };
188
- //# sourceMappingURL=api.js.map
291
+ export { BaseApi, createCrudApi, isAggregatePagination, isKeysetPagination, isOffsetPagination };