@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/README.md +272 -660
- package/dist/api.d.ts +115 -194
- package/dist/api.js +69 -116
- package/dist/cache.d.ts +125 -0
- package/dist/cache.js +211 -0
- package/dist/client.d.ts +295 -5
- package/dist/client.js +392 -12
- package/dist/hooks.d.ts +162 -22
- package/dist/hooks.js +249 -89
- package/dist/mutation.d.ts +18 -1
- package/dist/mutation.js +20 -1
- package/dist/prefetch.d.ts +36 -2
- package/dist/prefetch.js +47 -3
- package/dist/presets/bulk.d.ts +43 -0
- package/dist/presets/bulk.js +50 -0
- package/dist/presets/search.d.ts +56 -0
- package/dist/presets/search.js +60 -0
- package/dist/presets/slug.d.ts +28 -0
- package/dist/presets/slug.js +27 -0
- package/dist/presets/soft-delete.d.ts +34 -0
- package/dist/presets/soft-delete.js +45 -0
- package/dist/presets/tree.d.ts +32 -0
- package/dist/presets/tree.js +47 -0
- package/dist/query.d.ts +86 -69
- package/dist/query.js +59 -161
- package/dist/sse.d.ts +115 -36
- package/dist/sse.js +179 -103
- package/dist/upload.d.ts +181 -0
- package/dist/upload.js +346 -0
- package/dist/ws.d.ts +167 -0
- package/dist/ws.js +274 -0
- package/package.json +39 -1
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
|
-
|
|
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<
|
|
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<
|
|
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<
|
|
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<
|
|
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<
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
248
|
-
|
|
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
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
|
341
|
-
declare function isOffsetPagination<T>(response:
|
|
342
|
-
declare function isKeysetPagination<T>(response:
|
|
343
|
-
declare function isAggregatePagination<T>(response:
|
|
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 {
|
|
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 (
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
201
|
-
|
|
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("
|
|
213
|
+
return this.requestFn("GET", endpoint, this.withHeaders(requestOptions));
|
|
206
214
|
}
|
|
207
|
-
async
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
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("
|
|
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
|