enlace 0.0.0-alpha.4 → 0.0.1-beta.2

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Enlace
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,417 @@
1
+ # enlace
2
+
3
+ Type-safe API client with React hooks and Next.js integration.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install enlace
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import { createEnlaceHook, Endpoint } from "enlace";
15
+
16
+ type ApiSchema = {
17
+ posts: {
18
+ $get: Endpoint<Post[]>;
19
+ $post: Endpoint<Post, ApiError, CreatePost>;
20
+ _: {
21
+ $get: Endpoint<Post>;
22
+ $delete: Endpoint<void>;
23
+ };
24
+ };
25
+ };
26
+
27
+ const useAPI = createEnlaceHook<ApiSchema>("https://api.example.com");
28
+ ```
29
+
30
+ ## Schema Conventions
31
+
32
+ Defining a schema is **recommended** for full type safety, but **optional**. You can go without types:
33
+
34
+ ```typescript
35
+ // Without schema (untyped, but still works!)
36
+ const useAPI = createEnlaceHook("https://api.example.com");
37
+ const { data } = useAPI((api) => api.any.path.you.want.get());
38
+ ```
39
+
40
+ ```typescript
41
+ // With schema (recommended for type safety)
42
+ const useAPI = createEnlaceHook<ApiSchema>("https://api.example.com");
43
+ ```
44
+
45
+ ### Schema Structure
46
+
47
+ - `$get`, `$post`, `$put`, `$patch`, `$delete` — HTTP method endpoints
48
+ - `_` — Dynamic path segment (e.g., `/users/:id`)
49
+
50
+ ```typescript
51
+ import { Endpoint } from "enlace";
52
+
53
+ type ApiSchema = {
54
+ users: {
55
+ $get: Endpoint<User[]>; // GET /users
56
+ $post: Endpoint<User>; // POST /users
57
+ _: { // /users/:id
58
+ $get: Endpoint<User>; // GET /users/:id
59
+ $put: Endpoint<User>; // PUT /users/:id
60
+ $delete: Endpoint<void>; // DELETE /users/:id
61
+ profile: {
62
+ $get: Endpoint<Profile>; // GET /users/:id/profile
63
+ };
64
+ };
65
+ };
66
+ };
67
+
68
+ // Usage
69
+ api.users.get(); // GET /users
70
+ api.users[123].get(); // GET /users/123
71
+ api.users[123].profile.get(); // GET /users/123/profile
72
+ ```
73
+
74
+ ### Endpoint Type
75
+
76
+ ```typescript
77
+ type Endpoint<TData, TError = unknown, TBody = never> = {
78
+ data: TData; // Response data type
79
+ error: TError; // Error response type
80
+ body: TBody; // Request body type
81
+ };
82
+
83
+ // Examples
84
+ type GetUsers = Endpoint<User[]>; // GET, no body
85
+ type CreateUser = Endpoint<User, ApiError, CreateUserInput>; // POST with body
86
+ type DeleteUser = Endpoint<void, NotFoundError>; // DELETE, no response data
87
+ ```
88
+
89
+ ## React Hooks
90
+
91
+ ### Query Mode (Auto-Fetch)
92
+
93
+ For GET requests that fetch data automatically:
94
+
95
+ ```typescript
96
+ function Posts({ page, limit }: { page: number; limit: number }) {
97
+ const { data, loading, error, ok } = useAPI((api) =>
98
+ api.posts.get({ query: { page, limit, published: true } })
99
+ );
100
+
101
+ if (loading) return <div>Loading...</div>;
102
+ if (!ok) return <div>Error: {error.message}</div>;
103
+
104
+ return (
105
+ <ul>
106
+ {data.map((post) => (
107
+ <li key={post.id}>{post.title}</li>
108
+ ))}
109
+ </ul>
110
+ );
111
+ }
112
+ ```
113
+
114
+ **Features:**
115
+ - Auto-fetches on mount
116
+ - Re-fetches when dependencies change (no deps array needed!)
117
+ - Returns cached data while revalidating
118
+
119
+ ```typescript
120
+ function Post({ id }: { id: number }) {
121
+ // Automatically re-fetches when `id` or query values change
122
+ const { data } = useAPI((api) => api.posts[id].get({ query: { include: "author" } }));
123
+ return <div>{data?.title}</div>;
124
+ }
125
+ ```
126
+
127
+ ### Selector Mode (Manual Trigger)
128
+
129
+ For mutations or lazy-loaded requests:
130
+
131
+ ```typescript
132
+ function DeleteButton({ id }: { id: number }) {
133
+ const { trigger, loading } = useAPI((api) => api.posts[id].delete);
134
+
135
+ return (
136
+ <button onClick={() => trigger()} disabled={loading}>
137
+ {loading ? "Deleting..." : "Delete"}
138
+ </button>
139
+ );
140
+ }
141
+ ```
142
+
143
+ **With request body:**
144
+
145
+ ```typescript
146
+ function CreatePost() {
147
+ const { trigger, loading, data } = useAPI((api) => api.posts.post);
148
+
149
+ const handleSubmit = async (title: string) => {
150
+ const result = await trigger({ body: { title } });
151
+ if (result.ok) {
152
+ console.log("Created:", result.data);
153
+ }
154
+ };
155
+
156
+ return <button onClick={() => handleSubmit("New Post")}>Create</button>;
157
+ }
158
+ ```
159
+
160
+ ## Caching & Auto-Revalidation
161
+
162
+ ### Automatic Cache Tags (Zero Config)
163
+
164
+ **Tags are automatically generated from URL paths** — no manual configuration needed:
165
+
166
+ ```typescript
167
+ // GET /posts → tags: ['posts']
168
+ // GET /posts/123 → tags: ['posts', 'posts/123']
169
+ // GET /users/5/posts → tags: ['users', 'users/5', 'users/5/posts']
170
+ ```
171
+
172
+ **Mutations automatically revalidate matching tags:**
173
+
174
+ ```typescript
175
+ const { trigger } = useAPI((api) => api.posts.post);
176
+
177
+ // POST /posts automatically revalidates 'posts' tag
178
+ // All queries with 'posts' tag will refetch!
179
+ trigger({ body: { title: "New Post" } });
180
+ ```
181
+
182
+ This means in most cases, **you don't need to specify any tags manually**. The cache just works.
183
+
184
+ ### How It Works
185
+
186
+ 1. **Queries** automatically cache with tags derived from the URL
187
+ 2. **Mutations** automatically revalidate tags derived from the URL
188
+ 3. All queries matching those tags refetch automatically
189
+
190
+ ```typescript
191
+ // Component A: fetches posts (cached with tag 'posts')
192
+ const { data } = useAPI((api) => api.posts.get());
193
+
194
+ // Component B: creates a post
195
+ const { trigger } = useAPI((api) => api.posts.post);
196
+ trigger({ body: { title: "New" } });
197
+ // → Automatically revalidates 'posts' tag
198
+ // → Component A refetches automatically!
199
+ ```
200
+
201
+ ### Stale Time
202
+
203
+ Control how long cached data is considered fresh:
204
+
205
+ ```typescript
206
+ const useAPI = createEnlaceHook<ApiSchema>("https://api.example.com", {}, {
207
+ staleTime: 5000, // 5 seconds
208
+ });
209
+ ```
210
+
211
+ - `staleTime: 0` (default) — Always revalidate on mount
212
+ - `staleTime: 5000` — Data is fresh for 5 seconds
213
+ - `staleTime: Infinity` — Never revalidate automatically
214
+
215
+ ### Manual Tag Override (Optional)
216
+
217
+ Override auto-generated tags when needed:
218
+
219
+ ```typescript
220
+ // Custom cache tags
221
+ const { data } = useAPI((api) => api.posts.get({ tags: ["my-custom-tag"] }));
222
+
223
+ // Custom revalidation tags
224
+ trigger({
225
+ body: { title: "New" },
226
+ revalidateTags: ["posts", "dashboard"], // Override auto-generated
227
+ });
228
+ ```
229
+
230
+ ### Disable Auto-Revalidation
231
+
232
+ ```typescript
233
+ const useAPI = createEnlaceHook<ApiSchema>("https://api.example.com", {}, {
234
+ autoGenerateTags: false, // Disable auto tag generation
235
+ autoRevalidateTags: false, // Disable auto revalidation
236
+ });
237
+ ```
238
+
239
+ ## Hook Options
240
+
241
+ ```typescript
242
+ const useAPI = createEnlaceHook<ApiSchema>(
243
+ "https://api.example.com",
244
+ {
245
+ // Default fetch options
246
+ headers: { Authorization: "Bearer token" },
247
+ },
248
+ {
249
+ // Hook options
250
+ autoGenerateTags: true, // Auto-generate cache tags from URL
251
+ autoRevalidateTags: true, // Auto-revalidate after mutations
252
+ staleTime: 0, // Cache freshness duration (ms)
253
+ }
254
+ );
255
+ ```
256
+
257
+ ## Return Types
258
+
259
+ ### Query Mode
260
+
261
+ ```typescript
262
+ type UseEnlaceQueryResult<TData, TError> = {
263
+ loading: boolean; // No cached data and fetching
264
+ fetching: boolean; // Request in progress
265
+ ok: boolean | undefined;
266
+ data: TData | undefined;
267
+ error: TError | undefined;
268
+ };
269
+ ```
270
+
271
+ ### Selector Mode
272
+
273
+ ```typescript
274
+ type UseEnlaceSelectorResult<TMethod> = {
275
+ trigger: TMethod; // Function to trigger the request
276
+ loading: boolean;
277
+ fetching: boolean;
278
+ ok: boolean | undefined;
279
+ data: TData | undefined;
280
+ error: TError | undefined;
281
+ };
282
+ ```
283
+
284
+ ---
285
+
286
+ ## Next.js Integration
287
+
288
+ ### Server Components
289
+
290
+ Use `createEnlace` from `enlace/next` for server components:
291
+
292
+ ```typescript
293
+ import { createEnlace } from "enlace/next";
294
+
295
+ const api = createEnlace<ApiSchema>("https://api.example.com", {}, {
296
+ autoGenerateTags: true,
297
+ });
298
+
299
+ export default async function Page() {
300
+ const { data } = await api.posts.get({
301
+ revalidate: 60, // ISR: revalidate every 60 seconds
302
+ });
303
+
304
+ return <PostList posts={data} />;
305
+ }
306
+ ```
307
+
308
+ ### Client Components
309
+
310
+ Use `createEnlaceHook` from `enlace/next/hook` for client components:
311
+
312
+ ```typescript
313
+ "use client";
314
+
315
+ import { createEnlaceHook } from "enlace/next/hook";
316
+
317
+ const useAPI = createEnlaceHook<ApiSchema>("https://api.example.com");
318
+ ```
319
+
320
+ ### Server-Side Revalidation
321
+
322
+ Trigger Next.js cache revalidation after mutations:
323
+
324
+ ```typescript
325
+ // actions.ts
326
+ "use server";
327
+
328
+ import { revalidateTag, revalidatePath } from "next/cache";
329
+
330
+ export async function revalidateAction(tags: string[], paths: string[]) {
331
+ for (const tag of tags) {
332
+ revalidateTag(tag);
333
+ }
334
+ for (const path of paths) {
335
+ revalidatePath(path);
336
+ }
337
+ }
338
+ ```
339
+
340
+ ```typescript
341
+ // useAPI.ts
342
+ import { createEnlaceHook } from "enlace/next/hook";
343
+ import { revalidateAction } from "./actions";
344
+
345
+ const useAPI = createEnlaceHook<ApiSchema>("/api", {}, {
346
+ revalidator: revalidateAction,
347
+ });
348
+ ```
349
+
350
+ **In components:**
351
+
352
+ ```typescript
353
+ function CreatePost() {
354
+ const { trigger } = useAPI((api) => api.posts.post);
355
+
356
+ const handleCreate = () => {
357
+ trigger({
358
+ body: { title: "New Post" },
359
+ revalidateTags: ["posts"], // Passed to revalidator
360
+ revalidatePaths: ["/posts"], // Passed to revalidator
361
+ });
362
+ };
363
+ }
364
+ ```
365
+
366
+ ### Next.js Request Options
367
+
368
+ ```typescript
369
+ api.posts.get({
370
+ tags: ["posts"], // Next.js cache tags
371
+ revalidate: 60, // ISR revalidation (seconds)
372
+ revalidateTags: ["posts"], // Tags to invalidate after mutation
373
+ revalidatePaths: ["/"], // Paths to revalidate after mutation
374
+ skipRevalidator: false, // Skip server-side revalidation
375
+ });
376
+ ```
377
+
378
+ ### Relative URLs
379
+
380
+ Works with Next.js API routes:
381
+
382
+ ```typescript
383
+ // Client component calling /api/posts
384
+ const useAPI = createEnlaceHook<ApiSchema>("/api");
385
+ ```
386
+
387
+ ---
388
+
389
+ ## API Reference
390
+
391
+ ### `createEnlaceHook<TSchema>(baseUrl, options?, hookOptions?)`
392
+
393
+ Creates a React hook for making API calls.
394
+
395
+ **Parameters:**
396
+ - `baseUrl` — Base URL for requests
397
+ - `options` — Default fetch options (headers, cache, etc.)
398
+ - `hookOptions` — Hook configuration
399
+
400
+ **Hook Options:**
401
+ ```typescript
402
+ type EnlaceHookOptions = {
403
+ autoGenerateTags?: boolean; // default: true
404
+ autoRevalidateTags?: boolean; // default: true
405
+ staleTime?: number; // default: 0
406
+ };
407
+ ```
408
+
409
+ ### Re-exports from enlace-core
410
+
411
+ - `Endpoint` — Type helper for schema definition
412
+ - `EnlaceResponse` — Response type
413
+ - `EnlaceOptions` — Fetch options type
414
+
415
+ ## License
416
+
417
+ MIT
package/dist/index.d.mts CHANGED
@@ -1,121 +1,97 @@
1
- type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
2
- type SchemaMethod = "$get" | "$post" | "$put" | "$patch" | "$delete";
3
- type EnlaceResponse<TData, TError> = {
4
- ok: true;
5
- status: number;
6
- data: TData;
7
- error?: never;
8
- } | {
9
- ok: false;
10
- status: number;
11
- data?: never;
12
- error: TError;
13
- };
14
- type EnlaceOptions = Omit<RequestInit, "method" | "body">;
15
- type FetchExecutor<TOptions = EnlaceOptions, TRequestOptions = RequestOptions<unknown>> = <TData, TError>(baseUrl: string, path: string[], method: HttpMethod, defaultOptions: TOptions, requestOptions?: TRequestOptions) => Promise<EnlaceResponse<TData, TError>>;
16
- type RequestOptions<TBody = never> = {
17
- body?: TBody;
18
- query?: Record<string, string | number | boolean | undefined>;
19
- headers?: HeadersInit;
20
- cache?: RequestCache;
1
+ import { WildcardClient, EnlaceClient, EnlaceResponse, EnlaceOptions } from 'enlace-core';
2
+ export * from 'enlace-core';
3
+
4
+ /** Per-request options for React hooks */
5
+ type ReactRequestOptionsBase = {
6
+ /**
7
+ * Cache tags for caching (GET requests only)
8
+ * This will auto generate tags from the URL path if not provided and autoGenerateTags is enabled.
9
+ * But can be manually specified to override auto-generation.
10
+ * */
11
+ tags?: string[];
12
+ /** Tags to invalidate after mutation (triggers refetch in matching queries) */
13
+ revalidateTags?: string[];
21
14
  };
22
- type MethodDefinition = {
15
+ type ApiClient<TSchema, TOptions = ReactRequestOptionsBase> = unknown extends TSchema ? WildcardClient<TOptions> : EnlaceClient<TSchema, TOptions>;
16
+ type QueryFn<TSchema, TData, TError, TOptions = ReactRequestOptionsBase> = (api: ApiClient<TSchema, TOptions>) => Promise<EnlaceResponse<TData, TError>>;
17
+ type SelectorFn<TSchema, TMethod, TOptions = ReactRequestOptionsBase> = (api: ApiClient<TSchema, TOptions>) => TMethod;
18
+ type HookState = {
19
+ loading: boolean;
20
+ fetching: boolean;
21
+ ok: boolean | undefined;
23
22
  data: unknown;
24
23
  error: unknown;
25
- body?: unknown;
26
24
  };
27
- /**
28
- * Helper to define an endpoint with proper typing.
29
- * Provides cleaner syntax than writing { data, error, body } manually.
30
- *
31
- * @example
32
- * type MyApi = {
33
- * posts: {
34
- * $get: Endpoint<Post[], ApiError>;
35
- * $post: Endpoint<Post, ApiError, CreatePost>; // with body
36
- * _: {
37
- * $get: Endpoint<Post, NotFoundError>;
38
- * };
39
- * };
40
- * };
41
- */
42
- type Endpoint<TData, TError, TBody = never> = [TBody] extends [never] ? {
43
- data: TData;
44
- error: TError;
45
- } : {
25
+ type TrackedCall = {
26
+ path: string[];
27
+ method: string;
28
+ options: unknown;
29
+ };
30
+ declare const HTTP_METHODS: readonly ["get", "post", "put", "patch", "delete"];
31
+ type ExtractData<T> = T extends (...args: any[]) => Promise<EnlaceResponse<infer D, unknown>> ? D : never;
32
+ type ExtractError<T> = T extends (...args: any[]) => Promise<EnlaceResponse<unknown, infer E>> ? E : never;
33
+ /** Discriminated union for hook response state - enables type narrowing on ok check */
34
+ type HookResponseState<TData, TError> = {
35
+ ok: undefined;
36
+ data: undefined;
37
+ error: undefined;
38
+ } | {
39
+ ok: true;
46
40
  data: TData;
41
+ error: undefined;
42
+ } | {
43
+ ok: false;
44
+ data: undefined;
47
45
  error: TError;
48
- body: TBody;
49
- };
50
- type ExtractMethodDef<TSchema, TMethod extends SchemaMethod> = TSchema extends {
51
- [K in TMethod]: infer M;
52
- } ? M extends MethodDefinition ? M : never : never;
53
- type ExtractData<TSchema, TMethod extends SchemaMethod> = ExtractMethodDef<TSchema, TMethod> extends {
54
- data: infer D;
55
- } ? D : never;
56
- type ExtractError<TSchema, TMethod extends SchemaMethod> = ExtractMethodDef<TSchema, TMethod> extends {
57
- error: infer E;
58
- } ? E : never;
59
- type ExtractBody<TSchema, TMethod extends SchemaMethod> = ExtractMethodDef<TSchema, TMethod> extends {
60
- body: infer B;
61
- } ? B : never;
62
- type HasMethod<TSchema, TMethod extends SchemaMethod> = TSchema extends {
63
- [K in TMethod]: MethodDefinition;
64
- } ? true : false;
65
- type MethodFn<TSchema, TMethod extends SchemaMethod, TRequestOptionsBase = object> = HasMethod<TSchema, TMethod> extends true ? ExtractBody<TSchema, TMethod> extends never ? (options?: RequestOptions<never> & TRequestOptionsBase) => Promise<EnlaceResponse<ExtractData<TSchema, TMethod>, ExtractError<TSchema, TMethod>>> : (options: RequestOptions<ExtractBody<TSchema, TMethod>> & TRequestOptionsBase) => Promise<EnlaceResponse<ExtractData<TSchema, TMethod>, ExtractError<TSchema, TMethod>>> : never;
66
- type IsSpecialKey<K> = K extends SchemaMethod | "_" ? true : false;
67
- type StaticPathKeys<TSchema> = {
68
- [K in keyof TSchema as IsSpecialKey<K> extends true ? never : K extends string ? K : never]: TSchema[K];
69
46
  };
70
- type ExtractDynamicSchema<TSchema> = TSchema extends {
71
- _: infer D;
72
- } ? D : never;
73
- type MethodOrPath<TSchema, TMethodName extends string, TSchemaMethod extends SchemaMethod, TRequestOptionsBase = object> = TMethodName extends keyof TSchema ? EnlaceClient<TSchema[TMethodName], TRequestOptionsBase> : MethodFn<TSchema, TSchemaMethod, TRequestOptionsBase>;
74
- type HttpMethods<TSchema, TRequestOptionsBase = object> = {
75
- get: MethodOrPath<TSchema, "get", "$get", TRequestOptionsBase>;
76
- post: MethodOrPath<TSchema, "post", "$post", TRequestOptionsBase>;
77
- put: MethodOrPath<TSchema, "put", "$put", TRequestOptionsBase>;
78
- patch: MethodOrPath<TSchema, "patch", "$patch", TRequestOptionsBase>;
79
- delete: MethodOrPath<TSchema, "delete", "$delete", TRequestOptionsBase>;
80
- };
81
- type DynamicAccess<TSchema, TRequestOptionsBase = object> = ExtractDynamicSchema<TSchema> extends never ? object : {
82
- [key: string]: EnlaceClient<ExtractDynamicSchema<TSchema>, TRequestOptionsBase>;
83
- [key: number]: EnlaceClient<ExtractDynamicSchema<TSchema>, TRequestOptionsBase>;
47
+ /** Result when hook is called with query function (auto-fetch mode) */
48
+ type UseEnlaceQueryResult<TData, TError> = {
49
+ loading: boolean;
50
+ fetching: boolean;
51
+ } & HookResponseState<TData, TError>;
52
+ /** Result when hook is called with method selector (trigger mode) */
53
+ type UseEnlaceSelectorResult<TMethod> = {
54
+ trigger: TMethod;
55
+ loading: boolean;
56
+ fetching: boolean;
57
+ } & HookResponseState<ExtractData<TMethod>, ExtractError<TMethod>>;
58
+
59
+ type EnlaceHookOptions = {
60
+ /**
61
+ * Auto-generate cache tags from URL path for GET requests.
62
+ * e.g., `/posts/1` generates tags `['posts', 'posts/1']`
63
+ * @default true
64
+ */
65
+ autoGenerateTags?: boolean;
66
+ /** Auto-revalidate generated tags after successful mutations. @default true */
67
+ autoRevalidateTags?: boolean;
68
+ /** Time in ms before cached data is considered stale. @default 0 (always stale) */
69
+ staleTime?: number;
84
70
  };
85
- type MethodNameKeys = "get" | "post" | "put" | "patch" | "delete";
86
- type EnlaceClient<TSchema, TRequestOptionsBase = object> = HttpMethods<TSchema, TRequestOptionsBase> & DynamicAccess<TSchema, TRequestOptionsBase> & {
87
- [K in keyof StaticPathKeys<TSchema> as K extends MethodNameKeys ? never : K]: EnlaceClient<TSchema[K], TRequestOptionsBase>;
71
+ type EnlaceHook<TSchema> = {
72
+ <TMethod extends (...args: any[]) => Promise<EnlaceResponse<unknown, unknown>>>(selector: SelectorFn<TSchema, TMethod>): UseEnlaceSelectorResult<TMethod>;
73
+ <TData, TError>(queryFn: QueryFn<TSchema, TData, TError>): UseEnlaceQueryResult<TData, TError>;
88
74
  };
89
- type WildcardMethodFn<TRequestOptionsBase = object> = (options?: RequestOptions<unknown> & TRequestOptionsBase) => Promise<EnlaceResponse<unknown, unknown>>;
90
75
  /**
91
- * Wildcard client type - allows any path access when no schema is provided.
92
- * All methods are available at every level and return unknown types.
93
- */
94
- type WildcardClient<TRequestOptionsBase = object> = {
95
- get: WildcardMethodFn<TRequestOptionsBase>;
96
- post: WildcardMethodFn<TRequestOptionsBase>;
97
- put: WildcardMethodFn<TRequestOptionsBase>;
98
- patch: WildcardMethodFn<TRequestOptionsBase>;
99
- delete: WildcardMethodFn<TRequestOptionsBase>;
100
- } & {
101
- [key: string]: WildcardClient<TRequestOptionsBase>;
102
- [key: number]: WildcardClient<TRequestOptionsBase>;
103
- };
104
-
105
- declare function createProxyHandler<TSchema extends object, TOptions = EnlaceOptions>(baseUrl: string, defaultOptions: TOptions, path?: string[], fetchExecutor?: FetchExecutor<TOptions, RequestOptions<unknown>>): TSchema;
106
-
107
- declare function executeFetch<TData, TError>(baseUrl: string, path: string[], method: HttpMethod, defaultOptions: EnlaceOptions, requestOptions?: RequestOptions<unknown>): Promise<EnlaceResponse<TData, TError>>;
108
-
109
- /**
110
- * Creates an API client.
76
+ * Creates a React hook for making API calls.
77
+ * Called at module level to create a reusable hook.
111
78
  *
112
79
  * @example
113
- * // Typed mode - with schema
114
- * const api = createEnlace<MyApi>('https://api.example.com');
80
+ * const useAPI = createEnlaceHook<ApiSchema>('https://api.com');
115
81
  *
116
- * // Untyped mode - no schema
117
- * const api = createEnlace('https://api.example.com');
82
+ * // Query mode - auto-fetch (auto-tracks changes, no deps array needed)
83
+ * const { loading, data, error } = useAPI((api) => api.posts.get({ query: { userId } }));
84
+ *
85
+ * // Selector mode - typed trigger for lazy calls
86
+ * const { trigger, loading, data, error } = useAPI((api) => api.posts.delete);
87
+ * onClick={() => trigger({ body: { id: 1 } })}
118
88
  */
119
- declare function createEnlace<TSchema = unknown>(baseUrl: string, defaultOptions?: EnlaceOptions): unknown extends TSchema ? WildcardClient : EnlaceClient<TSchema>;
89
+ declare function createEnlaceHook<TSchema = unknown>(baseUrl: string, defaultOptions?: EnlaceOptions, hookOptions?: EnlaceHookOptions): EnlaceHook<TSchema>;
90
+
91
+ type Listener = (tags: string[]) => void;
92
+ declare function invalidateTags(tags: string[]): void;
93
+ declare function onRevalidate(callback: Listener): () => void;
94
+
95
+ declare function clearCache(key?: string): void;
120
96
 
121
- export { type Endpoint, type EnlaceClient, type EnlaceOptions, type EnlaceResponse, type FetchExecutor, type HttpMethod, type MethodDefinition, type RequestOptions, type SchemaMethod, type WildcardClient, createEnlace, createProxyHandler, executeFetch };
97
+ export { type ApiClient, type EnlaceHookOptions, HTTP_METHODS, type HookState, type QueryFn, type ReactRequestOptionsBase, type SelectorFn, type TrackedCall, type UseEnlaceQueryResult, type UseEnlaceSelectorResult, clearCache, createEnlaceHook, invalidateTags, onRevalidate };