@classytic/arc-next 0.4.0 → 0.5.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 -616
- package/dist/api.d.ts +64 -127
- package/dist/api.js +55 -121
- package/dist/cache.d.ts +108 -0
- package/dist/cache.js +197 -0
- package/dist/client.d.ts +340 -8
- package/dist/client.js +427 -22
- package/dist/hooks.d.ts +96 -20
- package/dist/hooks.js +247 -115
- package/dist/mutation.d.ts +18 -4
- package/dist/mutation.js +20 -4
- package/dist/prefetch.d.ts +37 -3
- package/dist/prefetch.js +56 -17
- package/dist/presets/bulk.d.ts +42 -0
- package/dist/presets/bulk.js +50 -0
- package/dist/presets/search.d.ts +55 -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 +33 -0
- package/dist/presets/soft-delete.js +45 -0
- package/dist/presets/tree.d.ts +31 -0
- package/dist/presets/tree.js +47 -0
- package/dist/query.d.ts +86 -64
- package/dist/query.js +69 -151
- package/dist/sse.d.ts +116 -30
- package/dist/sse.js +179 -102
- 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 +37 -1
package/dist/mutation.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { isArcApiError, isAutoIdempotency } from "./client.js";
|
|
4
|
-
import { QUERY_CONFIGS } from "./query.js";
|
|
5
4
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
6
5
|
import { useCallback, useRef, useTransition } from "react";
|
|
7
6
|
|
|
@@ -22,6 +21,25 @@ let toastHandler = {
|
|
|
22
21
|
function configureToast(handler) {
|
|
23
22
|
toastHandler = handler;
|
|
24
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Get the configured toast handler. Returns the console-based default when
|
|
26
|
+
* no handler has been configured yet.
|
|
27
|
+
*
|
|
28
|
+
* Use this when domain code outside the react-query lifecycle needs to fire
|
|
29
|
+
* ad-hoc success/error toasts using the same handler the SDK uses internally
|
|
30
|
+
* — avoids the need for consumer SDKs to keep a parallel cache of the handler.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* // In a domain helper outside any mutation lifecycle:
|
|
34
|
+
* import { getToastHandler } from '@classytic/arc-next/mutation';
|
|
35
|
+
*
|
|
36
|
+
* function notifySaved(label: string) {
|
|
37
|
+
* getToastHandler().success(`${label} saved`);
|
|
38
|
+
* }
|
|
39
|
+
*/
|
|
40
|
+
function getToastHandler() {
|
|
41
|
+
return toastHandler;
|
|
42
|
+
}
|
|
25
43
|
function showToast(type, messages, data, variables, error, handler) {
|
|
26
44
|
const activeHandler = handler ?? toastHandler;
|
|
27
45
|
if (type === "success") {
|
|
@@ -162,8 +180,6 @@ function useOptimisticMutation(config) {
|
|
|
162
180
|
}
|
|
163
181
|
});
|
|
164
182
|
}
|
|
165
|
-
/** @deprecated Use `useOptimisticMutation` */
|
|
166
|
-
const createOptimisticMutation = useOptimisticMutation;
|
|
167
183
|
|
|
168
184
|
//#endregion
|
|
169
|
-
export {
|
|
185
|
+
export { configureToast, getToastHandler, useMutationWithOptimistic, useMutationWithTransition, useOptimisticMutation };
|
package/dist/prefetch.d.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
import { QueryClient, dehydrate } from "@tanstack/react-query";
|
|
1
|
+
import { HydrationBoundary, InfiniteData, QueryClient, dehydrate } from "@tanstack/react-query";
|
|
2
2
|
|
|
3
3
|
//#region src/prefetch.d.ts
|
|
4
|
-
interface
|
|
4
|
+
interface PrefetchAuthContext {
|
|
5
|
+
/** Auth token for protected endpoints. Required for bearer/header auth on server. */
|
|
6
|
+
token?: string | null;
|
|
7
|
+
/** Organization ID for multi-tenant prefetch. Sent as x-organization-id header. */
|
|
8
|
+
organizationId?: string | null;
|
|
9
|
+
/** Additional headers (e.g., x-api-key for header auth). */
|
|
10
|
+
headers?: Record<string, string>;
|
|
11
|
+
}
|
|
12
|
+
interface PrefetchOptions extends PrefetchAuthContext {
|
|
5
13
|
staleTime?: number;
|
|
6
14
|
}
|
|
7
15
|
interface PrefetchDetailOptions extends PrefetchOptions {
|
|
@@ -43,6 +51,17 @@ interface CrudPrefetcher {
|
|
|
43
51
|
* Only available when the API has a `getTree` method (tree preset).
|
|
44
52
|
*/
|
|
45
53
|
prefetchTree: (queryClient: QueryClient, params?: Record<string, unknown>, options?: PrefetchOptions) => Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* Prefetch an infinite list query (cursor / page-based pagination). Uses the
|
|
56
|
+
* same query keys as `useInfiniteList` and seeds the `{ pages, pageParams }`
|
|
57
|
+
* shape TanStack Query expects for `useInfiniteQuery` — a flat
|
|
58
|
+
* `prefetchQuery` would NOT match the cache shape and the client hook would
|
|
59
|
+
* re-fetch from scratch, defeating the prefetch.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* await productsPrefetcher.prefetchInfiniteList(queryClient, { limit: 20 });
|
|
63
|
+
*/
|
|
64
|
+
prefetchInfiniteList: (queryClient: QueryClient, params?: Record<string, unknown>, options?: PrefetchOptions) => Promise<void>;
|
|
46
65
|
}
|
|
47
66
|
/**
|
|
48
67
|
* Create server-safe prefetch helpers for CRUD queries.
|
|
@@ -74,28 +93,43 @@ declare function createCrudPrefetcher(api: {
|
|
|
74
93
|
params?: Record<string, unknown>;
|
|
75
94
|
token?: string | null;
|
|
76
95
|
organizationId?: string | null;
|
|
96
|
+
options?: {
|
|
97
|
+
headerOptions?: Record<string, string>;
|
|
98
|
+
};
|
|
77
99
|
}) => Promise<unknown>;
|
|
78
100
|
getById: (opts: {
|
|
79
101
|
id: string;
|
|
80
102
|
token?: string | null;
|
|
81
103
|
organizationId?: string | null;
|
|
104
|
+
options?: {
|
|
105
|
+
headerOptions?: Record<string, string>;
|
|
106
|
+
};
|
|
82
107
|
}) => Promise<unknown>;
|
|
83
108
|
getBySlug?: (opts: {
|
|
84
109
|
slug: string;
|
|
85
110
|
token?: string | null;
|
|
86
111
|
organizationId?: string | null;
|
|
87
112
|
params?: Record<string, unknown>;
|
|
113
|
+
options?: {
|
|
114
|
+
headerOptions?: Record<string, string>;
|
|
115
|
+
};
|
|
88
116
|
}) => Promise<unknown>;
|
|
89
117
|
getDeleted?: (opts: {
|
|
90
118
|
params?: Record<string, unknown>;
|
|
91
119
|
token?: string | null;
|
|
92
120
|
organizationId?: string | null;
|
|
121
|
+
options?: {
|
|
122
|
+
headerOptions?: Record<string, string>;
|
|
123
|
+
};
|
|
93
124
|
}) => Promise<unknown>;
|
|
94
125
|
getTree?: (opts: {
|
|
95
126
|
params?: Record<string, unknown>;
|
|
96
127
|
token?: string | null;
|
|
97
128
|
organizationId?: string | null;
|
|
129
|
+
options?: {
|
|
130
|
+
headerOptions?: Record<string, string>;
|
|
131
|
+
};
|
|
98
132
|
}) => Promise<unknown>;
|
|
99
133
|
}, entityKey: string): CrudPrefetcher;
|
|
100
134
|
//#endregion
|
|
101
|
-
export { CrudPrefetcher, PrefetchDetailOptions, PrefetchOptions, createCrudPrefetcher, dehydrate };
|
|
135
|
+
export { CrudPrefetcher, HydrationBoundary, type InfiniteData, PrefetchAuthContext, PrefetchDetailOptions, PrefetchOptions, createCrudPrefetcher, dehydrate };
|
package/dist/prefetch.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createQueryKeys } from "./
|
|
2
|
-
import { dehydrate } from "@tanstack/react-query";
|
|
1
|
+
import { createQueryKeys } from "./cache.js";
|
|
2
|
+
import { HydrationBoundary, dehydrate } from "@tanstack/react-query";
|
|
3
3
|
|
|
4
4
|
//#region src/prefetch.ts
|
|
5
5
|
/**
|
|
@@ -31,81 +31,120 @@ function createCrudPrefetcher(api, entityKey) {
|
|
|
31
31
|
const KEYS = createQueryKeys(entityKey);
|
|
32
32
|
return {
|
|
33
33
|
async prefetchList(queryClient, params = {}, options = {}) {
|
|
34
|
-
const { organizationId, ...restParams } = params;
|
|
35
|
-
const
|
|
34
|
+
const { organizationId: paramOrgId, ...restParams } = params;
|
|
35
|
+
const orgId = paramOrgId ?? options.organizationId ?? null;
|
|
36
|
+
const scope = orgId ? "tenant" : "super-admin";
|
|
36
37
|
const queryKey = KEYS.scopedList(scope, {
|
|
37
|
-
organizationId,
|
|
38
|
+
...orgId ? { organizationId: orgId } : {},
|
|
38
39
|
...restParams
|
|
39
40
|
});
|
|
40
41
|
await queryClient.prefetchQuery({
|
|
41
42
|
queryKey,
|
|
42
43
|
queryFn: () => api.getAll({
|
|
43
44
|
params: restParams,
|
|
44
|
-
|
|
45
|
+
token: options.token ?? null,
|
|
46
|
+
organizationId: orgId,
|
|
47
|
+
...options.headers ? { options: { headerOptions: options.headers } } : {}
|
|
45
48
|
}),
|
|
46
49
|
staleTime: options.staleTime
|
|
47
50
|
});
|
|
48
51
|
},
|
|
49
52
|
async prefetchDetail(queryClient, id, options = {}) {
|
|
50
|
-
const { params, staleTime } = options;
|
|
53
|
+
const { params, staleTime, token, organizationId } = options;
|
|
51
54
|
const baseKey = KEYS.detail(id);
|
|
52
55
|
const queryKey = params ? [...baseKey, params] : baseKey;
|
|
53
56
|
await queryClient.prefetchQuery({
|
|
54
57
|
queryKey,
|
|
55
58
|
queryFn: () => api.getById({
|
|
56
59
|
id,
|
|
57
|
-
|
|
60
|
+
token: token ?? null,
|
|
61
|
+
organizationId: organizationId ?? null,
|
|
62
|
+
...params ? { params } : {},
|
|
63
|
+
...options.headers ? { options: { headerOptions: options.headers } } : {}
|
|
58
64
|
}),
|
|
59
65
|
staleTime
|
|
60
66
|
});
|
|
61
67
|
},
|
|
62
68
|
async prefetchBySlug(queryClient, slug, options = {}) {
|
|
63
69
|
if (!api.getBySlug) throw new Error(`[arc-next] prefetchBySlug requires an api with getBySlug (slugLookup preset)`);
|
|
64
|
-
const { params, staleTime } = options;
|
|
70
|
+
const { params, staleTime, token, organizationId } = options;
|
|
65
71
|
const queryKey = params ? KEYS.custom("slug", slug, params) : KEYS.custom("slug", slug);
|
|
66
72
|
await queryClient.prefetchQuery({
|
|
67
73
|
queryKey,
|
|
68
74
|
queryFn: () => api.getBySlug({
|
|
69
75
|
slug,
|
|
70
|
-
|
|
76
|
+
token: token ?? null,
|
|
77
|
+
organizationId: organizationId ?? null,
|
|
78
|
+
...params ? { params } : {},
|
|
79
|
+
...options.headers ? { options: { headerOptions: options.headers } } : {}
|
|
71
80
|
}),
|
|
72
81
|
staleTime
|
|
73
82
|
});
|
|
74
83
|
},
|
|
75
84
|
async prefetchDeleted(queryClient, params = {}, options = {}) {
|
|
76
85
|
if (!api.getDeleted) throw new Error(`[arc-next] prefetchDeleted requires an api with getDeleted (softDelete preset)`);
|
|
77
|
-
const { organizationId, ...restParams } = params;
|
|
86
|
+
const { organizationId: paramOrgId, ...restParams } = params;
|
|
87
|
+
const orgId = paramOrgId ?? options.organizationId ?? null;
|
|
78
88
|
const queryKey = KEYS.custom("deleted", {
|
|
79
|
-
organizationId,
|
|
89
|
+
...orgId ? { organizationId: orgId } : {},
|
|
80
90
|
...restParams
|
|
81
91
|
});
|
|
82
92
|
await queryClient.prefetchQuery({
|
|
83
93
|
queryKey,
|
|
84
94
|
queryFn: () => api.getDeleted({
|
|
85
95
|
params: restParams,
|
|
86
|
-
|
|
96
|
+
token: options.token ?? null,
|
|
97
|
+
organizationId: orgId,
|
|
98
|
+
...options.headers ? { options: { headerOptions: options.headers } } : {}
|
|
87
99
|
}),
|
|
88
100
|
staleTime: options.staleTime
|
|
89
101
|
});
|
|
90
102
|
},
|
|
91
103
|
async prefetchTree(queryClient, params = {}, options = {}) {
|
|
92
104
|
if (!api.getTree) throw new Error(`[arc-next] prefetchTree requires an api with getTree (tree preset)`);
|
|
93
|
-
const { organizationId, ...restParams } = params;
|
|
105
|
+
const { organizationId: paramOrgId, ...restParams } = params;
|
|
106
|
+
const orgId = paramOrgId ?? options.organizationId ?? null;
|
|
94
107
|
const queryKey = KEYS.custom("tree", {
|
|
95
|
-
organizationId,
|
|
108
|
+
...orgId ? { organizationId: orgId } : {},
|
|
96
109
|
...restParams
|
|
97
110
|
});
|
|
98
111
|
await queryClient.prefetchQuery({
|
|
99
112
|
queryKey,
|
|
100
113
|
queryFn: () => api.getTree({
|
|
101
114
|
params: restParams,
|
|
102
|
-
|
|
115
|
+
token: options.token ?? null,
|
|
116
|
+
organizationId: orgId,
|
|
117
|
+
...options.headers ? { options: { headerOptions: options.headers } } : {}
|
|
103
118
|
}),
|
|
104
119
|
staleTime: options.staleTime
|
|
105
120
|
});
|
|
121
|
+
},
|
|
122
|
+
async prefetchInfiniteList(queryClient, params = {}, options = {}) {
|
|
123
|
+
const { organizationId: paramOrgId, ...restParams } = params;
|
|
124
|
+
const orgId = paramOrgId ?? options.organizationId ?? null;
|
|
125
|
+
const scope = orgId ? "tenant" : "super-admin";
|
|
126
|
+
const queryKey = [...KEYS.scopedList(scope, {
|
|
127
|
+
...orgId ? { organizationId: orgId } : {},
|
|
128
|
+
...restParams
|
|
129
|
+
}), "infinite"];
|
|
130
|
+
await queryClient.prefetchInfiniteQuery({
|
|
131
|
+
queryKey,
|
|
132
|
+
queryFn: ({ pageParam }) => api.getAll({
|
|
133
|
+
params: {
|
|
134
|
+
...restParams,
|
|
135
|
+
...pageParam ? { page: pageParam } : {}
|
|
136
|
+
},
|
|
137
|
+
token: options.token ?? null,
|
|
138
|
+
organizationId: orgId,
|
|
139
|
+
...options.headers ? { options: { headerOptions: options.headers } } : {}
|
|
140
|
+
}),
|
|
141
|
+
initialPageParam: 1,
|
|
142
|
+
getNextPageParam: () => void 0,
|
|
143
|
+
staleTime: options.staleTime
|
|
144
|
+
});
|
|
106
145
|
}
|
|
107
146
|
};
|
|
108
147
|
}
|
|
109
148
|
|
|
110
149
|
//#endregion
|
|
111
|
-
export { createCrudPrefetcher, dehydrate };
|
|
150
|
+
export { HydrationBoundary, createCrudPrefetcher, dehydrate };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { BaseApi, BulkCreateResponse, BulkDeleteResponse, BulkUpdateResponse, ScopedArgs } from "../api.js";
|
|
2
|
+
|
|
3
|
+
//#region src/presets/bulk.d.ts
|
|
4
|
+
interface BulkMethods<TDoc, TCreate = Partial<TDoc>, TUpdate = Partial<TDoc>> {
|
|
5
|
+
/** Insert many docs in one round-trip. Backend mounts `POST /:resource/bulk`. */
|
|
6
|
+
bulkCreate(args: ScopedArgs & {
|
|
7
|
+
data: TCreate[];
|
|
8
|
+
}): Promise<BulkCreateResponse<TDoc>>;
|
|
9
|
+
/**
|
|
10
|
+
* Update all docs matching `filter` with `data`.
|
|
11
|
+
* Backend mounts `PATCH /:resource/bulk`.
|
|
12
|
+
*/
|
|
13
|
+
bulkUpdate(args: ScopedArgs & {
|
|
14
|
+
filter: Record<string, unknown>;
|
|
15
|
+
data: TUpdate;
|
|
16
|
+
}): Promise<BulkUpdateResponse>;
|
|
17
|
+
/**
|
|
18
|
+
* Delete all docs matching `filter`.
|
|
19
|
+
* Backend mounts `DELETE /:resource/bulk`.
|
|
20
|
+
*/
|
|
21
|
+
bulkDelete(args: ScopedArgs & {
|
|
22
|
+
filter: Record<string, unknown>;
|
|
23
|
+
}): Promise<BulkDeleteResponse>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Adds bulk preset methods to a BaseApi.
|
|
27
|
+
*
|
|
28
|
+
* Mirrors arc's `bulk` preset — three /bulk routes for batch ops. Bulk endpoints
|
|
29
|
+
* require explicit per-resource permissions on the backend (the standard CRUD
|
|
30
|
+
* `allowPublic()` doesn't cover them).
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* import { withBulk } from '@classytic/arc-next/presets/bulk';
|
|
34
|
+
* const todos = withBulk(createCrudApi<Todo>('todos'));
|
|
35
|
+
*
|
|
36
|
+
* await todos.bulkCreate({ data: [{ title: 'A' }, { title: 'B' }] });
|
|
37
|
+
* await todos.bulkUpdate({ filter: { status: 'pending' }, data: { status: 'archived' } });
|
|
38
|
+
* await todos.bulkDelete({ filter: { archivedBefore: '2024-01-01' } });
|
|
39
|
+
*/
|
|
40
|
+
declare function withBulk<TDoc, TCreate, TUpdate>(api: BaseApi<TDoc, TCreate, TUpdate>): BaseApi<TDoc, TCreate, TUpdate> & BulkMethods<TDoc, TCreate, TUpdate>;
|
|
41
|
+
//#endregion
|
|
42
|
+
export { BulkMethods, withBulk };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
//#region src/presets/bulk.ts
|
|
2
|
+
/**
|
|
3
|
+
* Adds bulk preset methods to a BaseApi.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors arc's `bulk` preset — three /bulk routes for batch ops. Bulk endpoints
|
|
6
|
+
* require explicit per-resource permissions on the backend (the standard CRUD
|
|
7
|
+
* `allowPublic()` doesn't cover them).
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* import { withBulk } from '@classytic/arc-next/presets/bulk';
|
|
11
|
+
* const todos = withBulk(createCrudApi<Todo>('todos'));
|
|
12
|
+
*
|
|
13
|
+
* await todos.bulkCreate({ data: [{ title: 'A' }, { title: 'B' }] });
|
|
14
|
+
* await todos.bulkUpdate({ filter: { status: 'pending' }, data: { status: 'archived' } });
|
|
15
|
+
* await todos.bulkDelete({ filter: { archivedBefore: '2024-01-01' } });
|
|
16
|
+
*/
|
|
17
|
+
function withBulk(api) {
|
|
18
|
+
return Object.assign(api, {
|
|
19
|
+
async bulkCreate({ token = null, organizationId = null, data, options = {} }) {
|
|
20
|
+
return api.request("POST", `${api.baseUrl}/bulk`, {
|
|
21
|
+
token,
|
|
22
|
+
organizationId,
|
|
23
|
+
data: { items: data },
|
|
24
|
+
options
|
|
25
|
+
});
|
|
26
|
+
},
|
|
27
|
+
async bulkUpdate({ token = null, organizationId = null, filter, data, options = {} }) {
|
|
28
|
+
return api.request("PATCH", `${api.baseUrl}/bulk`, {
|
|
29
|
+
token,
|
|
30
|
+
organizationId,
|
|
31
|
+
data: {
|
|
32
|
+
filter,
|
|
33
|
+
data
|
|
34
|
+
},
|
|
35
|
+
options
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
async bulkDelete({ token = null, organizationId = null, filter, options = {} }) {
|
|
39
|
+
return api.request("DELETE", `${api.baseUrl}/bulk`, {
|
|
40
|
+
token,
|
|
41
|
+
organizationId,
|
|
42
|
+
data: { filter },
|
|
43
|
+
options
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
//#endregion
|
|
50
|
+
export { withBulk };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { ApiResponse, BaseApi, PaginatedResponse, ScopedArgs } from "../api.js";
|
|
2
|
+
|
|
3
|
+
//#region src/presets/search.d.ts
|
|
4
|
+
interface SearchPresetMethods<TDoc> {
|
|
5
|
+
/**
|
|
6
|
+
* Engine-backed full-text search (Elastic, Algolia, Typesense...).
|
|
7
|
+
* Backend mounts `POST /:resource/search` via `searchPreset()`.
|
|
8
|
+
*/
|
|
9
|
+
searchEngine<TResult = TDoc, TBody extends Record<string, unknown> = Record<string, unknown>>(args?: ScopedArgs & {
|
|
10
|
+
/** Free-text query forwarded as `body.query`. */query?: string; /** Engine-specific options merged into the request body. */
|
|
11
|
+
body?: TBody; /** Override path (default `/search`). */
|
|
12
|
+
path?: string;
|
|
13
|
+
}): Promise<ApiResponse<TResult[]> | PaginatedResponse<TResult>>;
|
|
14
|
+
/**
|
|
15
|
+
* Vector / semantic similarity (Atlas, Pinecone, Qdrant...).
|
|
16
|
+
* Backend mounts `POST /:resource/search-similar`.
|
|
17
|
+
*/
|
|
18
|
+
searchSimilar<TResult = TDoc, TBody extends Record<string, unknown> = Record<string, unknown>>(args?: ScopedArgs & {
|
|
19
|
+
/** Text query — backend embeds and searches for nearest neighbors. */query?: string; /** Pre-computed embedding vector — used directly for similarity search. */
|
|
20
|
+
vector?: number[]; /** Vector-engine options (`topK`, `filter`, `index`, ...). */
|
|
21
|
+
body?: TBody; /** Override path (default `/search-similar`). */
|
|
22
|
+
path?: string;
|
|
23
|
+
}): Promise<ApiResponse<TResult[]>>;
|
|
24
|
+
/**
|
|
25
|
+
* Convert text/media to a vector embedding via the engine the resource is wired to.
|
|
26
|
+
* Backend mounts `POST /:resource/embed`.
|
|
27
|
+
*/
|
|
28
|
+
embed(args: ScopedArgs & {
|
|
29
|
+
/** Text or array of texts to embed. */input: string | string[]; /** Embed-engine options (`model`, `dimensions`, ...). */
|
|
30
|
+
body?: Record<string, unknown>; /** Override path (default `/embed`). */
|
|
31
|
+
path?: string;
|
|
32
|
+
}): Promise<ApiResponse<number[] | number[][]>>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Adds search preset methods to a BaseApi.
|
|
36
|
+
*
|
|
37
|
+
* Mirrors arc's `searchPreset()` — three POST routes for engine-backed search,
|
|
38
|
+
* vector similarity, and embedding generation. Each route is independently
|
|
39
|
+
* permission-gated on the backend.
|
|
40
|
+
*
|
|
41
|
+
* Differs from {@link BaseApi.search} (a GET against the list endpoint with
|
|
42
|
+
* filter params) — that's the legacy filtered-list pattern. These methods hit
|
|
43
|
+
* the new POST routes the preset mounts.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* import { withSearchPreset } from '@classytic/arc-next/presets/search';
|
|
47
|
+
* const places = withSearchPreset(createCrudApi<Place>('places'));
|
|
48
|
+
*
|
|
49
|
+
* await places.searchEngine({ query: 'park', body: { filter: { category: 'park' } } });
|
|
50
|
+
* await places.searchSimilar({ vector: [0.1, 0.2, ...], body: { topK: 5 } });
|
|
51
|
+
* await places.embed({ input: 'hello world' });
|
|
52
|
+
*/
|
|
53
|
+
declare function withSearchPreset<TDoc, TCreate, TUpdate>(api: BaseApi<TDoc, TCreate, TUpdate>): BaseApi<TDoc, TCreate, TUpdate> & SearchPresetMethods<TDoc>;
|
|
54
|
+
//#endregion
|
|
55
|
+
export { SearchPresetMethods, withSearchPreset };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
//#region src/presets/search.ts
|
|
2
|
+
/**
|
|
3
|
+
* Adds search preset methods to a BaseApi.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors arc's `searchPreset()` — three POST routes for engine-backed search,
|
|
6
|
+
* vector similarity, and embedding generation. Each route is independently
|
|
7
|
+
* permission-gated on the backend.
|
|
8
|
+
*
|
|
9
|
+
* Differs from {@link BaseApi.search} (a GET against the list endpoint with
|
|
10
|
+
* filter params) — that's the legacy filtered-list pattern. These methods hit
|
|
11
|
+
* the new POST routes the preset mounts.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* import { withSearchPreset } from '@classytic/arc-next/presets/search';
|
|
15
|
+
* const places = withSearchPreset(createCrudApi<Place>('places'));
|
|
16
|
+
*
|
|
17
|
+
* await places.searchEngine({ query: 'park', body: { filter: { category: 'park' } } });
|
|
18
|
+
* await places.searchSimilar({ vector: [0.1, 0.2, ...], body: { topK: 5 } });
|
|
19
|
+
* await places.embed({ input: 'hello world' });
|
|
20
|
+
*/
|
|
21
|
+
function withSearchPreset(api) {
|
|
22
|
+
return Object.assign(api, {
|
|
23
|
+
async searchEngine({ token = null, organizationId = null, query, body, path = "/search", options = {} } = {}) {
|
|
24
|
+
const requestBody = { ...body ?? {} };
|
|
25
|
+
if (query !== void 0) requestBody.query = query;
|
|
26
|
+
return api.request("POST", `${api.baseUrl}${path}`, {
|
|
27
|
+
token,
|
|
28
|
+
organizationId,
|
|
29
|
+
data: requestBody,
|
|
30
|
+
options
|
|
31
|
+
});
|
|
32
|
+
},
|
|
33
|
+
async searchSimilar({ token = null, organizationId = null, query, vector, body, path = "/search-similar", options = {} } = {}) {
|
|
34
|
+
const requestBody = { ...body ?? {} };
|
|
35
|
+
if (query !== void 0) requestBody.query = query;
|
|
36
|
+
if (vector !== void 0) requestBody.vector = vector;
|
|
37
|
+
return api.request("POST", `${api.baseUrl}${path}`, {
|
|
38
|
+
token,
|
|
39
|
+
organizationId,
|
|
40
|
+
data: requestBody,
|
|
41
|
+
options
|
|
42
|
+
});
|
|
43
|
+
},
|
|
44
|
+
async embed({ token = null, organizationId = null, input, body, path = "/embed", options = {} }) {
|
|
45
|
+
const requestBody = {
|
|
46
|
+
input,
|
|
47
|
+
...body ?? {}
|
|
48
|
+
};
|
|
49
|
+
return api.request("POST", `${api.baseUrl}${path}`, {
|
|
50
|
+
token,
|
|
51
|
+
organizationId,
|
|
52
|
+
data: requestBody,
|
|
53
|
+
options
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
//#endregion
|
|
60
|
+
export { withSearchPreset };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ApiResponse, BaseApi, ScopedArgs } from "../api.js";
|
|
2
|
+
|
|
3
|
+
//#region src/presets/slug.d.ts
|
|
4
|
+
interface SlugLookupMethods<TDoc> {
|
|
5
|
+
/** Fetch a single doc by slug. Backend mounts `GET /:resource/slug/:slug`. */
|
|
6
|
+
getBySlug(args: ScopedArgs & {
|
|
7
|
+
slug: string;
|
|
8
|
+
params?: {
|
|
9
|
+
select?: string;
|
|
10
|
+
populate?: string | string[];
|
|
11
|
+
};
|
|
12
|
+
}): Promise<ApiResponse<TDoc>>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Adds slug-lookup preset methods to a BaseApi.
|
|
16
|
+
*
|
|
17
|
+
* Mirrors arc's `slugLookup` preset — exposes `getBySlug` for resources keyed
|
|
18
|
+
* by URL-friendly slugs alongside the canonical id.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* import { withSlugLookup } from '@classytic/arc-next/presets/slug';
|
|
22
|
+
* const categories = withSlugLookup(createCrudApi<Category>('categories'));
|
|
23
|
+
*
|
|
24
|
+
* const cat = await categories.getBySlug({ slug: 'engineering' });
|
|
25
|
+
*/
|
|
26
|
+
declare function withSlugLookup<TDoc, TCreate, TUpdate>(api: BaseApi<TDoc, TCreate, TUpdate>): BaseApi<TDoc, TCreate, TUpdate> & SlugLookupMethods<TDoc>;
|
|
27
|
+
//#endregion
|
|
28
|
+
export { SlugLookupMethods, withSlugLookup };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//#region src/presets/slug.ts
|
|
2
|
+
/**
|
|
3
|
+
* Adds slug-lookup preset methods to a BaseApi.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors arc's `slugLookup` preset — exposes `getBySlug` for resources keyed
|
|
6
|
+
* by URL-friendly slugs alongside the canonical id.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* import { withSlugLookup } from '@classytic/arc-next/presets/slug';
|
|
10
|
+
* const categories = withSlugLookup(createCrudApi<Category>('categories'));
|
|
11
|
+
*
|
|
12
|
+
* const cat = await categories.getBySlug({ slug: 'engineering' });
|
|
13
|
+
*/
|
|
14
|
+
function withSlugLookup(api) {
|
|
15
|
+
return Object.assign(api, { async getBySlug({ token = null, organizationId = null, slug, params = {}, options = {} }) {
|
|
16
|
+
if (!slug) throw new Error("Slug is required");
|
|
17
|
+
return api.request("GET", `${api.baseUrl}/slug/${slug}`, {
|
|
18
|
+
token,
|
|
19
|
+
organizationId,
|
|
20
|
+
params,
|
|
21
|
+
options
|
|
22
|
+
});
|
|
23
|
+
} });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
//#endregion
|
|
27
|
+
export { withSlugLookup };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ApiResponse, BaseApi, PaginatedResponse, QueryParams, ScopedArgs } from "../api.js";
|
|
2
|
+
|
|
3
|
+
//#region src/presets/soft-delete.d.ts
|
|
4
|
+
interface SoftDeleteMethods<TDoc> {
|
|
5
|
+
/** List soft-deleted docs. Backend mounts `GET /:resource/deleted`. */
|
|
6
|
+
getDeleted(args?: ScopedArgs & {
|
|
7
|
+
params?: QueryParams;
|
|
8
|
+
}): Promise<PaginatedResponse<TDoc>>;
|
|
9
|
+
/** Undo a soft-delete. Backend mounts `POST /:resource/:id/restore`. */
|
|
10
|
+
restore(args: ScopedArgs & {
|
|
11
|
+
id: string;
|
|
12
|
+
}): Promise<ApiResponse<TDoc>>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Adds soft-delete preset methods to a BaseApi.
|
|
16
|
+
*
|
|
17
|
+
* Mirrors arc's server-side `softDelete` preset routes. Use when your resource
|
|
18
|
+
* is registered with `presets: ['softDelete']`. With this preset, `delete()`
|
|
19
|
+
* becomes soft-delete on the backend and {@link SoftDeleteMethods.restore}
|
|
20
|
+
* undoes it; {@link SoftDeleteMethods.getDeleted} lists the tombstones.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* import { createCrudApi } from '@classytic/arc-next/api';
|
|
24
|
+
* import { withSoftDelete } from '@classytic/arc-next/presets/soft-delete';
|
|
25
|
+
*
|
|
26
|
+
* const todos = withSoftDelete(createCrudApi<Todo>('todos'));
|
|
27
|
+
* await todos.delete({ id }); // soft delete (backend)
|
|
28
|
+
* await todos.restore({ id }); // undo
|
|
29
|
+
* const trash = await todos.getDeleted();
|
|
30
|
+
*/
|
|
31
|
+
declare function withSoftDelete<TDoc, TCreate, TUpdate>(api: BaseApi<TDoc, TCreate, TUpdate>): BaseApi<TDoc, TCreate, TUpdate> & SoftDeleteMethods<TDoc>;
|
|
32
|
+
//#endregion
|
|
33
|
+
export { SoftDeleteMethods, withSoftDelete };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
//#region src/presets/soft-delete.ts
|
|
2
|
+
/**
|
|
3
|
+
* Adds soft-delete preset methods to a BaseApi.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors arc's server-side `softDelete` preset routes. Use when your resource
|
|
6
|
+
* is registered with `presets: ['softDelete']`. With this preset, `delete()`
|
|
7
|
+
* becomes soft-delete on the backend and {@link SoftDeleteMethods.restore}
|
|
8
|
+
* undoes it; {@link SoftDeleteMethods.getDeleted} lists the tombstones.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* import { createCrudApi } from '@classytic/arc-next/api';
|
|
12
|
+
* import { withSoftDelete } from '@classytic/arc-next/presets/soft-delete';
|
|
13
|
+
*
|
|
14
|
+
* const todos = withSoftDelete(createCrudApi<Todo>('todos'));
|
|
15
|
+
* await todos.delete({ id }); // soft delete (backend)
|
|
16
|
+
* await todos.restore({ id }); // undo
|
|
17
|
+
* const trash = await todos.getDeleted();
|
|
18
|
+
*/
|
|
19
|
+
function withSoftDelete(api) {
|
|
20
|
+
return Object.assign(api, {
|
|
21
|
+
async getDeleted({ token = null, organizationId = null, params = {}, options = {} } = {}) {
|
|
22
|
+
const merged = {
|
|
23
|
+
...api.config.defaultParams,
|
|
24
|
+
...params
|
|
25
|
+
};
|
|
26
|
+
return api.request("GET", `${api.baseUrl}/deleted`, {
|
|
27
|
+
token,
|
|
28
|
+
organizationId,
|
|
29
|
+
params: merged,
|
|
30
|
+
options
|
|
31
|
+
});
|
|
32
|
+
},
|
|
33
|
+
async restore({ token = null, organizationId = null, id, options = {} }) {
|
|
34
|
+
if (!id) throw new Error("ID is required");
|
|
35
|
+
return api.request("POST", `${api.baseUrl}/${id}/restore`, {
|
|
36
|
+
token,
|
|
37
|
+
organizationId,
|
|
38
|
+
options
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
//#endregion
|
|
45
|
+
export { withSoftDelete };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ApiResponse, BaseApi, PaginatedResponse, QueryParams, ScopedArgs } from "../api.js";
|
|
2
|
+
|
|
3
|
+
//#region src/presets/tree.d.ts
|
|
4
|
+
interface TreeMethods<TDoc> {
|
|
5
|
+
/** Fetch the full hierarchy. Backend mounts `GET /:resource/tree`. */
|
|
6
|
+
getTree(args?: ScopedArgs & {
|
|
7
|
+
params?: QueryParams;
|
|
8
|
+
}): Promise<ApiResponse<TDoc[]>>;
|
|
9
|
+
/** Fetch direct children of a node. Backend mounts `GET /:resource/:id/children`. */
|
|
10
|
+
getChildren(args: ScopedArgs & {
|
|
11
|
+
parentId: string;
|
|
12
|
+
params?: QueryParams;
|
|
13
|
+
}): Promise<PaginatedResponse<TDoc>>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Adds tree preset methods to a BaseApi.
|
|
17
|
+
*
|
|
18
|
+
* Mirrors arc's `tree` preset — for resources with a `parentId` field, exposes
|
|
19
|
+
* `getTree` (full hierarchy) and `getChildren` (one level deep). Backend caps
|
|
20
|
+
* tree depth via the preset config.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* import { withTree } from '@classytic/arc-next/presets/tree';
|
|
24
|
+
* const categories = withTree(createCrudApi<Category>('categories'));
|
|
25
|
+
*
|
|
26
|
+
* const root = await categories.getTree();
|
|
27
|
+
* const kids = await categories.getChildren({ parentId: 'engineering' });
|
|
28
|
+
*/
|
|
29
|
+
declare function withTree<TDoc, TCreate, TUpdate>(api: BaseApi<TDoc, TCreate, TUpdate>): BaseApi<TDoc, TCreate, TUpdate> & TreeMethods<TDoc>;
|
|
30
|
+
//#endregion
|
|
31
|
+
export { TreeMethods, withTree };
|