enlace 0.0.1-beta.10 → 0.0.1-beta.12

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 CHANGED
@@ -85,37 +85,108 @@ api.users[123].get(); // GET /users/123
85
85
  api.users[123].profile.get(); // GET /users/123/profile
86
86
  ```
87
87
 
88
- ### Endpoint Type
88
+ ### Endpoint Types
89
89
 
90
- The `Endpoint` type helper lets you define response data, request body, and optionally override the error type:
90
+ The `Endpoint` type helpers let you define response data, request body, query params, formData, and error types.
91
+
92
+ #### `Endpoint<TData, TBody?, TError?>`
93
+
94
+ For endpoints with JSON body:
91
95
 
92
96
  ```typescript
93
- // Signature: Endpoint<TData, TBody?, TError?>
94
- type Endpoint<TData, TBody = never, TError = never>;
97
+ import { Endpoint } from "enlace";
98
+
99
+ type ApiSchema = {
100
+ posts: {
101
+ $get: Post[]; // Direct type (simplest)
102
+ $post: Endpoint<Post, CreatePost>; // Data + Body
103
+ $put: Endpoint<Post, UpdatePost, ValidationError>; // Data + Body + Error
104
+ $delete: void; // void response
105
+ $patch: Endpoint<Post, never, NotFoundError>; // Custom error without body
106
+ };
107
+ };
95
108
  ```
96
109
 
97
- **Three ways to define endpoints:**
110
+ #### `EndpointWithQuery<TData, TQuery, TError?>`
111
+
112
+ For endpoints with typed query parameters:
98
113
 
99
114
  ```typescript
115
+ import { EndpointWithQuery } from "enlace";
116
+
100
117
  type ApiSchema = {
118
+ users: {
119
+ $get: EndpointWithQuery<User[], { page: number; limit: number; search?: string }>;
120
+ };
101
121
  posts: {
102
- // 1. Direct type (simplest) - just the data type
103
- // Error comes from global default
104
- $get: Post[];
122
+ $get: EndpointWithQuery<Post[], { status: "draft" | "published" }, ApiError>;
123
+ };
124
+ };
125
+
126
+ // Usage - query params are fully typed
127
+ const { data } = useAPI((api) => api.users.get({ query: { page: 1, limit: 10 } }));
128
+ // api.users.get({ query: { foo: "bar" } }); // ✗ Error: 'foo' does not exist
129
+ ```
130
+
131
+ #### `EndpointWithFormData<TData, TFormData, TError?>`
132
+
133
+ For file uploads (multipart/form-data):
134
+
135
+ ```typescript
136
+ import { EndpointWithFormData } from "enlace";
137
+
138
+ type ApiSchema = {
139
+ uploads: {
140
+ $post: EndpointWithFormData<Upload, { file: Blob | File; name: string }>;
141
+ };
142
+ avatars: {
143
+ $post: EndpointWithFormData<Avatar, { image: File }, UploadError>;
144
+ };
145
+ };
146
+
147
+ // Usage - formData is automatically converted to FormData
148
+ const { trigger } = useAPI((api) => api.uploads.post);
149
+ trigger({
150
+ formData: {
151
+ file: selectedFile, // File object
152
+ name: "document.pdf", // String - converted automatically
153
+ }
154
+ });
155
+ // → Sends as multipart/form-data
156
+ ```
157
+
158
+ **FormData conversion rules:**
105
159
 
106
- // 2. Endpoint with body - Endpoint<Data, Body>
107
- // Error comes from global default
108
- $post: Endpoint<Post, CreatePost>;
160
+ | Type | Conversion |
161
+ |------|------------|
162
+ | `File` / `Blob` | Appended directly |
163
+ | `string` / `number` / `boolean` | Converted to string |
164
+ | `object` (nested) | JSON stringified |
165
+ | `array` of primitives | Each item appended separately |
166
+ | `array` of files | Each file appended with same key |
109
167
 
110
- // 3. Endpoint with custom error - Endpoint<Data, Body, Error>
111
- // Overrides global error type for this endpoint
112
- $put: Endpoint<Post, UpdatePost, ValidationError>;
168
+ #### `EndpointFull<T>`
113
169
 
114
- // void response - use void directly
115
- $delete: void;
170
+ Object-style for complex endpoints:
116
171
 
117
- // Custom error without body - use `never` for body
118
- $patch: Endpoint<Post, never, NotFoundError>;
172
+ ```typescript
173
+ import { EndpointFull } from "enlace";
174
+
175
+ type ApiSchema = {
176
+ products: {
177
+ $post: EndpointFull<{
178
+ data: Product;
179
+ body: CreateProduct;
180
+ query: { categoryId: string };
181
+ error: ValidationError;
182
+ }>;
183
+ };
184
+ files: {
185
+ $post: EndpointFull<{
186
+ data: FileUpload;
187
+ formData: { file: File; description: string };
188
+ query: { folder: string };
189
+ }>;
119
190
  };
120
191
  };
121
192
  ```
@@ -270,7 +341,7 @@ function PostList({ posts }: { posts: Post[] }) {
270
341
 
271
342
  const handleDelete = (postId: number) => {
272
343
  // Pass the actual ID when triggering
273
- trigger({ pathParams: { id: postId } });
344
+ trigger({ params: { id: postId } });
274
345
  };
275
346
 
276
347
  return (
@@ -295,7 +366,7 @@ const { trigger } = useAPI(
295
366
  (api) => api.users[":userId"].posts[":postId"].delete
296
367
  );
297
368
 
298
- trigger({ pathParams: { userId: "1", postId: "42" } });
369
+ trigger({ params: { userId: "1", postId: "42" } });
299
370
  // → DELETE /users/1/posts/42
300
371
  ```
301
372
 
@@ -305,7 +376,7 @@ trigger({ pathParams: { userId: "1", postId: "42" } });
305
376
  const { trigger } = useAPI((api) => api.products[":id"].patch);
306
377
 
307
378
  trigger({
308
- pathParams: { id: "123" },
379
+ params: { id: "123" },
309
380
  body: { name: "Updated Product" },
310
381
  });
311
382
  // → PATCH /products/123 with body
@@ -541,12 +612,13 @@ type UseEnlaceSelectorResult<TMethod> = {
541
612
 
542
613
  ```typescript
543
614
  type RequestOptions = {
544
- query?: Record<string, unknown>; // Query parameters
545
- body?: TBody; // Request body
615
+ query?: TQuery; // Query parameters (typed when using EndpointWithQuery/EndpointFull)
616
+ body?: TBody; // Request body (JSON)
617
+ formData?: TFormData; // FormData fields (auto-converted, for file uploads)
546
618
  headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>); // Request headers
547
619
  tags?: string[]; // Cache tags (GET only)
548
620
  revalidateTags?: string[]; // Tags to invalidate after mutation
549
- pathParams?: Record<string, string | number>; // Dynamic path parameters
621
+ params?: Record<string, string | number>; // Dynamic path parameters
550
622
  };
551
623
  ```
552
624
 
@@ -623,7 +695,7 @@ const useAPI = createEnlaceHookNext<ApiSchema, ApiError>(
623
695
  "/api",
624
696
  {},
625
697
  {
626
- revalidator: revalidateAction,
698
+ serverRevalidator: revalidateAction,
627
699
  }
628
700
  );
629
701
  ```
@@ -637,13 +709,46 @@ function CreatePost() {
637
709
  const handleCreate = () => {
638
710
  trigger({
639
711
  body: { title: "New Post" },
640
- revalidateTags: ["posts"], // Passed to revalidator
641
- revalidatePaths: ["/posts"], // Passed to revalidator
712
+ revalidateTags: ["posts"], // Passed to serverRevalidator
713
+ revalidatePaths: ["/posts"], // Passed to serverRevalidator
642
714
  });
643
715
  };
644
716
  }
645
717
  ```
646
718
 
719
+ ### CSR-Heavy Projects
720
+
721
+ For projects that primarily use client-side rendering with minimal SSR, you can disable server-side revalidation by default:
722
+
723
+ ```typescript
724
+ const useAPI = createEnlaceHookNext<ApiSchema, ApiError>(
725
+ "/api",
726
+ {},
727
+ {
728
+ serverRevalidator: revalidateAction,
729
+ skipServerRevalidation: true, // Disable server revalidation by default
730
+ }
731
+ );
732
+
733
+ // Mutations won't trigger server revalidation by default
734
+ await trigger({ body: { title: "New Post" } });
735
+
736
+ // Opt-in to server revalidation when needed
737
+ await trigger({ body: { title: "New Post" }, serverRevalidate: true });
738
+ ```
739
+
740
+ ### Per-Request Server Revalidation Control
741
+
742
+ Override the global setting for individual requests:
743
+
744
+ ```typescript
745
+ // Skip server revalidation for this request
746
+ await trigger({ body: data, serverRevalidate: false });
747
+
748
+ // Force server revalidation for this request
749
+ await trigger({ body: data, serverRevalidate: true });
750
+ ```
751
+
647
752
  ### Next.js Request Options
648
753
 
649
754
  ```typescript
@@ -652,7 +757,7 @@ api.posts.get({
652
757
  revalidate: 60, // ISR revalidation (seconds)
653
758
  revalidateTags: ["posts"], // Tags to invalidate after mutation
654
759
  revalidatePaths: ["/"], // Paths to revalidate after mutation
655
- skipRevalidator: false, // Skip server-side revalidation
760
+ serverRevalidate: true, // Control server-side revalidation per-request
656
761
  });
657
762
  ```
658
763
 
@@ -710,7 +815,10 @@ type EnlaceHookOptions = {
710
815
 
711
816
  ### Re-exports from enlace-core
712
817
 
713
- - `Endpoint` — Type helper for schema definition
818
+ - `Endpoint` — Type helper for endpoints with JSON body
819
+ - `EndpointWithQuery` — Type helper for endpoints with typed query params
820
+ - `EndpointWithFormData` — Type helper for file upload endpoints
821
+ - `EndpointFull` — Object-style type helper for complex endpoints
714
822
  - `EnlaceResponse` — Response type
715
823
  - `EnlaceOptions` — Fetch options type
716
824
 
@@ -15,9 +15,9 @@ type ReactRequestOptionsBase = {
15
15
  * Used to replace :paramName placeholders in the URL path.
16
16
  * @example
17
17
  * // With path api.products[':id'].delete
18
- * trigger({ pathParams: { id: '123' } }) // → DELETE /products/123
18
+ * trigger({ params: { id: '123' } }) // → DELETE /products/123
19
19
  */
20
- pathParams?: Record<string, string | number>;
20
+ params?: Record<string, string | number>;
21
21
  };
22
22
  /** Options for query mode hooks */
23
23
  type UseEnlaceQueryOptions = {
@@ -108,7 +108,7 @@ declare function createEnlaceHookReact<TSchema = unknown, TDefaultError = unknow
108
108
  * @param tags - Cache tags to revalidate
109
109
  * @param paths - URL paths to revalidate
110
110
  */
111
- type RevalidateHandler = (tags: string[], paths: string[]) => void | Promise<void>;
111
+ type ServerRevalidateHandler = (tags: string[], paths: string[]) => void | Promise<void>;
112
112
  /** Next.js-specific options (third argument for createEnlaceNext) */
113
113
  type NextOptions = Pick<EnlaceHookOptions, "autoGenerateTags" | "autoRevalidateTags"> & EnlaceCallbacks & {
114
114
  /**
@@ -117,32 +117,37 @@ type NextOptions = Pick<EnlaceHookOptions, "autoGenerateTags" | "autoRevalidateT
117
117
  * @example
118
118
  * ```ts
119
119
  * createEnlaceNext("http://localhost:3000/api/", {}, {
120
- * revalidator: (tags, paths) => revalidateServerAction(tags, paths)
120
+ * serverRevalidator: (tags, paths) => revalidateServerAction(tags, paths)
121
121
  * });
122
122
  * ```
123
123
  */
124
- revalidator?: RevalidateHandler;
124
+ serverRevalidator?: ServerRevalidateHandler;
125
+ /**
126
+ * Skip server-side revalidation by default for all mutations.
127
+ * Individual requests can override with serverRevalidate: true.
128
+ * Useful for CSR-heavy apps where server cache invalidation is rarely needed.
129
+ * @default false
130
+ */
131
+ skipServerRevalidation?: boolean;
125
132
  };
126
133
  /** Next.js hook options (third argument for createEnlaceHookNext) - extends React's EnlaceHookOptions */
127
- type NextHookOptions = EnlaceHookOptions & Pick<NextOptions, "revalidator">;
134
+ type NextHookOptions = EnlaceHookOptions & Pick<NextOptions, "serverRevalidator" | "skipServerRevalidation">;
128
135
  /** Per-request options for Next.js fetch - extends React's base options */
129
136
  type NextRequestOptionsBase = ReactRequestOptionsBase & {
130
137
  /** Time in seconds to revalidate, or false to disable */
131
138
  revalidate?: number | false;
132
139
  /**
133
- * URL paths to revalidate after mutation
134
- * This doesn't do anything on the client by itself - it's passed to the revalidator handler.
135
- * You must implement the revalidation logic in the revalidator.
140
+ * URL paths to revalidate after mutation.
141
+ * Passed to the serverRevalidator handler.
136
142
  */
137
143
  revalidatePaths?: string[];
138
144
  /**
139
- * Skip server-side revalidation for this request.
140
- * Useful when autoRevalidateTags is enabled but you want to opt-out for specific mutations.
141
- * You can still pass empty [] to revalidateTags to skip triggering revalidation.
142
- * But this flag can be used if you want to revalidate client-side and skip server-side entirely.
143
- * Eg. you don't fetch any data on server component and you might want to skip the overhead of revalidation.
145
+ * Control server-side revalidation for this specific request.
146
+ * - true: Force server revalidation
147
+ * - false: Skip server revalidation
148
+ * When undefined, follows the global skipServerRevalidation setting.
144
149
  */
145
- skipRevalidator?: boolean;
150
+ serverRevalidate?: boolean;
146
151
  };
147
152
  type NextQueryFn<TSchema, TData, TError, TDefaultError = unknown> = QueryFn<TSchema, TData, TError, TDefaultError, NextRequestOptionsBase>;
148
153
  type NextSelectorFn<TSchema, TMethod, TDefaultError = unknown> = SelectorFn<TSchema, TMethod, TDefaultError, NextRequestOptionsBase>;
@@ -154,11 +159,11 @@ type NextEnlaceHook<TSchema, TDefaultError = unknown> = {
154
159
 
155
160
  /**
156
161
  * Creates a React hook for making API calls in Next.js Client Components.
157
- * Uses Next.js-specific features like revalidator for server-side cache invalidation.
162
+ * Uses Next.js-specific features like serverRevalidator for server-side cache invalidation.
158
163
  *
159
164
  * @example
160
165
  * const useAPI = createEnlaceHookNext<ApiSchema>('https://api.com', {}, {
161
- * revalidator: (tags) => revalidateTagsAction(tags),
166
+ * serverRevalidator: (tags) => revalidateTagsAction(tags),
162
167
  * staleTime: 5000,
163
168
  * });
164
169
  *
@@ -15,9 +15,9 @@ type ReactRequestOptionsBase = {
15
15
  * Used to replace :paramName placeholders in the URL path.
16
16
  * @example
17
17
  * // With path api.products[':id'].delete
18
- * trigger({ pathParams: { id: '123' } }) // → DELETE /products/123
18
+ * trigger({ params: { id: '123' } }) // → DELETE /products/123
19
19
  */
20
- pathParams?: Record<string, string | number>;
20
+ params?: Record<string, string | number>;
21
21
  };
22
22
  /** Options for query mode hooks */
23
23
  type UseEnlaceQueryOptions = {
@@ -108,7 +108,7 @@ declare function createEnlaceHookReact<TSchema = unknown, TDefaultError = unknow
108
108
  * @param tags - Cache tags to revalidate
109
109
  * @param paths - URL paths to revalidate
110
110
  */
111
- type RevalidateHandler = (tags: string[], paths: string[]) => void | Promise<void>;
111
+ type ServerRevalidateHandler = (tags: string[], paths: string[]) => void | Promise<void>;
112
112
  /** Next.js-specific options (third argument for createEnlaceNext) */
113
113
  type NextOptions = Pick<EnlaceHookOptions, "autoGenerateTags" | "autoRevalidateTags"> & EnlaceCallbacks & {
114
114
  /**
@@ -117,32 +117,37 @@ type NextOptions = Pick<EnlaceHookOptions, "autoGenerateTags" | "autoRevalidateT
117
117
  * @example
118
118
  * ```ts
119
119
  * createEnlaceNext("http://localhost:3000/api/", {}, {
120
- * revalidator: (tags, paths) => revalidateServerAction(tags, paths)
120
+ * serverRevalidator: (tags, paths) => revalidateServerAction(tags, paths)
121
121
  * });
122
122
  * ```
123
123
  */
124
- revalidator?: RevalidateHandler;
124
+ serverRevalidator?: ServerRevalidateHandler;
125
+ /**
126
+ * Skip server-side revalidation by default for all mutations.
127
+ * Individual requests can override with serverRevalidate: true.
128
+ * Useful for CSR-heavy apps where server cache invalidation is rarely needed.
129
+ * @default false
130
+ */
131
+ skipServerRevalidation?: boolean;
125
132
  };
126
133
  /** Next.js hook options (third argument for createEnlaceHookNext) - extends React's EnlaceHookOptions */
127
- type NextHookOptions = EnlaceHookOptions & Pick<NextOptions, "revalidator">;
134
+ type NextHookOptions = EnlaceHookOptions & Pick<NextOptions, "serverRevalidator" | "skipServerRevalidation">;
128
135
  /** Per-request options for Next.js fetch - extends React's base options */
129
136
  type NextRequestOptionsBase = ReactRequestOptionsBase & {
130
137
  /** Time in seconds to revalidate, or false to disable */
131
138
  revalidate?: number | false;
132
139
  /**
133
- * URL paths to revalidate after mutation
134
- * This doesn't do anything on the client by itself - it's passed to the revalidator handler.
135
- * You must implement the revalidation logic in the revalidator.
140
+ * URL paths to revalidate after mutation.
141
+ * Passed to the serverRevalidator handler.
136
142
  */
137
143
  revalidatePaths?: string[];
138
144
  /**
139
- * Skip server-side revalidation for this request.
140
- * Useful when autoRevalidateTags is enabled but you want to opt-out for specific mutations.
141
- * You can still pass empty [] to revalidateTags to skip triggering revalidation.
142
- * But this flag can be used if you want to revalidate client-side and skip server-side entirely.
143
- * Eg. you don't fetch any data on server component and you might want to skip the overhead of revalidation.
145
+ * Control server-side revalidation for this specific request.
146
+ * - true: Force server revalidation
147
+ * - false: Skip server revalidation
148
+ * When undefined, follows the global skipServerRevalidation setting.
144
149
  */
145
- skipRevalidator?: boolean;
150
+ serverRevalidate?: boolean;
146
151
  };
147
152
  type NextQueryFn<TSchema, TData, TError, TDefaultError = unknown> = QueryFn<TSchema, TData, TError, TDefaultError, NextRequestOptionsBase>;
148
153
  type NextSelectorFn<TSchema, TMethod, TDefaultError = unknown> = SelectorFn<TSchema, TMethod, TDefaultError, NextRequestOptionsBase>;
@@ -154,11 +159,11 @@ type NextEnlaceHook<TSchema, TDefaultError = unknown> = {
154
159
 
155
160
  /**
156
161
  * Creates a React hook for making API calls in Next.js Client Components.
157
- * Uses Next.js-specific features like revalidator for server-side cache invalidation.
162
+ * Uses Next.js-specific features like serverRevalidator for server-side cache invalidation.
158
163
  *
159
164
  * @example
160
165
  * const useAPI = createEnlaceHookNext<ApiSchema>('https://api.com', {}, {
161
- * revalidator: (tags) => revalidateTagsAction(tags),
166
+ * serverRevalidator: (tags) => revalidateTagsAction(tags),
162
167
  * staleTime: 5000,
163
168
  * });
164
169
  *
@@ -49,7 +49,8 @@ function hookReducer(state, action) {
49
49
  return {
50
50
  ...state,
51
51
  loading: state.data === void 0,
52
- fetching: true
52
+ fetching: true,
53
+ error: void 0
53
54
  };
54
55
  case "FETCH_SUCCESS":
55
56
  return {
@@ -78,12 +79,21 @@ function generateTags(path) {
78
79
  }
79
80
 
80
81
  // src/utils/sortObjectKeys.ts
81
- function sortObjectKeys(obj) {
82
+ function sortObjectKeys(obj, seen = /* @__PURE__ */ new WeakSet()) {
82
83
  if (obj === null || typeof obj !== "object") return obj;
83
- if (Array.isArray(obj)) return obj.map(sortObjectKeys);
84
+ if (seen.has(obj)) {
85
+ return "[Circular]";
86
+ }
87
+ seen.add(obj);
88
+ if (Array.isArray(obj)) {
89
+ return obj.map((item) => sortObjectKeys(item, seen));
90
+ }
84
91
  return Object.keys(obj).sort().reduce(
85
92
  (sorted, key) => {
86
- sorted[key] = sortObjectKeys(obj[key]);
93
+ sorted[key] = sortObjectKeys(
94
+ obj[key],
95
+ seen
96
+ );
87
97
  return sorted;
88
98
  },
89
99
  {}
@@ -149,10 +159,9 @@ function clearCacheByTags(tags) {
149
159
  cache.forEach((entry) => {
150
160
  const hasMatch = entry.tags.some((tag) => tags.includes(tag));
151
161
  if (hasMatch) {
152
- entry.data = void 0;
153
- entry.error = void 0;
154
162
  entry.timestamp = 0;
155
163
  delete entry.promise;
164
+ entry.subscribers.forEach((cb) => cb());
156
165
  }
157
166
  });
158
167
  }
@@ -169,12 +178,12 @@ function onRevalidate(callback) {
169
178
  }
170
179
 
171
180
  // src/react/useQueryMode.ts
172
- function resolvePath(path, pathParams) {
173
- if (!pathParams) return path;
181
+ function resolvePath(path, params) {
182
+ if (!params) return path;
174
183
  return path.map((segment) => {
175
184
  if (segment.startsWith(":")) {
176
185
  const paramName = segment.slice(1);
177
- const value = pathParams[paramName];
186
+ const value = params[paramName];
178
187
  if (value === void 0) {
179
188
  throw new Error(`Missing path parameter: ${paramName}`);
180
189
  }
@@ -187,16 +196,14 @@ function useQueryMode(api, trackedCall, options) {
187
196
  const { autoGenerateTags, staleTime, enabled } = options;
188
197
  const queryKey = createQueryKey(trackedCall);
189
198
  const requestOptions = trackedCall.options;
190
- const resolvedPath = resolvePath(
191
- trackedCall.path,
192
- requestOptions?.pathParams
193
- );
199
+ const resolvedPath = resolvePath(trackedCall.path, requestOptions?.params);
194
200
  const queryTags = requestOptions?.tags ?? (autoGenerateTags ? generateTags(resolvedPath) : []);
195
201
  const getCacheState = (includeNeedsFetch = false) => {
196
202
  const cached = getCache(queryKey);
197
203
  const hasCachedData = cached?.data !== void 0;
198
204
  const isFetching = !!cached?.promise;
199
- const needsFetch = includeNeedsFetch && (!hasCachedData || isStale(queryKey, staleTime));
205
+ const stale = isStale(queryKey, staleTime);
206
+ const needsFetch = includeNeedsFetch && (!hasCachedData || stale);
200
207
  return {
201
208
  loading: !hasCachedData && (isFetching || needsFetch),
202
209
  fetching: isFetching || needsFetch,
@@ -240,6 +247,15 @@ function useQueryMode(api, trackedCall, options) {
240
247
  tags: queryTags
241
248
  });
242
249
  }
250
+ }).catch((err) => {
251
+ if (mountedRef.current) {
252
+ setCache(queryKey, {
253
+ data: void 0,
254
+ error: err,
255
+ timestamp: Date.now(),
256
+ tags: queryTags
257
+ });
258
+ }
243
259
  });
244
260
  setCache(queryKey, {
245
261
  promise: fetchPromise,
@@ -310,12 +326,12 @@ function createTrackingProxy(onTrack) {
310
326
 
311
327
  // src/react/useSelectorMode.ts
312
328
  var import_react2 = require("react");
313
- function resolvePath2(path, pathParams) {
314
- if (!pathParams) return path;
329
+ function resolvePath2(path, params) {
330
+ if (!params) return path;
315
331
  return path.map((segment) => {
316
332
  if (segment.startsWith(":")) {
317
333
  const paramName = segment.slice(1);
318
- const value = pathParams[paramName];
334
+ const value = params[paramName];
319
335
  if (value === void 0) {
320
336
  throw new Error(`Missing path parameter: ${paramName}`);
321
337
  }
@@ -345,7 +361,7 @@ function useSelectorMode(config) {
345
361
  triggerRef.current = (async (...args) => {
346
362
  dispatch({ type: "FETCH_START" });
347
363
  const options = args[0];
348
- const resolvedPath = resolvePath2(pathRef.current, options?.pathParams);
364
+ const resolvedPath = resolvePath2(pathRef.current, options?.params);
349
365
  let res;
350
366
  if (hasPathParams(pathRef.current)) {
351
367
  let current = apiRef.current;
@@ -384,7 +400,10 @@ function createEnlaceHookReact(baseUrl, defaultOptions = {}, hookOptions = {}) {
384
400
  onSuccess,
385
401
  onError
386
402
  } = hookOptions;
387
- const api = (0, import_enlace_core.createEnlace)(baseUrl, defaultOptions, { onSuccess, onError });
403
+ const api = (0, import_enlace_core.createEnlace)(baseUrl, defaultOptions, {
404
+ onSuccess,
405
+ onError
406
+ });
388
407
  function useEnlaceHook(selectorOrQuery, queryOptions) {
389
408
  let trackingResult = {
390
409
  trackedCall: null,
@@ -394,9 +413,7 @@ function createEnlaceHookReact(baseUrl, defaultOptions = {}, hookOptions = {}) {
394
413
  const trackingProxy = createTrackingProxy((result2) => {
395
414
  trackingResult = result2;
396
415
  });
397
- const result = selectorOrQuery(
398
- trackingProxy
399
- );
416
+ const result = selectorOrQuery(trackingProxy);
400
417
  if (typeof result === "function") {
401
418
  const actualResult = selectorOrQuery(api);
402
419
  return useSelectorMode({
@@ -407,6 +424,11 @@ function createEnlaceHookReact(baseUrl, defaultOptions = {}, hookOptions = {}) {
407
424
  autoRevalidateTags
408
425
  });
409
426
  }
427
+ if (!trackingResult.trackedCall) {
428
+ throw new Error(
429
+ "useAPI query mode requires calling an HTTP method (get, post, etc.). Did you mean to use selector mode? Example: useAPI((api) => api.posts.get())"
430
+ );
431
+ }
410
432
  return useQueryMode(
411
433
  api,
412
434
  trackingResult.trackedCall,
@@ -425,18 +447,22 @@ async function executeNextFetch(baseUrl, path, method, combinedOptions, requestO
425
447
  const {
426
448
  autoGenerateTags = true,
427
449
  autoRevalidateTags = true,
428
- revalidator,
450
+ skipServerRevalidation = false,
451
+ serverRevalidator,
429
452
  onSuccess,
430
453
  ...coreOptions
431
454
  } = combinedOptions;
432
455
  const isGet = method === "GET";
433
456
  const autoTags = generateTags(path);
434
457
  const nextOnSuccess = (payload) => {
435
- if (!isGet && !requestOptions?.skipRevalidator) {
436
- const revalidateTags = requestOptions?.revalidateTags ?? (autoRevalidateTags ? autoTags : []);
437
- const revalidatePaths = requestOptions?.revalidatePaths ?? [];
438
- if (revalidateTags.length || revalidatePaths.length) {
439
- revalidator?.(revalidateTags, revalidatePaths);
458
+ if (!isGet) {
459
+ const shouldRevalidateServer = requestOptions?.serverRevalidate ?? !skipServerRevalidation;
460
+ if (shouldRevalidateServer) {
461
+ const revalidateTags = requestOptions?.revalidateTags ?? (autoRevalidateTags ? autoTags : []);
462
+ const revalidatePaths = requestOptions?.revalidatePaths ?? [];
463
+ if (revalidateTags.length || revalidatePaths.length) {
464
+ serverRevalidator?.(revalidateTags, revalidatePaths);
465
+ }
440
466
  }
441
467
  }
442
468
  onSuccess?.(payload);
@@ -481,11 +507,15 @@ function createEnlaceHookNext(baseUrl, defaultOptions = {}, hookOptions = {}) {
481
507
  staleTime = 0,
482
508
  ...nextOptions
483
509
  } = hookOptions;
484
- const api = createEnlaceNext(baseUrl, defaultOptions, {
485
- autoGenerateTags,
486
- autoRevalidateTags,
487
- ...nextOptions
488
- });
510
+ const api = createEnlaceNext(
511
+ baseUrl,
512
+ defaultOptions,
513
+ {
514
+ autoGenerateTags,
515
+ autoRevalidateTags,
516
+ ...nextOptions
517
+ }
518
+ );
489
519
  function useEnlaceHook(selectorOrQuery, queryOptions) {
490
520
  let trackedCall = null;
491
521
  let selectorPath = null;
@@ -506,6 +536,11 @@ function createEnlaceHookNext(baseUrl, defaultOptions = {}, hookOptions = {}) {
506
536
  autoRevalidateTags
507
537
  });
508
538
  }
539
+ if (!trackedCall) {
540
+ throw new Error(
541
+ "useAPI query mode requires calling an HTTP method (get, post, etc.). Did you mean to use selector mode? Example: useAPI((api) => api.posts.get())"
542
+ );
543
+ }
509
544
  return useQueryMode(
510
545
  api,
511
546
  trackedCall,
@@ -2,7 +2,9 @@
2
2
  "use client";
3
3
 
4
4
  // src/react/createEnlaceHookReact.ts
5
- import { createEnlace } from "enlace-core";
5
+ import {
6
+ createEnlace
7
+ } from "enlace-core";
6
8
 
7
9
  // src/react/useQueryMode.ts
8
10
  import { useRef, useReducer, useEffect } from "react";
@@ -22,7 +24,8 @@ function hookReducer(state, action) {
22
24
  return {
23
25
  ...state,
24
26
  loading: state.data === void 0,
25
- fetching: true
27
+ fetching: true,
28
+ error: void 0
26
29
  };
27
30
  case "FETCH_SUCCESS":
28
31
  return {
@@ -51,12 +54,21 @@ function generateTags(path) {
51
54
  }
52
55
 
53
56
  // src/utils/sortObjectKeys.ts
54
- function sortObjectKeys(obj) {
57
+ function sortObjectKeys(obj, seen = /* @__PURE__ */ new WeakSet()) {
55
58
  if (obj === null || typeof obj !== "object") return obj;
56
- if (Array.isArray(obj)) return obj.map(sortObjectKeys);
59
+ if (seen.has(obj)) {
60
+ return "[Circular]";
61
+ }
62
+ seen.add(obj);
63
+ if (Array.isArray(obj)) {
64
+ return obj.map((item) => sortObjectKeys(item, seen));
65
+ }
57
66
  return Object.keys(obj).sort().reduce(
58
67
  (sorted, key) => {
59
- sorted[key] = sortObjectKeys(obj[key]);
68
+ sorted[key] = sortObjectKeys(
69
+ obj[key],
70
+ seen
71
+ );
60
72
  return sorted;
61
73
  },
62
74
  {}
@@ -122,10 +134,9 @@ function clearCacheByTags(tags) {
122
134
  cache.forEach((entry) => {
123
135
  const hasMatch = entry.tags.some((tag) => tags.includes(tag));
124
136
  if (hasMatch) {
125
- entry.data = void 0;
126
- entry.error = void 0;
127
137
  entry.timestamp = 0;
128
138
  delete entry.promise;
139
+ entry.subscribers.forEach((cb) => cb());
129
140
  }
130
141
  });
131
142
  }
@@ -142,12 +153,12 @@ function onRevalidate(callback) {
142
153
  }
143
154
 
144
155
  // src/react/useQueryMode.ts
145
- function resolvePath(path, pathParams) {
146
- if (!pathParams) return path;
156
+ function resolvePath(path, params) {
157
+ if (!params) return path;
147
158
  return path.map((segment) => {
148
159
  if (segment.startsWith(":")) {
149
160
  const paramName = segment.slice(1);
150
- const value = pathParams[paramName];
161
+ const value = params[paramName];
151
162
  if (value === void 0) {
152
163
  throw new Error(`Missing path parameter: ${paramName}`);
153
164
  }
@@ -160,16 +171,14 @@ function useQueryMode(api, trackedCall, options) {
160
171
  const { autoGenerateTags, staleTime, enabled } = options;
161
172
  const queryKey = createQueryKey(trackedCall);
162
173
  const requestOptions = trackedCall.options;
163
- const resolvedPath = resolvePath(
164
- trackedCall.path,
165
- requestOptions?.pathParams
166
- );
174
+ const resolvedPath = resolvePath(trackedCall.path, requestOptions?.params);
167
175
  const queryTags = requestOptions?.tags ?? (autoGenerateTags ? generateTags(resolvedPath) : []);
168
176
  const getCacheState = (includeNeedsFetch = false) => {
169
177
  const cached = getCache(queryKey);
170
178
  const hasCachedData = cached?.data !== void 0;
171
179
  const isFetching = !!cached?.promise;
172
- const needsFetch = includeNeedsFetch && (!hasCachedData || isStale(queryKey, staleTime));
180
+ const stale = isStale(queryKey, staleTime);
181
+ const needsFetch = includeNeedsFetch && (!hasCachedData || stale);
173
182
  return {
174
183
  loading: !hasCachedData && (isFetching || needsFetch),
175
184
  fetching: isFetching || needsFetch,
@@ -213,6 +222,15 @@ function useQueryMode(api, trackedCall, options) {
213
222
  tags: queryTags
214
223
  });
215
224
  }
225
+ }).catch((err) => {
226
+ if (mountedRef.current) {
227
+ setCache(queryKey, {
228
+ data: void 0,
229
+ error: err,
230
+ timestamp: Date.now(),
231
+ tags: queryTags
232
+ });
233
+ }
216
234
  });
217
235
  setCache(queryKey, {
218
236
  promise: fetchPromise,
@@ -283,12 +301,12 @@ function createTrackingProxy(onTrack) {
283
301
 
284
302
  // src/react/useSelectorMode.ts
285
303
  import { useRef as useRef2, useReducer as useReducer2 } from "react";
286
- function resolvePath2(path, pathParams) {
287
- if (!pathParams) return path;
304
+ function resolvePath2(path, params) {
305
+ if (!params) return path;
288
306
  return path.map((segment) => {
289
307
  if (segment.startsWith(":")) {
290
308
  const paramName = segment.slice(1);
291
- const value = pathParams[paramName];
309
+ const value = params[paramName];
292
310
  if (value === void 0) {
293
311
  throw new Error(`Missing path parameter: ${paramName}`);
294
312
  }
@@ -318,7 +336,7 @@ function useSelectorMode(config) {
318
336
  triggerRef.current = (async (...args) => {
319
337
  dispatch({ type: "FETCH_START" });
320
338
  const options = args[0];
321
- const resolvedPath = resolvePath2(pathRef.current, options?.pathParams);
339
+ const resolvedPath = resolvePath2(pathRef.current, options?.params);
322
340
  let res;
323
341
  if (hasPathParams(pathRef.current)) {
324
342
  let current = apiRef.current;
@@ -357,7 +375,10 @@ function createEnlaceHookReact(baseUrl, defaultOptions = {}, hookOptions = {}) {
357
375
  onSuccess,
358
376
  onError
359
377
  } = hookOptions;
360
- const api = createEnlace(baseUrl, defaultOptions, { onSuccess, onError });
378
+ const api = createEnlace(baseUrl, defaultOptions, {
379
+ onSuccess,
380
+ onError
381
+ });
361
382
  function useEnlaceHook(selectorOrQuery, queryOptions) {
362
383
  let trackingResult = {
363
384
  trackedCall: null,
@@ -367,9 +388,7 @@ function createEnlaceHookReact(baseUrl, defaultOptions = {}, hookOptions = {}) {
367
388
  const trackingProxy = createTrackingProxy((result2) => {
368
389
  trackingResult = result2;
369
390
  });
370
- const result = selectorOrQuery(
371
- trackingProxy
372
- );
391
+ const result = selectorOrQuery(trackingProxy);
373
392
  if (typeof result === "function") {
374
393
  const actualResult = selectorOrQuery(api);
375
394
  return useSelectorMode({
@@ -380,6 +399,11 @@ function createEnlaceHookReact(baseUrl, defaultOptions = {}, hookOptions = {}) {
380
399
  autoRevalidateTags
381
400
  });
382
401
  }
402
+ if (!trackingResult.trackedCall) {
403
+ throw new Error(
404
+ "useAPI query mode requires calling an HTTP method (get, post, etc.). Did you mean to use selector mode? Example: useAPI((api) => api.posts.get())"
405
+ );
406
+ }
383
407
  return useQueryMode(
384
408
  api,
385
409
  trackingResult.trackedCall,
@@ -402,18 +426,22 @@ async function executeNextFetch(baseUrl, path, method, combinedOptions, requestO
402
426
  const {
403
427
  autoGenerateTags = true,
404
428
  autoRevalidateTags = true,
405
- revalidator,
429
+ skipServerRevalidation = false,
430
+ serverRevalidator,
406
431
  onSuccess,
407
432
  ...coreOptions
408
433
  } = combinedOptions;
409
434
  const isGet = method === "GET";
410
435
  const autoTags = generateTags(path);
411
436
  const nextOnSuccess = (payload) => {
412
- if (!isGet && !requestOptions?.skipRevalidator) {
413
- const revalidateTags = requestOptions?.revalidateTags ?? (autoRevalidateTags ? autoTags : []);
414
- const revalidatePaths = requestOptions?.revalidatePaths ?? [];
415
- if (revalidateTags.length || revalidatePaths.length) {
416
- revalidator?.(revalidateTags, revalidatePaths);
437
+ if (!isGet) {
438
+ const shouldRevalidateServer = requestOptions?.serverRevalidate ?? !skipServerRevalidation;
439
+ if (shouldRevalidateServer) {
440
+ const revalidateTags = requestOptions?.revalidateTags ?? (autoRevalidateTags ? autoTags : []);
441
+ const revalidatePaths = requestOptions?.revalidatePaths ?? [];
442
+ if (revalidateTags.length || revalidatePaths.length) {
443
+ serverRevalidator?.(revalidateTags, revalidatePaths);
444
+ }
417
445
  }
418
446
  }
419
447
  onSuccess?.(payload);
@@ -458,11 +486,15 @@ function createEnlaceHookNext(baseUrl, defaultOptions = {}, hookOptions = {}) {
458
486
  staleTime = 0,
459
487
  ...nextOptions
460
488
  } = hookOptions;
461
- const api = createEnlaceNext(baseUrl, defaultOptions, {
462
- autoGenerateTags,
463
- autoRevalidateTags,
464
- ...nextOptions
465
- });
489
+ const api = createEnlaceNext(
490
+ baseUrl,
491
+ defaultOptions,
492
+ {
493
+ autoGenerateTags,
494
+ autoRevalidateTags,
495
+ ...nextOptions
496
+ }
497
+ );
466
498
  function useEnlaceHook(selectorOrQuery, queryOptions) {
467
499
  let trackedCall = null;
468
500
  let selectorPath = null;
@@ -483,6 +515,11 @@ function createEnlaceHookNext(baseUrl, defaultOptions = {}, hookOptions = {}) {
483
515
  autoRevalidateTags
484
516
  });
485
517
  }
518
+ if (!trackedCall) {
519
+ throw new Error(
520
+ "useAPI query mode requires calling an HTTP method (get, post, etc.). Did you mean to use selector mode? Example: useAPI((api) => api.posts.get())"
521
+ );
522
+ }
486
523
  return useQueryMode(
487
524
  api,
488
525
  trackedCall,
package/dist/index.d.mts CHANGED
@@ -16,9 +16,9 @@ type ReactRequestOptionsBase = {
16
16
  * Used to replace :paramName placeholders in the URL path.
17
17
  * @example
18
18
  * // With path api.products[':id'].delete
19
- * trigger({ pathParams: { id: '123' } }) // → DELETE /products/123
19
+ * trigger({ params: { id: '123' } }) // → DELETE /products/123
20
20
  */
21
- pathParams?: Record<string, string | number>;
21
+ params?: Record<string, string | number>;
22
22
  };
23
23
  /** Options for createEnlaceHookReact factory */
24
24
  type EnlaceHookOptions = {
@@ -43,7 +43,7 @@ type EnlaceHookOptions = {
43
43
  * @param tags - Cache tags to revalidate
44
44
  * @param paths - URL paths to revalidate
45
45
  */
46
- type RevalidateHandler = (tags: string[], paths: string[]) => void | Promise<void>;
46
+ type ServerRevalidateHandler = (tags: string[], paths: string[]) => void | Promise<void>;
47
47
  /** Next.js-specific options (third argument for createEnlaceNext) */
48
48
  type NextOptions = Pick<EnlaceHookOptions, "autoGenerateTags" | "autoRevalidateTags"> & EnlaceCallbacks & {
49
49
  /**
@@ -52,30 +52,35 @@ type NextOptions = Pick<EnlaceHookOptions, "autoGenerateTags" | "autoRevalidateT
52
52
  * @example
53
53
  * ```ts
54
54
  * createEnlaceNext("http://localhost:3000/api/", {}, {
55
- * revalidator: (tags, paths) => revalidateServerAction(tags, paths)
55
+ * serverRevalidator: (tags, paths) => revalidateServerAction(tags, paths)
56
56
  * });
57
57
  * ```
58
58
  */
59
- revalidator?: RevalidateHandler;
59
+ serverRevalidator?: ServerRevalidateHandler;
60
+ /**
61
+ * Skip server-side revalidation by default for all mutations.
62
+ * Individual requests can override with serverRevalidate: true.
63
+ * Useful for CSR-heavy apps where server cache invalidation is rarely needed.
64
+ * @default false
65
+ */
66
+ skipServerRevalidation?: boolean;
60
67
  };
61
68
  /** Per-request options for Next.js fetch - extends React's base options */
62
69
  type NextRequestOptionsBase = ReactRequestOptionsBase & {
63
70
  /** Time in seconds to revalidate, or false to disable */
64
71
  revalidate?: number | false;
65
72
  /**
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.
73
+ * URL paths to revalidate after mutation.
74
+ * Passed to the serverRevalidator handler.
69
75
  */
70
76
  revalidatePaths?: string[];
71
77
  /**
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.
78
+ * Control server-side revalidation for this specific request.
79
+ * - true: Force server revalidation
80
+ * - false: Skip server revalidation
81
+ * When undefined, follows the global skipServerRevalidation setting.
77
82
  */
78
- skipRevalidator?: boolean;
83
+ serverRevalidate?: boolean;
79
84
  };
80
85
 
81
86
  declare function createEnlaceNext<TSchema = unknown, TDefaultError = unknown>(baseUrl: string, defaultOptions?: EnlaceOptions | null, nextOptions?: NextOptions): unknown extends TSchema ? WildcardClient<NextRequestOptionsBase> : EnlaceClient<TSchema, TDefaultError, NextRequestOptionsBase>;
package/dist/index.d.ts CHANGED
@@ -16,9 +16,9 @@ type ReactRequestOptionsBase = {
16
16
  * Used to replace :paramName placeholders in the URL path.
17
17
  * @example
18
18
  * // With path api.products[':id'].delete
19
- * trigger({ pathParams: { id: '123' } }) // → DELETE /products/123
19
+ * trigger({ params: { id: '123' } }) // → DELETE /products/123
20
20
  */
21
- pathParams?: Record<string, string | number>;
21
+ params?: Record<string, string | number>;
22
22
  };
23
23
  /** Options for createEnlaceHookReact factory */
24
24
  type EnlaceHookOptions = {
@@ -43,7 +43,7 @@ type EnlaceHookOptions = {
43
43
  * @param tags - Cache tags to revalidate
44
44
  * @param paths - URL paths to revalidate
45
45
  */
46
- type RevalidateHandler = (tags: string[], paths: string[]) => void | Promise<void>;
46
+ type ServerRevalidateHandler = (tags: string[], paths: string[]) => void | Promise<void>;
47
47
  /** Next.js-specific options (third argument for createEnlaceNext) */
48
48
  type NextOptions = Pick<EnlaceHookOptions, "autoGenerateTags" | "autoRevalidateTags"> & EnlaceCallbacks & {
49
49
  /**
@@ -52,30 +52,35 @@ type NextOptions = Pick<EnlaceHookOptions, "autoGenerateTags" | "autoRevalidateT
52
52
  * @example
53
53
  * ```ts
54
54
  * createEnlaceNext("http://localhost:3000/api/", {}, {
55
- * revalidator: (tags, paths) => revalidateServerAction(tags, paths)
55
+ * serverRevalidator: (tags, paths) => revalidateServerAction(tags, paths)
56
56
  * });
57
57
  * ```
58
58
  */
59
- revalidator?: RevalidateHandler;
59
+ serverRevalidator?: ServerRevalidateHandler;
60
+ /**
61
+ * Skip server-side revalidation by default for all mutations.
62
+ * Individual requests can override with serverRevalidate: true.
63
+ * Useful for CSR-heavy apps where server cache invalidation is rarely needed.
64
+ * @default false
65
+ */
66
+ skipServerRevalidation?: boolean;
60
67
  };
61
68
  /** Per-request options for Next.js fetch - extends React's base options */
62
69
  type NextRequestOptionsBase = ReactRequestOptionsBase & {
63
70
  /** Time in seconds to revalidate, or false to disable */
64
71
  revalidate?: number | false;
65
72
  /**
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.
73
+ * URL paths to revalidate after mutation.
74
+ * Passed to the serverRevalidator handler.
69
75
  */
70
76
  revalidatePaths?: string[];
71
77
  /**
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.
78
+ * Control server-side revalidation for this specific request.
79
+ * - true: Force server revalidation
80
+ * - false: Skip server revalidation
81
+ * When undefined, follows the global skipServerRevalidation setting.
77
82
  */
78
- skipRevalidator?: boolean;
83
+ serverRevalidate?: boolean;
79
84
  };
80
85
 
81
86
  declare function createEnlaceNext<TSchema = unknown, TDefaultError = unknown>(baseUrl: string, defaultOptions?: EnlaceOptions | null, nextOptions?: NextOptions): unknown extends TSchema ? WildcardClient<NextRequestOptionsBase> : EnlaceClient<TSchema, TDefaultError, NextRequestOptionsBase>;
package/dist/index.js CHANGED
@@ -42,18 +42,22 @@ async function executeNextFetch(baseUrl, path, method, combinedOptions, requestO
42
42
  const {
43
43
  autoGenerateTags = true,
44
44
  autoRevalidateTags = true,
45
- revalidator,
45
+ skipServerRevalidation = false,
46
+ serverRevalidator,
46
47
  onSuccess,
47
48
  ...coreOptions
48
49
  } = combinedOptions;
49
50
  const isGet = method === "GET";
50
51
  const autoTags = generateTags(path);
51
52
  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);
53
+ if (!isGet) {
54
+ const shouldRevalidateServer = requestOptions?.serverRevalidate ?? !skipServerRevalidation;
55
+ if (shouldRevalidateServer) {
56
+ const revalidateTags = requestOptions?.revalidateTags ?? (autoRevalidateTags ? autoTags : []);
57
+ const revalidatePaths = requestOptions?.revalidatePaths ?? [];
58
+ if (revalidateTags.length || revalidatePaths.length) {
59
+ serverRevalidator?.(revalidateTags, revalidatePaths);
60
+ }
57
61
  }
58
62
  }
59
63
  onSuccess?.(payload);
package/dist/index.mjs CHANGED
@@ -21,18 +21,22 @@ async function executeNextFetch(baseUrl, path, method, combinedOptions, requestO
21
21
  const {
22
22
  autoGenerateTags = true,
23
23
  autoRevalidateTags = true,
24
- revalidator,
24
+ skipServerRevalidation = false,
25
+ serverRevalidator,
25
26
  onSuccess,
26
27
  ...coreOptions
27
28
  } = combinedOptions;
28
29
  const isGet = method === "GET";
29
30
  const autoTags = generateTags(path);
30
31
  const nextOnSuccess = (payload) => {
31
- if (!isGet && !requestOptions?.skipRevalidator) {
32
- const revalidateTags = requestOptions?.revalidateTags ?? (autoRevalidateTags ? autoTags : []);
33
- const revalidatePaths = requestOptions?.revalidatePaths ?? [];
34
- if (revalidateTags.length || revalidatePaths.length) {
35
- revalidator?.(revalidateTags, revalidatePaths);
32
+ if (!isGet) {
33
+ const shouldRevalidateServer = requestOptions?.serverRevalidate ?? !skipServerRevalidation;
34
+ if (shouldRevalidateServer) {
35
+ const revalidateTags = requestOptions?.revalidateTags ?? (autoRevalidateTags ? autoTags : []);
36
+ const revalidatePaths = requestOptions?.revalidatePaths ?? [];
37
+ if (revalidateTags.length || revalidatePaths.length) {
38
+ serverRevalidator?.(revalidateTags, revalidatePaths);
39
+ }
36
40
  }
37
41
  }
38
42
  onSuccess?.(payload);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "enlace",
3
- "version": "0.0.1-beta.10",
3
+ "version": "0.0.1-beta.12",
4
4
  "license": "MIT",
5
5
  "files": [
6
6
  "dist"