@classytic/arc-next 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -22
- package/dist/api.d.ts +1 -1
- package/dist/api.js +9 -9
- package/dist/client.d.ts +43 -6
- package/dist/client.js +74 -16
- package/dist/hooks.d.ts +1 -1
- package/dist/hooks.js +63 -45
- package/dist/mutation.d.ts +1 -4
- package/dist/mutation.js +1 -4
- package/dist/prefetch.d.ts +25 -2
- package/dist/prefetch.js +29 -14
- package/dist/query.d.ts +12 -7
- package/dist/query.js +18 -8
- package/dist/sse.d.ts +11 -4
- package/dist/sse.js +7 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -440,34 +440,55 @@ Subscribe to Arc SSE events with auto-reconnect and query invalidation:
|
|
|
440
440
|
```ts
|
|
441
441
|
import { useEventStream } from "@classytic/arc-next/sse";
|
|
442
442
|
|
|
443
|
-
|
|
443
|
+
// Global stream — all events (matches Arc's /events/stream)
|
|
444
|
+
const { isConnected } = useEventStream({
|
|
445
|
+
invalidateQueries: [agentKeys.lists()],
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
// Filtered by resource — auto-generates patterns: ['agents.*']
|
|
449
|
+
const { lastEvent } = useEventStream({
|
|
444
450
|
resource: "agents",
|
|
445
|
-
patterns: ["agents.created", "agents.updated"],
|
|
446
451
|
invalidateQueries: [agentKeys.lists()],
|
|
447
452
|
});
|
|
453
|
+
|
|
454
|
+
// Custom SSE path or explicit patterns
|
|
455
|
+
const { isConnected } = useEventStream({
|
|
456
|
+
path: "/api/v2/events",
|
|
457
|
+
patterns: ["orders.created", "orders.updated"],
|
|
458
|
+
});
|
|
448
459
|
```
|
|
449
460
|
|
|
450
461
|
### Query Keys (`KEYS`)
|
|
451
462
|
|
|
452
463
|
```ts
|
|
453
|
-
KEYS.all
|
|
454
|
-
KEYS.lists()
|
|
455
|
-
KEYS.list(params)
|
|
456
|
-
KEYS.details()
|
|
457
|
-
KEYS.detail(id)
|
|
458
|
-
KEYS.
|
|
459
|
-
KEYS.
|
|
464
|
+
KEYS.all // ["products"]
|
|
465
|
+
KEYS.lists() // ["products", "list"]
|
|
466
|
+
KEYS.list(params) // ["products", "list", params]
|
|
467
|
+
KEYS.details() // ["products", "detail"]
|
|
468
|
+
KEYS.detail(id) // ["products", "detail", id]
|
|
469
|
+
KEYS.scopedDetail(id, orgId) // ["products", "detail", id, { _org: orgId }] (or bare when null)
|
|
470
|
+
KEYS.custom("stats", orgId) // ["products", "stats", orgId]
|
|
471
|
+
KEYS.scopedList("tenant", params) // ["products", "list", { _scope: "tenant", ...params }]
|
|
460
472
|
```
|
|
461
473
|
|
|
462
474
|
### Cache Utilities (`cache`)
|
|
463
475
|
|
|
464
476
|
```ts
|
|
465
|
-
|
|
466
|
-
await cache.invalidateLists(queryClient);
|
|
467
|
-
await cache.invalidateDetail(queryClient, id);
|
|
477
|
+
// Bare (single-tenant or public)
|
|
468
478
|
cache.setDetail(queryClient, id, data);
|
|
469
|
-
cache.getDetail(queryClient, id);
|
|
479
|
+
cache.getDetail(queryClient, id);
|
|
470
480
|
cache.removeDetail(queryClient, id);
|
|
481
|
+
await cache.invalidateDetail(queryClient, id); // prefix-matches ALL scoped variants
|
|
482
|
+
|
|
483
|
+
// Tenant-scoped (multi-tenant — isolated per org)
|
|
484
|
+
cache.setScopedDetail(queryClient, id, orgId, data);
|
|
485
|
+
cache.getScopedDetail(queryClient, id, orgId);
|
|
486
|
+
cache.removeScopedDetail(queryClient, id, orgId);
|
|
487
|
+
await cache.invalidateScopedDetail(queryClient, id, orgId);
|
|
488
|
+
|
|
489
|
+
// Global
|
|
490
|
+
await cache.invalidateAll(queryClient);
|
|
491
|
+
await cache.invalidateLists(queryClient);
|
|
471
492
|
```
|
|
472
493
|
|
|
473
494
|
### `getQueryClient(overrides?)`
|
|
@@ -521,7 +542,17 @@ export default async function ProductsPage() {
|
|
|
521
542
|
}
|
|
522
543
|
```
|
|
523
544
|
|
|
524
|
-
**Methods:** `prefetchList
|
|
545
|
+
**Methods:** `prefetchList`, `prefetchDetail`, `prefetchBySlug`, `prefetchDeleted`, `prefetchTree`
|
|
546
|
+
|
|
547
|
+
All accept auth options for protected routes:
|
|
548
|
+
|
|
549
|
+
```ts
|
|
550
|
+
await prefetcher.prefetchList(queryClient, { limit: 20 }, {
|
|
551
|
+
token: serverToken, // for bearer/header auth
|
|
552
|
+
organizationId: "org-1", // for multi-tenant
|
|
553
|
+
headers: { "x-api-key": apiKey }, // for custom header auth
|
|
554
|
+
});
|
|
555
|
+
```
|
|
525
556
|
|
|
526
557
|
## Custom Mutations
|
|
527
558
|
|
|
@@ -620,16 +651,30 @@ By default, `configureClient()` sets a single global `baseUrl`. Use `createClien
|
|
|
620
651
|
|
|
621
652
|
### Create isolated clients
|
|
622
653
|
|
|
654
|
+
Each client gets its own `baseUrl`, auth, and headers — fully independent from the global config:
|
|
655
|
+
|
|
623
656
|
```ts
|
|
624
657
|
import { createClient } from "@classytic/arc-next/client";
|
|
625
|
-
import { toast } from "sonner";
|
|
626
|
-
import { useRouter } from "next/navigation";
|
|
627
658
|
|
|
659
|
+
// Bearer auth for main API
|
|
660
|
+
const mainClient = createClient({
|
|
661
|
+
baseUrl: "https://api.example.com",
|
|
662
|
+
getToken: () => session.jwt,
|
|
663
|
+
getOrgId: () => currentOrg.id,
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
// API key auth for analytics (no token needed — hooks auto-enable)
|
|
628
667
|
const analyticsClient = createClient({
|
|
629
|
-
baseUrl: "https://analytics.
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
668
|
+
baseUrl: "https://analytics.internal",
|
|
669
|
+
authMode: "header",
|
|
670
|
+
getToken: () => env.ANALYTICS_KEY,
|
|
671
|
+
headerName: "x-analytics-key",
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
// Cookie auth for auth service
|
|
675
|
+
const authClient = createClient({
|
|
676
|
+
baseUrl: "https://auth.example.com",
|
|
677
|
+
authMode: "cookie",
|
|
633
678
|
});
|
|
634
679
|
```
|
|
635
680
|
|
|
@@ -763,19 +808,20 @@ const adminApi = createCrudApi("users", {
|
|
|
763
808
|
|
|
764
809
|
- **CRUD Factory** — `createCrudApi` + `createCrudHooks` generates typed API clients and React Query hooks
|
|
765
810
|
- **Optimistic Updates** — Create, update, delete with instant UI feedback and automatic rollback
|
|
766
|
-
- **Multi-Tenant Scoping** —
|
|
811
|
+
- **Multi-Tenant Scoping** — `scopedDetail(id, orgId)` + `scopedList` isolate cache per tenant. Scoped cache utils for reads/writes. Navigation prefill is tenant-aware.
|
|
767
812
|
- **Pagination Normalization** — Handles `docs`/`data`/`items`/`results` + any custom key, offset/keyset/aggregate pagination
|
|
768
813
|
- **Detail Cache Prefilling** — List results auto-populate detail query cache
|
|
769
814
|
- **React 19 Transitions** — `useMutationWithTransition` wraps invalidation in `startTransition`
|
|
770
815
|
- **Cookie, Bearer & Header Auth** — `authMode: 'cookie'` / `'bearer'` / `'header'` (custom header like `x-api-key`)
|
|
771
816
|
- **Custom ID Fields** — `idField` on `createCrudHooks` for resources keyed by `sku`, `slug`, `code`, etc.
|
|
772
817
|
- **Preset Hooks** — `useDeleted`, `useBulkActions`, `useDetailBySlug`, `useTree`, `useChildren`, `useFindBy`
|
|
773
|
-
- **SSE Real-Time** — `useEventStream` with auto-reconnect, pattern
|
|
818
|
+
- **SSE Real-Time** — `useEventStream` with auto-reconnect, auto-pattern derivation from `resource`, query invalidation. Works with zero config or custom `path`.
|
|
774
819
|
- **Infinite Scroll** — `maxPages` for memory management with automatic scroll-back support
|
|
775
820
|
- **Idempotency** — `autoIdempotency` generates retry-safe keys at mutation level
|
|
776
821
|
- **API Versioning** — `apiVersion` sends `Accept-Version` header
|
|
777
822
|
- **SSR Prefetch** — `createCrudPrefetcher` + `prefetchBySlug` / `prefetchDeleted` / `prefetchTree`
|
|
778
823
|
- **SSR Safety** — warns when `configureClient`/`configureAuth` called on the server
|
|
824
|
+
- **Per-Client Auth** — `createClient({ getToken, getOrgId, headerName })` — each backend gets its own auth, queries auto-enable
|
|
779
825
|
- **Multi-Client** — `createClient()` for multiple API backends side by side
|
|
780
826
|
- **Pluggable Toast** — `configureToast()` — use sonner, react-hot-toast, or anything
|
|
781
827
|
- **Pluggable Navigation** — `configureNavigation()` — use Next.js, React Router, or any router
|
package/dist/api.d.ts
CHANGED
package/dist/api.js
CHANGED
|
@@ -96,7 +96,7 @@ var BaseApi = class {
|
|
|
96
96
|
if (organizationId) requestOptions.organizationId = organizationId;
|
|
97
97
|
return this.requestFn("GET", url, this.withHeaders(requestOptions));
|
|
98
98
|
}
|
|
99
|
-
async create({ token, organizationId = null, data, options = {} }) {
|
|
99
|
+
async create({ token = null, organizationId = null, data, options = {} }) {
|
|
100
100
|
const requestOptions = {
|
|
101
101
|
body: data,
|
|
102
102
|
...options
|
|
@@ -105,7 +105,7 @@ var BaseApi = class {
|
|
|
105
105
|
if (organizationId) requestOptions.organizationId = organizationId;
|
|
106
106
|
return this.requestFn("POST", this.baseUrl, this.withHeaders(requestOptions));
|
|
107
107
|
}
|
|
108
|
-
async update({ token, organizationId = null, id, data, options = {} }) {
|
|
108
|
+
async update({ token = null, organizationId = null, id, data, options = {} }) {
|
|
109
109
|
if (!id) throw new Error("ID is required");
|
|
110
110
|
const requestOptions = {
|
|
111
111
|
body: data,
|
|
@@ -115,14 +115,14 @@ var BaseApi = class {
|
|
|
115
115
|
if (organizationId) requestOptions.organizationId = organizationId;
|
|
116
116
|
return this.requestFn("PATCH", `${this.baseUrl}/${id}`, this.withHeaders(requestOptions));
|
|
117
117
|
}
|
|
118
|
-
async delete({ token, organizationId = null, id, options = {} }) {
|
|
118
|
+
async delete({ token = null, organizationId = null, id, options = {} }) {
|
|
119
119
|
if (!id) throw new Error("ID is required");
|
|
120
120
|
const requestOptions = { ...options };
|
|
121
121
|
if (token) requestOptions.token = token;
|
|
122
122
|
if (organizationId) requestOptions.organizationId = organizationId;
|
|
123
123
|
return this.requestFn("DELETE", `${this.baseUrl}/${id}`, this.withHeaders(requestOptions));
|
|
124
124
|
}
|
|
125
|
-
async upload({ token, organizationId = null, data, id, path, options = {} }) {
|
|
125
|
+
async upload({ token = null, organizationId = null, data, id, path, options = {} }) {
|
|
126
126
|
const suffix = path ?? (id ? `${id}/upload` : void 0);
|
|
127
127
|
const url = suffix ? `${this.baseUrl}/${suffix}` : this.baseUrl;
|
|
128
128
|
const requestOptions = {
|
|
@@ -167,7 +167,7 @@ var BaseApi = class {
|
|
|
167
167
|
if (organizationId) requestOptions.organizationId = organizationId;
|
|
168
168
|
return this.requestFn("GET", `${this.baseUrl}?${queryString}`, this.withHeaders(requestOptions));
|
|
169
169
|
}
|
|
170
|
-
async request(method, endpoint, { token, organizationId = null, data, params, options = {} } = {}) {
|
|
170
|
+
async request(method, endpoint, { token = null, organizationId = null, data, params, options = {} } = {}) {
|
|
171
171
|
let url = endpoint;
|
|
172
172
|
if (params) {
|
|
173
173
|
const processedParams = this.prepareParams(params);
|
|
@@ -197,14 +197,14 @@ var BaseApi = class {
|
|
|
197
197
|
if (organizationId) requestOptions.organizationId = organizationId;
|
|
198
198
|
return this.requestFn("GET", `${this.baseUrl}/deleted?${queryString}`, this.withHeaders(requestOptions));
|
|
199
199
|
}
|
|
200
|
-
async restore({ token, organizationId = null, id, options = {} }) {
|
|
200
|
+
async restore({ token = null, organizationId = null, id, options = {} }) {
|
|
201
201
|
if (!id) throw new Error("ID is required");
|
|
202
202
|
const requestOptions = { ...options };
|
|
203
203
|
if (token) requestOptions.token = token;
|
|
204
204
|
if (organizationId) requestOptions.organizationId = organizationId;
|
|
205
205
|
return this.requestFn("POST", `${this.baseUrl}/${id}/restore`, this.withHeaders(requestOptions));
|
|
206
206
|
}
|
|
207
|
-
async bulkCreate({ token, organizationId = null, data, options = {} }) {
|
|
207
|
+
async bulkCreate({ token = null, organizationId = null, data, options = {} }) {
|
|
208
208
|
const requestOptions = {
|
|
209
209
|
body: data,
|
|
210
210
|
...options
|
|
@@ -213,7 +213,7 @@ var BaseApi = class {
|
|
|
213
213
|
if (organizationId) requestOptions.organizationId = organizationId;
|
|
214
214
|
return this.requestFn("POST", `${this.baseUrl}/bulk`, this.withHeaders(requestOptions));
|
|
215
215
|
}
|
|
216
|
-
async bulkUpdate({ token, organizationId = null, filter, data, options = {} }) {
|
|
216
|
+
async bulkUpdate({ token = null, organizationId = null, filter, data, options = {} }) {
|
|
217
217
|
const requestOptions = {
|
|
218
218
|
body: {
|
|
219
219
|
filter,
|
|
@@ -225,7 +225,7 @@ var BaseApi = class {
|
|
|
225
225
|
if (organizationId) requestOptions.organizationId = organizationId;
|
|
226
226
|
return this.requestFn("PATCH", `${this.baseUrl}/bulk`, this.withHeaders(requestOptions));
|
|
227
227
|
}
|
|
228
|
-
async bulkDelete({ token, organizationId = null, filter, options = {} }) {
|
|
228
|
+
async bulkDelete({ token = null, organizationId = null, filter, options = {} }) {
|
|
229
229
|
const requestOptions = {
|
|
230
230
|
body: { filter },
|
|
231
231
|
...options
|
package/dist/client.d.ts
CHANGED
|
@@ -99,6 +99,8 @@ declare function configureClient(config: ClientConfig): void;
|
|
|
99
99
|
* Get the configured auth mode. Returns 'bearer' if not configured.
|
|
100
100
|
*/
|
|
101
101
|
declare function getAuthMode(): 'bearer' | 'cookie' | 'header';
|
|
102
|
+
/** Get the configured base URL. Returns empty string if not configured. */
|
|
103
|
+
declare function getBaseUrl(): string;
|
|
102
104
|
/** Whether auto-idempotency is enabled on the global client. */
|
|
103
105
|
declare function isAutoIdempotency(): boolean;
|
|
104
106
|
interface AuthConfig {
|
|
@@ -132,6 +134,16 @@ declare function getAuthContext(): {
|
|
|
132
134
|
organizationId: string | null;
|
|
133
135
|
};
|
|
134
136
|
type HttpMethod = 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
|
|
137
|
+
/** Returned by `handleApiRequest` for PDF, image, and CSV responses. */
|
|
138
|
+
interface BlobResponse {
|
|
139
|
+
data: Blob;
|
|
140
|
+
response: Response;
|
|
141
|
+
}
|
|
142
|
+
/** Returned by `handleApiRequest` for text/plain and text/html responses. */
|
|
143
|
+
interface TextResponse {
|
|
144
|
+
data: string;
|
|
145
|
+
response: Response;
|
|
146
|
+
}
|
|
135
147
|
interface ApiRequestOptions {
|
|
136
148
|
body?: unknown;
|
|
137
149
|
token?: string | null;
|
|
@@ -147,27 +159,52 @@ interface ApiRequestOptions {
|
|
|
147
159
|
interface ArcClientConfig extends ClientConfig {
|
|
148
160
|
toast?: ToastHandler;
|
|
149
161
|
navigation?: UseRouterHook;
|
|
162
|
+
/** Per-client token provider. Overrides global configureAuth().getToken. */
|
|
163
|
+
getToken?: () => string | null;
|
|
164
|
+
/** Per-client org ID provider. Overrides global configureAuth().getOrgId. */
|
|
165
|
+
getOrgId?: () => string | null;
|
|
166
|
+
/** Per-client custom auth header name. Used when authMode is 'header'. */
|
|
167
|
+
headerName?: string;
|
|
150
168
|
}
|
|
151
169
|
interface ArcClient {
|
|
152
170
|
request: <T = unknown>(method: HttpMethod, endpoint: string, options?: ApiRequestOptions) => Promise<T>;
|
|
153
171
|
config: ClientConfig;
|
|
154
172
|
toast?: ToastHandler;
|
|
155
173
|
navigation?: UseRouterHook;
|
|
174
|
+
/** Per-client auth context. Falls back to global configureAuth() when not set. */
|
|
175
|
+
auth?: {
|
|
176
|
+
getToken?: () => string | null;
|
|
177
|
+
getOrgId?: () => string | null;
|
|
178
|
+
headerName?: string;
|
|
179
|
+
};
|
|
156
180
|
}
|
|
157
181
|
/**
|
|
158
182
|
* Create an isolated API client for a specific backend.
|
|
159
|
-
* Use this when your app needs to talk to multiple APIs.
|
|
183
|
+
* Use this when your app needs to talk to multiple APIs with different auth.
|
|
160
184
|
*
|
|
161
185
|
* @example
|
|
186
|
+
* // Bearer auth for main API
|
|
187
|
+
* const mainClient = createClient({
|
|
188
|
+
* baseUrl: 'https://api.example.com',
|
|
189
|
+
* getToken: () => session.accessToken,
|
|
190
|
+
* });
|
|
191
|
+
*
|
|
192
|
+
* // API key auth for analytics
|
|
162
193
|
* const analyticsClient = createClient({
|
|
163
194
|
* baseUrl: 'https://analytics.example.com',
|
|
164
|
-
*
|
|
165
|
-
*
|
|
195
|
+
* authMode: 'header',
|
|
196
|
+
* getToken: () => env.ANALYTICS_KEY,
|
|
197
|
+
* headerName: 'x-api-key',
|
|
166
198
|
* });
|
|
167
|
-
*
|
|
168
|
-
* const eventsApi = createCrudApi('events', { client: analyticsClient });
|
|
169
199
|
*/
|
|
170
200
|
declare function createClient(config: ArcClientConfig): ArcClient;
|
|
201
|
+
/**
|
|
202
|
+
* Get auth context for a specific client instance, falling back to global.
|
|
203
|
+
*/
|
|
204
|
+
declare function getClientAuthContext(client?: ArcClient): {
|
|
205
|
+
token: string | null;
|
|
206
|
+
organizationId: string | null;
|
|
207
|
+
};
|
|
171
208
|
/**
|
|
172
209
|
* Universal API request handler.
|
|
173
210
|
* Handles JSON, binary (PDF, images), CSV, and text responses.
|
|
@@ -195,4 +232,4 @@ declare function handleApiRequest<T = unknown>(method: HttpMethod, endpoint: str
|
|
|
195
232
|
*/
|
|
196
233
|
declare function createQueryString<T extends Record<string, unknown>>(params?: T): string;
|
|
197
234
|
//#endregion
|
|
198
|
-
export { ApiRequestOptions, ArcApiError, ArcApiErrorOptions, ArcClient, ArcClientConfig, AuthConfig, ClientConfig, HttpMethod, ToastHandler, UseRouterHook, configureAuth, configureClient, createClient, createQueryString, getAuthContext, getAuthMode, handleApiRequest, isArcApiError, isAutoIdempotency };
|
|
235
|
+
export { ApiRequestOptions, ArcApiError, ArcApiErrorOptions, ArcClient, ArcClientConfig, AuthConfig, BlobResponse, ClientConfig, HttpMethod, TextResponse, ToastHandler, UseRouterHook, configureAuth, configureClient, createClient, createQueryString, getAuthContext, getAuthMode, getBaseUrl, getClientAuthContext, handleApiRequest, isArcApiError, isAutoIdempotency };
|
package/dist/client.js
CHANGED
|
@@ -64,6 +64,10 @@ function configureClient(config) {
|
|
|
64
64
|
function getAuthMode() {
|
|
65
65
|
return clientConfig?.authMode ?? "bearer";
|
|
66
66
|
}
|
|
67
|
+
/** Get the configured base URL. Returns empty string if not configured. */
|
|
68
|
+
function getBaseUrl() {
|
|
69
|
+
return clientConfig?.baseUrl ?? "";
|
|
70
|
+
}
|
|
67
71
|
/** Whether auto-idempotency is enabled on the global client. */
|
|
68
72
|
function isAutoIdempotency() {
|
|
69
73
|
return clientConfig?.autoIdempotency ?? false;
|
|
@@ -100,25 +104,62 @@ function getAuthContext() {
|
|
|
100
104
|
}
|
|
101
105
|
/**
|
|
102
106
|
* Create an isolated API client for a specific backend.
|
|
103
|
-
* Use this when your app needs to talk to multiple APIs.
|
|
107
|
+
* Use this when your app needs to talk to multiple APIs with different auth.
|
|
104
108
|
*
|
|
105
109
|
* @example
|
|
110
|
+
* // Bearer auth for main API
|
|
111
|
+
* const mainClient = createClient({
|
|
112
|
+
* baseUrl: 'https://api.example.com',
|
|
113
|
+
* getToken: () => session.accessToken,
|
|
114
|
+
* });
|
|
115
|
+
*
|
|
116
|
+
* // API key auth for analytics
|
|
106
117
|
* const analyticsClient = createClient({
|
|
107
118
|
* baseUrl: 'https://analytics.example.com',
|
|
108
|
-
*
|
|
109
|
-
*
|
|
119
|
+
* authMode: 'header',
|
|
120
|
+
* getToken: () => env.ANALYTICS_KEY,
|
|
121
|
+
* headerName: 'x-api-key',
|
|
110
122
|
* });
|
|
111
|
-
*
|
|
112
|
-
* const eventsApi = createCrudApi('events', { client: analyticsClient });
|
|
113
123
|
*/
|
|
114
124
|
function createClient(config) {
|
|
115
|
-
const { toast, navigation, ...clientCfg } = config;
|
|
125
|
+
const { toast, navigation, getToken, getOrgId, headerName, ...clientCfg } = config;
|
|
126
|
+
const clientAuth = getToken || getOrgId || headerName ? {
|
|
127
|
+
getToken,
|
|
128
|
+
getOrgId,
|
|
129
|
+
headerName
|
|
130
|
+
} : void 0;
|
|
116
131
|
return {
|
|
117
|
-
request: (method, endpoint, options) =>
|
|
132
|
+
request: (method, endpoint, options) => {
|
|
133
|
+
if (clientAuth) {
|
|
134
|
+
const resolved = { ...options };
|
|
135
|
+
if (resolved.token === void 0 && clientAuth.getToken) resolved.token = clientAuth.getToken();
|
|
136
|
+
if (resolved.organizationId === void 0 && clientAuth.getOrgId) resolved.organizationId = clientAuth.getOrgId();
|
|
137
|
+
if (clientCfg.authMode === "header" && resolved.token) {
|
|
138
|
+
resolved.headerOptions = {
|
|
139
|
+
[clientAuth.headerName ?? "x-api-key"]: resolved.token,
|
|
140
|
+
...resolved.headerOptions ?? {}
|
|
141
|
+
};
|
|
142
|
+
resolved.token = void 0;
|
|
143
|
+
}
|
|
144
|
+
return executeRequest(clientCfg, method, endpoint, resolved);
|
|
145
|
+
}
|
|
146
|
+
return executeRequest(clientCfg, method, endpoint, options);
|
|
147
|
+
},
|
|
118
148
|
config: clientCfg,
|
|
119
149
|
toast,
|
|
120
|
-
navigation
|
|
150
|
+
navigation,
|
|
151
|
+
auth: clientAuth
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Get auth context for a specific client instance, falling back to global.
|
|
156
|
+
*/
|
|
157
|
+
function getClientAuthContext(client) {
|
|
158
|
+
if (client?.auth) return {
|
|
159
|
+
token: client.auth.getToken?.() ?? authConfig?.getToken?.() ?? null,
|
|
160
|
+
organizationId: client.auth.getOrgId?.() ?? authConfig?.getOrgId?.() ?? null
|
|
121
161
|
};
|
|
162
|
+
return getAuthContext();
|
|
122
163
|
}
|
|
123
164
|
async function executeRequest(config, method, endpoint, options = {}) {
|
|
124
165
|
const { body, token, organizationId, revalidate, headerOptions, tags, cache, signal, idempotencyKey } = options;
|
|
@@ -158,8 +199,21 @@ async function executeRequest(config, method, endpoint, options = {}) {
|
|
|
158
199
|
};
|
|
159
200
|
const response = await fetch(`${config.baseUrl}${endpoint}`, fetchOptions);
|
|
160
201
|
if (!response.ok) {
|
|
161
|
-
|
|
162
|
-
|
|
202
|
+
let json = null;
|
|
203
|
+
let errorMessage = response.statusText;
|
|
204
|
+
try {
|
|
205
|
+
json = await response.clone().json();
|
|
206
|
+
errorMessage = json?.message || response.statusText;
|
|
207
|
+
} catch {
|
|
208
|
+
try {
|
|
209
|
+
const text = await response.text();
|
|
210
|
+
if (text) {
|
|
211
|
+
json = { rawBody: text };
|
|
212
|
+
errorMessage = text.slice(0, 200) || response.statusText;
|
|
213
|
+
}
|
|
214
|
+
} catch {}
|
|
215
|
+
}
|
|
216
|
+
throw new ArcApiError(errorMessage, {
|
|
163
217
|
status: response.status,
|
|
164
218
|
statusText: response.statusText,
|
|
165
219
|
json,
|
|
@@ -187,11 +241,15 @@ async function executeRequest(config, method, endpoint, options = {}) {
|
|
|
187
241
|
data: await response.clone().blob(),
|
|
188
242
|
response
|
|
189
243
|
};
|
|
190
|
-
} catch {
|
|
191
|
-
|
|
192
|
-
data
|
|
193
|
-
|
|
194
|
-
|
|
244
|
+
} catch (blobError) {
|
|
245
|
+
try {
|
|
246
|
+
data = {
|
|
247
|
+
data: await response.text(),
|
|
248
|
+
response
|
|
249
|
+
};
|
|
250
|
+
} catch {
|
|
251
|
+
throw new Error(`Failed to parse response body from ${method} ${endpoint}: blob error: ${blobError instanceof Error ? blobError.message : String(blobError)}`);
|
|
252
|
+
}
|
|
195
253
|
}
|
|
196
254
|
return data;
|
|
197
255
|
} catch (error) {
|
|
@@ -256,4 +314,4 @@ function createQueryString(params = {}) {
|
|
|
256
314
|
}
|
|
257
315
|
|
|
258
316
|
//#endregion
|
|
259
|
-
export { ArcApiError, configureAuth, configureClient, createClient, createQueryString, getAuthContext, getAuthMode, handleApiRequest, isArcApiError, isAutoIdempotency };
|
|
317
|
+
export { ArcApiError, configureAuth, configureClient, createClient, createQueryString, getAuthContext, getAuthMode, getBaseUrl, getClientAuthContext, handleApiRequest, isArcApiError, isAutoIdempotency };
|
package/dist/hooks.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ArcClient, UseRouterHook } from "./client.js";
|
|
2
2
|
import { BaseApi, FilterOperator } from "./api.js";
|
|
3
|
-
import { CacheUtils, DetailQueryOptions, DetailQueryResult, InfiniteListQueryOptions, InfiniteListQueryResult, ListQueryOptions, ListQueryResult, QueryKeys } from "./query.js";
|
|
4
3
|
import { MutationCallbacks, MutationMessages, TransitionMutationReturn } from "./mutation.js";
|
|
4
|
+
import { CacheUtils, DetailQueryOptions, DetailQueryResult, InfiniteListQueryOptions, InfiniteListQueryResult, ListQueryOptions, ListQueryResult, QueryKeys } from "./query.js";
|
|
5
5
|
import { QueryKey } from "@tanstack/react-query";
|
|
6
6
|
|
|
7
7
|
//#region src/hooks.d.ts
|
package/dist/hooks.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { getAuthMode, getClientAuthContext } from "./client.js";
|
|
4
4
|
import { isKeysetPagination, isOffsetPagination } from "./api.js";
|
|
5
5
|
import { DEFAULT_QUERY_CONFIG, createCacheUtils, createQueryKeys, extractItem, getItemId, updateListCache, useDetailQuery, useInfiniteListQuery, useListQuery } from "./query.js";
|
|
6
6
|
import { useMutationWithTransition, useOptimisticMutation } from "./mutation.js";
|
|
@@ -21,12 +21,17 @@ let useRouterHook = null;
|
|
|
21
21
|
function configureNavigation(hook) {
|
|
22
22
|
useRouterHook = hook;
|
|
23
23
|
}
|
|
24
|
-
function createEnabledRule(token, options, authMode = getAuthMode()) {
|
|
24
|
+
function createEnabledRule(token, options, authMode = getAuthMode(), hasStaticAuth = false) {
|
|
25
25
|
if (authMode === "cookie" || options.public) return options.enabled ?? true;
|
|
26
|
+
if (hasStaticAuth) return options.enabled ?? true;
|
|
26
27
|
return options.enabled !== void 0 ? options.enabled && !!token : !!token;
|
|
27
28
|
}
|
|
28
29
|
function createCrudHooks({ api, entityKey, singular, plural, idField, defaults = {}, callbacks = {}, client }) {
|
|
29
30
|
const pluralName = plural ?? `${singular}s`;
|
|
31
|
+
/** Resolve auth context — per-client auth takes priority over global */
|
|
32
|
+
const resolveAuth = () => getClientAuthContext(client);
|
|
33
|
+
/** Whether auth is provided via static config (headers, internalApiKey, per-client auth) — no token needed for enablement */
|
|
34
|
+
const hasStaticAuth = !!(client?.config?.defaultHeaders || client?.config?.internalApiKey || client?.auth);
|
|
30
35
|
/** Extract ID from an item using configured idField, falling back to _id → id */
|
|
31
36
|
function resolveItemId(item) {
|
|
32
37
|
if (!item || typeof item !== "object") return null;
|
|
@@ -60,12 +65,12 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
|
|
|
60
65
|
let token;
|
|
61
66
|
let params;
|
|
62
67
|
let options;
|
|
63
|
-
if (tokenOrParams ===
|
|
68
|
+
if (typeof tokenOrParams === "string" || tokenOrParams === null && maybeOptions !== void 0) {
|
|
64
69
|
token = tokenOrParams;
|
|
65
70
|
params = paramsOrOptions ?? {};
|
|
66
71
|
options = maybeOptions ?? {};
|
|
67
72
|
} else {
|
|
68
|
-
const auth =
|
|
73
|
+
const auth = resolveAuth();
|
|
69
74
|
token = auth.token;
|
|
70
75
|
params = tokenOrParams ?? {};
|
|
71
76
|
options = paramsOrOptions ?? {};
|
|
@@ -91,7 +96,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
|
|
|
91
96
|
...requestOpts
|
|
92
97
|
}
|
|
93
98
|
}),
|
|
94
|
-
enabled: createEnabledRule(token, queryOpts, resolveAuthMode()),
|
|
99
|
+
enabled: createEnabledRule(token, queryOpts, resolveAuthMode(), hasStaticAuth),
|
|
95
100
|
options: {
|
|
96
101
|
staleTime: queryOpts.staleTime ?? config.staleTime,
|
|
97
102
|
gcTime: queryOpts.gcTime ?? config.gcTime,
|
|
@@ -101,7 +106,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
|
|
|
101
106
|
refetchIntervalInBackground: queryOpts.refetchIntervalInBackground
|
|
102
107
|
},
|
|
103
108
|
prefillDetailCache: queryOpts.prefillDetailCache ?? true,
|
|
104
|
-
detailKeyBuilder: (id) => KEYS.
|
|
109
|
+
detailKeyBuilder: (id) => KEYS.scopedDetail(id, organizationId ?? null),
|
|
105
110
|
itemIdResolver: resolveItemId,
|
|
106
111
|
select: queryOpts.select
|
|
107
112
|
});
|
|
@@ -109,11 +114,11 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
|
|
|
109
114
|
function useDetail(id, tokenOrOptions, maybeOptions) {
|
|
110
115
|
let token;
|
|
111
116
|
let options;
|
|
112
|
-
if (tokenOrOptions ===
|
|
117
|
+
if (typeof tokenOrOptions === "string" || tokenOrOptions === null && maybeOptions !== void 0) {
|
|
113
118
|
token = tokenOrOptions;
|
|
114
119
|
options = maybeOptions ?? {};
|
|
115
120
|
} else {
|
|
116
|
-
const auth =
|
|
121
|
+
const auth = resolveAuth();
|
|
117
122
|
token = auth.token;
|
|
118
123
|
options = tokenOrOptions ?? {};
|
|
119
124
|
if (auth.organizationId && !options.organizationId) options = {
|
|
@@ -122,8 +127,9 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
|
|
|
122
127
|
};
|
|
123
128
|
}
|
|
124
129
|
const { organizationId, params: queryParams, request: requestOpts, ...restOptions } = options;
|
|
130
|
+
const detailKey = KEYS.scopedDetail(id || "", organizationId ?? null);
|
|
125
131
|
return useDetailQuery({
|
|
126
|
-
queryKey: queryParams ? [...
|
|
132
|
+
queryKey: queryParams ? [...detailKey, queryParams] : detailKey,
|
|
127
133
|
queryFn: ({ signal }) => api.getById({
|
|
128
134
|
id,
|
|
129
135
|
token,
|
|
@@ -134,7 +140,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
|
|
|
134
140
|
...requestOpts
|
|
135
141
|
}
|
|
136
142
|
}),
|
|
137
|
-
enabled: !!id && createEnabledRule(token, restOptions, resolveAuthMode()),
|
|
143
|
+
enabled: !!id && createEnabledRule(token, restOptions, resolveAuthMode(), hasStaticAuth),
|
|
138
144
|
options: {
|
|
139
145
|
staleTime: restOptions.staleTime ?? config.staleTime,
|
|
140
146
|
gcTime: restOptions.gcTime ?? config.gcTime,
|
|
@@ -197,13 +203,16 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
|
|
|
197
203
|
...item,
|
|
198
204
|
...data
|
|
199
205
|
} : item));
|
|
200
|
-
|
|
206
|
+
const detailUpdater = (current) => current ? {
|
|
201
207
|
...current,
|
|
202
208
|
data: {
|
|
203
209
|
...current.data || {},
|
|
204
210
|
...data
|
|
205
211
|
}
|
|
206
|
-
} : current
|
|
212
|
+
} : current;
|
|
213
|
+
queryClient.getQueriesData({ queryKey: KEYS.detail(id) }).forEach(([qKey, qData]) => {
|
|
214
|
+
if (qData) queryClient.setQueryData(qKey, detailUpdater);
|
|
215
|
+
});
|
|
207
216
|
return updated;
|
|
208
217
|
},
|
|
209
218
|
onSuccess: (raw, { id, data: updateData }) => {
|
|
@@ -276,8 +285,8 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
|
|
|
276
285
|
},
|
|
277
286
|
toastHandler: instanceToast
|
|
278
287
|
});
|
|
279
|
-
const
|
|
280
|
-
const auth =
|
|
288
|
+
const resolveActionAuth = useCallback((params) => {
|
|
289
|
+
const auth = resolveAuth();
|
|
281
290
|
return {
|
|
282
291
|
...params,
|
|
283
292
|
token: params.token ?? auth.token,
|
|
@@ -288,7 +297,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
|
|
|
288
297
|
create: useCallback(async (params, options) => {
|
|
289
298
|
silentRef.current = options?.silent ?? false;
|
|
290
299
|
try {
|
|
291
|
-
const entity = extractItem(await createMutation.mutateAsync(
|
|
300
|
+
const entity = extractItem(await createMutation.mutateAsync(resolveActionAuth(params)));
|
|
292
301
|
options?.onSuccess?.(entity);
|
|
293
302
|
options?.onSettled?.(entity, null);
|
|
294
303
|
return entity;
|
|
@@ -299,11 +308,11 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
|
|
|
299
308
|
} finally {
|
|
300
309
|
silentRef.current = false;
|
|
301
310
|
}
|
|
302
|
-
}, [createMutation,
|
|
311
|
+
}, [createMutation, resolveActionAuth]),
|
|
303
312
|
update: useCallback(async (params, options) => {
|
|
304
313
|
silentRef.current = options?.silent ?? false;
|
|
305
314
|
try {
|
|
306
|
-
const entity = extractItem(await updateMutation.mutateAsync(
|
|
315
|
+
const entity = extractItem(await updateMutation.mutateAsync(resolveActionAuth(params)));
|
|
307
316
|
options?.onSuccess?.(entity);
|
|
308
317
|
options?.onSettled?.(entity, null);
|
|
309
318
|
return entity;
|
|
@@ -314,11 +323,11 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
|
|
|
314
323
|
} finally {
|
|
315
324
|
silentRef.current = false;
|
|
316
325
|
}
|
|
317
|
-
}, [updateMutation,
|
|
326
|
+
}, [updateMutation, resolveActionAuth]),
|
|
318
327
|
remove: useCallback(async (params, options) => {
|
|
319
328
|
silentRef.current = options?.silent ?? false;
|
|
320
329
|
try {
|
|
321
|
-
const result = await deleteMutation.mutateAsync(
|
|
330
|
+
const result = await deleteMutation.mutateAsync(resolveActionAuth(params));
|
|
322
331
|
options?.onSuccess?.(result);
|
|
323
332
|
options?.onSettled?.(result, null);
|
|
324
333
|
return result;
|
|
@@ -329,11 +338,11 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
|
|
|
329
338
|
} finally {
|
|
330
339
|
silentRef.current = false;
|
|
331
340
|
}
|
|
332
|
-
}, [deleteMutation,
|
|
341
|
+
}, [deleteMutation, resolveActionAuth]),
|
|
333
342
|
restore: useCallback(async (params, options) => {
|
|
334
343
|
silentRef.current = options?.silent ?? false;
|
|
335
344
|
try {
|
|
336
|
-
const entity = extractItem(await restoreMutation.mutateAsync(
|
|
345
|
+
const entity = extractItem(await restoreMutation.mutateAsync(resolveActionAuth(params)));
|
|
337
346
|
options?.onSuccess?.(entity);
|
|
338
347
|
options?.onSettled?.(entity, null);
|
|
339
348
|
return entity;
|
|
@@ -344,7 +353,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
|
|
|
344
353
|
} finally {
|
|
345
354
|
silentRef.current = false;
|
|
346
355
|
}
|
|
347
|
-
}, [restoreMutation,
|
|
356
|
+
}, [restoreMutation, resolveActionAuth]),
|
|
348
357
|
isCreating: createMutation.isPending,
|
|
349
358
|
isUpdating: updateMutation.isPending,
|
|
350
359
|
isDeleting: deleteMutation.isPending,
|
|
@@ -356,12 +365,12 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
|
|
|
356
365
|
let token;
|
|
357
366
|
let params;
|
|
358
367
|
let options;
|
|
359
|
-
if (tokenOrParams ===
|
|
368
|
+
if (typeof tokenOrParams === "string" || tokenOrParams === null && maybeOptions !== void 0) {
|
|
360
369
|
token = tokenOrParams;
|
|
361
370
|
params = paramsOrOptions ?? {};
|
|
362
371
|
options = maybeOptions ?? {};
|
|
363
372
|
} else {
|
|
364
|
-
const auth =
|
|
373
|
+
const auth = resolveAuth();
|
|
365
374
|
token = auth.token;
|
|
366
375
|
params = tokenOrParams ?? {};
|
|
367
376
|
options = paramsOrOptions ?? {};
|
|
@@ -393,7 +402,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
|
|
|
393
402
|
}
|
|
394
403
|
});
|
|
395
404
|
},
|
|
396
|
-
enabled: createEnabledRule(token, queryOpts, resolveAuthMode()),
|
|
405
|
+
enabled: createEnabledRule(token, queryOpts, resolveAuthMode(), hasStaticAuth),
|
|
397
406
|
initialPageParam: restParams.after ? restParams.after : 1,
|
|
398
407
|
getNextPageParam: (lastPage) => {
|
|
399
408
|
const page = lastPage;
|
|
@@ -423,7 +432,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
|
|
|
423
432
|
return useMutationWithTransition({
|
|
424
433
|
mutationFn: ({ data, id, path }) => {
|
|
425
434
|
if (!api.upload) return Promise.reject(/* @__PURE__ */ new Error(`[arc-next] "${entityKey}" api does not define an upload method`));
|
|
426
|
-
const auth =
|
|
435
|
+
const auth = resolveAuth();
|
|
427
436
|
return api.upload({
|
|
428
437
|
token: auth.token,
|
|
429
438
|
organizationId: auth.organizationId,
|
|
@@ -444,7 +453,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
|
|
|
444
453
|
});
|
|
445
454
|
}
|
|
446
455
|
function useSearch(query, params, options) {
|
|
447
|
-
const auth =
|
|
456
|
+
const auth = resolveAuth();
|
|
448
457
|
const token = params?.token ?? auth.token;
|
|
449
458
|
const organizationId = params?.organizationId ?? auth.organizationId;
|
|
450
459
|
const { organizationId: _, token: _t, ...restParams } = params ?? {};
|
|
@@ -475,7 +484,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
|
|
|
475
484
|
}
|
|
476
485
|
});
|
|
477
486
|
},
|
|
478
|
-
enabled: !!api.search && query.length > 0 && createEnabledRule(token, queryOpts, resolveAuthMode()),
|
|
487
|
+
enabled: !!api.search && query.length > 0 && createEnabledRule(token, queryOpts, resolveAuthMode(), hasStaticAuth),
|
|
479
488
|
options: {
|
|
480
489
|
staleTime: queryOpts.staleTime ?? config.staleTime,
|
|
481
490
|
gcTime: queryOpts.gcTime ?? config.gcTime
|
|
@@ -495,7 +504,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
|
|
|
495
504
|
});
|
|
496
505
|
}
|
|
497
506
|
function useDeleted(params, options) {
|
|
498
|
-
const auth =
|
|
507
|
+
const auth = resolveAuth();
|
|
499
508
|
const token = auth.token;
|
|
500
509
|
const mergedParams = params ?? {};
|
|
501
510
|
const organizationId = mergedParams.organizationId ?? auth.organizationId;
|
|
@@ -518,7 +527,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
|
|
|
518
527
|
}
|
|
519
528
|
});
|
|
520
529
|
},
|
|
521
|
-
enabled: !!api.getDeleted && createEnabledRule(token, queryOpts, resolveAuthMode()),
|
|
530
|
+
enabled: !!api.getDeleted && createEnabledRule(token, queryOpts, resolveAuthMode(), hasStaticAuth),
|
|
522
531
|
options: {
|
|
523
532
|
staleTime: queryOpts.staleTime ?? config.staleTime,
|
|
524
533
|
gcTime: queryOpts.gcTime ?? config.gcTime
|
|
@@ -527,7 +536,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
|
|
|
527
536
|
});
|
|
528
537
|
}
|
|
529
538
|
function useDetailBySlug(slug, options) {
|
|
530
|
-
const auth =
|
|
539
|
+
const auth = resolveAuth();
|
|
531
540
|
const token = auth.token;
|
|
532
541
|
const resolvedOptions = options ?? {};
|
|
533
542
|
const organizationId = resolvedOptions.organizationId ?? auth.organizationId;
|
|
@@ -547,7 +556,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
|
|
|
547
556
|
}
|
|
548
557
|
});
|
|
549
558
|
},
|
|
550
|
-
enabled: !!api.getBySlug && !!slug && createEnabledRule(token, restOptions, resolveAuthMode()),
|
|
559
|
+
enabled: !!api.getBySlug && !!slug && createEnabledRule(token, restOptions, resolveAuthMode(), hasStaticAuth),
|
|
551
560
|
options: {
|
|
552
561
|
staleTime: restOptions.staleTime ?? config.staleTime,
|
|
553
562
|
gcTime: restOptions.gcTime ?? config.gcTime,
|
|
@@ -558,7 +567,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
|
|
|
558
567
|
});
|
|
559
568
|
}
|
|
560
569
|
function useTree(params, options) {
|
|
561
|
-
const auth =
|
|
570
|
+
const auth = resolveAuth();
|
|
562
571
|
const token = auth.token;
|
|
563
572
|
const mergedParams = params ?? {};
|
|
564
573
|
const organizationId = mergedParams.organizationId ?? auth.organizationId;
|
|
@@ -581,7 +590,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
|
|
|
581
590
|
}
|
|
582
591
|
});
|
|
583
592
|
},
|
|
584
|
-
enabled: !!api.getTree && createEnabledRule(token, queryOpts, resolveAuthMode()),
|
|
593
|
+
enabled: !!api.getTree && createEnabledRule(token, queryOpts, resolveAuthMode(), hasStaticAuth),
|
|
585
594
|
options: {
|
|
586
595
|
staleTime: queryOpts.staleTime ?? config.staleTime,
|
|
587
596
|
gcTime: queryOpts.gcTime ?? config.gcTime
|
|
@@ -590,7 +599,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
|
|
|
590
599
|
});
|
|
591
600
|
}
|
|
592
601
|
function useChildren(parentId, params, options) {
|
|
593
|
-
const auth =
|
|
602
|
+
const auth = resolveAuth();
|
|
594
603
|
const token = auth.token;
|
|
595
604
|
const mergedParams = params ?? {};
|
|
596
605
|
const organizationId = mergedParams.organizationId ?? auth.organizationId;
|
|
@@ -614,24 +623,29 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
|
|
|
614
623
|
}
|
|
615
624
|
});
|
|
616
625
|
},
|
|
617
|
-
enabled: !!api.getChildren && !!parentId && createEnabledRule(token, queryOpts, resolveAuthMode()),
|
|
626
|
+
enabled: !!api.getChildren && !!parentId && createEnabledRule(token, queryOpts, resolveAuthMode(), hasStaticAuth),
|
|
618
627
|
options: {
|
|
619
628
|
staleTime: queryOpts.staleTime ?? config.staleTime,
|
|
620
629
|
gcTime: queryOpts.gcTime ?? config.gcTime
|
|
621
630
|
},
|
|
622
631
|
prefillDetailCache: queryOpts.prefillDetailCache ?? true,
|
|
623
|
-
detailKeyBuilder: (id) => KEYS.
|
|
632
|
+
detailKeyBuilder: (id) => KEYS.scopedDetail(id, organizationId ?? null),
|
|
624
633
|
itemIdResolver: resolveItemId,
|
|
625
634
|
select: queryOpts.select
|
|
626
635
|
});
|
|
627
636
|
}
|
|
628
637
|
function useFindBy(field, value, options) {
|
|
629
|
-
const auth =
|
|
638
|
+
const auth = resolveAuth();
|
|
630
639
|
const token = auth.token;
|
|
631
640
|
const organizationId = auth.organizationId;
|
|
632
641
|
const { operator, request: requestOpts, ...queryOpts } = options ?? {};
|
|
633
642
|
return useListQuery({
|
|
634
|
-
queryKey: KEYS.custom("findBy",
|
|
643
|
+
queryKey: KEYS.custom("findBy", {
|
|
644
|
+
field,
|
|
645
|
+
value,
|
|
646
|
+
operator,
|
|
647
|
+
organizationId
|
|
648
|
+
}),
|
|
635
649
|
queryFn: ({ signal }) => {
|
|
636
650
|
if (!api.findBy) return Promise.reject(/* @__PURE__ */ new Error(`[arc-next] "${entityKey}" api does not define a findBy method`));
|
|
637
651
|
return api.findBy({
|
|
@@ -646,13 +660,13 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
|
|
|
646
660
|
}
|
|
647
661
|
});
|
|
648
662
|
},
|
|
649
|
-
enabled: !!api.findBy && value !== void 0 && value !== null && createEnabledRule(token, queryOpts, resolveAuthMode()),
|
|
663
|
+
enabled: !!api.findBy && value !== void 0 && value !== null && createEnabledRule(token, queryOpts, resolveAuthMode(), hasStaticAuth),
|
|
650
664
|
options: {
|
|
651
665
|
staleTime: queryOpts.staleTime ?? config.staleTime,
|
|
652
666
|
gcTime: queryOpts.gcTime ?? config.gcTime
|
|
653
667
|
},
|
|
654
668
|
prefillDetailCache: queryOpts.prefillDetailCache ?? true,
|
|
655
|
-
detailKeyBuilder: (id) => KEYS.
|
|
669
|
+
detailKeyBuilder: (id) => KEYS.scopedDetail(id, organizationId ?? null),
|
|
656
670
|
itemIdResolver: resolveItemId,
|
|
657
671
|
select: queryOpts.select
|
|
658
672
|
});
|
|
@@ -661,7 +675,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
|
|
|
661
675
|
const bulkCreateMutation = useMutationWithTransition({
|
|
662
676
|
mutationFn: (vars) => {
|
|
663
677
|
if (!api.bulkCreate) return Promise.reject(/* @__PURE__ */ new Error(`[arc-next] "${entityKey}" api does not define a bulkCreate method`));
|
|
664
|
-
const auth =
|
|
678
|
+
const auth = resolveAuth();
|
|
665
679
|
return api.bulkCreate({
|
|
666
680
|
token: vars.token ?? auth.token,
|
|
667
681
|
organizationId: vars.organizationId ?? auth.organizationId,
|
|
@@ -678,7 +692,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
|
|
|
678
692
|
const bulkUpdateMutation = useMutationWithTransition({
|
|
679
693
|
mutationFn: (vars) => {
|
|
680
694
|
if (!api.bulkUpdate) return Promise.reject(/* @__PURE__ */ new Error(`[arc-next] "${entityKey}" api does not define a bulkUpdate method`));
|
|
681
|
-
const auth =
|
|
695
|
+
const auth = resolveAuth();
|
|
682
696
|
return api.bulkUpdate({
|
|
683
697
|
token: vars.token ?? auth.token,
|
|
684
698
|
organizationId: vars.organizationId ?? auth.organizationId,
|
|
@@ -696,7 +710,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
|
|
|
696
710
|
const bulkDeleteMutation = useMutationWithTransition({
|
|
697
711
|
mutationFn: (vars) => {
|
|
698
712
|
if (!api.bulkDelete) return Promise.reject(/* @__PURE__ */ new Error(`[arc-next] "${entityKey}" api does not define a bulkDelete method`));
|
|
699
|
-
const auth =
|
|
713
|
+
const auth = resolveAuth();
|
|
700
714
|
return api.bulkDelete({
|
|
701
715
|
token: vars.token ?? auth.token,
|
|
702
716
|
organizationId: vars.organizationId ?? auth.organizationId,
|
|
@@ -740,7 +754,11 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
|
|
|
740
754
|
const router = resolvedRouterHook();
|
|
741
755
|
return useCallback((href, item, options = {}) => {
|
|
742
756
|
const id = resolveItemId(item);
|
|
743
|
-
if (id)
|
|
757
|
+
if (id) {
|
|
758
|
+
const orgId = resolveAuth().organizationId;
|
|
759
|
+
queryClient.setQueryData(KEYS.scopedDetail(id, orgId), { data: item });
|
|
760
|
+
if (orgId) queryClient.setQueryData(KEYS.detail(id), { data: item });
|
|
761
|
+
}
|
|
744
762
|
if (!router) return;
|
|
745
763
|
const { scroll = true, replace = false } = options;
|
|
746
764
|
if (replace) router.replace(href, { scroll });
|
package/dist/mutation.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { ToastHandler } from "./client.js";
|
|
2
|
-
import { QUERY_CONFIGS } from "./query.js";
|
|
3
2
|
import * as _$_tanstack_react_query0 from "@tanstack/react-query";
|
|
4
3
|
import { QueryClient, QueryKey, UseMutateAsyncFunction, UseMutateFunction } from "@tanstack/react-query";
|
|
5
4
|
|
|
@@ -107,7 +106,5 @@ declare function useOptimisticMutation<TData, TVariables>(config: CreateOptimist
|
|
|
107
106
|
data: [readonly unknown[], unknown][];
|
|
108
107
|
}[];
|
|
109
108
|
}>;
|
|
110
|
-
/** @deprecated Use `useOptimisticMutation` */
|
|
111
|
-
declare const createOptimisticMutation: typeof useOptimisticMutation;
|
|
112
109
|
//#endregion
|
|
113
|
-
export { CreateOptimisticMutationConfig, MutationCallbacks, MutationMessages, OptimisticMutationConfig,
|
|
110
|
+
export { CreateOptimisticMutationConfig, MutationCallbacks, MutationMessages, OptimisticMutationConfig, TransitionMutationConfig, TransitionMutationReturn, configureToast, useMutationWithOptimistic, useMutationWithTransition, useOptimisticMutation };
|
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
|
|
|
@@ -162,8 +161,6 @@ function useOptimisticMutation(config) {
|
|
|
162
161
|
}
|
|
163
162
|
});
|
|
164
163
|
}
|
|
165
|
-
/** @deprecated Use `useOptimisticMutation` */
|
|
166
|
-
const createOptimisticMutation = useOptimisticMutation;
|
|
167
164
|
|
|
168
165
|
//#endregion
|
|
169
|
-
export {
|
|
166
|
+
export { configureToast, useMutationWithOptimistic, useMutationWithTransition, useOptimisticMutation };
|
package/dist/prefetch.d.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import { 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 {
|
|
@@ -74,28 +82,43 @@ declare function createCrudPrefetcher(api: {
|
|
|
74
82
|
params?: Record<string, unknown>;
|
|
75
83
|
token?: string | null;
|
|
76
84
|
organizationId?: string | null;
|
|
85
|
+
options?: {
|
|
86
|
+
headerOptions?: Record<string, string>;
|
|
87
|
+
};
|
|
77
88
|
}) => Promise<unknown>;
|
|
78
89
|
getById: (opts: {
|
|
79
90
|
id: string;
|
|
80
91
|
token?: string | null;
|
|
81
92
|
organizationId?: string | null;
|
|
93
|
+
options?: {
|
|
94
|
+
headerOptions?: Record<string, string>;
|
|
95
|
+
};
|
|
82
96
|
}) => Promise<unknown>;
|
|
83
97
|
getBySlug?: (opts: {
|
|
84
98
|
slug: string;
|
|
85
99
|
token?: string | null;
|
|
86
100
|
organizationId?: string | null;
|
|
87
101
|
params?: Record<string, unknown>;
|
|
102
|
+
options?: {
|
|
103
|
+
headerOptions?: Record<string, string>;
|
|
104
|
+
};
|
|
88
105
|
}) => Promise<unknown>;
|
|
89
106
|
getDeleted?: (opts: {
|
|
90
107
|
params?: Record<string, unknown>;
|
|
91
108
|
token?: string | null;
|
|
92
109
|
organizationId?: string | null;
|
|
110
|
+
options?: {
|
|
111
|
+
headerOptions?: Record<string, string>;
|
|
112
|
+
};
|
|
93
113
|
}) => Promise<unknown>;
|
|
94
114
|
getTree?: (opts: {
|
|
95
115
|
params?: Record<string, unknown>;
|
|
96
116
|
token?: string | null;
|
|
97
117
|
organizationId?: string | null;
|
|
118
|
+
options?: {
|
|
119
|
+
headerOptions?: Record<string, string>;
|
|
120
|
+
};
|
|
98
121
|
}) => Promise<unknown>;
|
|
99
122
|
}, entityKey: string): CrudPrefetcher;
|
|
100
123
|
//#endregion
|
|
101
|
-
export { CrudPrefetcher, PrefetchDetailOptions, PrefetchOptions, createCrudPrefetcher, dehydrate };
|
|
124
|
+
export { CrudPrefetcher, PrefetchAuthContext, PrefetchDetailOptions, PrefetchOptions, createCrudPrefetcher, dehydrate };
|
package/dist/prefetch.js
CHANGED
|
@@ -31,75 +31,90 @@ 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
|
});
|
package/dist/query.d.ts
CHANGED
|
@@ -86,16 +86,27 @@ interface QueryKeys {
|
|
|
86
86
|
list: (params?: unknown) => QueryKey;
|
|
87
87
|
details: () => QueryKey;
|
|
88
88
|
detail: (id: string) => QueryKey;
|
|
89
|
+
/** Tenant-scoped detail key. Use when IDs are only unique within an org. */
|
|
90
|
+
scopedDetail: (id: string, organizationId: string | null) => QueryKey;
|
|
89
91
|
custom: (key: string, ...args: unknown[]) => QueryKey;
|
|
90
92
|
scopedList: (scope: string, params?: unknown) => QueryKey;
|
|
91
93
|
}
|
|
92
94
|
interface CacheUtils<T> {
|
|
93
95
|
invalidateAll: (client: QueryClient) => Promise<void>;
|
|
94
96
|
invalidateLists: (client: QueryClient) => Promise<void>;
|
|
97
|
+
/** Invalidate detail by ID (prefix-matches all scoped/parameterized variants). */
|
|
95
98
|
invalidateDetail: (client: QueryClient, id: string) => Promise<void>;
|
|
96
99
|
setDetail: (client: QueryClient, id: string, data: T) => void;
|
|
97
100
|
getDetail: (client: QueryClient, id: string) => T | undefined;
|
|
98
101
|
removeDetail: (client: QueryClient, id: string) => void;
|
|
102
|
+
/** Invalidate tenant-scoped detail (prefix-matches parameterized variants within org). */
|
|
103
|
+
invalidateScopedDetail: (client: QueryClient, id: string, organizationId: string | null) => Promise<void>;
|
|
104
|
+
/** Set tenant-scoped detail cache. */
|
|
105
|
+
setScopedDetail: (client: QueryClient, id: string, organizationId: string | null, data: T) => void;
|
|
106
|
+
/** Get tenant-scoped detail from cache. */
|
|
107
|
+
getScopedDetail: (client: QueryClient, id: string, organizationId: string | null) => T | undefined;
|
|
108
|
+
/** Remove tenant-scoped detail from cache. */
|
|
109
|
+
removeScopedDetail: (client: QueryClient, id: string, organizationId: string | null) => void;
|
|
99
110
|
}
|
|
100
111
|
declare const DEFAULT_QUERY_CONFIG: {
|
|
101
112
|
readonly staleTime: number;
|
|
@@ -221,11 +232,5 @@ declare function useInfiniteListQuery<T>({
|
|
|
221
232
|
getPreviousPageParam,
|
|
222
233
|
maxPages
|
|
223
234
|
}: CreateInfiniteListQueryConfig): InfiniteListQueryResult<T>;
|
|
224
|
-
/** @deprecated Use `useListQuery` */
|
|
225
|
-
declare const createListQuery: typeof useListQuery;
|
|
226
|
-
/** @deprecated Use `useDetailQuery` */
|
|
227
|
-
declare const createDetailQuery: typeof useDetailQuery;
|
|
228
|
-
/** @deprecated Use `useInfiniteListQuery` */
|
|
229
|
-
declare const createInfiniteListQuery: typeof useInfiniteListQuery;
|
|
230
235
|
//#endregion
|
|
231
|
-
export { CacheUtils, CreateDetailQueryConfig, CreateInfiniteListQueryConfig, CreateListQueryConfig, DEFAULT_QUERY_CONFIG, DetailQueryOptions, DetailQueryResult, InfiniteListQueryOptions, InfiniteListQueryResult, ListQueryOptions, ListQueryResult, PaginationData, QUERY_CONFIGS, QueryKeys, RequestPassthrough, createCacheUtils,
|
|
236
|
+
export { CacheUtils, CreateDetailQueryConfig, CreateInfiniteListQueryConfig, CreateListQueryConfig, DEFAULT_QUERY_CONFIG, DetailQueryOptions, DetailQueryResult, InfiniteListQueryOptions, InfiniteListQueryResult, ListQueryOptions, ListQueryResult, PaginationData, QUERY_CONFIGS, QueryKeys, RequestPassthrough, createCacheUtils, createQueryKeys, extractItem, getItemId, updateListCache, useDetailQuery, useInfiniteListQuery, useListQuery };
|
package/dist/query.js
CHANGED
|
@@ -120,6 +120,16 @@ function createQueryKeys(entityKey) {
|
|
|
120
120
|
"detail",
|
|
121
121
|
id
|
|
122
122
|
],
|
|
123
|
+
scopedDetail: (id, organizationId) => organizationId ? [
|
|
124
|
+
entityKey,
|
|
125
|
+
"detail",
|
|
126
|
+
id,
|
|
127
|
+
{ _org: organizationId }
|
|
128
|
+
] : [
|
|
129
|
+
entityKey,
|
|
130
|
+
"detail",
|
|
131
|
+
id
|
|
132
|
+
],
|
|
123
133
|
custom: (key, ...args) => [
|
|
124
134
|
entityKey,
|
|
125
135
|
key,
|
|
@@ -144,7 +154,13 @@ function createCacheUtils(KEYS) {
|
|
|
144
154
|
getDetail: (client, id) => {
|
|
145
155
|
return client.getQueryData(KEYS.detail(id))?.data;
|
|
146
156
|
},
|
|
147
|
-
removeDetail: (client, id) => client.removeQueries({ queryKey: KEYS.detail(id) })
|
|
157
|
+
removeDetail: (client, id) => client.removeQueries({ queryKey: KEYS.detail(id) }),
|
|
158
|
+
invalidateScopedDetail: (client, id, organizationId) => client.invalidateQueries({ queryKey: KEYS.scopedDetail(id, organizationId) }),
|
|
159
|
+
setScopedDetail: (client, id, organizationId, data) => client.setQueryData(KEYS.scopedDetail(id, organizationId), { data }),
|
|
160
|
+
getScopedDetail: (client, id, organizationId) => {
|
|
161
|
+
return client.getQueryData(KEYS.scopedDetail(id, organizationId))?.data;
|
|
162
|
+
},
|
|
163
|
+
removeScopedDetail: (client, id, organizationId) => client.removeQueries({ queryKey: KEYS.scopedDetail(id, organizationId) })
|
|
148
164
|
};
|
|
149
165
|
}
|
|
150
166
|
function useListQuery({ queryKey, queryFn, enabled = true, options = {}, prefillDetailCache = true, detailKeyBuilder, itemIdResolver, select }) {
|
|
@@ -239,12 +255,6 @@ function useInfiniteListQuery({ queryKey, queryFn, enabled = true, options = {},
|
|
|
239
255
|
data: query.data
|
|
240
256
|
};
|
|
241
257
|
}
|
|
242
|
-
/** @deprecated Use `useListQuery` */
|
|
243
|
-
const createListQuery = useListQuery;
|
|
244
|
-
/** @deprecated Use `useDetailQuery` */
|
|
245
|
-
const createDetailQuery = useDetailQuery;
|
|
246
|
-
/** @deprecated Use `useInfiniteListQuery` */
|
|
247
|
-
const createInfiniteListQuery = useInfiniteListQuery;
|
|
248
258
|
|
|
249
259
|
//#endregion
|
|
250
|
-
export { DEFAULT_QUERY_CONFIG, QUERY_CONFIGS, createCacheUtils,
|
|
260
|
+
export { DEFAULT_QUERY_CONFIG, QUERY_CONFIGS, createCacheUtils, createQueryKeys, extractItem, getItemId, updateListCache, useDetailQuery, useInfiniteListQuery, useListQuery };
|
package/dist/sse.d.ts
CHANGED
|
@@ -9,12 +9,19 @@ interface ArcServerEvent {
|
|
|
9
9
|
id?: string;
|
|
10
10
|
}
|
|
11
11
|
interface EventStreamOptions {
|
|
12
|
-
/** SSE endpoint URL
|
|
12
|
+
/** Full SSE endpoint URL. When set, overrides `path` and `baseUrl`. Use for non-Arc backends or custom URLs. */
|
|
13
13
|
url?: string;
|
|
14
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* Resource name for automatic pattern filtering.
|
|
16
|
+
* When set and `patterns` is empty, auto-generates `['resource.*']` filter.
|
|
17
|
+
* Does NOT affect the endpoint URL — use `path` for that.
|
|
18
|
+
*/
|
|
15
19
|
resource?: string;
|
|
16
|
-
/**
|
|
17
|
-
|
|
20
|
+
/**
|
|
21
|
+
* SSE endpoint path (appended to baseUrl). Default: '/events/stream'.
|
|
22
|
+
* Matches Arc's ssePlugin default. Override if your backend uses a custom path.
|
|
23
|
+
*/
|
|
24
|
+
path?: string;
|
|
18
25
|
/** Event patterns to listen for (e.g., ['agents.created', 'agents.updated']). When empty, all events are received. */
|
|
19
26
|
patterns?: string[];
|
|
20
27
|
/** Query keys to invalidate when any event is received. */
|
package/dist/sse.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { getAuthContext, getAuthMode } from "./client.js";
|
|
3
|
+
import { getAuthContext, getAuthMode, getBaseUrl } from "./client.js";
|
|
4
4
|
import { useQueryClient } from "@tanstack/react-query";
|
|
5
5
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
6
6
|
|
|
@@ -19,7 +19,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
|
|
19
19
|
* });
|
|
20
20
|
*/
|
|
21
21
|
function useEventStream(options) {
|
|
22
|
-
const { url, resource,
|
|
22
|
+
const { url, resource, path: ssePath = "/events/stream", enabled = true, reconnectDelay = 3e3, maxReconnectAttempts = Infinity, withCredentials } = options;
|
|
23
23
|
const queryClient = useQueryClient();
|
|
24
24
|
const [isConnected, setIsConnected] = useState(false);
|
|
25
25
|
const [lastEvent, setLastEvent] = useState(null);
|
|
@@ -38,20 +38,20 @@ function useEventStream(options) {
|
|
|
38
38
|
invalidateKeysRef.current = options.invalidateQueries ?? [];
|
|
39
39
|
const buildUrl = useCallback(() => {
|
|
40
40
|
if (url) return url;
|
|
41
|
-
if (!resource) throw new Error("[arc-next] useEventStream requires either `url` or `resource`");
|
|
42
41
|
const auth = getAuthContext();
|
|
43
42
|
const params = new URLSearchParams();
|
|
44
43
|
const patterns = patternsRef.current;
|
|
45
|
-
|
|
44
|
+
const effectivePatterns = patterns.length > 0 ? patterns : resource ? [`${resource}.*`] : [];
|
|
45
|
+
if (effectivePatterns.length > 0) params.set("patterns", effectivePatterns.join(","));
|
|
46
46
|
if (auth.organizationId) params.set("organizationId", auth.organizationId);
|
|
47
47
|
if (auth.token) params.set("token", auth.token);
|
|
48
48
|
const qs = params.toString();
|
|
49
|
-
const base = `${
|
|
49
|
+
const base = `${getBaseUrl()}${ssePath}`;
|
|
50
50
|
return qs ? `${base}?${qs}` : base;
|
|
51
51
|
}, [
|
|
52
52
|
url,
|
|
53
53
|
resource,
|
|
54
|
-
|
|
54
|
+
ssePath
|
|
55
55
|
]);
|
|
56
56
|
const connect = useCallback(() => {
|
|
57
57
|
if (esRef.current) esRef.current.close();
|
|
@@ -110,6 +110,7 @@ function useEventStream(options) {
|
|
|
110
110
|
}, []);
|
|
111
111
|
const reconnect = useCallback(() => {
|
|
112
112
|
reconnectAttemptsRef.current = 0;
|
|
113
|
+
manualCloseRef.current = false;
|
|
113
114
|
connect();
|
|
114
115
|
}, [connect]);
|
|
115
116
|
useEffect(() => {
|