enlace 0.0.1-beta.15 → 0.0.1-beta.16
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 +68 -45
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,9 +30,7 @@ type ApiSchema = {
|
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
// Pass global error type as second generic
|
|
33
|
-
const useAPI = enlaceHookReact<ApiSchema, ApiError>(
|
|
34
|
-
"https://api.example.com"
|
|
35
|
-
);
|
|
33
|
+
const useAPI = enlaceHookReact<ApiSchema, ApiError>("https://api.example.com");
|
|
36
34
|
```
|
|
37
35
|
|
|
38
36
|
## Schema Conventions
|
|
@@ -98,11 +96,11 @@ import { Endpoint } from "enlace";
|
|
|
98
96
|
|
|
99
97
|
type ApiSchema = {
|
|
100
98
|
posts: {
|
|
101
|
-
$get: Post[];
|
|
102
|
-
$post: Endpoint<Post, CreatePost>;
|
|
99
|
+
$get: Post[]; // Direct type (simplest)
|
|
100
|
+
$post: Endpoint<Post, CreatePost>; // Data + Body
|
|
103
101
|
$put: Endpoint<Post, UpdatePost, ValidationError>; // Data + Body + Error
|
|
104
|
-
$delete: void;
|
|
105
|
-
$patch: Endpoint<Post, never, NotFoundError>;
|
|
102
|
+
$delete: void; // void response
|
|
103
|
+
$patch: Endpoint<Post, never, NotFoundError>; // Custom error without body
|
|
106
104
|
};
|
|
107
105
|
};
|
|
108
106
|
```
|
|
@@ -116,15 +114,24 @@ import { EndpointWithQuery } from "enlace";
|
|
|
116
114
|
|
|
117
115
|
type ApiSchema = {
|
|
118
116
|
users: {
|
|
119
|
-
$get: EndpointWithQuery<
|
|
117
|
+
$get: EndpointWithQuery<
|
|
118
|
+
User[],
|
|
119
|
+
{ page: number; limit: number; search?: string }
|
|
120
|
+
>;
|
|
120
121
|
};
|
|
121
122
|
posts: {
|
|
122
|
-
$get: EndpointWithQuery<
|
|
123
|
+
$get: EndpointWithQuery<
|
|
124
|
+
Post[],
|
|
125
|
+
{ status: "draft" | "published" },
|
|
126
|
+
ApiError
|
|
127
|
+
>;
|
|
123
128
|
};
|
|
124
129
|
};
|
|
125
130
|
|
|
126
131
|
// Usage - query params are fully typed
|
|
127
|
-
const { data } = useAPI((api) =>
|
|
132
|
+
const { data } = useAPI((api) =>
|
|
133
|
+
api.users.$get({ query: { page: 1, limit: 10 } })
|
|
134
|
+
);
|
|
128
135
|
// api.users.$get({ query: { foo: "bar" } }); // ✗ Error: 'foo' does not exist
|
|
129
136
|
```
|
|
130
137
|
|
|
@@ -148,22 +155,22 @@ type ApiSchema = {
|
|
|
148
155
|
const { trigger } = useAPI((api) => api.uploads.$post);
|
|
149
156
|
trigger({
|
|
150
157
|
formData: {
|
|
151
|
-
file: selectedFile,
|
|
152
|
-
name: "document.pdf",
|
|
153
|
-
}
|
|
158
|
+
file: selectedFile, // File object
|
|
159
|
+
name: "document.pdf", // String - converted automatically
|
|
160
|
+
},
|
|
154
161
|
});
|
|
155
162
|
// → Sends as multipart/form-data
|
|
156
163
|
```
|
|
157
164
|
|
|
158
165
|
**FormData conversion rules:**
|
|
159
166
|
|
|
160
|
-
| Type
|
|
161
|
-
|
|
162
|
-
| `File` / `Blob`
|
|
163
|
-
| `string` / `number` / `boolean` | Converted to string
|
|
164
|
-
| `object` (nested)
|
|
165
|
-
| `array` of primitives
|
|
166
|
-
| `array` of files
|
|
167
|
+
| Type | Conversion |
|
|
168
|
+
| ------------------------------- | -------------------------------- |
|
|
169
|
+
| `File` / `Blob` | Appended directly |
|
|
170
|
+
| `string` / `number` / `boolean` | Converted to string |
|
|
171
|
+
| `object` (nested) | JSON stringified |
|
|
172
|
+
| `array` of primitives | Each item appended separately |
|
|
173
|
+
| `array` of files | Each file appended with same key |
|
|
167
174
|
|
|
168
175
|
#### `EndpointFull<T>`
|
|
169
176
|
|
|
@@ -311,28 +318,30 @@ function OrderStatus({ orderId }: { orderId: string }) {
|
|
|
311
318
|
```
|
|
312
319
|
|
|
313
320
|
The function receives `(data, error)` and should return:
|
|
321
|
+
|
|
314
322
|
- `number`: Interval in milliseconds
|
|
315
323
|
- `false`: Stop polling
|
|
316
324
|
|
|
317
325
|
```typescript
|
|
318
326
|
// Poll faster when there's an error (retry), slower otherwise
|
|
319
|
-
{
|
|
327
|
+
{
|
|
328
|
+
pollingInterval: (data, error) => (error ? 1000 : 10000);
|
|
329
|
+
}
|
|
320
330
|
|
|
321
331
|
// Stop polling once data meets a condition
|
|
322
|
-
{
|
|
332
|
+
{
|
|
333
|
+
pollingInterval: (order) => (order?.status === "completed" ? false : 3000);
|
|
334
|
+
}
|
|
323
335
|
```
|
|
324
336
|
|
|
325
337
|
**Combined with conditional fetching:**
|
|
326
338
|
|
|
327
339
|
```typescript
|
|
328
340
|
function OrderStatus({ orderId }: { orderId: string | undefined }) {
|
|
329
|
-
const { data } = useAPI(
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
pollingInterval: 10000, // Poll every 10 seconds
|
|
334
|
-
}
|
|
335
|
-
);
|
|
341
|
+
const { data } = useAPI((api) => api.orders[orderId!].$get(), {
|
|
342
|
+
enabled: !!orderId,
|
|
343
|
+
pollingInterval: 10000, // Poll every 10 seconds
|
|
344
|
+
});
|
|
336
345
|
// Polling only runs when orderId is defined
|
|
337
346
|
}
|
|
338
347
|
```
|
|
@@ -651,21 +660,15 @@ type EnlaceErrorCallbackPayload<T> =
|
|
|
651
660
|
const result = useAPI((api) => api.posts.$get());
|
|
652
661
|
|
|
653
662
|
// With options
|
|
654
|
-
const result = useAPI(
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
pollingInterval: 5000 // Refetch every 5s after previous request completes
|
|
659
|
-
}
|
|
660
|
-
);
|
|
663
|
+
const result = useAPI((api) => api.posts.$get(), {
|
|
664
|
+
enabled: true, // Skip fetching when false
|
|
665
|
+
pollingInterval: 5000, // Refetch every 5s after previous request completes
|
|
666
|
+
});
|
|
661
667
|
|
|
662
668
|
// With dynamic polling
|
|
663
|
-
const result = useAPI(
|
|
664
|
-
(
|
|
665
|
-
|
|
666
|
-
pollingInterval: (order) => order?.status === "pending" ? 2000 : false
|
|
667
|
-
}
|
|
668
|
-
);
|
|
669
|
+
const result = useAPI((api) => api.orders[id].$get(), {
|
|
670
|
+
pollingInterval: (order) => (order?.status === "pending" ? 2000 : false),
|
|
671
|
+
});
|
|
669
672
|
|
|
670
673
|
type UseEnlaceQueryResult<TData, TError> = {
|
|
671
674
|
loading: boolean; // No cached data and fetching
|
|
@@ -750,9 +753,7 @@ import { enlaceHookNext } from "enlace/hook";
|
|
|
750
753
|
|
|
751
754
|
type ApiError = { message: string };
|
|
752
755
|
|
|
753
|
-
const useAPI = enlaceHookNext<ApiSchema, ApiError>(
|
|
754
|
-
"https://api.example.com"
|
|
755
|
-
);
|
|
756
|
+
const useAPI = enlaceHookNext<ApiSchema, ApiError>("https://api.example.com");
|
|
756
757
|
```
|
|
757
758
|
|
|
758
759
|
### Server-Side Revalidation
|
|
@@ -922,6 +923,28 @@ npm install enlace-openapi
|
|
|
922
923
|
enlace-openapi --schema ./types/APISchema.ts --output ./openapi.json
|
|
923
924
|
```
|
|
924
925
|
|
|
926
|
+
## Framework Adapters
|
|
927
|
+
|
|
928
|
+
### Hono
|
|
929
|
+
|
|
930
|
+
Use [`enlace-hono`](../hono/README.md) to automatically generate Enlace schemas from your Hono app:
|
|
931
|
+
|
|
932
|
+
```typescript
|
|
933
|
+
import { Hono } from "hono";
|
|
934
|
+
import type { HonoToEnlace } from "enlace-hono";
|
|
935
|
+
|
|
936
|
+
const app = new Hono()
|
|
937
|
+
.basePath("/api")
|
|
938
|
+
.get("/posts", (c) => c.json([{ id: 1, title: "Hello" }]))
|
|
939
|
+
.get("/posts/:id", (c) => c.json({ id: c.req.param("id") }));
|
|
940
|
+
|
|
941
|
+
// Auto-generate schema from Hono types
|
|
942
|
+
type ApiSchema = HonoToEnlace<typeof app>;
|
|
943
|
+
|
|
944
|
+
// Use with Enlace
|
|
945
|
+
const client = enlace<ApiSchema["api"]>("http://localhost:3000/api");
|
|
946
|
+
```
|
|
947
|
+
|
|
925
948
|
## License
|
|
926
949
|
|
|
927
950
|
MIT
|