enlace 0.0.1-beta.3 → 0.0.1-beta.5
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 +188 -0
- package/dist/index.d.mts +27 -4
- package/dist/index.d.ts +27 -4
- package/dist/index.js +89 -24
- package/dist/index.mjs +90 -27
- package/dist/next/hook/index.d.mts +27 -5
- package/dist/next/hook/index.d.ts +27 -5
- package/dist/next/hook/index.js +113 -70
- package/dist/next/hook/index.mjs +114 -73
- package/dist/next/index.d.mts +65 -6
- package/dist/next/index.d.ts +65 -6
- package/dist/next/index.js +26 -48
- package/dist/next/index.mjs +27 -51
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
export * from "enlace-core";
|
|
3
3
|
|
|
4
4
|
// src/react/createEnlaceHook.ts
|
|
5
|
-
import {
|
|
6
|
-
createEnlace
|
|
7
|
-
} from "enlace-core";
|
|
5
|
+
import { createEnlace } from "enlace-core";
|
|
8
6
|
|
|
9
7
|
// src/react/useQueryMode.ts
|
|
10
8
|
import { useRef, useReducer, useEffect } from "react";
|
|
@@ -20,7 +18,7 @@ var initialState = {
|
|
|
20
18
|
function hookReducer(state, action) {
|
|
21
19
|
switch (action.type) {
|
|
22
20
|
case "RESET":
|
|
23
|
-
return action.state;
|
|
21
|
+
return action.state ?? initialState;
|
|
24
22
|
case "FETCH_START":
|
|
25
23
|
return {
|
|
26
24
|
...state,
|
|
@@ -157,11 +155,29 @@ function onRevalidate(callback) {
|
|
|
157
155
|
}
|
|
158
156
|
|
|
159
157
|
// src/react/useQueryMode.ts
|
|
158
|
+
function resolvePath(path, pathParams) {
|
|
159
|
+
if (!pathParams) return path;
|
|
160
|
+
return path.map((segment) => {
|
|
161
|
+
if (segment.startsWith(":")) {
|
|
162
|
+
const paramName = segment.slice(1);
|
|
163
|
+
const value = pathParams[paramName];
|
|
164
|
+
if (value === void 0) {
|
|
165
|
+
throw new Error(`Missing path parameter: ${paramName}`);
|
|
166
|
+
}
|
|
167
|
+
return String(value);
|
|
168
|
+
}
|
|
169
|
+
return segment;
|
|
170
|
+
});
|
|
171
|
+
}
|
|
160
172
|
function useQueryMode(api, trackedCall, options) {
|
|
161
|
-
const { autoGenerateTags, staleTime } = options;
|
|
173
|
+
const { autoGenerateTags, staleTime, enabled } = options;
|
|
162
174
|
const queryKey = createQueryKey(trackedCall);
|
|
163
175
|
const requestOptions = trackedCall.options;
|
|
164
|
-
const
|
|
176
|
+
const resolvedPath = resolvePath(
|
|
177
|
+
trackedCall.path,
|
|
178
|
+
requestOptions?.pathParams
|
|
179
|
+
);
|
|
180
|
+
const queryTags = requestOptions?.tags ?? (autoGenerateTags ? generateTags(resolvedPath) : []);
|
|
165
181
|
const getCacheState = (includeNeedsFetch = false) => {
|
|
166
182
|
const cached = getCache(queryKey);
|
|
167
183
|
const hasCachedData = cached?.data !== void 0;
|
|
@@ -175,17 +191,22 @@ function useQueryMode(api, trackedCall, options) {
|
|
|
175
191
|
error: cached?.error
|
|
176
192
|
};
|
|
177
193
|
};
|
|
178
|
-
const [state, dispatch] = useReducer(
|
|
194
|
+
const [state, dispatch] = useReducer(
|
|
195
|
+
hookReducer,
|
|
196
|
+
null,
|
|
197
|
+
() => getCacheState(true)
|
|
198
|
+
);
|
|
179
199
|
const mountedRef = useRef(true);
|
|
180
200
|
const fetchRef = useRef(null);
|
|
181
201
|
useEffect(() => {
|
|
182
202
|
mountedRef.current = true;
|
|
203
|
+
if (!enabled) {
|
|
204
|
+
dispatch({ type: "RESET" });
|
|
205
|
+
return () => {
|
|
206
|
+
mountedRef.current = false;
|
|
207
|
+
};
|
|
208
|
+
}
|
|
183
209
|
dispatch({ type: "RESET", state: getCacheState(true) });
|
|
184
|
-
const unsubscribe = subscribeCache(queryKey, () => {
|
|
185
|
-
if (mountedRef.current) {
|
|
186
|
-
dispatch({ type: "SYNC_CACHE", state: getCacheState() });
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
210
|
const doFetch = () => {
|
|
190
211
|
const cached2 = getCache(queryKey);
|
|
191
212
|
if (cached2?.promise) {
|
|
@@ -193,7 +214,7 @@ function useQueryMode(api, trackedCall, options) {
|
|
|
193
214
|
}
|
|
194
215
|
dispatch({ type: "FETCH_START" });
|
|
195
216
|
let current = api;
|
|
196
|
-
for (const segment of
|
|
217
|
+
for (const segment of resolvedPath) {
|
|
197
218
|
current = current[segment];
|
|
198
219
|
}
|
|
199
220
|
const method = current[trackedCall.method];
|
|
@@ -201,7 +222,7 @@ function useQueryMode(api, trackedCall, options) {
|
|
|
201
222
|
if (mountedRef.current) {
|
|
202
223
|
setCache(queryKey, {
|
|
203
224
|
data: res.ok ? res.data : void 0,
|
|
204
|
-
error: res.ok ? void 0 : res.error,
|
|
225
|
+
error: res.ok || res.status === 0 ? void 0 : res.error,
|
|
205
226
|
ok: res.ok,
|
|
206
227
|
timestamp: Date.now(),
|
|
207
228
|
tags: queryTags
|
|
@@ -220,12 +241,17 @@ function useQueryMode(api, trackedCall, options) {
|
|
|
220
241
|
} else {
|
|
221
242
|
doFetch();
|
|
222
243
|
}
|
|
244
|
+
const unsubscribe = subscribeCache(queryKey, () => {
|
|
245
|
+
if (mountedRef.current) {
|
|
246
|
+
dispatch({ type: "SYNC_CACHE", state: getCacheState() });
|
|
247
|
+
}
|
|
248
|
+
});
|
|
223
249
|
return () => {
|
|
224
250
|
mountedRef.current = false;
|
|
225
251
|
fetchRef.current = null;
|
|
226
252
|
unsubscribe();
|
|
227
253
|
};
|
|
228
|
-
}, [queryKey]);
|
|
254
|
+
}, [queryKey, enabled]);
|
|
229
255
|
useEffect(() => {
|
|
230
256
|
if (queryTags.length === 0) return;
|
|
231
257
|
return onRevalidate((invalidatedTags) => {
|
|
@@ -272,23 +298,56 @@ function createTrackingProxy(onTrack) {
|
|
|
272
298
|
|
|
273
299
|
// src/react/useSelectorMode.ts
|
|
274
300
|
import { useRef as useRef2, useReducer as useReducer2 } from "react";
|
|
275
|
-
function
|
|
301
|
+
function resolvePath2(path, pathParams) {
|
|
302
|
+
if (!pathParams) return path;
|
|
303
|
+
return path.map((segment) => {
|
|
304
|
+
if (segment.startsWith(":")) {
|
|
305
|
+
const paramName = segment.slice(1);
|
|
306
|
+
const value = pathParams[paramName];
|
|
307
|
+
if (value === void 0) {
|
|
308
|
+
throw new Error(`Missing path parameter: ${paramName}`);
|
|
309
|
+
}
|
|
310
|
+
return String(value);
|
|
311
|
+
}
|
|
312
|
+
return segment;
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
function hasPathParams(path) {
|
|
316
|
+
return path.some((segment) => segment.startsWith(":"));
|
|
317
|
+
}
|
|
318
|
+
function useSelectorMode(config) {
|
|
319
|
+
const { method, api, path, methodName, autoRevalidateTags } = config;
|
|
276
320
|
const [state, dispatch] = useReducer2(hookReducer, initialState);
|
|
277
321
|
const methodRef = useRef2(method);
|
|
322
|
+
const apiRef = useRef2(api);
|
|
278
323
|
const triggerRef = useRef2(null);
|
|
279
324
|
const pathRef = useRef2(path);
|
|
325
|
+
const methodNameRef = useRef2(methodName);
|
|
280
326
|
const autoRevalidateRef = useRef2(autoRevalidateTags);
|
|
281
327
|
methodRef.current = method;
|
|
328
|
+
apiRef.current = api;
|
|
282
329
|
pathRef.current = path;
|
|
330
|
+
methodNameRef.current = methodName;
|
|
283
331
|
autoRevalidateRef.current = autoRevalidateTags;
|
|
284
332
|
if (!triggerRef.current) {
|
|
285
333
|
triggerRef.current = (async (...args) => {
|
|
286
334
|
dispatch({ type: "FETCH_START" });
|
|
287
|
-
const
|
|
335
|
+
const options = args[0];
|
|
336
|
+
const resolvedPath = resolvePath2(pathRef.current, options?.pathParams);
|
|
337
|
+
let res;
|
|
338
|
+
if (hasPathParams(pathRef.current)) {
|
|
339
|
+
let current = apiRef.current;
|
|
340
|
+
for (const segment of resolvedPath) {
|
|
341
|
+
current = current[segment];
|
|
342
|
+
}
|
|
343
|
+
const resolvedMethod = current[methodNameRef.current];
|
|
344
|
+
res = await resolvedMethod(...args);
|
|
345
|
+
} else {
|
|
346
|
+
res = await methodRef.current(...args);
|
|
347
|
+
}
|
|
288
348
|
if (res.ok) {
|
|
289
349
|
dispatch({ type: "FETCH_SUCCESS", data: res.data });
|
|
290
|
-
const
|
|
291
|
-
const tagsToInvalidate = options?.revalidateTags ?? (autoRevalidateRef.current ? generateTags(pathRef.current) : []);
|
|
350
|
+
const tagsToInvalidate = options?.revalidateTags ?? (autoRevalidateRef.current ? generateTags(resolvedPath) : []);
|
|
292
351
|
if (tagsToInvalidate.length > 0) {
|
|
293
352
|
invalidateTags(tagsToInvalidate);
|
|
294
353
|
}
|
|
@@ -306,13 +365,15 @@ function useSelectorMode(method, path, autoRevalidateTags) {
|
|
|
306
365
|
|
|
307
366
|
// src/react/createEnlaceHook.ts
|
|
308
367
|
function createEnlaceHook(baseUrl, defaultOptions = {}, hookOptions = {}) {
|
|
309
|
-
const api = createEnlace(baseUrl, defaultOptions);
|
|
310
368
|
const {
|
|
311
369
|
autoGenerateTags = true,
|
|
312
370
|
autoRevalidateTags = true,
|
|
313
|
-
staleTime = 0
|
|
371
|
+
staleTime = 0,
|
|
372
|
+
onSuccess,
|
|
373
|
+
onError
|
|
314
374
|
} = hookOptions;
|
|
315
|
-
|
|
375
|
+
const api = createEnlace(baseUrl, defaultOptions, { onSuccess, onError });
|
|
376
|
+
function useEnlaceHook(selectorOrQuery, queryOptions) {
|
|
316
377
|
let trackingResult = {
|
|
317
378
|
trackedCall: null,
|
|
318
379
|
selectorPath: null,
|
|
@@ -326,16 +387,18 @@ function createEnlaceHook(baseUrl, defaultOptions = {}, hookOptions = {}) {
|
|
|
326
387
|
);
|
|
327
388
|
if (typeof result === "function") {
|
|
328
389
|
const actualResult = selectorOrQuery(api);
|
|
329
|
-
return useSelectorMode(
|
|
330
|
-
actualResult,
|
|
331
|
-
|
|
390
|
+
return useSelectorMode({
|
|
391
|
+
method: actualResult,
|
|
392
|
+
api,
|
|
393
|
+
path: trackingResult.selectorPath ?? [],
|
|
394
|
+
methodName: trackingResult.selectorMethod ?? "",
|
|
332
395
|
autoRevalidateTags
|
|
333
|
-
);
|
|
396
|
+
});
|
|
334
397
|
}
|
|
335
398
|
return useQueryMode(
|
|
336
399
|
api,
|
|
337
400
|
trackingResult.trackedCall,
|
|
338
|
-
{ autoGenerateTags, staleTime }
|
|
401
|
+
{ autoGenerateTags, staleTime, enabled: queryOptions?.enabled ?? true }
|
|
339
402
|
);
|
|
340
403
|
}
|
|
341
404
|
return useEnlaceHook;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { WildcardClient, EnlaceClient, EnlaceResponse, EnlaceOptions } from 'enlace-core';
|
|
1
|
+
import { EnlaceCallbackPayload, EnlaceErrorCallbackPayload, WildcardClient, EnlaceClient, EnlaceResponse, EnlaceCallbacks, EnlaceOptions } from 'enlace-core';
|
|
2
2
|
|
|
3
3
|
/** Per-request options for React hooks */
|
|
4
4
|
type ReactRequestOptionsBase = {
|
|
@@ -10,6 +10,23 @@ type ReactRequestOptionsBase = {
|
|
|
10
10
|
tags?: string[];
|
|
11
11
|
/** Tags to invalidate after mutation (triggers refetch in matching queries) */
|
|
12
12
|
revalidateTags?: string[];
|
|
13
|
+
/**
|
|
14
|
+
* Path parameters for dynamic URL segments.
|
|
15
|
+
* Used to replace :paramName placeholders in the URL path.
|
|
16
|
+
* @example
|
|
17
|
+
* // With path api.products[':id'].delete
|
|
18
|
+
* trigger({ pathParams: { id: '123' } }) // → DELETE /products/123
|
|
19
|
+
*/
|
|
20
|
+
pathParams?: Record<string, string | number>;
|
|
21
|
+
};
|
|
22
|
+
/** Options for query mode hooks */
|
|
23
|
+
type UseEnlaceQueryOptions = {
|
|
24
|
+
/**
|
|
25
|
+
* Whether the query should execute.
|
|
26
|
+
* Set to false to skip fetching (useful when ID is "new" or undefined).
|
|
27
|
+
* @default true
|
|
28
|
+
*/
|
|
29
|
+
enabled?: boolean;
|
|
13
30
|
};
|
|
14
31
|
type ApiClient<TSchema, TOptions = ReactRequestOptionsBase> = unknown extends TSchema ? WildcardClient<TOptions> : EnlaceClient<TSchema, TOptions>;
|
|
15
32
|
type QueryFn<TSchema, TData, TError, TOptions = ReactRequestOptionsBase> = (api: ApiClient<TSchema, TOptions>) => Promise<EnlaceResponse<TData, TError>>;
|
|
@@ -41,7 +58,7 @@ type UseEnlaceSelectorResult<TMethod> = {
|
|
|
41
58
|
loading: boolean;
|
|
42
59
|
fetching: boolean;
|
|
43
60
|
} & HookResponseState<ExtractData<TMethod>, ExtractError<TMethod>>;
|
|
44
|
-
|
|
61
|
+
/** Options for createEnlaceHook factory */
|
|
45
62
|
type EnlaceHookOptions = {
|
|
46
63
|
/**
|
|
47
64
|
* Auto-generate cache tags from URL path for GET requests.
|
|
@@ -53,6 +70,10 @@ type EnlaceHookOptions = {
|
|
|
53
70
|
autoRevalidateTags?: boolean;
|
|
54
71
|
/** Time in ms before cached data is considered stale. @default 0 (always stale) */
|
|
55
72
|
staleTime?: number;
|
|
73
|
+
/** Callback called on successful API responses */
|
|
74
|
+
onSuccess?: (payload: EnlaceCallbackPayload<unknown>) => void;
|
|
75
|
+
/** Callback called on error responses (HTTP errors or network failures) */
|
|
76
|
+
onError?: (payload: EnlaceErrorCallbackPayload<unknown>) => void;
|
|
56
77
|
};
|
|
57
78
|
|
|
58
79
|
/**
|
|
@@ -62,7 +83,7 @@ type EnlaceHookOptions = {
|
|
|
62
83
|
*/
|
|
63
84
|
type RevalidateHandler = (tags: string[], paths: string[]) => void | Promise<void>;
|
|
64
85
|
/** Next.js-specific options (third argument for createEnlace) */
|
|
65
|
-
type NextOptions = Pick<EnlaceHookOptions, "autoGenerateTags" | "autoRevalidateTags"> & {
|
|
86
|
+
type NextOptions = Pick<EnlaceHookOptions, "autoGenerateTags" | "autoRevalidateTags"> & EnlaceCallbacks & {
|
|
66
87
|
/**
|
|
67
88
|
* Handler called after successful mutations to trigger server-side revalidation.
|
|
68
89
|
* Receives auto-generated or manually specified tags and paths.
|
|
@@ -96,13 +117,14 @@ type NextRequestOptionsBase = ReactRequestOptionsBase & {
|
|
|
96
117
|
*/
|
|
97
118
|
skipRevalidator?: boolean;
|
|
98
119
|
};
|
|
99
|
-
|
|
100
120
|
type NextQueryFn<TSchema, TData, TError> = QueryFn<TSchema, TData, TError, NextRequestOptionsBase>;
|
|
101
121
|
type NextSelectorFn<TSchema, TMethod> = SelectorFn<TSchema, TMethod, NextRequestOptionsBase>;
|
|
122
|
+
/** Hook type returned by Next.js createEnlaceHook */
|
|
102
123
|
type NextEnlaceHook<TSchema> = {
|
|
103
124
|
<TMethod extends (...args: any[]) => Promise<EnlaceResponse<unknown, unknown>>>(selector: NextSelectorFn<TSchema, TMethod>): UseEnlaceSelectorResult<TMethod>;
|
|
104
|
-
<TData, TError>(queryFn: NextQueryFn<TSchema, TData, TError
|
|
125
|
+
<TData, TError>(queryFn: NextQueryFn<TSchema, TData, TError>, options?: UseEnlaceQueryOptions): UseEnlaceQueryResult<TData, TError>;
|
|
105
126
|
};
|
|
127
|
+
|
|
106
128
|
/**
|
|
107
129
|
* Creates a React hook for making API calls in Next.js Client Components.
|
|
108
130
|
* Uses Next.js-specific features like revalidator for server-side cache invalidation.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { WildcardClient, EnlaceClient, EnlaceResponse, EnlaceOptions } from 'enlace-core';
|
|
1
|
+
import { EnlaceCallbackPayload, EnlaceErrorCallbackPayload, WildcardClient, EnlaceClient, EnlaceResponse, EnlaceCallbacks, EnlaceOptions } from 'enlace-core';
|
|
2
2
|
|
|
3
3
|
/** Per-request options for React hooks */
|
|
4
4
|
type ReactRequestOptionsBase = {
|
|
@@ -10,6 +10,23 @@ type ReactRequestOptionsBase = {
|
|
|
10
10
|
tags?: string[];
|
|
11
11
|
/** Tags to invalidate after mutation (triggers refetch in matching queries) */
|
|
12
12
|
revalidateTags?: string[];
|
|
13
|
+
/**
|
|
14
|
+
* Path parameters for dynamic URL segments.
|
|
15
|
+
* Used to replace :paramName placeholders in the URL path.
|
|
16
|
+
* @example
|
|
17
|
+
* // With path api.products[':id'].delete
|
|
18
|
+
* trigger({ pathParams: { id: '123' } }) // → DELETE /products/123
|
|
19
|
+
*/
|
|
20
|
+
pathParams?: Record<string, string | number>;
|
|
21
|
+
};
|
|
22
|
+
/** Options for query mode hooks */
|
|
23
|
+
type UseEnlaceQueryOptions = {
|
|
24
|
+
/**
|
|
25
|
+
* Whether the query should execute.
|
|
26
|
+
* Set to false to skip fetching (useful when ID is "new" or undefined).
|
|
27
|
+
* @default true
|
|
28
|
+
*/
|
|
29
|
+
enabled?: boolean;
|
|
13
30
|
};
|
|
14
31
|
type ApiClient<TSchema, TOptions = ReactRequestOptionsBase> = unknown extends TSchema ? WildcardClient<TOptions> : EnlaceClient<TSchema, TOptions>;
|
|
15
32
|
type QueryFn<TSchema, TData, TError, TOptions = ReactRequestOptionsBase> = (api: ApiClient<TSchema, TOptions>) => Promise<EnlaceResponse<TData, TError>>;
|
|
@@ -41,7 +58,7 @@ type UseEnlaceSelectorResult<TMethod> = {
|
|
|
41
58
|
loading: boolean;
|
|
42
59
|
fetching: boolean;
|
|
43
60
|
} & HookResponseState<ExtractData<TMethod>, ExtractError<TMethod>>;
|
|
44
|
-
|
|
61
|
+
/** Options for createEnlaceHook factory */
|
|
45
62
|
type EnlaceHookOptions = {
|
|
46
63
|
/**
|
|
47
64
|
* Auto-generate cache tags from URL path for GET requests.
|
|
@@ -53,6 +70,10 @@ type EnlaceHookOptions = {
|
|
|
53
70
|
autoRevalidateTags?: boolean;
|
|
54
71
|
/** Time in ms before cached data is considered stale. @default 0 (always stale) */
|
|
55
72
|
staleTime?: number;
|
|
73
|
+
/** Callback called on successful API responses */
|
|
74
|
+
onSuccess?: (payload: EnlaceCallbackPayload<unknown>) => void;
|
|
75
|
+
/** Callback called on error responses (HTTP errors or network failures) */
|
|
76
|
+
onError?: (payload: EnlaceErrorCallbackPayload<unknown>) => void;
|
|
56
77
|
};
|
|
57
78
|
|
|
58
79
|
/**
|
|
@@ -62,7 +83,7 @@ type EnlaceHookOptions = {
|
|
|
62
83
|
*/
|
|
63
84
|
type RevalidateHandler = (tags: string[], paths: string[]) => void | Promise<void>;
|
|
64
85
|
/** Next.js-specific options (third argument for createEnlace) */
|
|
65
|
-
type NextOptions = Pick<EnlaceHookOptions, "autoGenerateTags" | "autoRevalidateTags"> & {
|
|
86
|
+
type NextOptions = Pick<EnlaceHookOptions, "autoGenerateTags" | "autoRevalidateTags"> & EnlaceCallbacks & {
|
|
66
87
|
/**
|
|
67
88
|
* Handler called after successful mutations to trigger server-side revalidation.
|
|
68
89
|
* Receives auto-generated or manually specified tags and paths.
|
|
@@ -96,13 +117,14 @@ type NextRequestOptionsBase = ReactRequestOptionsBase & {
|
|
|
96
117
|
*/
|
|
97
118
|
skipRevalidator?: boolean;
|
|
98
119
|
};
|
|
99
|
-
|
|
100
120
|
type NextQueryFn<TSchema, TData, TError> = QueryFn<TSchema, TData, TError, NextRequestOptionsBase>;
|
|
101
121
|
type NextSelectorFn<TSchema, TMethod> = SelectorFn<TSchema, TMethod, NextRequestOptionsBase>;
|
|
122
|
+
/** Hook type returned by Next.js createEnlaceHook */
|
|
102
123
|
type NextEnlaceHook<TSchema> = {
|
|
103
124
|
<TMethod extends (...args: any[]) => Promise<EnlaceResponse<unknown, unknown>>>(selector: NextSelectorFn<TSchema, TMethod>): UseEnlaceSelectorResult<TMethod>;
|
|
104
|
-
<TData, TError>(queryFn: NextQueryFn<TSchema, TData, TError
|
|
125
|
+
<TData, TError>(queryFn: NextQueryFn<TSchema, TData, TError>, options?: UseEnlaceQueryOptions): UseEnlaceQueryResult<TData, TError>;
|
|
105
126
|
};
|
|
127
|
+
|
|
106
128
|
/**
|
|
107
129
|
* Creates a React hook for making API calls in Next.js Client Components.
|
|
108
130
|
* Uses Next.js-specific features like revalidator for server-side cache invalidation.
|
package/dist/next/hook/index.js
CHANGED
|
@@ -48,20 +48,22 @@ async function executeNextFetch(baseUrl, path, method, combinedOptions, requestO
|
|
|
48
48
|
autoGenerateTags = true,
|
|
49
49
|
autoRevalidateTags = true,
|
|
50
50
|
revalidator,
|
|
51
|
-
|
|
52
|
-
...
|
|
51
|
+
onSuccess,
|
|
52
|
+
...coreOptions
|
|
53
53
|
} = combinedOptions;
|
|
54
|
-
const url = (0, import_enlace_core.buildUrl)(baseUrl, path, requestOptions?.query);
|
|
55
|
-
let headers = (0, import_enlace_core.mergeHeaders)(defaultHeaders, requestOptions?.headers);
|
|
56
54
|
const isGet = method === "GET";
|
|
57
55
|
const autoTags = generateTags(path);
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
const nextOnSuccess = (payload) => {
|
|
57
|
+
if (!isGet && !requestOptions?.skipRevalidator) {
|
|
58
|
+
const revalidateTags = requestOptions?.revalidateTags ?? (autoRevalidateTags ? autoTags : []);
|
|
59
|
+
const revalidatePaths = requestOptions?.revalidatePaths ?? [];
|
|
60
|
+
if (revalidateTags.length || revalidatePaths.length) {
|
|
61
|
+
revalidator?.(revalidateTags, revalidatePaths);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
onSuccess?.(payload);
|
|
61
65
|
};
|
|
62
|
-
|
|
63
|
-
fetchOptions.cache = requestOptions.cache;
|
|
64
|
-
}
|
|
66
|
+
const nextRequestOptions = { ...requestOptions };
|
|
65
67
|
if (isGet) {
|
|
66
68
|
const tags = requestOptions?.tags ?? (autoGenerateTags ? autoTags : void 0);
|
|
67
69
|
const nextFetchOptions = {};
|
|
@@ -71,51 +73,27 @@ async function executeNextFetch(baseUrl, path, method, combinedOptions, requestO
|
|
|
71
73
|
if (requestOptions?.revalidate !== void 0) {
|
|
72
74
|
nextFetchOptions.revalidate = requestOptions.revalidate;
|
|
73
75
|
}
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
if (headers) {
|
|
77
|
-
fetchOptions.headers = headers;
|
|
78
|
-
}
|
|
79
|
-
if (requestOptions?.body !== void 0) {
|
|
80
|
-
if ((0, import_enlace_core.isJsonBody)(requestOptions.body)) {
|
|
81
|
-
fetchOptions.body = JSON.stringify(requestOptions.body);
|
|
82
|
-
headers = (0, import_enlace_core.mergeHeaders)(headers, { "Content-Type": "application/json" });
|
|
83
|
-
if (headers) {
|
|
84
|
-
fetchOptions.headers = headers;
|
|
85
|
-
}
|
|
86
|
-
} else {
|
|
87
|
-
fetchOptions.body = requestOptions.body;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
const response = await fetch(url, fetchOptions);
|
|
91
|
-
const contentType = response.headers.get("content-type");
|
|
92
|
-
const isJson = contentType?.includes("application/json");
|
|
93
|
-
if (response.ok) {
|
|
94
|
-
if (!isGet && !requestOptions?.skipRevalidator) {
|
|
95
|
-
const revalidateTags = requestOptions?.revalidateTags ?? (autoRevalidateTags ? autoTags : []);
|
|
96
|
-
const revalidatePaths = requestOptions?.revalidatePaths ?? [];
|
|
97
|
-
if (revalidateTags.length || revalidatePaths.length) {
|
|
98
|
-
revalidator?.(revalidateTags, revalidatePaths);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
return {
|
|
102
|
-
ok: true,
|
|
103
|
-
status: response.status,
|
|
104
|
-
data: isJson ? await response.json() : response
|
|
105
|
-
};
|
|
76
|
+
nextRequestOptions.next = nextFetchOptions;
|
|
106
77
|
}
|
|
107
|
-
return
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
78
|
+
return (0, import_enlace_core.executeFetch)(
|
|
79
|
+
baseUrl,
|
|
80
|
+
path,
|
|
81
|
+
method,
|
|
82
|
+
{ ...coreOptions, onSuccess: nextOnSuccess },
|
|
83
|
+
nextRequestOptions
|
|
84
|
+
);
|
|
112
85
|
}
|
|
113
86
|
|
|
114
87
|
// src/next/index.ts
|
|
115
88
|
__reExport(next_exports, require("enlace-core"));
|
|
116
89
|
function createEnlace(baseUrl, defaultOptions = {}, nextOptions = {}) {
|
|
117
90
|
const combinedOptions = { ...defaultOptions, ...nextOptions };
|
|
118
|
-
return (0, import_enlace_core2.createProxyHandler)(
|
|
91
|
+
return (0, import_enlace_core2.createProxyHandler)(
|
|
92
|
+
baseUrl,
|
|
93
|
+
combinedOptions,
|
|
94
|
+
[],
|
|
95
|
+
executeNextFetch
|
|
96
|
+
);
|
|
119
97
|
}
|
|
120
98
|
|
|
121
99
|
// src/react/useQueryMode.ts
|
|
@@ -132,7 +110,7 @@ var initialState = {
|
|
|
132
110
|
function hookReducer(state, action) {
|
|
133
111
|
switch (action.type) {
|
|
134
112
|
case "RESET":
|
|
135
|
-
return action.state;
|
|
113
|
+
return action.state ?? initialState;
|
|
136
114
|
case "FETCH_START":
|
|
137
115
|
return {
|
|
138
116
|
...state,
|
|
@@ -257,11 +235,29 @@ function onRevalidate(callback) {
|
|
|
257
235
|
}
|
|
258
236
|
|
|
259
237
|
// src/react/useQueryMode.ts
|
|
238
|
+
function resolvePath(path, pathParams) {
|
|
239
|
+
if (!pathParams) return path;
|
|
240
|
+
return path.map((segment) => {
|
|
241
|
+
if (segment.startsWith(":")) {
|
|
242
|
+
const paramName = segment.slice(1);
|
|
243
|
+
const value = pathParams[paramName];
|
|
244
|
+
if (value === void 0) {
|
|
245
|
+
throw new Error(`Missing path parameter: ${paramName}`);
|
|
246
|
+
}
|
|
247
|
+
return String(value);
|
|
248
|
+
}
|
|
249
|
+
return segment;
|
|
250
|
+
});
|
|
251
|
+
}
|
|
260
252
|
function useQueryMode(api, trackedCall, options) {
|
|
261
|
-
const { autoGenerateTags, staleTime } = options;
|
|
253
|
+
const { autoGenerateTags, staleTime, enabled } = options;
|
|
262
254
|
const queryKey = createQueryKey(trackedCall);
|
|
263
255
|
const requestOptions = trackedCall.options;
|
|
264
|
-
const
|
|
256
|
+
const resolvedPath = resolvePath(
|
|
257
|
+
trackedCall.path,
|
|
258
|
+
requestOptions?.pathParams
|
|
259
|
+
);
|
|
260
|
+
const queryTags = requestOptions?.tags ?? (autoGenerateTags ? generateTags(resolvedPath) : []);
|
|
265
261
|
const getCacheState = (includeNeedsFetch = false) => {
|
|
266
262
|
const cached = getCache(queryKey);
|
|
267
263
|
const hasCachedData = cached?.data !== void 0;
|
|
@@ -275,17 +271,22 @@ function useQueryMode(api, trackedCall, options) {
|
|
|
275
271
|
error: cached?.error
|
|
276
272
|
};
|
|
277
273
|
};
|
|
278
|
-
const [state, dispatch] = (0, import_react.useReducer)(
|
|
274
|
+
const [state, dispatch] = (0, import_react.useReducer)(
|
|
275
|
+
hookReducer,
|
|
276
|
+
null,
|
|
277
|
+
() => getCacheState(true)
|
|
278
|
+
);
|
|
279
279
|
const mountedRef = (0, import_react.useRef)(true);
|
|
280
280
|
const fetchRef = (0, import_react.useRef)(null);
|
|
281
281
|
(0, import_react.useEffect)(() => {
|
|
282
282
|
mountedRef.current = true;
|
|
283
|
+
if (!enabled) {
|
|
284
|
+
dispatch({ type: "RESET" });
|
|
285
|
+
return () => {
|
|
286
|
+
mountedRef.current = false;
|
|
287
|
+
};
|
|
288
|
+
}
|
|
283
289
|
dispatch({ type: "RESET", state: getCacheState(true) });
|
|
284
|
-
const unsubscribe = subscribeCache(queryKey, () => {
|
|
285
|
-
if (mountedRef.current) {
|
|
286
|
-
dispatch({ type: "SYNC_CACHE", state: getCacheState() });
|
|
287
|
-
}
|
|
288
|
-
});
|
|
289
290
|
const doFetch = () => {
|
|
290
291
|
const cached2 = getCache(queryKey);
|
|
291
292
|
if (cached2?.promise) {
|
|
@@ -293,7 +294,7 @@ function useQueryMode(api, trackedCall, options) {
|
|
|
293
294
|
}
|
|
294
295
|
dispatch({ type: "FETCH_START" });
|
|
295
296
|
let current = api;
|
|
296
|
-
for (const segment of
|
|
297
|
+
for (const segment of resolvedPath) {
|
|
297
298
|
current = current[segment];
|
|
298
299
|
}
|
|
299
300
|
const method = current[trackedCall.method];
|
|
@@ -301,7 +302,7 @@ function useQueryMode(api, trackedCall, options) {
|
|
|
301
302
|
if (mountedRef.current) {
|
|
302
303
|
setCache(queryKey, {
|
|
303
304
|
data: res.ok ? res.data : void 0,
|
|
304
|
-
error: res.ok ? void 0 : res.error,
|
|
305
|
+
error: res.ok || res.status === 0 ? void 0 : res.error,
|
|
305
306
|
ok: res.ok,
|
|
306
307
|
timestamp: Date.now(),
|
|
307
308
|
tags: queryTags
|
|
@@ -320,12 +321,17 @@ function useQueryMode(api, trackedCall, options) {
|
|
|
320
321
|
} else {
|
|
321
322
|
doFetch();
|
|
322
323
|
}
|
|
324
|
+
const unsubscribe = subscribeCache(queryKey, () => {
|
|
325
|
+
if (mountedRef.current) {
|
|
326
|
+
dispatch({ type: "SYNC_CACHE", state: getCacheState() });
|
|
327
|
+
}
|
|
328
|
+
});
|
|
323
329
|
return () => {
|
|
324
330
|
mountedRef.current = false;
|
|
325
331
|
fetchRef.current = null;
|
|
326
332
|
unsubscribe();
|
|
327
333
|
};
|
|
328
|
-
}, [queryKey]);
|
|
334
|
+
}, [queryKey, enabled]);
|
|
329
335
|
(0, import_react.useEffect)(() => {
|
|
330
336
|
if (queryTags.length === 0) return;
|
|
331
337
|
return onRevalidate((invalidatedTags) => {
|
|
@@ -340,23 +346,56 @@ function useQueryMode(api, trackedCall, options) {
|
|
|
340
346
|
|
|
341
347
|
// src/react/useSelectorMode.ts
|
|
342
348
|
var import_react2 = require("react");
|
|
343
|
-
function
|
|
349
|
+
function resolvePath2(path, pathParams) {
|
|
350
|
+
if (!pathParams) return path;
|
|
351
|
+
return path.map((segment) => {
|
|
352
|
+
if (segment.startsWith(":")) {
|
|
353
|
+
const paramName = segment.slice(1);
|
|
354
|
+
const value = pathParams[paramName];
|
|
355
|
+
if (value === void 0) {
|
|
356
|
+
throw new Error(`Missing path parameter: ${paramName}`);
|
|
357
|
+
}
|
|
358
|
+
return String(value);
|
|
359
|
+
}
|
|
360
|
+
return segment;
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
function hasPathParams(path) {
|
|
364
|
+
return path.some((segment) => segment.startsWith(":"));
|
|
365
|
+
}
|
|
366
|
+
function useSelectorMode(config) {
|
|
367
|
+
const { method, api, path, methodName, autoRevalidateTags } = config;
|
|
344
368
|
const [state, dispatch] = (0, import_react2.useReducer)(hookReducer, initialState);
|
|
345
369
|
const methodRef = (0, import_react2.useRef)(method);
|
|
370
|
+
const apiRef = (0, import_react2.useRef)(api);
|
|
346
371
|
const triggerRef = (0, import_react2.useRef)(null);
|
|
347
372
|
const pathRef = (0, import_react2.useRef)(path);
|
|
373
|
+
const methodNameRef = (0, import_react2.useRef)(methodName);
|
|
348
374
|
const autoRevalidateRef = (0, import_react2.useRef)(autoRevalidateTags);
|
|
349
375
|
methodRef.current = method;
|
|
376
|
+
apiRef.current = api;
|
|
350
377
|
pathRef.current = path;
|
|
378
|
+
methodNameRef.current = methodName;
|
|
351
379
|
autoRevalidateRef.current = autoRevalidateTags;
|
|
352
380
|
if (!triggerRef.current) {
|
|
353
381
|
triggerRef.current = (async (...args) => {
|
|
354
382
|
dispatch({ type: "FETCH_START" });
|
|
355
|
-
const
|
|
383
|
+
const options = args[0];
|
|
384
|
+
const resolvedPath = resolvePath2(pathRef.current, options?.pathParams);
|
|
385
|
+
let res;
|
|
386
|
+
if (hasPathParams(pathRef.current)) {
|
|
387
|
+
let current = apiRef.current;
|
|
388
|
+
for (const segment of resolvedPath) {
|
|
389
|
+
current = current[segment];
|
|
390
|
+
}
|
|
391
|
+
const resolvedMethod = current[methodNameRef.current];
|
|
392
|
+
res = await resolvedMethod(...args);
|
|
393
|
+
} else {
|
|
394
|
+
res = await methodRef.current(...args);
|
|
395
|
+
}
|
|
356
396
|
if (res.ok) {
|
|
357
397
|
dispatch({ type: "FETCH_SUCCESS", data: res.data });
|
|
358
|
-
const
|
|
359
|
-
const tagsToInvalidate = options?.revalidateTags ?? (autoRevalidateRef.current ? generateTags(pathRef.current) : []);
|
|
398
|
+
const tagsToInvalidate = options?.revalidateTags ?? (autoRevalidateRef.current ? generateTags(resolvedPath) : []);
|
|
360
399
|
if (tagsToInvalidate.length > 0) {
|
|
361
400
|
invalidateTags(tagsToInvalidate);
|
|
362
401
|
}
|
|
@@ -417,26 +456,30 @@ function createEnlaceHook(baseUrl, defaultOptions = {}, hookOptions = {}) {
|
|
|
417
456
|
autoRevalidateTags,
|
|
418
457
|
...nextOptions
|
|
419
458
|
});
|
|
420
|
-
function useEnlaceHook(selectorOrQuery) {
|
|
459
|
+
function useEnlaceHook(selectorOrQuery, queryOptions) {
|
|
421
460
|
let trackedCall = null;
|
|
422
461
|
let selectorPath = null;
|
|
462
|
+
let selectorMethod = null;
|
|
423
463
|
const trackingProxy = createTrackingProxy((result2) => {
|
|
424
464
|
trackedCall = result2.trackedCall;
|
|
425
465
|
selectorPath = result2.selectorPath;
|
|
466
|
+
selectorMethod = result2.selectorMethod;
|
|
426
467
|
});
|
|
427
468
|
const result = selectorOrQuery(trackingProxy);
|
|
428
469
|
if (typeof result === "function") {
|
|
429
470
|
const actualResult = selectorOrQuery(api);
|
|
430
|
-
return useSelectorMode(
|
|
431
|
-
actualResult,
|
|
432
|
-
|
|
471
|
+
return useSelectorMode({
|
|
472
|
+
method: actualResult,
|
|
473
|
+
api,
|
|
474
|
+
path: selectorPath ?? [],
|
|
475
|
+
methodName: selectorMethod ?? "",
|
|
433
476
|
autoRevalidateTags
|
|
434
|
-
);
|
|
477
|
+
});
|
|
435
478
|
}
|
|
436
479
|
return useQueryMode(
|
|
437
480
|
api,
|
|
438
481
|
trackedCall,
|
|
439
|
-
{ autoGenerateTags, staleTime }
|
|
482
|
+
{ autoGenerateTags, staleTime, enabled: queryOptions?.enabled ?? true }
|
|
440
483
|
);
|
|
441
484
|
}
|
|
442
485
|
return useEnlaceHook;
|