@classytic/arc-next 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/api.d.ts CHANGED
@@ -1,4 +1,7 @@
1
1
  import { ArcClient } from "./client.js";
2
+ import { AggregatePaginationResult, KeysetPaginationResult, OffsetPaginationResult, PaginatedResult } from "@classytic/repo-core/pagination";
3
+ import { AggResult, AggRow, BulkCreateResult, DeleteManyResult, DeleteResult, UpdateManyResult } from "@classytic/repo-core/repository";
4
+ import { BracketOperator, BracketOperator as BracketOperator$1 } from "@classytic/repo-core/query-parser";
2
5
 
3
6
  //#region src/api.d.ts
4
7
  interface PopulateOption {
@@ -6,68 +9,24 @@ interface PopulateOption {
6
9
  select?: string;
7
10
  match?: Record<string, unknown>;
8
11
  }
9
- interface ApiResponse<T = unknown> {
10
- success: boolean;
11
- data?: T;
12
- message?: string;
13
- }
14
- interface OffsetPaginationResponse<T = unknown> {
15
- success: boolean;
16
- method: 'offset';
17
- docs: T[];
18
- page: number;
19
- limit: number;
20
- total: number;
21
- pages: number;
22
- hasNext: boolean;
23
- hasPrev: boolean;
24
- warning?: string;
25
- }
26
- interface KeysetPaginationResponse<T = unknown> {
27
- success: boolean;
28
- method: 'keyset';
29
- docs: T[];
30
- limit: number;
31
- hasMore: boolean;
32
- next: string | null;
33
- }
34
- interface AggregatePaginationResponse<T = unknown> {
35
- success: boolean;
36
- method: 'aggregate';
37
- docs: T[];
38
- page: number;
39
- limit: number;
40
- total: number;
41
- pages: number;
42
- hasNext: boolean;
43
- hasPrev: boolean;
44
- warning?: string;
45
- }
46
- type PaginatedResponse<T = unknown> = OffsetPaginationResponse<T> | KeysetPaginationResponse<T> | AggregatePaginationResponse<T>;
47
- interface DeleteResponse {
48
- success: boolean;
49
- data?: {
50
- message?: string;
51
- id?: string;
52
- soft?: boolean;
53
- };
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
- }
68
12
  type SortDirection = 1 | -1 | 'asc' | 'desc';
69
13
  type SortSpec = Record<string, SortDirection> | string;
70
- type FilterOperator = 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'nin' | 'contains' | 'startsWith' | 'endsWith' | 'regex' | 'like' | 'exists' | 'size' | 'type';
14
+ /**
15
+ * Filter operators supported by arc-next URL emission.
16
+ *
17
+ * Composes:
18
+ * - **Canonical** ({@link BracketOperator}) — every operator repo-core's
19
+ * `parseUrl` reverses. Cross-kit portable: mongokit, sqlitekit, prismakit,
20
+ * and any future kit that consumes the canonical Filter IR all support
21
+ * these out of the box.
22
+ * - **Driver-specific extensions** — operators that require kit-native
23
+ * support. Geo (`near`, `nearSphere`, `geoWithin`, `withinRadius`) is
24
+ * mongokit + sqlitekit-spatialite. `size` / `type` are mongokit
25
+ * array/BSON helpers. Hosts using kits without these features just
26
+ * don't emit them; the union stays open with `(string & {})` so custom
27
+ * domain operators still satisfy the type.
28
+ */
29
+ type FilterOperator = BracketOperator$1 | 'size' | 'type' | 'near' | 'nearSphere' | 'geoWithin' | 'withinRadius' | (string & {});
71
30
  interface QueryParams {
72
31
  page?: number;
73
32
  limit?: number;
@@ -97,6 +56,18 @@ interface RequestOptions {
97
56
  responseType?: 'json' | 'blob' | 'text';
98
57
  signal?: AbortSignal;
99
58
  }
59
+ /**
60
+ * Common args every BaseApi-style method accepts: `token`, `organizationId`,
61
+ * and `options` (per-request RequestOptions minus the auth fields).
62
+ *
63
+ * Preset method signatures extend this so the auth-injection contract stays
64
+ * uniform across every call (BaseApi method, preset method, custom user wrapper).
65
+ */
66
+ interface ScopedArgs {
67
+ token?: string | null;
68
+ organizationId?: string | null;
69
+ options?: Omit<RequestOptions, 'token' | 'organizationId'>;
70
+ }
100
71
  interface BaseApiConfig {
101
72
  basePath?: string;
102
73
  defaultParams?: {
@@ -128,7 +99,7 @@ declare class BaseApi<TDoc = Record<string, unknown>, TCreate = Partial<TDoc>, T
128
99
  organizationId?: string | null;
129
100
  params?: QueryParams;
130
101
  options?: Omit<RequestOptions, 'token' | 'organizationId'>;
131
- }): Promise<PaginatedResponse<TDoc>>;
102
+ }): Promise<PaginatedResult<TDoc>>;
132
103
  getById({
133
104
  token,
134
105
  organizationId,
@@ -144,7 +115,7 @@ declare class BaseApi<TDoc = Record<string, unknown>, TCreate = Partial<TDoc>, T
144
115
  populate?: string | string[];
145
116
  };
146
117
  options?: Omit<RequestOptions, 'token' | 'organizationId'>;
147
- }): Promise<ApiResponse<TDoc>>;
118
+ }): Promise<TDoc>;
148
119
  create({
149
120
  token,
150
121
  organizationId,
@@ -155,7 +126,7 @@ declare class BaseApi<TDoc = Record<string, unknown>, TCreate = Partial<TDoc>, T
155
126
  organizationId?: string | null;
156
127
  data: TCreate;
157
128
  options?: Omit<RequestOptions, 'token' | 'organizationId'>;
158
- }): Promise<ApiResponse<TDoc>>;
129
+ }): Promise<TDoc>;
159
130
  update({
160
131
  token,
161
132
  organizationId,
@@ -168,7 +139,7 @@ declare class BaseApi<TDoc = Record<string, unknown>, TCreate = Partial<TDoc>, T
168
139
  id: string;
169
140
  data: TUpdate;
170
141
  options?: Omit<RequestOptions, 'token' | 'organizationId'>;
171
- }): Promise<ApiResponse<TDoc>>;
142
+ }): Promise<TDoc>;
172
143
  delete({
173
144
  token,
174
145
  organizationId,
@@ -179,7 +150,7 @@ declare class BaseApi<TDoc = Record<string, unknown>, TCreate = Partial<TDoc>, T
179
150
  organizationId?: string | null;
180
151
  id: string;
181
152
  options?: Omit<RequestOptions, 'token' | 'organizationId'>;
182
- }): Promise<DeleteResponse>;
153
+ }): Promise<DeleteResult>;
183
154
  upload({
184
155
  token,
185
156
  organizationId,
@@ -187,159 +158,109 @@ declare class BaseApi<TDoc = Record<string, unknown>, TCreate = Partial<TDoc>, T
187
158
  id,
188
159
  path,
189
160
  options
190
- }: {
191
- token?: string | null;
192
- organizationId?: string | null;
161
+ }: ScopedArgs & {
193
162
  data: FormData; /** Resource ID — shorthand for path, appended as `baseUrl/{id}/upload` */
194
163
  id?: string; /** Custom sub-path appended as `baseUrl/{path}`. Takes precedence over `id`. */
195
164
  path?: string;
196
- options?: Omit<RequestOptions, 'token' | 'organizationId'>;
197
- }): Promise<ApiResponse<TDoc>>;
198
- search({
199
- token,
200
- organizationId,
201
- searchParams,
202
- params,
203
- options
204
- }?: {
205
- token?: string | null;
206
- organizationId?: string | null;
207
- searchParams?: Record<string, unknown>;
208
- params?: QueryParams;
209
- options?: Omit<RequestOptions, 'token' | 'organizationId'>;
210
- }): Promise<PaginatedResponse<TDoc>>;
211
- findBy({
212
- token,
213
- organizationId,
214
- field,
215
- value,
216
- operator,
217
- params,
218
- options
219
- }: {
220
- token?: string | null;
221
- organizationId?: string | null;
222
- field: string;
223
- value: unknown;
224
- operator?: FilterOperator;
225
- params?: QueryParams;
226
- options?: Omit<RequestOptions, 'token' | 'organizationId'>;
227
- }): Promise<PaginatedResponse<TDoc>>;
165
+ }): Promise<TDoc>;
228
166
  request<TResponse = unknown>(method: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE', endpoint: string, {
229
167
  token,
230
168
  organizationId,
231
169
  data,
232
170
  params,
233
171
  options
234
- }?: {
235
- token?: string | null;
236
- organizationId?: string | null;
172
+ }?: ScopedArgs & {
237
173
  data?: unknown;
238
174
  params?: QueryParams;
239
- options?: Omit<RequestOptions, 'token' | 'organizationId'>;
240
175
  }): Promise<TResponse>;
241
- getDeleted({
176
+ /**
177
+ * Invoke a custom route mounted on this resource (e.g. `/todos/stats`,
178
+ * `/todos/recent`). Resource-relative wrapper around {@link request} that
179
+ * prepends `this.baseUrl` so callers don't have to remember the prefix.
180
+ *
181
+ * Use this for `defineResource({ routes: [{ method, path, handler }] })` —
182
+ * Arc's escape hatch for endpoints that don't fit CRUD or actions.
183
+ *
184
+ * For aggregates / reports, prefer the response-aware {@link useApiQuery} hook
185
+ * and pass `invokeRoute` as the queryFn — arc 2.13+ emits raw payloads, so
186
+ * the response IS the data.
187
+ *
188
+ * @example
189
+ * // GET /todos/stats → { total, byStatus }
190
+ * const stats = await api.invokeRoute<{ total: number; byStatus: Record<string, number> }>({
191
+ * method: 'GET',
192
+ * path: '/stats',
193
+ * });
194
+ *
195
+ * // GET /todos/recent?limit=5 → paginated shape spread to root
196
+ * const recent = await api.invokeRoute<PaginatedResult<Todo>>({
197
+ * method: 'GET',
198
+ * path: '/recent',
199
+ * params: { limit: 5 },
200
+ * });
201
+ *
202
+ * // POST /products/import — body + path
203
+ * await api.invokeRoute({
204
+ * method: 'POST',
205
+ * path: '/import',
206
+ * data: { source: 'csv', items: [...] },
207
+ * });
208
+ */
209
+ invokeRoute<TResponse = unknown>({
242
210
  token,
243
211
  organizationId,
212
+ method,
213
+ path,
214
+ data,
244
215
  params,
245
216
  options
246
- }?: {
247
- token?: string | null;
248
- organizationId?: string | null;
217
+ }: ScopedArgs & {
218
+ method?: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE'; /** Path relative to the resource baseUrl. Leading slash optional. */
219
+ path: string;
220
+ data?: unknown;
249
221
  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({
222
+ }): Promise<TResponse>;
223
+ /**
224
+ * Fetch a declared aggregation by name.
225
+ *
226
+ * @example
227
+ * const { rows } = await api.aggregate<{ day: string; total: number }>({
228
+ * name: 'salesByDay',
229
+ * filter: { from: '2025-01-01', to: '2025-12-31' },
230
+ * });
231
+ */
232
+ aggregate<TRow extends AggRow = AggRow>({
288
233
  token,
289
234
  organizationId,
235
+ name,
290
236
  filter,
291
237
  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({
238
+ }: ScopedArgs & {
239
+ /** Aggregation name as declared on the resource. */name: string;
240
+ /**
241
+ * URL-encoded filter narrows + dimension args. Reserved keys (`page`,
242
+ * `limit`, etc.) are stripped server-side; everything else flows into
243
+ * the AggRequest filter via shallow merge with the host's base filter.
244
+ */
245
+ filter?: Record<string, unknown>;
246
+ }): Promise<AggResult<TRow>>;
247
+ dispatchAction<TResult = unknown, TBody extends Record<string, unknown> = Record<string, unknown>>({
299
248
  token,
300
249
  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,
250
+ id,
251
+ action,
252
+ data,
330
253
  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>>;
254
+ }: ScopedArgs & {
255
+ id: string;
256
+ action: string;
257
+ data?: TBody;
258
+ }): Promise<TResult>;
338
259
  }
339
260
  declare function createCrudApi<TDoc = Record<string, unknown>, TCreate = Partial<TDoc>, TUpdate = Partial<TDoc>>(entity: string, config?: BaseApiConfig): BaseApi<TDoc, TCreate, TUpdate>;
340
- type ExtractDoc<T> = T extends PaginatedResponse<infer D> ? D : never;
341
- declare function isOffsetPagination<T>(response: PaginatedResponse<T>): response is OffsetPaginationResponse<T>;
342
- declare function isKeysetPagination<T>(response: PaginatedResponse<T>): response is KeysetPaginationResponse<T>;
343
- declare function isAggregatePagination<T>(response: PaginatedResponse<T>): response is AggregatePaginationResponse<T>;
261
+ type ExtractDoc<T> = T extends PaginatedResult<infer D> ? D : never;
262
+ declare function isOffsetPagination<T>(response: PaginatedResult<T>): response is OffsetPaginationResult<T>;
263
+ declare function isKeysetPagination<T>(response: PaginatedResult<T>): response is KeysetPaginationResult<T>;
264
+ declare function isAggregatePagination<T>(response: PaginatedResult<T>): response is AggregatePaginationResult<T>;
344
265
  //#endregion
345
- export { AggregatePaginationResponse, ApiResponse, BaseApi, BaseApiConfig, BulkCreateResponse, BulkDeleteResponse, BulkUpdateResponse, DeleteResponse, ExtractDoc, FilterOperator, KeysetPaginationResponse, OffsetPaginationResponse, PaginatedResponse, PopulateOption, QueryParams, RequestOptions, SortDirection, SortSpec, createCrudApi, isAggregatePagination, isKeysetPagination, isOffsetPagination };
266
+ export { type AggResult, type AggRow, BaseApi, BaseApiConfig, type BracketOperator, type BulkCreateResult, type DeleteManyResult, type DeleteResult, ExtractDoc, FilterOperator, PopulateOption, QueryParams, RequestOptions, ScopedArgs, SortDirection, SortSpec, type UpdateManyResult, createCrudApi, isAggregatePagination, isKeysetPagination, isOffsetPagination };
package/dist/api.js CHANGED
@@ -63,7 +63,8 @@ var BaseApi = class {
63
63
  }
64
64
  if (value !== void 0 && value !== "") if (["page", "limit"].includes(key)) result[key] = parseInt(String(value)) || (key === "page" ? 1 : 10);
65
65
  else if (Array.isArray(value)) {
66
- if (value.length > 1) result[`${key}[in]`] = value.join(",");
66
+ if (/\[([^\]]+)\]$/.test(key)) result[key] = value.join(",");
67
+ else if (value.length > 1) result[`${key}[in]`] = value.join(",");
67
68
  else if (value.length === 1) result[key] = value[0];
68
69
  } else result[key] = value;
69
70
  });
@@ -133,40 +134,6 @@ var BaseApi = class {
133
134
  if (organizationId) requestOptions.organizationId = organizationId;
134
135
  return this.requestFn("POST", url, this.withHeaders(requestOptions));
135
136
  }
136
- async search({ token = null, organizationId = null, searchParams = {}, params = {}, options = {} } = {}) {
137
- const queryParams = {
138
- ...this.config.defaultParams,
139
- ...params,
140
- ...searchParams
141
- };
142
- const processedParams = this.prepareParams(queryParams);
143
- const queryString = this.createQueryString(processedParams);
144
- const requestOptions = {
145
- cache: this.config.cache,
146
- ...options
147
- };
148
- if (token) requestOptions.token = token;
149
- if (organizationId) requestOptions.organizationId = organizationId;
150
- return this.requestFn("GET", `${this.baseUrl}?${queryString}`, this.withHeaders(requestOptions));
151
- }
152
- async findBy({ token = null, organizationId = null, field, value, operator, params = {}, options = {} }) {
153
- if (!field || value === void 0) throw new Error("Field and value are required");
154
- const queryParams = {
155
- ...this.config.defaultParams,
156
- ...params
157
- };
158
- if (operator) queryParams[`${field}[${operator}]`] = Array.isArray(value) ? value.join(",") : value;
159
- else queryParams[field] = value;
160
- const processedParams = this.prepareParams(queryParams);
161
- const queryString = this.createQueryString(processedParams);
162
- const requestOptions = {
163
- cache: this.config.cache,
164
- ...options
165
- };
166
- if (token) requestOptions.token = token;
167
- if (organizationId) requestOptions.organizationId = organizationId;
168
- return this.requestFn("GET", `${this.baseUrl}?${queryString}`, this.withHeaders(requestOptions));
169
- }
170
137
  async request(method, endpoint, { token = null, organizationId = null, data, params, options = {} } = {}) {
171
138
  let url = endpoint;
172
139
  if (params) {
@@ -182,109 +149,95 @@ var BaseApi = class {
182
149
  if (organizationId) requestOptions.organizationId = organizationId;
183
150
  return this.requestFn(method, url, this.withHeaders(requestOptions));
184
151
  }
185
- async getDeleted({ token = null, organizationId = null, params = {}, options = {} } = {}) {
186
- const mergedParams = {
187
- ...this.config.defaultParams,
188
- ...params
189
- };
190
- const processedParams = this.prepareParams(mergedParams);
191
- const queryString = this.createQueryString(processedParams);
192
- const requestOptions = {
193
- cache: this.config.cache,
194
- ...options
195
- };
196
- if (token) requestOptions.token = token;
197
- if (organizationId) requestOptions.organizationId = organizationId;
198
- return this.requestFn("GET", `${this.baseUrl}/deleted?${queryString}`, this.withHeaders(requestOptions));
152
+ /**
153
+ * Invoke a custom route mounted on this resource (e.g. `/todos/stats`,
154
+ * `/todos/recent`). Resource-relative wrapper around {@link request} that
155
+ * prepends `this.baseUrl` so callers don't have to remember the prefix.
156
+ *
157
+ * Use this for `defineResource({ routes: [{ method, path, handler }] })` —
158
+ * Arc's escape hatch for endpoints that don't fit CRUD or actions.
159
+ *
160
+ * For aggregates / reports, prefer the response-aware {@link useApiQuery} hook
161
+ * and pass `invokeRoute` as the queryFn — arc 2.13+ emits raw payloads, so
162
+ * the response IS the data.
163
+ *
164
+ * @example
165
+ * // GET /todos/stats → { total, byStatus }
166
+ * const stats = await api.invokeRoute<{ total: number; byStatus: Record<string, number> }>({
167
+ * method: 'GET',
168
+ * path: '/stats',
169
+ * });
170
+ *
171
+ * // GET /todos/recent?limit=5 → paginated shape spread to root
172
+ * const recent = await api.invokeRoute<PaginatedResult<Todo>>({
173
+ * method: 'GET',
174
+ * path: '/recent',
175
+ * params: { limit: 5 },
176
+ * });
177
+ *
178
+ * // POST /products/import — body + path
179
+ * await api.invokeRoute({
180
+ * method: 'POST',
181
+ * path: '/import',
182
+ * data: { source: 'csv', items: [...] },
183
+ * });
184
+ */
185
+ async invokeRoute({ token = null, organizationId = null, method = "GET", path, data, params, options = {} }) {
186
+ if (!path) throw new Error("path is required");
187
+ const normalized = path.startsWith("/") ? path : `/${path}`;
188
+ const endpoint = `${this.baseUrl}${normalized}`;
189
+ return this.request(method, endpoint, {
190
+ token,
191
+ organizationId,
192
+ data,
193
+ params,
194
+ options
195
+ });
199
196
  }
200
- async restore({ token = null, organizationId = null, id, options = {} }) {
201
- if (!id) throw new Error("ID is required");
197
+ /**
198
+ * Fetch a declared aggregation by name.
199
+ *
200
+ * @example
201
+ * const { rows } = await api.aggregate<{ day: string; total: number }>({
202
+ * name: 'salesByDay',
203
+ * filter: { from: '2025-01-01', to: '2025-12-31' },
204
+ * });
205
+ */
206
+ async aggregate({ token = null, organizationId = null, name, filter, options = {} }) {
207
+ if (!name) throw new Error("Aggregation name is required");
208
+ const queryString = filter ? this.createQueryString(filter) : "";
209
+ const endpoint = `${this.baseUrl}/aggregations/${name}${queryString ? `?${queryString}` : ""}`;
202
210
  const requestOptions = { ...options };
203
211
  if (token) requestOptions.token = token;
204
212
  if (organizationId) requestOptions.organizationId = organizationId;
205
- return this.requestFn("POST", `${this.baseUrl}/${id}/restore`, this.withHeaders(requestOptions));
213
+ return this.requestFn("GET", endpoint, this.withHeaders(requestOptions));
206
214
  }
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 = {} }) {
215
+ async dispatchAction({ token = null, organizationId = null, id, action, data, options = {} }) {
216
+ if (!id) throw new Error("ID is required");
217
+ if (!action) throw new Error("Action name is required");
217
218
  const requestOptions = {
218
219
  body: {
219
- filter,
220
- data
220
+ action,
221
+ ...data ?? {}
221
222
  },
222
223
  ...options
223
224
  };
224
225
  if (token) requestOptions.token = token;
225
226
  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));
227
+ return this.requestFn("POST", `${this.baseUrl}/${id}/action`, this.withHeaders(requestOptions));
275
228
  }
276
229
  };
277
230
  function createCrudApi(entity, config = {}) {
278
231
  return new BaseApi(entity, config);
279
232
  }
280
233
  function isOffsetPagination(response) {
281
- return response.method === "offset";
234
+ return "method" in response && response.method === "offset";
282
235
  }
283
236
  function isKeysetPagination(response) {
284
- return response.method === "keyset";
237
+ return "method" in response && response.method === "keyset";
285
238
  }
286
239
  function isAggregatePagination(response) {
287
- return response.method === "aggregate";
240
+ return "method" in response && response.method === "aggregate";
288
241
  }
289
242
 
290
243
  //#endregion