enlace 0.0.1-beta.4 → 0.0.1-beta.6

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.
@@ -1,120 +1,8 @@
1
1
  "use client";
2
2
  "use client";
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- var __export = (target, all) => {
8
- for (var name in all)
9
- __defProp(target, name, { get: all[name], enumerable: true });
10
- };
11
- var __copyProps = (to, from, except, desc) => {
12
- if (from && typeof from === "object" || typeof from === "function") {
13
- for (let key of __getOwnPropNames(from))
14
- if (!__hasOwnProp.call(to, key) && key !== except)
15
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
- }
17
- return to;
18
- };
19
- var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
20
3
 
21
- // src/next/index.ts
22
- var next_exports = {};
23
- __export(next_exports, {
24
- createEnlace: () => createEnlace
25
- });
26
- import {
27
- createProxyHandler
28
- } from "enlace-core";
29
-
30
- // src/next/fetch.ts
31
- import {
32
- buildUrl,
33
- isJsonBody,
34
- mergeHeaders
35
- } from "enlace-core";
36
-
37
- // src/utils/generateTags.ts
38
- function generateTags(path) {
39
- return path.map((_, i) => path.slice(0, i + 1).join("/"));
40
- }
41
-
42
- // src/next/fetch.ts
43
- async function executeNextFetch(baseUrl, path, method, combinedOptions, requestOptions) {
44
- const {
45
- autoGenerateTags = true,
46
- autoRevalidateTags = true,
47
- revalidator,
48
- headers: defaultHeaders,
49
- ...restOptions
50
- } = combinedOptions;
51
- const url = buildUrl(baseUrl, path, requestOptions?.query);
52
- let headers = mergeHeaders(defaultHeaders, requestOptions?.headers);
53
- const isGet = method === "GET";
54
- const autoTags = generateTags(path);
55
- const fetchOptions = {
56
- ...restOptions,
57
- method
58
- };
59
- if (requestOptions?.cache) {
60
- fetchOptions.cache = requestOptions.cache;
61
- }
62
- if (isGet) {
63
- const tags = requestOptions?.tags ?? (autoGenerateTags ? autoTags : void 0);
64
- const nextFetchOptions = {};
65
- if (tags) {
66
- nextFetchOptions.tags = tags;
67
- }
68
- if (requestOptions?.revalidate !== void 0) {
69
- nextFetchOptions.revalidate = requestOptions.revalidate;
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
- };
103
- }
104
- return {
105
- ok: false,
106
- status: response.status,
107
- error: isJson ? await response.json() : response
108
- };
109
- }
110
-
111
- // src/next/index.ts
112
- __reExport(next_exports, enlace_core_star);
113
- import * as enlace_core_star from "enlace-core";
114
- function createEnlace(baseUrl, defaultOptions = {}, nextOptions = {}) {
115
- const combinedOptions = { ...defaultOptions, ...nextOptions };
116
- return createProxyHandler(baseUrl, combinedOptions, [], executeNextFetch);
117
- }
4
+ // src/react/createEnlaceHookReact.ts
5
+ import { createEnlace } from "enlace-core";
118
6
 
119
7
  // src/react/useQueryMode.ts
120
8
  import { useRef, useReducer, useEffect } from "react";
@@ -130,7 +18,7 @@ var initialState = {
130
18
  function hookReducer(state, action) {
131
19
  switch (action.type) {
132
20
  case "RESET":
133
- return action.state;
21
+ return action.state ?? initialState;
134
22
  case "FETCH_START":
135
23
  return {
136
24
  ...state,
@@ -160,6 +48,11 @@ function hookReducer(state, action) {
160
48
  }
161
49
  }
162
50
 
51
+ // src/utils/generateTags.ts
52
+ function generateTags(path) {
53
+ return path.map((_, i) => path.slice(0, i + 1).join("/"));
54
+ }
55
+
163
56
  // src/utils/sortObjectKeys.ts
164
57
  function sortObjectKeys(obj) {
165
58
  if (obj === null || typeof obj !== "object") return obj;
@@ -273,7 +166,10 @@ function useQueryMode(api, trackedCall, options) {
273
166
  const { autoGenerateTags, staleTime, enabled } = options;
274
167
  const queryKey = createQueryKey(trackedCall);
275
168
  const requestOptions = trackedCall.options;
276
- const resolvedPath = resolvePath(trackedCall.path, requestOptions?.pathParams);
169
+ const resolvedPath = resolvePath(
170
+ trackedCall.path,
171
+ requestOptions?.pathParams
172
+ );
277
173
  const queryTags = requestOptions?.tags ?? (autoGenerateTags ? generateTags(resolvedPath) : []);
278
174
  const getCacheState = (includeNeedsFetch = false) => {
279
175
  const cached = getCache(queryKey);
@@ -288,26 +184,22 @@ function useQueryMode(api, trackedCall, options) {
288
184
  error: cached?.error
289
185
  };
290
186
  };
291
- const [state, dispatch] = useReducer(hookReducer, null, () => getCacheState(true));
187
+ const [state, dispatch] = useReducer(
188
+ hookReducer,
189
+ null,
190
+ () => getCacheState(true)
191
+ );
292
192
  const mountedRef = useRef(true);
293
193
  const fetchRef = useRef(null);
294
194
  useEffect(() => {
295
195
  mountedRef.current = true;
296
196
  if (!enabled) {
297
- dispatch({
298
- type: "RESET",
299
- state: { loading: false, fetching: false, ok: void 0, data: void 0, error: void 0 }
300
- });
197
+ dispatch({ type: "RESET" });
301
198
  return () => {
302
199
  mountedRef.current = false;
303
200
  };
304
201
  }
305
202
  dispatch({ type: "RESET", state: getCacheState(true) });
306
- const unsubscribe = subscribeCache(queryKey, () => {
307
- if (mountedRef.current) {
308
- dispatch({ type: "SYNC_CACHE", state: getCacheState() });
309
- }
310
- });
311
203
  const doFetch = () => {
312
204
  const cached2 = getCache(queryKey);
313
205
  if (cached2?.promise) {
@@ -323,7 +215,7 @@ function useQueryMode(api, trackedCall, options) {
323
215
  if (mountedRef.current) {
324
216
  setCache(queryKey, {
325
217
  data: res.ok ? res.data : void 0,
326
- error: res.ok ? void 0 : res.error,
218
+ error: res.ok || res.status === 0 ? void 0 : res.error,
327
219
  ok: res.ok,
328
220
  timestamp: Date.now(),
329
221
  tags: queryTags
@@ -342,6 +234,11 @@ function useQueryMode(api, trackedCall, options) {
342
234
  } else {
343
235
  doFetch();
344
236
  }
237
+ const unsubscribe = subscribeCache(queryKey, () => {
238
+ if (mountedRef.current) {
239
+ dispatch({ type: "SYNC_CACHE", state: getCacheState() });
240
+ }
241
+ });
345
242
  return () => {
346
243
  mountedRef.current = false;
347
244
  fetchRef.current = null;
@@ -360,6 +257,38 @@ function useQueryMode(api, trackedCall, options) {
360
257
  return state;
361
258
  }
362
259
 
260
+ // src/react/types.ts
261
+ var HTTP_METHODS = ["get", "post", "put", "patch", "delete"];
262
+
263
+ // src/react/trackingProxy.ts
264
+ function createTrackingProxy(onTrack) {
265
+ const createProxy = (path = []) => {
266
+ return new Proxy(() => {
267
+ }, {
268
+ get(_, prop) {
269
+ if (HTTP_METHODS.includes(prop)) {
270
+ const methodFn = (options) => {
271
+ onTrack({
272
+ trackedCall: { path, method: prop, options },
273
+ selectorPath: null,
274
+ selectorMethod: null
275
+ });
276
+ return Promise.resolve({ ok: true, data: void 0 });
277
+ };
278
+ onTrack({
279
+ trackedCall: null,
280
+ selectorPath: path,
281
+ selectorMethod: prop
282
+ });
283
+ return methodFn;
284
+ }
285
+ return createProxy([...path, prop]);
286
+ }
287
+ });
288
+ };
289
+ return createProxy();
290
+ }
291
+
363
292
  // src/react/useSelectorMode.ts
364
293
  import { useRef as useRef2, useReducer as useReducer2 } from "react";
365
294
  function resolvePath2(path, pathParams) {
@@ -427,47 +356,117 @@ function useSelectorMode(config) {
427
356
  };
428
357
  }
429
358
 
430
- // src/react/types.ts
431
- var HTTP_METHODS = ["get", "post", "put", "patch", "delete"];
359
+ // src/react/createEnlaceHookReact.ts
360
+ function createEnlaceHookReact(baseUrl, defaultOptions = {}, hookOptions = {}) {
361
+ const {
362
+ autoGenerateTags = true,
363
+ autoRevalidateTags = true,
364
+ staleTime = 0,
365
+ onSuccess,
366
+ onError
367
+ } = hookOptions;
368
+ const api = createEnlace(baseUrl, defaultOptions, { onSuccess, onError });
369
+ function useEnlaceHook(selectorOrQuery, queryOptions) {
370
+ let trackingResult = {
371
+ trackedCall: null,
372
+ selectorPath: null,
373
+ selectorMethod: null
374
+ };
375
+ const trackingProxy = createTrackingProxy((result2) => {
376
+ trackingResult = result2;
377
+ });
378
+ const result = selectorOrQuery(
379
+ trackingProxy
380
+ );
381
+ if (typeof result === "function") {
382
+ const actualResult = selectorOrQuery(api);
383
+ return useSelectorMode({
384
+ method: actualResult,
385
+ api,
386
+ path: trackingResult.selectorPath ?? [],
387
+ methodName: trackingResult.selectorMethod ?? "",
388
+ autoRevalidateTags
389
+ });
390
+ }
391
+ return useQueryMode(
392
+ api,
393
+ trackingResult.trackedCall,
394
+ { autoGenerateTags, staleTime, enabled: queryOptions?.enabled ?? true }
395
+ );
396
+ }
397
+ return useEnlaceHook;
398
+ }
432
399
 
433
- // src/react/trackingProxy.ts
434
- function createTrackingProxy(onTrack) {
435
- const createProxy = (path = []) => {
436
- return new Proxy(() => {
437
- }, {
438
- get(_, prop) {
439
- if (HTTP_METHODS.includes(prop)) {
440
- const methodFn = (options) => {
441
- onTrack({
442
- trackedCall: { path, method: prop, options },
443
- selectorPath: null,
444
- selectorMethod: null
445
- });
446
- return Promise.resolve({ ok: true, data: void 0 });
447
- };
448
- onTrack({
449
- trackedCall: null,
450
- selectorPath: path,
451
- selectorMethod: prop
452
- });
453
- return methodFn;
454
- }
455
- return createProxy([...path, prop]);
400
+ // src/next/index.ts
401
+ import {
402
+ createProxyHandler
403
+ } from "enlace-core";
404
+
405
+ // src/next/fetch.ts
406
+ import {
407
+ executeFetch
408
+ } from "enlace-core";
409
+ async function executeNextFetch(baseUrl, path, method, combinedOptions, requestOptions) {
410
+ const {
411
+ autoGenerateTags = true,
412
+ autoRevalidateTags = true,
413
+ revalidator,
414
+ onSuccess,
415
+ ...coreOptions
416
+ } = combinedOptions;
417
+ const isGet = method === "GET";
418
+ const autoTags = generateTags(path);
419
+ const nextOnSuccess = (payload) => {
420
+ if (!isGet && !requestOptions?.skipRevalidator) {
421
+ const revalidateTags = requestOptions?.revalidateTags ?? (autoRevalidateTags ? autoTags : []);
422
+ const revalidatePaths = requestOptions?.revalidatePaths ?? [];
423
+ if (revalidateTags.length || revalidatePaths.length) {
424
+ revalidator?.(revalidateTags, revalidatePaths);
456
425
  }
457
- });
426
+ }
427
+ onSuccess?.(payload);
458
428
  };
459
- return createProxy();
429
+ const nextRequestOptions = { ...requestOptions };
430
+ if (isGet) {
431
+ const tags = requestOptions?.tags ?? (autoGenerateTags ? autoTags : void 0);
432
+ const nextFetchOptions = {};
433
+ if (tags) {
434
+ nextFetchOptions.tags = tags;
435
+ }
436
+ if (requestOptions?.revalidate !== void 0) {
437
+ nextFetchOptions.revalidate = requestOptions.revalidate;
438
+ }
439
+ nextRequestOptions.next = nextFetchOptions;
440
+ }
441
+ return executeFetch(
442
+ baseUrl,
443
+ path,
444
+ method,
445
+ { ...coreOptions, onSuccess: nextOnSuccess },
446
+ nextRequestOptions
447
+ );
448
+ }
449
+
450
+ // src/next/index.ts
451
+ function createEnlaceNext(baseUrl, defaultOptions = {}, nextOptions = {}) {
452
+ const combinedOptions = { ...defaultOptions, ...nextOptions };
453
+ return createProxyHandler(
454
+ baseUrl,
455
+ combinedOptions,
456
+ [],
457
+ executeNextFetch
458
+ );
460
459
  }
461
460
 
462
- // src/next/createEnlaceHook.ts
463
- function createEnlaceHook(baseUrl, defaultOptions = {}, hookOptions = {}) {
461
+ // src/next/createEnlaceHookNext.ts
462
+ function createEnlaceHookNext(baseUrl, defaultOptions = {}, hookOptions = {}) {
464
463
  const {
465
464
  autoGenerateTags = true,
466
465
  autoRevalidateTags = true,
467
466
  staleTime = 0,
468
467
  ...nextOptions
469
468
  } = hookOptions;
470
- const api = createEnlace(baseUrl, defaultOptions, {
469
+ const api = createEnlaceNext(baseUrl, defaultOptions, {
471
470
  autoGenerateTags,
472
471
  autoRevalidateTags,
473
472
  ...nextOptions
@@ -501,5 +500,7 @@ function createEnlaceHook(baseUrl, defaultOptions = {}, hookOptions = {}) {
501
500
  return useEnlaceHook;
502
501
  }
503
502
  export {
504
- createEnlaceHook
503
+ HTTP_METHODS,
504
+ createEnlaceHookNext,
505
+ createEnlaceHookReact
505
506
  };
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { WildcardClient, EnlaceClient, EnlaceResponse, EnlaceOptions } from 'enlace-core';
1
+ import { EnlaceCallbackPayload, EnlaceErrorCallbackPayload, EnlaceCallbacks, EnlaceOptions, WildcardClient, EnlaceClient } from 'enlace-core';
2
2
  export * from 'enlace-core';
3
3
 
4
4
  /** Per-request options for React hooks */
@@ -20,59 +20,7 @@ type ReactRequestOptionsBase = {
20
20
  */
21
21
  pathParams?: Record<string, string | number>;
22
22
  };
23
- /** Options for query mode hooks */
24
- type UseEnlaceQueryOptions = {
25
- /**
26
- * Whether the query should execute.
27
- * Set to false to skip fetching (useful when ID is "new" or undefined).
28
- * @default true
29
- */
30
- enabled?: boolean;
31
- };
32
- type ApiClient<TSchema, TOptions = ReactRequestOptionsBase> = unknown extends TSchema ? WildcardClient<TOptions> : EnlaceClient<TSchema, TOptions>;
33
- type QueryFn<TSchema, TData, TError, TOptions = ReactRequestOptionsBase> = (api: ApiClient<TSchema, TOptions>) => Promise<EnlaceResponse<TData, TError>>;
34
- type SelectorFn<TSchema, TMethod, TOptions = ReactRequestOptionsBase> = (api: ApiClient<TSchema, TOptions>) => TMethod;
35
- type HookState = {
36
- loading: boolean;
37
- fetching: boolean;
38
- ok: boolean | undefined;
39
- data: unknown;
40
- error: unknown;
41
- };
42
- type TrackedCall = {
43
- path: string[];
44
- method: string;
45
- options: unknown;
46
- };
47
- declare const HTTP_METHODS: readonly ["get", "post", "put", "patch", "delete"];
48
- type ExtractData<T> = T extends (...args: any[]) => Promise<EnlaceResponse<infer D, unknown>> ? D : never;
49
- type ExtractError<T> = T extends (...args: any[]) => Promise<EnlaceResponse<unknown, infer E>> ? E : never;
50
- /** Discriminated union for hook response state - enables type narrowing on ok check */
51
- type HookResponseState<TData, TError> = {
52
- ok: undefined;
53
- data: undefined;
54
- error: undefined;
55
- } | {
56
- ok: true;
57
- data: TData;
58
- error: undefined;
59
- } | {
60
- ok: false;
61
- data: undefined;
62
- error: TError;
63
- };
64
- /** Result when hook is called with query function (auto-fetch mode) */
65
- type UseEnlaceQueryResult<TData, TError> = {
66
- loading: boolean;
67
- fetching: boolean;
68
- } & HookResponseState<TData, TError>;
69
- /** Result when hook is called with method selector (trigger mode) */
70
- type UseEnlaceSelectorResult<TMethod> = {
71
- trigger: TMethod;
72
- loading: boolean;
73
- fetching: boolean;
74
- } & HookResponseState<ExtractData<TMethod>, ExtractError<TMethod>>;
75
-
23
+ /** Options for createEnlaceHookReact factory */
76
24
  type EnlaceHookOptions = {
77
25
  /**
78
26
  * Auto-generate cache tags from URL path for GET requests.
@@ -84,31 +32,52 @@ type EnlaceHookOptions = {
84
32
  autoRevalidateTags?: boolean;
85
33
  /** Time in ms before cached data is considered stale. @default 0 (always stale) */
86
34
  staleTime?: number;
35
+ /** Callback called on successful API responses */
36
+ onSuccess?: (payload: EnlaceCallbackPayload<unknown>) => void;
37
+ /** Callback called on error responses (HTTP errors or network failures) */
38
+ onError?: (payload: EnlaceErrorCallbackPayload<unknown>) => void;
87
39
  };
88
- type EnlaceHook<TSchema> = {
89
- <TMethod extends (...args: any[]) => Promise<EnlaceResponse<unknown, unknown>>>(selector: SelectorFn<TSchema, TMethod>): UseEnlaceSelectorResult<TMethod>;
90
- <TData, TError>(queryFn: QueryFn<TSchema, TData, TError>, options?: UseEnlaceQueryOptions): UseEnlaceQueryResult<TData, TError>;
91
- };
40
+
92
41
  /**
93
- * Creates a React hook for making API calls.
94
- * Called at module level to create a reusable hook.
95
- *
96
- * @example
97
- * const useAPI = createEnlaceHook<ApiSchema>('https://api.com');
98
- *
99
- * // Query mode - auto-fetch (auto-tracks changes, no deps array needed)
100
- * const { loading, data, error } = useAPI((api) => api.posts.get({ query: { userId } }));
101
- *
102
- * // Selector mode - typed trigger for lazy calls
103
- * const { trigger, loading, data, error } = useAPI((api) => api.posts.delete);
104
- * onClick={() => trigger({ body: { id: 1 } })}
42
+ * Handler function called after successful mutations to trigger server-side revalidation.
43
+ * @param tags - Cache tags to revalidate
44
+ * @param paths - URL paths to revalidate
105
45
  */
106
- declare function createEnlaceHook<TSchema = unknown>(baseUrl: string, defaultOptions?: EnlaceOptions, hookOptions?: EnlaceHookOptions): EnlaceHook<TSchema>;
107
-
108
- type Listener = (tags: string[]) => void;
109
- declare function invalidateTags(tags: string[]): void;
110
- declare function onRevalidate(callback: Listener): () => void;
46
+ type RevalidateHandler = (tags: string[], paths: string[]) => void | Promise<void>;
47
+ /** Next.js-specific options (third argument for createEnlaceNext) */
48
+ type NextOptions = Pick<EnlaceHookOptions, "autoGenerateTags" | "autoRevalidateTags"> & EnlaceCallbacks & {
49
+ /**
50
+ * Handler called after successful mutations to trigger server-side revalidation.
51
+ * Receives auto-generated or manually specified tags and paths.
52
+ * @example
53
+ * ```ts
54
+ * createEnlaceNext("http://localhost:3000/api/", {}, {
55
+ * revalidator: (tags, paths) => revalidateServerAction(tags, paths)
56
+ * });
57
+ * ```
58
+ */
59
+ revalidator?: RevalidateHandler;
60
+ };
61
+ /** Per-request options for Next.js fetch - extends React's base options */
62
+ type NextRequestOptionsBase = ReactRequestOptionsBase & {
63
+ /** Time in seconds to revalidate, or false to disable */
64
+ revalidate?: number | false;
65
+ /**
66
+ * URL paths to revalidate after mutation
67
+ * This doesn't do anything on the client by itself - it's passed to the revalidator handler.
68
+ * You must implement the revalidation logic in the revalidator.
69
+ */
70
+ revalidatePaths?: string[];
71
+ /**
72
+ * Skip server-side revalidation for this request.
73
+ * Useful when autoRevalidateTags is enabled but you want to opt-out for specific mutations.
74
+ * You can still pass empty [] to revalidateTags to skip triggering revalidation.
75
+ * But this flag can be used if you want to revalidate client-side and skip server-side entirely.
76
+ * Eg. you don't fetch any data on server component and you might want to skip the overhead of revalidation.
77
+ */
78
+ skipRevalidator?: boolean;
79
+ };
111
80
 
112
- declare function clearCache(key?: string): void;
81
+ declare function createEnlaceNext<TSchema = unknown>(baseUrl: string, defaultOptions?: EnlaceOptions | null, nextOptions?: NextOptions): unknown extends TSchema ? WildcardClient<NextRequestOptionsBase> : EnlaceClient<TSchema, NextRequestOptionsBase>;
113
82
 
114
- export { type ApiClient, type EnlaceHookOptions, HTTP_METHODS, type HookState, type QueryFn, type ReactRequestOptionsBase, type SelectorFn, type TrackedCall, type UseEnlaceQueryOptions, type UseEnlaceQueryResult, type UseEnlaceSelectorResult, clearCache, createEnlaceHook, invalidateTags, onRevalidate };
83
+ export { type NextOptions, type NextRequestOptionsBase, createEnlaceNext };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { WildcardClient, EnlaceClient, EnlaceResponse, EnlaceOptions } from 'enlace-core';
1
+ import { EnlaceCallbackPayload, EnlaceErrorCallbackPayload, EnlaceCallbacks, EnlaceOptions, WildcardClient, EnlaceClient } from 'enlace-core';
2
2
  export * from 'enlace-core';
3
3
 
4
4
  /** Per-request options for React hooks */
@@ -20,59 +20,7 @@ type ReactRequestOptionsBase = {
20
20
  */
21
21
  pathParams?: Record<string, string | number>;
22
22
  };
23
- /** Options for query mode hooks */
24
- type UseEnlaceQueryOptions = {
25
- /**
26
- * Whether the query should execute.
27
- * Set to false to skip fetching (useful when ID is "new" or undefined).
28
- * @default true
29
- */
30
- enabled?: boolean;
31
- };
32
- type ApiClient<TSchema, TOptions = ReactRequestOptionsBase> = unknown extends TSchema ? WildcardClient<TOptions> : EnlaceClient<TSchema, TOptions>;
33
- type QueryFn<TSchema, TData, TError, TOptions = ReactRequestOptionsBase> = (api: ApiClient<TSchema, TOptions>) => Promise<EnlaceResponse<TData, TError>>;
34
- type SelectorFn<TSchema, TMethod, TOptions = ReactRequestOptionsBase> = (api: ApiClient<TSchema, TOptions>) => TMethod;
35
- type HookState = {
36
- loading: boolean;
37
- fetching: boolean;
38
- ok: boolean | undefined;
39
- data: unknown;
40
- error: unknown;
41
- };
42
- type TrackedCall = {
43
- path: string[];
44
- method: string;
45
- options: unknown;
46
- };
47
- declare const HTTP_METHODS: readonly ["get", "post", "put", "patch", "delete"];
48
- type ExtractData<T> = T extends (...args: any[]) => Promise<EnlaceResponse<infer D, unknown>> ? D : never;
49
- type ExtractError<T> = T extends (...args: any[]) => Promise<EnlaceResponse<unknown, infer E>> ? E : never;
50
- /** Discriminated union for hook response state - enables type narrowing on ok check */
51
- type HookResponseState<TData, TError> = {
52
- ok: undefined;
53
- data: undefined;
54
- error: undefined;
55
- } | {
56
- ok: true;
57
- data: TData;
58
- error: undefined;
59
- } | {
60
- ok: false;
61
- data: undefined;
62
- error: TError;
63
- };
64
- /** Result when hook is called with query function (auto-fetch mode) */
65
- type UseEnlaceQueryResult<TData, TError> = {
66
- loading: boolean;
67
- fetching: boolean;
68
- } & HookResponseState<TData, TError>;
69
- /** Result when hook is called with method selector (trigger mode) */
70
- type UseEnlaceSelectorResult<TMethod> = {
71
- trigger: TMethod;
72
- loading: boolean;
73
- fetching: boolean;
74
- } & HookResponseState<ExtractData<TMethod>, ExtractError<TMethod>>;
75
-
23
+ /** Options for createEnlaceHookReact factory */
76
24
  type EnlaceHookOptions = {
77
25
  /**
78
26
  * Auto-generate cache tags from URL path for GET requests.
@@ -84,31 +32,52 @@ type EnlaceHookOptions = {
84
32
  autoRevalidateTags?: boolean;
85
33
  /** Time in ms before cached data is considered stale. @default 0 (always stale) */
86
34
  staleTime?: number;
35
+ /** Callback called on successful API responses */
36
+ onSuccess?: (payload: EnlaceCallbackPayload<unknown>) => void;
37
+ /** Callback called on error responses (HTTP errors or network failures) */
38
+ onError?: (payload: EnlaceErrorCallbackPayload<unknown>) => void;
87
39
  };
88
- type EnlaceHook<TSchema> = {
89
- <TMethod extends (...args: any[]) => Promise<EnlaceResponse<unknown, unknown>>>(selector: SelectorFn<TSchema, TMethod>): UseEnlaceSelectorResult<TMethod>;
90
- <TData, TError>(queryFn: QueryFn<TSchema, TData, TError>, options?: UseEnlaceQueryOptions): UseEnlaceQueryResult<TData, TError>;
91
- };
40
+
92
41
  /**
93
- * Creates a React hook for making API calls.
94
- * Called at module level to create a reusable hook.
95
- *
96
- * @example
97
- * const useAPI = createEnlaceHook<ApiSchema>('https://api.com');
98
- *
99
- * // Query mode - auto-fetch (auto-tracks changes, no deps array needed)
100
- * const { loading, data, error } = useAPI((api) => api.posts.get({ query: { userId } }));
101
- *
102
- * // Selector mode - typed trigger for lazy calls
103
- * const { trigger, loading, data, error } = useAPI((api) => api.posts.delete);
104
- * onClick={() => trigger({ body: { id: 1 } })}
42
+ * Handler function called after successful mutations to trigger server-side revalidation.
43
+ * @param tags - Cache tags to revalidate
44
+ * @param paths - URL paths to revalidate
105
45
  */
106
- declare function createEnlaceHook<TSchema = unknown>(baseUrl: string, defaultOptions?: EnlaceOptions, hookOptions?: EnlaceHookOptions): EnlaceHook<TSchema>;
107
-
108
- type Listener = (tags: string[]) => void;
109
- declare function invalidateTags(tags: string[]): void;
110
- declare function onRevalidate(callback: Listener): () => void;
46
+ type RevalidateHandler = (tags: string[], paths: string[]) => void | Promise<void>;
47
+ /** Next.js-specific options (third argument for createEnlaceNext) */
48
+ type NextOptions = Pick<EnlaceHookOptions, "autoGenerateTags" | "autoRevalidateTags"> & EnlaceCallbacks & {
49
+ /**
50
+ * Handler called after successful mutations to trigger server-side revalidation.
51
+ * Receives auto-generated or manually specified tags and paths.
52
+ * @example
53
+ * ```ts
54
+ * createEnlaceNext("http://localhost:3000/api/", {}, {
55
+ * revalidator: (tags, paths) => revalidateServerAction(tags, paths)
56
+ * });
57
+ * ```
58
+ */
59
+ revalidator?: RevalidateHandler;
60
+ };
61
+ /** Per-request options for Next.js fetch - extends React's base options */
62
+ type NextRequestOptionsBase = ReactRequestOptionsBase & {
63
+ /** Time in seconds to revalidate, or false to disable */
64
+ revalidate?: number | false;
65
+ /**
66
+ * URL paths to revalidate after mutation
67
+ * This doesn't do anything on the client by itself - it's passed to the revalidator handler.
68
+ * You must implement the revalidation logic in the revalidator.
69
+ */
70
+ revalidatePaths?: string[];
71
+ /**
72
+ * Skip server-side revalidation for this request.
73
+ * Useful when autoRevalidateTags is enabled but you want to opt-out for specific mutations.
74
+ * You can still pass empty [] to revalidateTags to skip triggering revalidation.
75
+ * But this flag can be used if you want to revalidate client-side and skip server-side entirely.
76
+ * Eg. you don't fetch any data on server component and you might want to skip the overhead of revalidation.
77
+ */
78
+ skipRevalidator?: boolean;
79
+ };
111
80
 
112
- declare function clearCache(key?: string): void;
81
+ declare function createEnlaceNext<TSchema = unknown>(baseUrl: string, defaultOptions?: EnlaceOptions | null, nextOptions?: NextOptions): unknown extends TSchema ? WildcardClient<NextRequestOptionsBase> : EnlaceClient<TSchema, NextRequestOptionsBase>;
113
82
 
114
- export { type ApiClient, type EnlaceHookOptions, HTTP_METHODS, type HookState, type QueryFn, type ReactRequestOptionsBase, type SelectorFn, type TrackedCall, type UseEnlaceQueryOptions, type UseEnlaceQueryResult, type UseEnlaceSelectorResult, clearCache, createEnlaceHook, invalidateTags, onRevalidate };
83
+ export { type NextOptions, type NextRequestOptionsBase, createEnlaceNext };