@spoosh/core 0.1.0-beta.0
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 +21 -0
- package/README.md +247 -0
- package/dist/index.d.mts +1318 -0
- package/dist/index.d.ts +1318 -0
- package/dist/index.js +1441 -0
- package/dist/index.mjs +1418 -0
- package/package.json +44 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Spoosh
|
|
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,247 @@
|
|
|
1
|
+
# @spoosh/core
|
|
2
|
+
|
|
3
|
+
Core client and plugin system for Spoosh - a type-safe API client framework.
|
|
4
|
+
|
|
5
|
+
**[Documentation](https://spoosh.dev/docs)** · **Requirements:** TypeScript >= 5.0
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @spoosh/core
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
### Define API Schema
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import type { Endpoint } from "@spoosh/core";
|
|
19
|
+
|
|
20
|
+
type User = { id: number; name: string; email: string };
|
|
21
|
+
|
|
22
|
+
type ApiSchema = {
|
|
23
|
+
users: {
|
|
24
|
+
$get: Endpoint<{ data: User[] }>;
|
|
25
|
+
$post: Endpoint<{ data: User; body: { name: string; email: string } }>;
|
|
26
|
+
_: {
|
|
27
|
+
$get: Endpoint<{ data: User }>;
|
|
28
|
+
$put: Endpoint<{ data: User; body: Partial<User> }>;
|
|
29
|
+
$delete: void;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
search: {
|
|
33
|
+
$get: Endpoint<{ data: User[]; query: { q: string; page?: number } }>;
|
|
34
|
+
};
|
|
35
|
+
upload: {
|
|
36
|
+
$post: Endpoint<{ data: { url: string }; formData: { file: File } }>;
|
|
37
|
+
};
|
|
38
|
+
payments: {
|
|
39
|
+
$post: Endpoint<{ data: { id: string }; urlEncoded: { amount: number; currency: string } }>;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Create Client
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { createClient } from "@spoosh/core";
|
|
48
|
+
|
|
49
|
+
const api = createClient<ApiSchema>({
|
|
50
|
+
baseUrl: "/api",
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Make API Calls
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
// GET /api/users
|
|
58
|
+
const { data, error, status } = await api.users.$get();
|
|
59
|
+
|
|
60
|
+
// GET /api/search?q=john&page=1
|
|
61
|
+
const { data } = await api.search.$get({ query: { q: "john", page: 1 } });
|
|
62
|
+
|
|
63
|
+
// POST /api/users with JSON body
|
|
64
|
+
const { data: newUser } = await api.users.$post({
|
|
65
|
+
body: { name: "John", email: "john@example.com" },
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// GET /api/users/123 (dynamic segment)
|
|
69
|
+
const { data: user } = await api.users[123].$get();
|
|
70
|
+
|
|
71
|
+
// Type-safe dynamic params with function syntax (recommended - params is typed)
|
|
72
|
+
const { data } = await api.users(":userId").$get({
|
|
73
|
+
params: { userId: "123" },
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Alternative bracket syntaxes (less recommended):
|
|
77
|
+
// api.users[":userId"].$get() - works but no type inference for params
|
|
78
|
+
// api.users[userId].$get() - works with variable, no type inference
|
|
79
|
+
|
|
80
|
+
// PUT /api/users/123
|
|
81
|
+
const { data: updated } = await api.users[123].$put({
|
|
82
|
+
body: { name: "John Updated" },
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// DELETE /api/users/123
|
|
86
|
+
await api.users[123].$delete();
|
|
87
|
+
|
|
88
|
+
// POST with FormData
|
|
89
|
+
const { data: uploaded } = await api.upload.$post({
|
|
90
|
+
formData: { file: myFile },
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// POST with URL-encoded body (auto Content-Type: application/x-www-form-urlencoded)
|
|
94
|
+
const { data: payment } = await api.payments.$post({
|
|
95
|
+
urlEncoded: { amount: 1000, currency: "usd" },
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Response Format
|
|
100
|
+
|
|
101
|
+
All API calls return a `SpooshResponse`:
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
type SpooshResponse<TData, TError> = {
|
|
105
|
+
status: number; // HTTP status code
|
|
106
|
+
data: TData | undefined; // Response data (if successful)
|
|
107
|
+
error: TError | undefined; // Error data (if failed)
|
|
108
|
+
headers?: Headers; // Response headers
|
|
109
|
+
aborted?: boolean; // True if request was aborted
|
|
110
|
+
};
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Next.js Server-Side Usage
|
|
114
|
+
|
|
115
|
+
When using `createClient`, Next.js cache tags are automatically generated from the API path:
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
// Server component
|
|
119
|
+
import { createClient } from "@spoosh/core";
|
|
120
|
+
|
|
121
|
+
const api = createClient<ApiSchema>({ baseUrl: process.env.API_URL! });
|
|
122
|
+
|
|
123
|
+
// Auto-generates next: { tags: ['posts'] }
|
|
124
|
+
const { data: posts } = await api.posts.$get();
|
|
125
|
+
|
|
126
|
+
// Auto-generates next: { tags: ['users', 'users/123', 'users/123/posts'] }
|
|
127
|
+
const { data: userPosts } = await api.users[123].posts.$get();
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
This enables automatic cache invalidation with `revalidateTag()` in Next.js.
|
|
131
|
+
|
|
132
|
+
### With Middlewares
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
import { createClient, createMiddleware } from "@spoosh/core";
|
|
136
|
+
|
|
137
|
+
const authMiddleware = createMiddleware("auth", "before", async (ctx) => {
|
|
138
|
+
ctx.requestOptions = {
|
|
139
|
+
...ctx.requestOptions,
|
|
140
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
141
|
+
};
|
|
142
|
+
return ctx;
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const api = createClient<ApiSchema>({
|
|
146
|
+
baseUrl: "/api",
|
|
147
|
+
middlewares: [authMiddleware],
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Middleware Utilities
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
import {
|
|
155
|
+
createMiddleware,
|
|
156
|
+
applyMiddlewares,
|
|
157
|
+
composeMiddlewares,
|
|
158
|
+
} from "@spoosh/core";
|
|
159
|
+
|
|
160
|
+
// createMiddleware(name, phase, handler) - Create a named middleware
|
|
161
|
+
const logMiddleware = createMiddleware("logger", "after", async (ctx) => {
|
|
162
|
+
console.log(ctx.response?.status);
|
|
163
|
+
return ctx;
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// composeMiddlewares(...lists) - Combine multiple middleware arrays
|
|
167
|
+
const allMiddlewares = composeMiddlewares(
|
|
168
|
+
[authMiddleware],
|
|
169
|
+
[logMiddleware],
|
|
170
|
+
conditionalMiddlewares
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
// applyMiddlewares(context, middlewares, phase) - Run middlewares for a phase
|
|
174
|
+
const updatedContext = await applyMiddlewares(context, middlewares, "before");
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Schema Types
|
|
178
|
+
|
|
179
|
+
| Type | Description | Example |
|
|
180
|
+
| --------------------------------------- | ----------------------------- | --------------------------------------------------------------------- |
|
|
181
|
+
| `Endpoint<{ data }>` | Endpoint with data only | `$get: Endpoint<{ data: User[] }>` |
|
|
182
|
+
| `Endpoint<{ data; body }>` | Endpoint with JSON body | `$post: Endpoint<{ data: User; body: CreateUserBody }>` |
|
|
183
|
+
| `Endpoint<{ data; query }>` | Endpoint with query params | `$get: Endpoint<{ data: User[]; query: { page: number } }>` |
|
|
184
|
+
| `Endpoint<{ data; formData }>` | Endpoint with multipart form | `$post: Endpoint<{ data: Result; formData: { file: File } }>` |
|
|
185
|
+
| `Endpoint<{ data; urlEncoded }>` | Endpoint with URL-encoded | `$post: Endpoint<{ data: Result; urlEncoded: { amount: number } }>` |
|
|
186
|
+
| `Endpoint<{ data; error }>` | Endpoint with typed error | `$get: Endpoint<{ data: User; error: ApiError }>` |
|
|
187
|
+
| `void` | No response body | `$delete: void` |
|
|
188
|
+
| `_` | Dynamic path segment | `users: { _: { $get: Endpoint<{ data: User }> } }` |
|
|
189
|
+
|
|
190
|
+
## API Reference
|
|
191
|
+
|
|
192
|
+
### createClient(config)
|
|
193
|
+
|
|
194
|
+
Creates a lightweight type-safe API client.
|
|
195
|
+
|
|
196
|
+
| Option | Type | Description |
|
|
197
|
+
| ---------------- | -------------------- | -------------------------------------------------- |
|
|
198
|
+
| `baseUrl` | `string` | Base URL for all API requests |
|
|
199
|
+
| `defaultOptions` | `RequestInit` | Default fetch options (headers, credentials, etc.) |
|
|
200
|
+
| `middlewares` | `SpooshMiddleware[]` | Request/response middlewares |
|
|
201
|
+
|
|
202
|
+
### createSpoosh(config)
|
|
203
|
+
|
|
204
|
+
Creates a full-featured client with plugin system. Use this with `@spoosh/react`.
|
|
205
|
+
|
|
206
|
+
| Option | Type | Description |
|
|
207
|
+
| ---------------- | ---------------- | ----------------------------- |
|
|
208
|
+
| `baseUrl` | `string` | Base URL for all API requests |
|
|
209
|
+
| `plugins` | `SpooshPlugin[]` | Array of plugins to use |
|
|
210
|
+
| `defaultOptions` | `RequestInit` | Default fetch options |
|
|
211
|
+
|
|
212
|
+
## Creating Plugins
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
import type { SpooshPlugin } from "@spoosh/core";
|
|
216
|
+
|
|
217
|
+
interface MyPluginOptions {
|
|
218
|
+
myOption?: string;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function myPlugin(): SpooshPlugin<{
|
|
222
|
+
readOptions: MyPluginOptions;
|
|
223
|
+
}> {
|
|
224
|
+
return {
|
|
225
|
+
name: "my:plugin",
|
|
226
|
+
operations: ["read"],
|
|
227
|
+
|
|
228
|
+
middleware: async (context, next) => {
|
|
229
|
+
// Before request
|
|
230
|
+
const response = await next();
|
|
231
|
+
// After request
|
|
232
|
+
return response;
|
|
233
|
+
},
|
|
234
|
+
|
|
235
|
+
onResponse: async (context) => {
|
|
236
|
+
// Handle response
|
|
237
|
+
// This always run after response
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
lifecycle: {
|
|
241
|
+
onMount(context) {},
|
|
242
|
+
onUpdate(context, prev) {},
|
|
243
|
+
onUnmount(context) {},
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
```
|