enlace 0.0.1-beta.7 → 0.0.1-beta.9

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
@@ -14,18 +14,25 @@ npm install enlace
14
14
  import { createEnlaceHookReact } from "enlace/hook";
15
15
  import { Endpoint } from "enlace";
16
16
 
17
+ // Define your API error type
18
+ type ApiError = { message: string; code: number };
19
+
17
20
  type ApiSchema = {
18
21
  posts: {
19
- $get: Endpoint<Post[], ApiError>;
20
- $post: Endpoint<Post, ApiError, CreatePost>;
22
+ $get: Post[]; // Simple: just data type
23
+ $post: Endpoint<Post, CreatePost>; // Data + Body
24
+ $put: Endpoint<Post, UpdatePost, CustomError>; // Data + Body + Custom Error
21
25
  _: {
22
- $get: Endpoint<Post, ApiError>;
23
- $delete: Endpoint<void, ApiError>;
26
+ $get: Post; // Simple: just data type
27
+ $delete: void; // Simple: void response
24
28
  };
25
29
  };
26
30
  };
27
31
 
28
- const useAPI = createEnlaceHookReact<ApiSchema>("https://api.example.com");
32
+ // Pass global error type as second generic
33
+ const useAPI = createEnlaceHookReact<ApiSchema, ApiError>(
34
+ "https://api.example.com"
35
+ );
29
36
  ```
30
37
 
31
38
  ## Schema Conventions
@@ -51,40 +58,77 @@ const useAPI = createEnlaceHookReact<ApiSchema>("https://api.example.com");
51
58
  ```typescript
52
59
  import { Endpoint } from "enlace";
53
60
 
61
+ type ApiError = { message: string };
62
+
54
63
  type ApiSchema = {
55
64
  users: {
56
- $get: Endpoint<User[], ApiError>; // GET /users
57
- $post: Endpoint<User, ApiError>; // POST /users
58
- _: { // /users/:id
59
- $get: Endpoint<User, ApiError>; // GET /users/:id
60
- $put: Endpoint<User, ApiError>; // PUT /users/:id
61
- $delete: Endpoint<void, ApiError>; // DELETE /users/:id
65
+ $get: User[]; // GET /users (simple)
66
+ $post: Endpoint<User, CreateUser>; // POST /users with body
67
+ _: {
68
+ // /users/:id
69
+ $get: User; // GET /users/:id (simple)
70
+ $put: Endpoint<User, UpdateUser>; // PUT /users/:id with body
71
+ $delete: void; // DELETE /users/:id (void response)
62
72
  profile: {
63
- $get: Endpoint<Profile, ApiError>; // GET /users/:id/profile
73
+ $get: Profile; // GET /users/:id/profile (simple)
64
74
  };
65
75
  };
66
76
  };
67
77
  };
68
78
 
79
+ // Pass global error type - applies to all endpoints
80
+ const api = createEnlace<ApiSchema, ApiError>("https://api.example.com");
81
+
69
82
  // Usage
70
- api.users.get(); // GET /users
71
- api.users[123].get(); // GET /users/123
83
+ api.users.get(); // GET /users
84
+ api.users[123].get(); // GET /users/123
72
85
  api.users[123].profile.get(); // GET /users/123/profile
73
86
  ```
74
87
 
75
88
  ### Endpoint Type
76
89
 
90
+ The `Endpoint` type helper lets you define response data, request body, and optionally override the error type:
91
+
77
92
  ```typescript
78
- type Endpoint<TData, TError, TBody = never> = {
79
- data: TData; // Response data type
80
- error: TError; // Error response type (required)
81
- body: TBody; // Request body type (optional)
93
+ // Signature: Endpoint<TData, TBody?, TError?>
94
+ type Endpoint<TData, TBody = never, TError = never>;
95
+ ```
96
+
97
+ **Three ways to define endpoints:**
98
+
99
+ ```typescript
100
+ type ApiSchema = {
101
+ posts: {
102
+ // 1. Direct type (simplest) - just the data type
103
+ // Error comes from global default
104
+ $get: Post[];
105
+
106
+ // 2. Endpoint with body - Endpoint<Data, Body>
107
+ // Error comes from global default
108
+ $post: Endpoint<Post, CreatePost>;
109
+
110
+ // 3. Endpoint with custom error - Endpoint<Data, Body, Error>
111
+ // Overrides global error type for this endpoint
112
+ $put: Endpoint<Post, UpdatePost, ValidationError>;
113
+
114
+ // void response - use void directly
115
+ $delete: void;
116
+
117
+ // Custom error without body - use `never` for body
118
+ $patch: Endpoint<Post, never, NotFoundError>;
119
+ };
82
120
  };
121
+ ```
122
+
123
+ **Global error type:**
124
+
125
+ ```typescript
126
+ type ApiError = { message: string; code: number };
83
127
 
84
- // Examples
85
- type GetUsers = Endpoint<User[], ApiError>; // GET, no body
86
- type CreateUser = Endpoint<User, ApiError, CreateUserInput>; // POST with body
87
- type DeleteUser = Endpoint<void, NotFoundError>; // DELETE, no response data
128
+ // Second generic sets default error type for all endpoints
129
+ const api = createEnlace<ApiSchema, ApiError>("https://api.example.com");
130
+ // const useAPI = createEnlaceHookReact<ApiSchema, ApiError>("...");
131
+ // const useAPI = createEnlaceHookNext<ApiSchema, ApiError>("...");
88
132
  ```
89
133
 
90
134
  ## React Hooks
@@ -95,12 +139,12 @@ For GET requests that fetch data automatically:
95
139
 
96
140
  ```typescript
97
141
  function Posts({ page, limit }: { page: number; limit: number }) {
98
- const { data, loading, error, ok } = useAPI((api) =>
142
+ const { data, loading, error } = useAPI((api) =>
99
143
  api.posts.get({ query: { page, limit, published: true } })
100
144
  );
101
145
 
102
146
  if (loading) return <div>Loading...</div>;
103
- if (!ok) return <div>Error: {error.message}</div>;
147
+ if (error) return <div>Error: {error.message}</div>;
104
148
 
105
149
  return (
106
150
  <ul>
@@ -113,6 +157,7 @@ function Posts({ page, limit }: { page: number; limit: number }) {
113
157
  ```
114
158
 
115
159
  **Features:**
160
+
116
161
  - Auto-fetches on mount
117
162
  - Re-fetches when dependencies change (no deps array needed!)
118
163
  - Returns cached data while revalidating
@@ -139,10 +184,9 @@ function ProductForm({ id }: { id: string | "new" }) {
139
184
  ```typescript
140
185
  // Also useful when waiting for a dependency
141
186
  function UserPosts({ userId }: { userId: string | undefined }) {
142
- const { data } = useAPI(
143
- (api) => api.users[userId!].posts.get(),
144
- { enabled: userId !== undefined }
145
- );
187
+ const { data } = useAPI((api) => api.users[userId!].posts.get(), {
188
+ enabled: userId !== undefined,
189
+ });
146
190
  }
147
191
  ```
148
192
 
@@ -206,7 +250,7 @@ function CreatePost() {
206
250
 
207
251
  const handleSubmit = async (title: string) => {
208
252
  const result = await trigger({ body: { title } });
209
- if (result.ok) {
253
+ if (!result.error) {
210
254
  console.log("Created:", result.data);
211
255
  }
212
256
  };
@@ -247,7 +291,9 @@ function PostList({ posts }: { posts: Post[] }) {
247
291
  **Multiple path parameters:**
248
292
 
249
293
  ```typescript
250
- const { trigger } = useAPI((api) => api.users[":userId"].posts[":postId"].delete);
294
+ const { trigger } = useAPI(
295
+ (api) => api.users[":userId"].posts[":postId"].delete
296
+ );
251
297
 
252
298
  trigger({ pathParams: { userId: "1", postId: "42" } });
253
299
  // → DELETE /users/1/posts/42
@@ -311,9 +357,13 @@ trigger({ body: { title: "New" } });
311
357
  Control how long cached data is considered fresh:
312
358
 
313
359
  ```typescript
314
- const useAPI = createEnlaceHookReact<ApiSchema>("https://api.example.com", {}, {
315
- staleTime: 5000, // 5 seconds
316
- });
360
+ const useAPI = createEnlaceHookReact<ApiSchema>(
361
+ "https://api.example.com",
362
+ {},
363
+ {
364
+ staleTime: 5000, // 5 seconds
365
+ }
366
+ );
317
367
  ```
318
368
 
319
369
  - `staleTime: 0` (default) — Always revalidate on mount
@@ -338,10 +388,14 @@ trigger({
338
388
  ### Disable Auto-Revalidation
339
389
 
340
390
  ```typescript
341
- const useAPI = createEnlaceHookReact<ApiSchema>("https://api.example.com", {}, {
342
- autoGenerateTags: false, // Disable auto tag generation
343
- autoRevalidateTags: false, // Disable auto revalidation
344
- });
391
+ const useAPI = createEnlaceHookReact<ApiSchema>(
392
+ "https://api.example.com",
393
+ {},
394
+ {
395
+ autoGenerateTags: false, // Disable auto tag generation
396
+ autoRevalidateTags: false, // Disable auto revalidation
397
+ }
398
+ );
345
399
  ```
346
400
 
347
401
  ## Hook Options
@@ -355,9 +409,9 @@ const useAPI = createEnlaceHookReact<ApiSchema>(
355
409
  },
356
410
  {
357
411
  // Hook options
358
- autoGenerateTags: true, // Auto-generate cache tags from URL
359
- autoRevalidateTags: true, // Auto-revalidate after mutations
360
- staleTime: 0, // Cache freshness duration (ms)
412
+ autoGenerateTags: true, // Auto-generate cache tags from URL
413
+ autoRevalidateTags: true, // Auto-revalidate after mutations
414
+ staleTime: 0, // Cache freshness duration (ms)
361
415
  }
362
416
  );
363
417
  ```
@@ -438,11 +492,12 @@ type EnlaceCallbackPayload<T> = {
438
492
 
439
493
  // onError payload (HTTP error or network error)
440
494
  type EnlaceErrorCallbackPayload<T> =
441
- | { status: number; error: T; headers: Headers } // HTTP error
442
- | { status: 0; error: Error; headers: null }; // Network error
495
+ | { status: number; error: T; headers: Headers } // HTTP error
496
+ | { status: 0; error: Error; headers: null }; // Network error
443
497
  ```
444
498
 
445
499
  **Use cases:**
500
+
446
501
  - Global error logging/reporting
447
502
  - Toast notifications for all API errors
448
503
  - Authentication refresh on 401 errors
@@ -459,13 +514,12 @@ const result = useAPI((api) => api.posts.get());
459
514
  // With options
460
515
  const result = useAPI(
461
516
  (api) => api.posts.get(),
462
- { enabled: true } // Skip fetching when false
517
+ { enabled: true } // Skip fetching when false
463
518
  );
464
519
 
465
520
  type UseEnlaceQueryResult<TData, TError> = {
466
- loading: boolean; // No cached data and fetching
467
- fetching: boolean; // Request in progress
468
- ok: boolean | undefined;
521
+ loading: boolean; // No cached data and fetching
522
+ fetching: boolean; // Request in progress
469
523
  data: TData | undefined;
470
524
  error: TError | undefined;
471
525
  };
@@ -475,10 +529,9 @@ type UseEnlaceQueryResult<TData, TError> = {
475
529
 
476
530
  ```typescript
477
531
  type UseEnlaceSelectorResult<TMethod> = {
478
- trigger: TMethod; // Function to trigger the request
532
+ trigger: TMethod; // Function to trigger the request
479
533
  loading: boolean;
480
534
  fetching: boolean;
481
- ok: boolean | undefined;
482
535
  data: TData | undefined;
483
536
  error: TError | undefined;
484
537
  };
@@ -488,12 +541,12 @@ type UseEnlaceSelectorResult<TMethod> = {
488
541
 
489
542
  ```typescript
490
543
  type RequestOptions = {
491
- query?: Record<string, unknown>; // Query parameters
492
- body?: TBody; // Request body
493
- headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>); // Request headers
494
- tags?: string[]; // Cache tags (GET only)
495
- revalidateTags?: string[]; // Tags to invalidate after mutation
496
- pathParams?: Record<string, string | number>; // Dynamic path parameters
544
+ query?: Record<string, unknown>; // Query parameters
545
+ body?: TBody; // Request body
546
+ headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>); // Request headers
547
+ tags?: string[]; // Cache tags (GET only)
548
+ revalidateTags?: string[]; // Tags to invalidate after mutation
549
+ pathParams?: Record<string, string | number>; // Dynamic path parameters
497
550
  };
498
551
  ```
499
552
 
@@ -508,7 +561,9 @@ Use `createEnlaceNext` from `enlace` for server components:
508
561
  ```typescript
509
562
  import { createEnlaceNext } from "enlace";
510
563
 
511
- const api = createEnlaceNext<ApiSchema>("https://api.example.com", {}, {
564
+ type ApiError = { message: string };
565
+
566
+ const api = createEnlaceNext<ApiSchema, ApiError>("https://api.example.com", {}, {
512
567
  autoGenerateTags: true,
513
568
  });
514
569
 
@@ -530,7 +585,11 @@ Use `createEnlaceHookNext` from `enlace/hook` for client components:
530
585
 
531
586
  import { createEnlaceHookNext } from "enlace/hook";
532
587
 
533
- const useAPI = createEnlaceHookNext<ApiSchema>("https://api.example.com");
588
+ type ApiError = { message: string };
589
+
590
+ const useAPI = createEnlaceHookNext<ApiSchema, ApiError>(
591
+ "https://api.example.com"
592
+ );
534
593
  ```
535
594
 
536
595
  ### Server-Side Revalidation
@@ -558,9 +617,15 @@ export async function revalidateAction(tags: string[], paths: string[]) {
558
617
  import { createEnlaceHookNext } from "enlace/hook";
559
618
  import { revalidateAction } from "./actions";
560
619
 
561
- const useAPI = createEnlaceHookNext<ApiSchema>("/api", {}, {
562
- revalidator: revalidateAction,
563
- });
620
+ type ApiError = { message: string };
621
+
622
+ const useAPI = createEnlaceHookNext<ApiSchema, ApiError>(
623
+ "/api",
624
+ {},
625
+ {
626
+ revalidator: revalidateAction,
627
+ }
628
+ );
564
629
  ```
565
630
 
566
631
  **In components:**
@@ -572,8 +637,8 @@ function CreatePost() {
572
637
  const handleCreate = () => {
573
638
  trigger({
574
639
  body: { title: "New Post" },
575
- revalidateTags: ["posts"], // Passed to revalidator
576
- revalidatePaths: ["/posts"], // Passed to revalidator
640
+ revalidateTags: ["posts"], // Passed to revalidator
641
+ revalidatePaths: ["/posts"], // Passed to revalidator
577
642
  });
578
643
  };
579
644
  }
@@ -583,11 +648,11 @@ function CreatePost() {
583
648
 
584
649
  ```typescript
585
650
  api.posts.get({
586
- tags: ["posts"], // Next.js cache tags
587
- revalidate: 60, // ISR revalidation (seconds)
651
+ tags: ["posts"], // Next.js cache tags
652
+ revalidate: 60, // ISR revalidation (seconds)
588
653
  revalidateTags: ["posts"], // Tags to invalidate after mutation
589
- revalidatePaths: ["/"], // Paths to revalidate after mutation
590
- skipRevalidator: false, // Skip server-side revalidation
654
+ revalidatePaths: ["/"], // Paths to revalidate after mutation
655
+ skipRevalidator: false, // Skip server-side revalidation
591
656
  });
592
657
  ```
593
658
 
@@ -597,32 +662,47 @@ Works with Next.js API routes:
597
662
 
598
663
  ```typescript
599
664
  // Client component calling /api/posts
600
- const useAPI = createEnlaceHookNext<ApiSchema>("/api");
665
+ const useAPI = createEnlaceHookNext<ApiSchema, ApiError>("/api");
601
666
  ```
602
667
 
603
668
  ---
604
669
 
605
670
  ## API Reference
606
671
 
607
- ### `createEnlaceHookReact<TSchema>(baseUrl, options?, hookOptions?)`
672
+ ### `createEnlaceHookReact<TSchema, TDefaultError>(baseUrl, options?, hookOptions?)`
608
673
 
609
674
  Creates a React hook for making API calls.
610
675
 
611
- ### `createEnlaceHookNext<TSchema>(baseUrl, options?, hookOptions?)`
676
+ ### `createEnlaceHookNext<TSchema, TDefaultError>(baseUrl, options?, hookOptions?)`
612
677
 
613
678
  Creates a Next.js hook with server revalidation support.
614
679
 
615
- **Parameters:**
680
+ ### `createEnlace<TSchema, TDefaultError>(baseUrl, options?, callbacks?)`
681
+
682
+ Creates a typed API client (non-hook, for direct calls or server components).
683
+
684
+ ### `createEnlaceNext<TSchema, TDefaultError>(baseUrl, options?, nextOptions?)`
685
+
686
+ Creates a Next.js typed API client with caching support.
687
+
688
+ **Generic Parameters:**
689
+
690
+ - `TSchema` — API schema type defining endpoints
691
+ - `TDefaultError` — Default error type for all endpoints (default: `unknown`)
692
+
693
+ **Function Parameters:**
694
+
616
695
  - `baseUrl` — Base URL for requests
617
696
  - `options` — Default fetch options (headers, cache, etc.)
618
- - `hookOptions` — Hook configuration
697
+ - `hookOptions` / `callbacks` / `nextOptions` Additional configuration
619
698
 
620
699
  **Hook Options:**
700
+
621
701
  ```typescript
622
702
  type EnlaceHookOptions = {
623
- autoGenerateTags?: boolean; // default: true
624
- autoRevalidateTags?: boolean; // default: true
625
- staleTime?: number; // default: 0
703
+ autoGenerateTags?: boolean; // default: true
704
+ autoRevalidateTags?: boolean; // default: true
705
+ staleTime?: number; // default: 0
626
706
  onSuccess?: (payload: EnlaceCallbackPayload<unknown>) => void;
627
707
  onError?: (payload: EnlaceErrorCallbackPayload<unknown>) => void;
628
708
  };
@@ -28,13 +28,12 @@ type UseEnlaceQueryOptions = {
28
28
  */
29
29
  enabled?: boolean;
30
30
  };
31
- type ApiClient<TSchema, TOptions = ReactRequestOptionsBase> = unknown extends TSchema ? WildcardClient<TOptions> : EnlaceClient<TSchema, TOptions>;
32
- type QueryFn<TSchema, TData, TError, TOptions = ReactRequestOptionsBase> = (api: ApiClient<TSchema, TOptions>) => Promise<EnlaceResponse<TData, TError>>;
33
- type SelectorFn<TSchema, TMethod, TOptions = ReactRequestOptionsBase> = (api: ApiClient<TSchema, TOptions>) => TMethod;
31
+ type ApiClient<TSchema, TDefaultError = unknown, TOptions = ReactRequestOptionsBase> = unknown extends TSchema ? WildcardClient<TOptions> : EnlaceClient<TSchema, TDefaultError, TOptions>;
32
+ type QueryFn<TSchema, TData, TError, TDefaultError = unknown, TOptions = ReactRequestOptionsBase> = (api: ApiClient<TSchema, TDefaultError, TOptions>) => Promise<EnlaceResponse<TData, TError>>;
33
+ type SelectorFn<TSchema, TMethod, TDefaultError = unknown, TOptions = ReactRequestOptionsBase> = (api: ApiClient<TSchema, TDefaultError, TOptions>) => TMethod;
34
34
  type HookState = {
35
35
  loading: boolean;
36
36
  fetching: boolean;
37
- ok: boolean | undefined;
38
37
  data: unknown;
39
38
  error: unknown;
40
39
  };
@@ -46,18 +45,12 @@ type TrackedCall = {
46
45
  declare const HTTP_METHODS: readonly ["get", "post", "put", "patch", "delete"];
47
46
  type ExtractData<T> = T extends (...args: any[]) => Promise<EnlaceResponse<infer D, unknown>> ? D : never;
48
47
  type ExtractError<T> = T extends (...args: any[]) => Promise<EnlaceResponse<unknown, infer E>> ? E : never;
49
- /** Discriminated union for hook response state - enables type narrowing on ok check */
48
+ /** Discriminated union for hook response state - enables type narrowing via error check */
50
49
  type HookResponseState<TData, TError> = {
51
- ok: undefined;
52
- data: undefined;
53
- error: undefined;
54
- } | {
55
- ok: true;
56
50
  data: TData;
57
- error: undefined;
51
+ error?: undefined;
58
52
  } | {
59
- ok: false;
60
- data: undefined;
53
+ data?: undefined;
61
54
  error: TError;
62
55
  };
63
56
  /** Result when hook is called with query function (auto-fetch mode) */
@@ -89,9 +82,9 @@ type EnlaceHookOptions = {
89
82
  onError?: (payload: EnlaceErrorCallbackPayload<unknown>) => void;
90
83
  };
91
84
  /** Hook type returned by createEnlaceHookReact */
92
- type EnlaceHook<TSchema> = {
93
- <TMethod extends (...args: any[]) => Promise<EnlaceResponse<unknown, unknown>>>(selector: SelectorFn<TSchema, TMethod>): UseEnlaceSelectorResult<TMethod>;
94
- <TData, TError>(queryFn: QueryFn<TSchema, TData, TError>, options?: UseEnlaceQueryOptions): UseEnlaceQueryResult<TData, TError>;
85
+ type EnlaceHook<TSchema, TDefaultError = unknown> = {
86
+ <TMethod extends (...args: any[]) => Promise<EnlaceResponse<unknown, unknown>>>(selector: SelectorFn<TSchema, TMethod, TDefaultError>): UseEnlaceSelectorResult<TMethod>;
87
+ <TData, TError>(queryFn: QueryFn<TSchema, TData, TError, TDefaultError>, options?: UseEnlaceQueryOptions): UseEnlaceQueryResult<TData, TError>;
95
88
  };
96
89
 
97
90
  /**
@@ -108,7 +101,7 @@ type EnlaceHook<TSchema> = {
108
101
  * const { trigger, loading, data, error } = useAPI((api) => api.posts.delete);
109
102
  * onClick={() => trigger({ body: { id: 1 } })}
110
103
  */
111
- declare function createEnlaceHookReact<TSchema = unknown>(baseUrl: string, defaultOptions?: EnlaceOptions, hookOptions?: EnlaceHookOptions): EnlaceHook<TSchema>;
104
+ declare function createEnlaceHookReact<TSchema = unknown, TDefaultError = unknown>(baseUrl: string, defaultOptions?: EnlaceOptions, hookOptions?: EnlaceHookOptions): EnlaceHook<TSchema, TDefaultError>;
112
105
 
113
106
  /**
114
107
  * Handler function called after successful mutations to trigger server-side revalidation.
@@ -151,12 +144,12 @@ type NextRequestOptionsBase = ReactRequestOptionsBase & {
151
144
  */
152
145
  skipRevalidator?: boolean;
153
146
  };
154
- type NextQueryFn<TSchema, TData, TError> = QueryFn<TSchema, TData, TError, NextRequestOptionsBase>;
155
- type NextSelectorFn<TSchema, TMethod> = SelectorFn<TSchema, TMethod, NextRequestOptionsBase>;
147
+ type NextQueryFn<TSchema, TData, TError, TDefaultError = unknown> = QueryFn<TSchema, TData, TError, TDefaultError, NextRequestOptionsBase>;
148
+ type NextSelectorFn<TSchema, TMethod, TDefaultError = unknown> = SelectorFn<TSchema, TMethod, TDefaultError, NextRequestOptionsBase>;
156
149
  /** Hook type returned by createEnlaceHookNext */
157
- type NextEnlaceHook<TSchema> = {
158
- <TMethod extends (...args: any[]) => Promise<EnlaceResponse<unknown, unknown>>>(selector: NextSelectorFn<TSchema, TMethod>): UseEnlaceSelectorResult<TMethod>;
159
- <TData, TError>(queryFn: NextQueryFn<TSchema, TData, TError>, options?: UseEnlaceQueryOptions): UseEnlaceQueryResult<TData, TError>;
150
+ type NextEnlaceHook<TSchema, TDefaultError = unknown> = {
151
+ <TMethod extends (...args: any[]) => Promise<EnlaceResponse<unknown, unknown>>>(selector: NextSelectorFn<TSchema, TMethod, TDefaultError>): UseEnlaceSelectorResult<TMethod>;
152
+ <TData, TError>(queryFn: NextQueryFn<TSchema, TData, TError, TDefaultError>, options?: UseEnlaceQueryOptions): UseEnlaceQueryResult<TData, TError>;
160
153
  };
161
154
 
162
155
  /**
@@ -175,6 +168,6 @@ type NextEnlaceHook<TSchema> = {
175
168
  * // Selector mode - trigger for mutations
176
169
  * const { trigger } = useAPI((api) => api.posts.delete);
177
170
  */
178
- declare function createEnlaceHookNext<TSchema = unknown>(baseUrl: string, defaultOptions?: EnlaceOptions, hookOptions?: NextHookOptions): NextEnlaceHook<TSchema>;
171
+ declare function createEnlaceHookNext<TSchema = unknown, TDefaultError = unknown>(baseUrl: string, defaultOptions?: EnlaceOptions, hookOptions?: NextHookOptions): NextEnlaceHook<TSchema, TDefaultError>;
179
172
 
180
173
  export { type ApiClient, type EnlaceHook, type EnlaceHookOptions, HTTP_METHODS, type HookState, type NextEnlaceHook, type NextHookOptions, type QueryFn, type ReactRequestOptionsBase, type SelectorFn, type TrackedCall, type UseEnlaceQueryOptions, type UseEnlaceQueryResult, type UseEnlaceSelectorResult, createEnlaceHookNext, createEnlaceHookReact };
@@ -28,13 +28,12 @@ type UseEnlaceQueryOptions = {
28
28
  */
29
29
  enabled?: boolean;
30
30
  };
31
- type ApiClient<TSchema, TOptions = ReactRequestOptionsBase> = unknown extends TSchema ? WildcardClient<TOptions> : EnlaceClient<TSchema, TOptions>;
32
- type QueryFn<TSchema, TData, TError, TOptions = ReactRequestOptionsBase> = (api: ApiClient<TSchema, TOptions>) => Promise<EnlaceResponse<TData, TError>>;
33
- type SelectorFn<TSchema, TMethod, TOptions = ReactRequestOptionsBase> = (api: ApiClient<TSchema, TOptions>) => TMethod;
31
+ type ApiClient<TSchema, TDefaultError = unknown, TOptions = ReactRequestOptionsBase> = unknown extends TSchema ? WildcardClient<TOptions> : EnlaceClient<TSchema, TDefaultError, TOptions>;
32
+ type QueryFn<TSchema, TData, TError, TDefaultError = unknown, TOptions = ReactRequestOptionsBase> = (api: ApiClient<TSchema, TDefaultError, TOptions>) => Promise<EnlaceResponse<TData, TError>>;
33
+ type SelectorFn<TSchema, TMethod, TDefaultError = unknown, TOptions = ReactRequestOptionsBase> = (api: ApiClient<TSchema, TDefaultError, TOptions>) => TMethod;
34
34
  type HookState = {
35
35
  loading: boolean;
36
36
  fetching: boolean;
37
- ok: boolean | undefined;
38
37
  data: unknown;
39
38
  error: unknown;
40
39
  };
@@ -46,18 +45,12 @@ type TrackedCall = {
46
45
  declare const HTTP_METHODS: readonly ["get", "post", "put", "patch", "delete"];
47
46
  type ExtractData<T> = T extends (...args: any[]) => Promise<EnlaceResponse<infer D, unknown>> ? D : never;
48
47
  type ExtractError<T> = T extends (...args: any[]) => Promise<EnlaceResponse<unknown, infer E>> ? E : never;
49
- /** Discriminated union for hook response state - enables type narrowing on ok check */
48
+ /** Discriminated union for hook response state - enables type narrowing via error check */
50
49
  type HookResponseState<TData, TError> = {
51
- ok: undefined;
52
- data: undefined;
53
- error: undefined;
54
- } | {
55
- ok: true;
56
50
  data: TData;
57
- error: undefined;
51
+ error?: undefined;
58
52
  } | {
59
- ok: false;
60
- data: undefined;
53
+ data?: undefined;
61
54
  error: TError;
62
55
  };
63
56
  /** Result when hook is called with query function (auto-fetch mode) */
@@ -89,9 +82,9 @@ type EnlaceHookOptions = {
89
82
  onError?: (payload: EnlaceErrorCallbackPayload<unknown>) => void;
90
83
  };
91
84
  /** Hook type returned by createEnlaceHookReact */
92
- type EnlaceHook<TSchema> = {
93
- <TMethod extends (...args: any[]) => Promise<EnlaceResponse<unknown, unknown>>>(selector: SelectorFn<TSchema, TMethod>): UseEnlaceSelectorResult<TMethod>;
94
- <TData, TError>(queryFn: QueryFn<TSchema, TData, TError>, options?: UseEnlaceQueryOptions): UseEnlaceQueryResult<TData, TError>;
85
+ type EnlaceHook<TSchema, TDefaultError = unknown> = {
86
+ <TMethod extends (...args: any[]) => Promise<EnlaceResponse<unknown, unknown>>>(selector: SelectorFn<TSchema, TMethod, TDefaultError>): UseEnlaceSelectorResult<TMethod>;
87
+ <TData, TError>(queryFn: QueryFn<TSchema, TData, TError, TDefaultError>, options?: UseEnlaceQueryOptions): UseEnlaceQueryResult<TData, TError>;
95
88
  };
96
89
 
97
90
  /**
@@ -108,7 +101,7 @@ type EnlaceHook<TSchema> = {
108
101
  * const { trigger, loading, data, error } = useAPI((api) => api.posts.delete);
109
102
  * onClick={() => trigger({ body: { id: 1 } })}
110
103
  */
111
- declare function createEnlaceHookReact<TSchema = unknown>(baseUrl: string, defaultOptions?: EnlaceOptions, hookOptions?: EnlaceHookOptions): EnlaceHook<TSchema>;
104
+ declare function createEnlaceHookReact<TSchema = unknown, TDefaultError = unknown>(baseUrl: string, defaultOptions?: EnlaceOptions, hookOptions?: EnlaceHookOptions): EnlaceHook<TSchema, TDefaultError>;
112
105
 
113
106
  /**
114
107
  * Handler function called after successful mutations to trigger server-side revalidation.
@@ -151,12 +144,12 @@ type NextRequestOptionsBase = ReactRequestOptionsBase & {
151
144
  */
152
145
  skipRevalidator?: boolean;
153
146
  };
154
- type NextQueryFn<TSchema, TData, TError> = QueryFn<TSchema, TData, TError, NextRequestOptionsBase>;
155
- type NextSelectorFn<TSchema, TMethod> = SelectorFn<TSchema, TMethod, NextRequestOptionsBase>;
147
+ type NextQueryFn<TSchema, TData, TError, TDefaultError = unknown> = QueryFn<TSchema, TData, TError, TDefaultError, NextRequestOptionsBase>;
148
+ type NextSelectorFn<TSchema, TMethod, TDefaultError = unknown> = SelectorFn<TSchema, TMethod, TDefaultError, NextRequestOptionsBase>;
156
149
  /** Hook type returned by createEnlaceHookNext */
157
- type NextEnlaceHook<TSchema> = {
158
- <TMethod extends (...args: any[]) => Promise<EnlaceResponse<unknown, unknown>>>(selector: NextSelectorFn<TSchema, TMethod>): UseEnlaceSelectorResult<TMethod>;
159
- <TData, TError>(queryFn: NextQueryFn<TSchema, TData, TError>, options?: UseEnlaceQueryOptions): UseEnlaceQueryResult<TData, TError>;
150
+ type NextEnlaceHook<TSchema, TDefaultError = unknown> = {
151
+ <TMethod extends (...args: any[]) => Promise<EnlaceResponse<unknown, unknown>>>(selector: NextSelectorFn<TSchema, TMethod, TDefaultError>): UseEnlaceSelectorResult<TMethod>;
152
+ <TData, TError>(queryFn: NextQueryFn<TSchema, TData, TError, TDefaultError>, options?: UseEnlaceQueryOptions): UseEnlaceQueryResult<TData, TError>;
160
153
  };
161
154
 
162
155
  /**
@@ -175,6 +168,6 @@ type NextEnlaceHook<TSchema> = {
175
168
  * // Selector mode - trigger for mutations
176
169
  * const { trigger } = useAPI((api) => api.posts.delete);
177
170
  */
178
- declare function createEnlaceHookNext<TSchema = unknown>(baseUrl: string, defaultOptions?: EnlaceOptions, hookOptions?: NextHookOptions): NextEnlaceHook<TSchema>;
171
+ declare function createEnlaceHookNext<TSchema = unknown, TDefaultError = unknown>(baseUrl: string, defaultOptions?: EnlaceOptions, hookOptions?: NextHookOptions): NextEnlaceHook<TSchema, TDefaultError>;
179
172
 
180
173
  export { type ApiClient, type EnlaceHook, type EnlaceHookOptions, HTTP_METHODS, type HookState, type NextEnlaceHook, type NextHookOptions, type QueryFn, type ReactRequestOptionsBase, type SelectorFn, type TrackedCall, type UseEnlaceQueryOptions, type UseEnlaceQueryResult, type UseEnlaceSelectorResult, createEnlaceHookNext, createEnlaceHookReact };
@@ -38,7 +38,6 @@ var import_react = require("react");
38
38
  var initialState = {
39
39
  loading: false,
40
40
  fetching: false,
41
- ok: void 0,
42
41
  data: void 0,
43
42
  error: void 0
44
43
  };
@@ -56,7 +55,6 @@ function hookReducer(state, action) {
56
55
  return {
57
56
  loading: false,
58
57
  fetching: false,
59
- ok: true,
60
58
  data: action.data,
61
59
  error: void 0
62
60
  };
@@ -64,7 +62,6 @@ function hookReducer(state, action) {
64
62
  return {
65
63
  loading: false,
66
64
  fetching: false,
67
- ok: false,
68
65
  data: void 0,
69
66
  error: action.error
70
67
  };
@@ -110,7 +107,7 @@ function getCache(key) {
110
107
  function setCache(key, entry) {
111
108
  const existing = cache.get(key);
112
109
  if (existing) {
113
- if ("ok" in entry) {
110
+ if ("data" in entry || "error" in entry) {
114
111
  delete existing.promise;
115
112
  }
116
113
  Object.assign(existing, entry);
@@ -119,7 +116,6 @@ function setCache(key, entry) {
119
116
  cache.set(key, {
120
117
  data: void 0,
121
118
  error: void 0,
122
- ok: void 0,
123
119
  timestamp: 0,
124
120
  tags: [],
125
121
  subscribers: /* @__PURE__ */ new Set(),
@@ -133,7 +129,6 @@ function subscribeCache(key, callback) {
133
129
  cache.set(key, {
134
130
  data: void 0,
135
131
  error: void 0,
136
- ok: void 0,
137
132
  timestamp: 0,
138
133
  tags: [],
139
134
  subscribers: /* @__PURE__ */ new Set()
@@ -156,7 +151,6 @@ function clearCacheByTags(tags) {
156
151
  if (hasMatch) {
157
152
  entry.data = void 0;
158
153
  entry.error = void 0;
159
- entry.ok = void 0;
160
154
  entry.timestamp = 0;
161
155
  delete entry.promise;
162
156
  }
@@ -206,7 +200,6 @@ function useQueryMode(api, trackedCall, options) {
206
200
  return {
207
201
  loading: !hasCachedData && (isFetching || needsFetch),
208
202
  fetching: isFetching || needsFetch,
209
- ok: cached?.ok,
210
203
  data: cached?.data,
211
204
  error: cached?.error
212
205
  };
@@ -241,9 +234,8 @@ function useQueryMode(api, trackedCall, options) {
241
234
  const fetchPromise = method(trackedCall.options).then((res) => {
242
235
  if (mountedRef.current) {
243
236
  setCache(queryKey, {
244
- data: res.ok ? res.data : void 0,
245
- error: res.ok || res.status === 0 ? void 0 : res.error,
246
- ok: res.ok,
237
+ data: res.error ? void 0 : res.data,
238
+ error: res.error,
247
239
  timestamp: Date.now(),
248
240
  tags: queryTags
249
241
  });
@@ -300,7 +292,7 @@ function createTrackingProxy(onTrack) {
300
292
  selectorPath: null,
301
293
  selectorMethod: null
302
294
  });
303
- return Promise.resolve({ ok: true, data: void 0 });
295
+ return Promise.resolve({ status: 200, data: void 0, error: void 0 });
304
296
  };
305
297
  onTrack({
306
298
  trackedCall: null,
@@ -365,7 +357,7 @@ function useSelectorMode(config) {
365
357
  } else {
366
358
  res = await methodRef.current(...args);
367
359
  }
368
- if (res.ok) {
360
+ if (!res.error) {
369
361
  dispatch({ type: "FETCH_SUCCESS", data: res.data });
370
362
  const tagsToInvalidate = options?.revalidateTags ?? (autoRevalidateRef.current ? generateTags(resolvedPath) : []);
371
363
  if (tagsToInvalidate.length > 0) {
@@ -11,7 +11,6 @@ import { useRef, useReducer, useEffect } from "react";
11
11
  var initialState = {
12
12
  loading: false,
13
13
  fetching: false,
14
- ok: void 0,
15
14
  data: void 0,
16
15
  error: void 0
17
16
  };
@@ -29,7 +28,6 @@ function hookReducer(state, action) {
29
28
  return {
30
29
  loading: false,
31
30
  fetching: false,
32
- ok: true,
33
31
  data: action.data,
34
32
  error: void 0
35
33
  };
@@ -37,7 +35,6 @@ function hookReducer(state, action) {
37
35
  return {
38
36
  loading: false,
39
37
  fetching: false,
40
- ok: false,
41
38
  data: void 0,
42
39
  error: action.error
43
40
  };
@@ -83,7 +80,7 @@ function getCache(key) {
83
80
  function setCache(key, entry) {
84
81
  const existing = cache.get(key);
85
82
  if (existing) {
86
- if ("ok" in entry) {
83
+ if ("data" in entry || "error" in entry) {
87
84
  delete existing.promise;
88
85
  }
89
86
  Object.assign(existing, entry);
@@ -92,7 +89,6 @@ function setCache(key, entry) {
92
89
  cache.set(key, {
93
90
  data: void 0,
94
91
  error: void 0,
95
- ok: void 0,
96
92
  timestamp: 0,
97
93
  tags: [],
98
94
  subscribers: /* @__PURE__ */ new Set(),
@@ -106,7 +102,6 @@ function subscribeCache(key, callback) {
106
102
  cache.set(key, {
107
103
  data: void 0,
108
104
  error: void 0,
109
- ok: void 0,
110
105
  timestamp: 0,
111
106
  tags: [],
112
107
  subscribers: /* @__PURE__ */ new Set()
@@ -129,7 +124,6 @@ function clearCacheByTags(tags) {
129
124
  if (hasMatch) {
130
125
  entry.data = void 0;
131
126
  entry.error = void 0;
132
- entry.ok = void 0;
133
127
  entry.timestamp = 0;
134
128
  delete entry.promise;
135
129
  }
@@ -179,7 +173,6 @@ function useQueryMode(api, trackedCall, options) {
179
173
  return {
180
174
  loading: !hasCachedData && (isFetching || needsFetch),
181
175
  fetching: isFetching || needsFetch,
182
- ok: cached?.ok,
183
176
  data: cached?.data,
184
177
  error: cached?.error
185
178
  };
@@ -214,9 +207,8 @@ function useQueryMode(api, trackedCall, options) {
214
207
  const fetchPromise = method(trackedCall.options).then((res) => {
215
208
  if (mountedRef.current) {
216
209
  setCache(queryKey, {
217
- data: res.ok ? res.data : void 0,
218
- error: res.ok || res.status === 0 ? void 0 : res.error,
219
- ok: res.ok,
210
+ data: res.error ? void 0 : res.data,
211
+ error: res.error,
220
212
  timestamp: Date.now(),
221
213
  tags: queryTags
222
214
  });
@@ -273,7 +265,7 @@ function createTrackingProxy(onTrack) {
273
265
  selectorPath: null,
274
266
  selectorMethod: null
275
267
  });
276
- return Promise.resolve({ ok: true, data: void 0 });
268
+ return Promise.resolve({ status: 200, data: void 0, error: void 0 });
277
269
  };
278
270
  onTrack({
279
271
  trackedCall: null,
@@ -338,7 +330,7 @@ function useSelectorMode(config) {
338
330
  } else {
339
331
  res = await methodRef.current(...args);
340
332
  }
341
- if (res.ok) {
333
+ if (!res.error) {
342
334
  dispatch({ type: "FETCH_SUCCESS", data: res.data });
343
335
  const tagsToInvalidate = options?.revalidateTags ?? (autoRevalidateRef.current ? generateTags(resolvedPath) : []);
344
336
  if (tagsToInvalidate.length > 0) {
package/dist/index.d.mts CHANGED
@@ -78,6 +78,6 @@ type NextRequestOptionsBase = ReactRequestOptionsBase & {
78
78
  skipRevalidator?: boolean;
79
79
  };
80
80
 
81
- declare function createEnlaceNext<TSchema = unknown>(baseUrl: string, defaultOptions?: EnlaceOptions | null, nextOptions?: NextOptions): unknown extends TSchema ? WildcardClient<NextRequestOptionsBase> : EnlaceClient<TSchema, NextRequestOptionsBase>;
81
+ declare function createEnlaceNext<TSchema = unknown, TDefaultError = unknown>(baseUrl: string, defaultOptions?: EnlaceOptions | null, nextOptions?: NextOptions): unknown extends TSchema ? WildcardClient<NextRequestOptionsBase> : EnlaceClient<TSchema, TDefaultError, NextRequestOptionsBase>;
82
82
 
83
83
  export { type NextOptions, type NextRequestOptionsBase, createEnlaceNext };
package/dist/index.d.ts CHANGED
@@ -78,6 +78,6 @@ type NextRequestOptionsBase = ReactRequestOptionsBase & {
78
78
  skipRevalidator?: boolean;
79
79
  };
80
80
 
81
- declare function createEnlaceNext<TSchema = unknown>(baseUrl: string, defaultOptions?: EnlaceOptions | null, nextOptions?: NextOptions): unknown extends TSchema ? WildcardClient<NextRequestOptionsBase> : EnlaceClient<TSchema, NextRequestOptionsBase>;
81
+ declare function createEnlaceNext<TSchema = unknown, TDefaultError = unknown>(baseUrl: string, defaultOptions?: EnlaceOptions | null, nextOptions?: NextOptions): unknown extends TSchema ? WildcardClient<NextRequestOptionsBase> : EnlaceClient<TSchema, TDefaultError, NextRequestOptionsBase>;
82
82
 
83
83
  export { type NextOptions, type NextRequestOptionsBase, createEnlaceNext };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "enlace",
3
- "version": "0.0.1-beta.7",
3
+ "version": "0.0.1-beta.9",
4
4
  "license": "MIT",
5
5
  "files": [
6
6
  "dist"
@@ -18,7 +18,7 @@
18
18
  }
19
19
  },
20
20
  "dependencies": {
21
- "enlace-core": "0.0.1-beta.5"
21
+ "enlace-core": "0.0.1-beta.7"
22
22
  },
23
23
  "peerDependencies": {
24
24
  "react": "^19"