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.
@@ -29,9 +29,7 @@ import {
29
29
 
30
30
  // src/next/fetch.ts
31
31
  import {
32
- buildUrl,
33
- isJsonBody,
34
- mergeHeaders
32
+ executeFetch
35
33
  } from "enlace-core";
36
34
 
37
35
  // src/utils/generateTags.ts
@@ -45,20 +43,22 @@ async function executeNextFetch(baseUrl, path, method, combinedOptions, requestO
45
43
  autoGenerateTags = true,
46
44
  autoRevalidateTags = true,
47
45
  revalidator,
48
- headers: defaultHeaders,
49
- ...restOptions
46
+ onSuccess,
47
+ ...coreOptions
50
48
  } = combinedOptions;
51
- const url = buildUrl(baseUrl, path, requestOptions?.query);
52
- let headers = mergeHeaders(defaultHeaders, requestOptions?.headers);
53
49
  const isGet = method === "GET";
54
50
  const autoTags = generateTags(path);
55
- const fetchOptions = {
56
- ...restOptions,
57
- method
51
+ const nextOnSuccess = (payload) => {
52
+ if (!isGet && !requestOptions?.skipRevalidator) {
53
+ const revalidateTags = requestOptions?.revalidateTags ?? (autoRevalidateTags ? autoTags : []);
54
+ const revalidatePaths = requestOptions?.revalidatePaths ?? [];
55
+ if (revalidateTags.length || revalidatePaths.length) {
56
+ revalidator?.(revalidateTags, revalidatePaths);
57
+ }
58
+ }
59
+ onSuccess?.(payload);
58
60
  };
59
- if (requestOptions?.cache) {
60
- fetchOptions.cache = requestOptions.cache;
61
- }
61
+ const nextRequestOptions = { ...requestOptions };
62
62
  if (isGet) {
63
63
  const tags = requestOptions?.tags ?? (autoGenerateTags ? autoTags : void 0);
64
64
  const nextFetchOptions = {};
@@ -68,44 +68,15 @@ async function executeNextFetch(baseUrl, path, method, combinedOptions, requestO
68
68
  if (requestOptions?.revalidate !== void 0) {
69
69
  nextFetchOptions.revalidate = requestOptions.revalidate;
70
70
  }
71
- fetchOptions.next = nextFetchOptions;
72
- }
73
- if (headers) {
74
- fetchOptions.headers = headers;
75
- }
76
- if (requestOptions?.body !== void 0) {
77
- if (isJsonBody(requestOptions.body)) {
78
- fetchOptions.body = JSON.stringify(requestOptions.body);
79
- headers = mergeHeaders(headers, { "Content-Type": "application/json" });
80
- if (headers) {
81
- fetchOptions.headers = headers;
82
- }
83
- } else {
84
- fetchOptions.body = requestOptions.body;
85
- }
86
- }
87
- const response = await fetch(url, fetchOptions);
88
- const contentType = response.headers.get("content-type");
89
- const isJson = contentType?.includes("application/json");
90
- if (response.ok) {
91
- if (!isGet && !requestOptions?.skipRevalidator) {
92
- const revalidateTags = requestOptions?.revalidateTags ?? (autoRevalidateTags ? autoTags : []);
93
- const revalidatePaths = requestOptions?.revalidatePaths ?? [];
94
- if (revalidateTags.length || revalidatePaths.length) {
95
- revalidator?.(revalidateTags, revalidatePaths);
96
- }
97
- }
98
- return {
99
- ok: true,
100
- status: response.status,
101
- data: isJson ? await response.json() : response
102
- };
71
+ nextRequestOptions.next = nextFetchOptions;
103
72
  }
104
- return {
105
- ok: false,
106
- status: response.status,
107
- error: isJson ? await response.json() : response
108
- };
73
+ return executeFetch(
74
+ baseUrl,
75
+ path,
76
+ method,
77
+ { ...coreOptions, onSuccess: nextOnSuccess },
78
+ nextRequestOptions
79
+ );
109
80
  }
110
81
 
111
82
  // src/next/index.ts
@@ -113,7 +84,12 @@ __reExport(next_exports, enlace_core_star);
113
84
  import * as enlace_core_star from "enlace-core";
114
85
  function createEnlace(baseUrl, defaultOptions = {}, nextOptions = {}) {
115
86
  const combinedOptions = { ...defaultOptions, ...nextOptions };
116
- return createProxyHandler(baseUrl, combinedOptions, [], executeNextFetch);
87
+ return createProxyHandler(
88
+ baseUrl,
89
+ combinedOptions,
90
+ [],
91
+ executeNextFetch
92
+ );
117
93
  }
118
94
 
119
95
  // src/react/useQueryMode.ts
@@ -130,7 +106,7 @@ var initialState = {
130
106
  function hookReducer(state, action) {
131
107
  switch (action.type) {
132
108
  case "RESET":
133
- return action.state;
109
+ return action.state ?? initialState;
134
110
  case "FETCH_START":
135
111
  return {
136
112
  ...state,
@@ -255,11 +231,29 @@ function onRevalidate(callback) {
255
231
  }
256
232
 
257
233
  // src/react/useQueryMode.ts
234
+ function resolvePath(path, pathParams) {
235
+ if (!pathParams) return path;
236
+ return path.map((segment) => {
237
+ if (segment.startsWith(":")) {
238
+ const paramName = segment.slice(1);
239
+ const value = pathParams[paramName];
240
+ if (value === void 0) {
241
+ throw new Error(`Missing path parameter: ${paramName}`);
242
+ }
243
+ return String(value);
244
+ }
245
+ return segment;
246
+ });
247
+ }
258
248
  function useQueryMode(api, trackedCall, options) {
259
- const { autoGenerateTags, staleTime } = options;
249
+ const { autoGenerateTags, staleTime, enabled } = options;
260
250
  const queryKey = createQueryKey(trackedCall);
261
251
  const requestOptions = trackedCall.options;
262
- const queryTags = requestOptions?.tags ?? (autoGenerateTags ? generateTags(trackedCall.path) : []);
252
+ const resolvedPath = resolvePath(
253
+ trackedCall.path,
254
+ requestOptions?.pathParams
255
+ );
256
+ const queryTags = requestOptions?.tags ?? (autoGenerateTags ? generateTags(resolvedPath) : []);
263
257
  const getCacheState = (includeNeedsFetch = false) => {
264
258
  const cached = getCache(queryKey);
265
259
  const hasCachedData = cached?.data !== void 0;
@@ -273,17 +267,22 @@ function useQueryMode(api, trackedCall, options) {
273
267
  error: cached?.error
274
268
  };
275
269
  };
276
- const [state, dispatch] = useReducer(hookReducer, null, () => getCacheState(true));
270
+ const [state, dispatch] = useReducer(
271
+ hookReducer,
272
+ null,
273
+ () => getCacheState(true)
274
+ );
277
275
  const mountedRef = useRef(true);
278
276
  const fetchRef = useRef(null);
279
277
  useEffect(() => {
280
278
  mountedRef.current = true;
279
+ if (!enabled) {
280
+ dispatch({ type: "RESET" });
281
+ return () => {
282
+ mountedRef.current = false;
283
+ };
284
+ }
281
285
  dispatch({ type: "RESET", state: getCacheState(true) });
282
- const unsubscribe = subscribeCache(queryKey, () => {
283
- if (mountedRef.current) {
284
- dispatch({ type: "SYNC_CACHE", state: getCacheState() });
285
- }
286
- });
287
286
  const doFetch = () => {
288
287
  const cached2 = getCache(queryKey);
289
288
  if (cached2?.promise) {
@@ -291,7 +290,7 @@ function useQueryMode(api, trackedCall, options) {
291
290
  }
292
291
  dispatch({ type: "FETCH_START" });
293
292
  let current = api;
294
- for (const segment of trackedCall.path) {
293
+ for (const segment of resolvedPath) {
295
294
  current = current[segment];
296
295
  }
297
296
  const method = current[trackedCall.method];
@@ -299,7 +298,7 @@ function useQueryMode(api, trackedCall, options) {
299
298
  if (mountedRef.current) {
300
299
  setCache(queryKey, {
301
300
  data: res.ok ? res.data : void 0,
302
- error: res.ok ? void 0 : res.error,
301
+ error: res.ok || res.status === 0 ? void 0 : res.error,
303
302
  ok: res.ok,
304
303
  timestamp: Date.now(),
305
304
  tags: queryTags
@@ -318,12 +317,17 @@ function useQueryMode(api, trackedCall, options) {
318
317
  } else {
319
318
  doFetch();
320
319
  }
320
+ const unsubscribe = subscribeCache(queryKey, () => {
321
+ if (mountedRef.current) {
322
+ dispatch({ type: "SYNC_CACHE", state: getCacheState() });
323
+ }
324
+ });
321
325
  return () => {
322
326
  mountedRef.current = false;
323
327
  fetchRef.current = null;
324
328
  unsubscribe();
325
329
  };
326
- }, [queryKey]);
330
+ }, [queryKey, enabled]);
327
331
  useEffect(() => {
328
332
  if (queryTags.length === 0) return;
329
333
  return onRevalidate((invalidatedTags) => {
@@ -338,23 +342,56 @@ function useQueryMode(api, trackedCall, options) {
338
342
 
339
343
  // src/react/useSelectorMode.ts
340
344
  import { useRef as useRef2, useReducer as useReducer2 } from "react";
341
- function useSelectorMode(method, path, autoRevalidateTags) {
345
+ function resolvePath2(path, pathParams) {
346
+ if (!pathParams) return path;
347
+ return path.map((segment) => {
348
+ if (segment.startsWith(":")) {
349
+ const paramName = segment.slice(1);
350
+ const value = pathParams[paramName];
351
+ if (value === void 0) {
352
+ throw new Error(`Missing path parameter: ${paramName}`);
353
+ }
354
+ return String(value);
355
+ }
356
+ return segment;
357
+ });
358
+ }
359
+ function hasPathParams(path) {
360
+ return path.some((segment) => segment.startsWith(":"));
361
+ }
362
+ function useSelectorMode(config) {
363
+ const { method, api, path, methodName, autoRevalidateTags } = config;
342
364
  const [state, dispatch] = useReducer2(hookReducer, initialState);
343
365
  const methodRef = useRef2(method);
366
+ const apiRef = useRef2(api);
344
367
  const triggerRef = useRef2(null);
345
368
  const pathRef = useRef2(path);
369
+ const methodNameRef = useRef2(methodName);
346
370
  const autoRevalidateRef = useRef2(autoRevalidateTags);
347
371
  methodRef.current = method;
372
+ apiRef.current = api;
348
373
  pathRef.current = path;
374
+ methodNameRef.current = methodName;
349
375
  autoRevalidateRef.current = autoRevalidateTags;
350
376
  if (!triggerRef.current) {
351
377
  triggerRef.current = (async (...args) => {
352
378
  dispatch({ type: "FETCH_START" });
353
- const res = await methodRef.current(...args);
379
+ const options = args[0];
380
+ const resolvedPath = resolvePath2(pathRef.current, options?.pathParams);
381
+ let res;
382
+ if (hasPathParams(pathRef.current)) {
383
+ let current = apiRef.current;
384
+ for (const segment of resolvedPath) {
385
+ current = current[segment];
386
+ }
387
+ const resolvedMethod = current[methodNameRef.current];
388
+ res = await resolvedMethod(...args);
389
+ } else {
390
+ res = await methodRef.current(...args);
391
+ }
354
392
  if (res.ok) {
355
393
  dispatch({ type: "FETCH_SUCCESS", data: res.data });
356
- const options = args[0];
357
- const tagsToInvalidate = options?.revalidateTags ?? (autoRevalidateRef.current ? generateTags(pathRef.current) : []);
394
+ const tagsToInvalidate = options?.revalidateTags ?? (autoRevalidateRef.current ? generateTags(resolvedPath) : []);
358
395
  if (tagsToInvalidate.length > 0) {
359
396
  invalidateTags(tagsToInvalidate);
360
397
  }
@@ -415,26 +452,30 @@ function createEnlaceHook(baseUrl, defaultOptions = {}, hookOptions = {}) {
415
452
  autoRevalidateTags,
416
453
  ...nextOptions
417
454
  });
418
- function useEnlaceHook(selectorOrQuery) {
455
+ function useEnlaceHook(selectorOrQuery, queryOptions) {
419
456
  let trackedCall = null;
420
457
  let selectorPath = null;
458
+ let selectorMethod = null;
421
459
  const trackingProxy = createTrackingProxy((result2) => {
422
460
  trackedCall = result2.trackedCall;
423
461
  selectorPath = result2.selectorPath;
462
+ selectorMethod = result2.selectorMethod;
424
463
  });
425
464
  const result = selectorOrQuery(trackingProxy);
426
465
  if (typeof result === "function") {
427
466
  const actualResult = selectorOrQuery(api);
428
- return useSelectorMode(
429
- actualResult,
430
- selectorPath ?? [],
467
+ return useSelectorMode({
468
+ method: actualResult,
469
+ api,
470
+ path: selectorPath ?? [],
471
+ methodName: selectorMethod ?? "",
431
472
  autoRevalidateTags
432
- );
473
+ });
433
474
  }
434
475
  return useQueryMode(
435
476
  api,
436
477
  trackedCall,
437
- { autoGenerateTags, staleTime }
478
+ { autoGenerateTags, staleTime, enabled: queryOptions?.enabled ?? true }
438
479
  );
439
480
  }
440
481
  return useEnlaceHook;
@@ -1,6 +1,6 @@
1
- import { EnlaceOptions, WildcardClient, EnlaceClient } from 'enlace-core';
1
+ import { EnlaceCallbackPayload, EnlaceErrorCallbackPayload, WildcardClient, EnlaceClient, EnlaceResponse, EnlaceCallbacks, EnlaceOptions } from 'enlace-core';
2
2
  export * from 'enlace-core';
3
- export { EnlaceOptions } from 'enlace-core';
3
+ export { EnlaceCallbacks, EnlaceOptions } from 'enlace-core';
4
4
 
5
5
  /** Per-request options for React hooks */
6
6
  type ReactRequestOptionsBase = {
@@ -12,8 +12,55 @@ type ReactRequestOptionsBase = {
12
12
  tags?: string[];
13
13
  /** Tags to invalidate after mutation (triggers refetch in matching queries) */
14
14
  revalidateTags?: string[];
15
+ /**
16
+ * Path parameters for dynamic URL segments.
17
+ * Used to replace :paramName placeholders in the URL path.
18
+ * @example
19
+ * // With path api.products[':id'].delete
20
+ * trigger({ pathParams: { id: '123' } }) // → DELETE /products/123
21
+ */
22
+ pathParams?: Record<string, string | number>;
15
23
  };
16
-
24
+ /** Options for query mode hooks */
25
+ type UseEnlaceQueryOptions = {
26
+ /**
27
+ * Whether the query should execute.
28
+ * Set to false to skip fetching (useful when ID is "new" or undefined).
29
+ * @default true
30
+ */
31
+ enabled?: boolean;
32
+ };
33
+ type ApiClient<TSchema, TOptions = ReactRequestOptionsBase> = unknown extends TSchema ? WildcardClient<TOptions> : EnlaceClient<TSchema, TOptions>;
34
+ type QueryFn<TSchema, TData, TError, TOptions = ReactRequestOptionsBase> = (api: ApiClient<TSchema, TOptions>) => Promise<EnlaceResponse<TData, TError>>;
35
+ type SelectorFn<TSchema, TMethod, TOptions = ReactRequestOptionsBase> = (api: ApiClient<TSchema, TOptions>) => TMethod;
36
+ type ExtractData<T> = T extends (...args: any[]) => Promise<EnlaceResponse<infer D, unknown>> ? D : never;
37
+ type ExtractError<T> = T extends (...args: any[]) => Promise<EnlaceResponse<unknown, infer E>> ? E : never;
38
+ /** Discriminated union for hook response state - enables type narrowing on ok check */
39
+ type HookResponseState<TData, TError> = {
40
+ ok: undefined;
41
+ data: undefined;
42
+ error: undefined;
43
+ } | {
44
+ ok: true;
45
+ data: TData;
46
+ error: undefined;
47
+ } | {
48
+ ok: false;
49
+ data: undefined;
50
+ error: TError;
51
+ };
52
+ /** Result when hook is called with query function (auto-fetch mode) */
53
+ type UseEnlaceQueryResult<TData, TError> = {
54
+ loading: boolean;
55
+ fetching: boolean;
56
+ } & HookResponseState<TData, TError>;
57
+ /** Result when hook is called with method selector (trigger mode) */
58
+ type UseEnlaceSelectorResult<TMethod> = {
59
+ trigger: TMethod;
60
+ loading: boolean;
61
+ fetching: boolean;
62
+ } & HookResponseState<ExtractData<TMethod>, ExtractError<TMethod>>;
63
+ /** Options for createEnlaceHook factory */
17
64
  type EnlaceHookOptions = {
18
65
  /**
19
66
  * Auto-generate cache tags from URL path for GET requests.
@@ -25,6 +72,10 @@ type EnlaceHookOptions = {
25
72
  autoRevalidateTags?: boolean;
26
73
  /** Time in ms before cached data is considered stale. @default 0 (always stale) */
27
74
  staleTime?: number;
75
+ /** Callback called on successful API responses */
76
+ onSuccess?: (payload: EnlaceCallbackPayload<unknown>) => void;
77
+ /** Callback called on error responses (HTTP errors or network failures) */
78
+ onError?: (payload: EnlaceErrorCallbackPayload<unknown>) => void;
28
79
  };
29
80
 
30
81
  /**
@@ -34,7 +85,7 @@ type EnlaceHookOptions = {
34
85
  */
35
86
  type RevalidateHandler = (tags: string[], paths: string[]) => void | Promise<void>;
36
87
  /** Next.js-specific options (third argument for createEnlace) */
37
- type NextOptions = Pick<EnlaceHookOptions, "autoGenerateTags" | "autoRevalidateTags"> & {
88
+ type NextOptions = Pick<EnlaceHookOptions, "autoGenerateTags" | "autoRevalidateTags"> & EnlaceCallbacks & {
38
89
  /**
39
90
  * Handler called after successful mutations to trigger server-side revalidation.
40
91
  * Receives auto-generated or manually specified tags and paths.
@@ -68,7 +119,15 @@ type NextRequestOptionsBase = ReactRequestOptionsBase & {
68
119
  */
69
120
  skipRevalidator?: boolean;
70
121
  };
122
+ type NextApiClient<TSchema> = ApiClient<TSchema, NextRequestOptionsBase>;
123
+ type NextQueryFn<TSchema, TData, TError> = QueryFn<TSchema, TData, TError, NextRequestOptionsBase>;
124
+ type NextSelectorFn<TSchema, TMethod> = SelectorFn<TSchema, TMethod, NextRequestOptionsBase>;
125
+ /** Hook type returned by Next.js createEnlaceHook */
126
+ type NextEnlaceHook<TSchema> = {
127
+ <TMethod extends (...args: any[]) => Promise<EnlaceResponse<unknown, unknown>>>(selector: NextSelectorFn<TSchema, TMethod>): UseEnlaceSelectorResult<TMethod>;
128
+ <TData, TError>(queryFn: NextQueryFn<TSchema, TData, TError>, options?: UseEnlaceQueryOptions): UseEnlaceQueryResult<TData, TError>;
129
+ };
71
130
 
72
- declare function createEnlace<TSchema = unknown>(baseUrl: string, defaultOptions?: EnlaceOptions, nextOptions?: NextOptions): unknown extends TSchema ? WildcardClient<NextRequestOptionsBase> : EnlaceClient<TSchema, NextRequestOptionsBase>;
131
+ declare function createEnlace<TSchema = unknown>(baseUrl: string, defaultOptions?: EnlaceOptions | null, nextOptions?: NextOptions): unknown extends TSchema ? WildcardClient<NextRequestOptionsBase> : EnlaceClient<TSchema, NextRequestOptionsBase>;
73
132
 
74
- export { type NextHookOptions, type NextOptions, type NextRequestOptionsBase, type RevalidateHandler, createEnlace };
133
+ export { type NextApiClient, type NextEnlaceHook, type NextHookOptions, type NextOptions, type NextQueryFn, type NextRequestOptionsBase, type NextSelectorFn, type RevalidateHandler, createEnlace };
@@ -1,6 +1,6 @@
1
- import { EnlaceOptions, WildcardClient, EnlaceClient } from 'enlace-core';
1
+ import { EnlaceCallbackPayload, EnlaceErrorCallbackPayload, WildcardClient, EnlaceClient, EnlaceResponse, EnlaceCallbacks, EnlaceOptions } from 'enlace-core';
2
2
  export * from 'enlace-core';
3
- export { EnlaceOptions } from 'enlace-core';
3
+ export { EnlaceCallbacks, EnlaceOptions } from 'enlace-core';
4
4
 
5
5
  /** Per-request options for React hooks */
6
6
  type ReactRequestOptionsBase = {
@@ -12,8 +12,55 @@ type ReactRequestOptionsBase = {
12
12
  tags?: string[];
13
13
  /** Tags to invalidate after mutation (triggers refetch in matching queries) */
14
14
  revalidateTags?: string[];
15
+ /**
16
+ * Path parameters for dynamic URL segments.
17
+ * Used to replace :paramName placeholders in the URL path.
18
+ * @example
19
+ * // With path api.products[':id'].delete
20
+ * trigger({ pathParams: { id: '123' } }) // → DELETE /products/123
21
+ */
22
+ pathParams?: Record<string, string | number>;
15
23
  };
16
-
24
+ /** Options for query mode hooks */
25
+ type UseEnlaceQueryOptions = {
26
+ /**
27
+ * Whether the query should execute.
28
+ * Set to false to skip fetching (useful when ID is "new" or undefined).
29
+ * @default true
30
+ */
31
+ enabled?: boolean;
32
+ };
33
+ type ApiClient<TSchema, TOptions = ReactRequestOptionsBase> = unknown extends TSchema ? WildcardClient<TOptions> : EnlaceClient<TSchema, TOptions>;
34
+ type QueryFn<TSchema, TData, TError, TOptions = ReactRequestOptionsBase> = (api: ApiClient<TSchema, TOptions>) => Promise<EnlaceResponse<TData, TError>>;
35
+ type SelectorFn<TSchema, TMethod, TOptions = ReactRequestOptionsBase> = (api: ApiClient<TSchema, TOptions>) => TMethod;
36
+ type ExtractData<T> = T extends (...args: any[]) => Promise<EnlaceResponse<infer D, unknown>> ? D : never;
37
+ type ExtractError<T> = T extends (...args: any[]) => Promise<EnlaceResponse<unknown, infer E>> ? E : never;
38
+ /** Discriminated union for hook response state - enables type narrowing on ok check */
39
+ type HookResponseState<TData, TError> = {
40
+ ok: undefined;
41
+ data: undefined;
42
+ error: undefined;
43
+ } | {
44
+ ok: true;
45
+ data: TData;
46
+ error: undefined;
47
+ } | {
48
+ ok: false;
49
+ data: undefined;
50
+ error: TError;
51
+ };
52
+ /** Result when hook is called with query function (auto-fetch mode) */
53
+ type UseEnlaceQueryResult<TData, TError> = {
54
+ loading: boolean;
55
+ fetching: boolean;
56
+ } & HookResponseState<TData, TError>;
57
+ /** Result when hook is called with method selector (trigger mode) */
58
+ type UseEnlaceSelectorResult<TMethod> = {
59
+ trigger: TMethod;
60
+ loading: boolean;
61
+ fetching: boolean;
62
+ } & HookResponseState<ExtractData<TMethod>, ExtractError<TMethod>>;
63
+ /** Options for createEnlaceHook factory */
17
64
  type EnlaceHookOptions = {
18
65
  /**
19
66
  * Auto-generate cache tags from URL path for GET requests.
@@ -25,6 +72,10 @@ type EnlaceHookOptions = {
25
72
  autoRevalidateTags?: boolean;
26
73
  /** Time in ms before cached data is considered stale. @default 0 (always stale) */
27
74
  staleTime?: number;
75
+ /** Callback called on successful API responses */
76
+ onSuccess?: (payload: EnlaceCallbackPayload<unknown>) => void;
77
+ /** Callback called on error responses (HTTP errors or network failures) */
78
+ onError?: (payload: EnlaceErrorCallbackPayload<unknown>) => void;
28
79
  };
29
80
 
30
81
  /**
@@ -34,7 +85,7 @@ type EnlaceHookOptions = {
34
85
  */
35
86
  type RevalidateHandler = (tags: string[], paths: string[]) => void | Promise<void>;
36
87
  /** Next.js-specific options (third argument for createEnlace) */
37
- type NextOptions = Pick<EnlaceHookOptions, "autoGenerateTags" | "autoRevalidateTags"> & {
88
+ type NextOptions = Pick<EnlaceHookOptions, "autoGenerateTags" | "autoRevalidateTags"> & EnlaceCallbacks & {
38
89
  /**
39
90
  * Handler called after successful mutations to trigger server-side revalidation.
40
91
  * Receives auto-generated or manually specified tags and paths.
@@ -68,7 +119,15 @@ type NextRequestOptionsBase = ReactRequestOptionsBase & {
68
119
  */
69
120
  skipRevalidator?: boolean;
70
121
  };
122
+ type NextApiClient<TSchema> = ApiClient<TSchema, NextRequestOptionsBase>;
123
+ type NextQueryFn<TSchema, TData, TError> = QueryFn<TSchema, TData, TError, NextRequestOptionsBase>;
124
+ type NextSelectorFn<TSchema, TMethod> = SelectorFn<TSchema, TMethod, NextRequestOptionsBase>;
125
+ /** Hook type returned by Next.js createEnlaceHook */
126
+ type NextEnlaceHook<TSchema> = {
127
+ <TMethod extends (...args: any[]) => Promise<EnlaceResponse<unknown, unknown>>>(selector: NextSelectorFn<TSchema, TMethod>): UseEnlaceSelectorResult<TMethod>;
128
+ <TData, TError>(queryFn: NextQueryFn<TSchema, TData, TError>, options?: UseEnlaceQueryOptions): UseEnlaceQueryResult<TData, TError>;
129
+ };
71
130
 
72
- declare function createEnlace<TSchema = unknown>(baseUrl: string, defaultOptions?: EnlaceOptions, nextOptions?: NextOptions): unknown extends TSchema ? WildcardClient<NextRequestOptionsBase> : EnlaceClient<TSchema, NextRequestOptionsBase>;
131
+ declare function createEnlace<TSchema = unknown>(baseUrl: string, defaultOptions?: EnlaceOptions | null, nextOptions?: NextOptions): unknown extends TSchema ? WildcardClient<NextRequestOptionsBase> : EnlaceClient<TSchema, NextRequestOptionsBase>;
73
132
 
74
- export { type NextHookOptions, type NextOptions, type NextRequestOptionsBase, type RevalidateHandler, createEnlace };
133
+ export { type NextApiClient, type NextEnlaceHook, type NextHookOptions, type NextOptions, type NextQueryFn, type NextRequestOptionsBase, type NextSelectorFn, type RevalidateHandler, createEnlace };
@@ -40,20 +40,22 @@ async function executeNextFetch(baseUrl, path, method, combinedOptions, requestO
40
40
  autoGenerateTags = true,
41
41
  autoRevalidateTags = true,
42
42
  revalidator,
43
- headers: defaultHeaders,
44
- ...restOptions
43
+ onSuccess,
44
+ ...coreOptions
45
45
  } = combinedOptions;
46
- const url = (0, import_enlace_core.buildUrl)(baseUrl, path, requestOptions?.query);
47
- let headers = (0, import_enlace_core.mergeHeaders)(defaultHeaders, requestOptions?.headers);
48
46
  const isGet = method === "GET";
49
47
  const autoTags = generateTags(path);
50
- const fetchOptions = {
51
- ...restOptions,
52
- method
48
+ const nextOnSuccess = (payload) => {
49
+ if (!isGet && !requestOptions?.skipRevalidator) {
50
+ const revalidateTags = requestOptions?.revalidateTags ?? (autoRevalidateTags ? autoTags : []);
51
+ const revalidatePaths = requestOptions?.revalidatePaths ?? [];
52
+ if (revalidateTags.length || revalidatePaths.length) {
53
+ revalidator?.(revalidateTags, revalidatePaths);
54
+ }
55
+ }
56
+ onSuccess?.(payload);
53
57
  };
54
- if (requestOptions?.cache) {
55
- fetchOptions.cache = requestOptions.cache;
56
- }
58
+ const nextRequestOptions = { ...requestOptions };
57
59
  if (isGet) {
58
60
  const tags = requestOptions?.tags ?? (autoGenerateTags ? autoTags : void 0);
59
61
  const nextFetchOptions = {};
@@ -63,49 +65,25 @@ async function executeNextFetch(baseUrl, path, method, combinedOptions, requestO
63
65
  if (requestOptions?.revalidate !== void 0) {
64
66
  nextFetchOptions.revalidate = requestOptions.revalidate;
65
67
  }
66
- fetchOptions.next = nextFetchOptions;
67
- }
68
- if (headers) {
69
- fetchOptions.headers = headers;
68
+ nextRequestOptions.next = nextFetchOptions;
70
69
  }
71
- if (requestOptions?.body !== void 0) {
72
- if ((0, import_enlace_core.isJsonBody)(requestOptions.body)) {
73
- fetchOptions.body = JSON.stringify(requestOptions.body);
74
- headers = (0, import_enlace_core.mergeHeaders)(headers, { "Content-Type": "application/json" });
75
- if (headers) {
76
- fetchOptions.headers = headers;
77
- }
78
- } else {
79
- fetchOptions.body = requestOptions.body;
80
- }
81
- }
82
- const response = await fetch(url, fetchOptions);
83
- const contentType = response.headers.get("content-type");
84
- const isJson = contentType?.includes("application/json");
85
- if (response.ok) {
86
- if (!isGet && !requestOptions?.skipRevalidator) {
87
- const revalidateTags = requestOptions?.revalidateTags ?? (autoRevalidateTags ? autoTags : []);
88
- const revalidatePaths = requestOptions?.revalidatePaths ?? [];
89
- if (revalidateTags.length || revalidatePaths.length) {
90
- revalidator?.(revalidateTags, revalidatePaths);
91
- }
92
- }
93
- return {
94
- ok: true,
95
- status: response.status,
96
- data: isJson ? await response.json() : response
97
- };
98
- }
99
- return {
100
- ok: false,
101
- status: response.status,
102
- error: isJson ? await response.json() : response
103
- };
70
+ return (0, import_enlace_core.executeFetch)(
71
+ baseUrl,
72
+ path,
73
+ method,
74
+ { ...coreOptions, onSuccess: nextOnSuccess },
75
+ nextRequestOptions
76
+ );
104
77
  }
105
78
 
106
79
  // src/next/index.ts
107
80
  __reExport(next_exports, require("enlace-core"), module.exports);
108
81
  function createEnlace(baseUrl, defaultOptions = {}, nextOptions = {}) {
109
82
  const combinedOptions = { ...defaultOptions, ...nextOptions };
110
- return (0, import_enlace_core2.createProxyHandler)(baseUrl, combinedOptions, [], executeNextFetch);
83
+ return (0, import_enlace_core2.createProxyHandler)(
84
+ baseUrl,
85
+ combinedOptions,
86
+ [],
87
+ executeNextFetch
88
+ );
111
89
  }