@spfn/core 0.2.0-beta.2 → 0.2.0-beta.21
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 +262 -1092
- package/dist/{boss-D-fGtVgM.d.ts → boss-DI1r4kTS.d.ts} +68 -11
- package/dist/codegen/index.d.ts +55 -8
- package/dist/codegen/index.js +179 -5
- package/dist/codegen/index.js.map +1 -1
- package/dist/config/index.d.ts +204 -6
- package/dist/config/index.js +44 -11
- package/dist/config/index.js.map +1 -1
- package/dist/db/index.d.ts +13 -0
- package/dist/db/index.js +92 -33
- package/dist/db/index.js.map +1 -1
- package/dist/env/index.d.ts +83 -3
- package/dist/env/index.js +83 -15
- package/dist/env/index.js.map +1 -1
- package/dist/env/loader.d.ts +95 -0
- package/dist/env/loader.js +78 -0
- package/dist/env/loader.js.map +1 -0
- package/dist/event/index.d.ts +29 -70
- package/dist/event/index.js +15 -1
- package/dist/event/index.js.map +1 -1
- package/dist/event/sse/client.d.ts +157 -0
- package/dist/event/sse/client.js +169 -0
- package/dist/event/sse/client.js.map +1 -0
- package/dist/event/sse/index.d.ts +46 -0
- package/dist/event/sse/index.js +205 -0
- package/dist/event/sse/index.js.map +1 -0
- package/dist/job/index.d.ts +54 -8
- package/dist/job/index.js +61 -12
- package/dist/job/index.js.map +1 -1
- package/dist/middleware/index.d.ts +124 -11
- package/dist/middleware/index.js +41 -7
- package/dist/middleware/index.js.map +1 -1
- package/dist/nextjs/index.d.ts +2 -2
- package/dist/nextjs/index.js +37 -5
- package/dist/nextjs/index.js.map +1 -1
- package/dist/nextjs/server.d.ts +45 -24
- package/dist/nextjs/server.js +87 -66
- package/dist/nextjs/server.js.map +1 -1
- package/dist/route/index.d.ts +207 -14
- package/dist/route/index.js +304 -31
- package/dist/route/index.js.map +1 -1
- package/dist/route/types.d.ts +2 -31
- package/dist/router-Di7ENoah.d.ts +151 -0
- package/dist/server/index.d.ts +321 -10
- package/dist/server/index.js +798 -189
- package/dist/server/index.js.map +1 -1
- package/dist/{types-DRG2XMTR.d.ts → types-7Mhoxnnt.d.ts} +97 -4
- package/dist/types-DHQMQlcb.d.ts +305 -0
- package/docs/cache.md +133 -0
- package/docs/codegen.md +74 -0
- package/docs/database.md +346 -0
- package/docs/entity.md +539 -0
- package/docs/env.md +499 -0
- package/docs/errors.md +319 -0
- package/docs/event.md +432 -0
- package/docs/file-upload.md +717 -0
- package/docs/job.md +131 -0
- package/docs/logger.md +108 -0
- package/docs/middleware.md +337 -0
- package/docs/nextjs.md +247 -0
- package/docs/repository.md +496 -0
- package/docs/route.md +497 -0
- package/docs/server.md +429 -0
- package/package.json +19 -3
package/docs/nextjs.md
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# Next.js Integration
|
|
2
|
+
|
|
3
|
+
RPC proxy and type-safe API client for Next.js.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
### 1. Create RPC Proxy
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
// app/api/rpc/[routeName]/route.ts
|
|
11
|
+
import '@spfn/auth/nextjs/api';
|
|
12
|
+
import { createRpcProxy } from '@spfn/core/nextjs/server';
|
|
13
|
+
import { authRouteMap } from '@spfn/auth';
|
|
14
|
+
import { eventRouteMap } from '@spfn/core/event';
|
|
15
|
+
import { routeMap } from '@/generated/route-map';
|
|
16
|
+
|
|
17
|
+
export const { GET, POST } = createRpcProxy({
|
|
18
|
+
routeMap: { ...routeMap, ...authRouteMap, ...eventRouteMap },
|
|
19
|
+
});
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### 2. Create API Client
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
// src/lib/api.ts
|
|
26
|
+
import { createApi } from '@spfn/core/nextjs';
|
|
27
|
+
import type { AppRouter } from '@/server/server.config';
|
|
28
|
+
|
|
29
|
+
export const api = createApi<AppRouter>();
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
### Server Components
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
// app/users/[id]/page.tsx
|
|
38
|
+
import { api } from '@/lib/api';
|
|
39
|
+
|
|
40
|
+
export default async function UserPage({ params }: { params: { id: string } })
|
|
41
|
+
{
|
|
42
|
+
const user = await api.getUser.call({
|
|
43
|
+
params: { id: params.id }
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return <div>{user.name}</div>;
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Client Components
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
'use client';
|
|
54
|
+
|
|
55
|
+
import { api } from '@/lib/api';
|
|
56
|
+
import { useState } from 'react';
|
|
57
|
+
|
|
58
|
+
export function CreateUserForm()
|
|
59
|
+
{
|
|
60
|
+
const [loading, setLoading] = useState(false);
|
|
61
|
+
|
|
62
|
+
async function handleSubmit(formData: FormData)
|
|
63
|
+
{
|
|
64
|
+
setLoading(true);
|
|
65
|
+
try
|
|
66
|
+
{
|
|
67
|
+
await api.createUser.call({
|
|
68
|
+
body: {
|
|
69
|
+
email: formData.get('email') as string,
|
|
70
|
+
name: formData.get('name') as string
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
finally
|
|
75
|
+
{
|
|
76
|
+
setLoading(false);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<form action={handleSubmit}>
|
|
82
|
+
{/* ... */}
|
|
83
|
+
</form>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Server Actions
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
// app/actions.ts
|
|
92
|
+
'use server';
|
|
93
|
+
|
|
94
|
+
import { api } from '@/lib/api';
|
|
95
|
+
|
|
96
|
+
export async function createUser(formData: FormData)
|
|
97
|
+
{
|
|
98
|
+
const user = await api.createUser.call({
|
|
99
|
+
body: {
|
|
100
|
+
email: formData.get('email') as string,
|
|
101
|
+
name: formData.get('name') as string
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
return user;
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## API Client Methods
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
// Call with params
|
|
113
|
+
const user = await api.getUser.call({
|
|
114
|
+
params: { id: '123' }
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Call with query
|
|
118
|
+
const users = await api.getUsers.call({
|
|
119
|
+
query: { page: 1, limit: 20, search: 'john' }
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Call with body
|
|
123
|
+
const created = await api.createUser.call({
|
|
124
|
+
body: { email: 'user@example.com', name: 'User' }
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Call with multiple inputs
|
|
128
|
+
const updated = await api.updateUser.call({
|
|
129
|
+
params: { id: '123' },
|
|
130
|
+
body: { name: 'Updated Name' }
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Interceptors
|
|
135
|
+
|
|
136
|
+
### Request Interceptor
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
export const { GET, POST } = createRpcProxy({
|
|
140
|
+
routeMap: { ...routeMap, ...authRouteMap },
|
|
141
|
+
apiUrl: process.env.SPFN_API_URL,
|
|
142
|
+
interceptors: {
|
|
143
|
+
request: async (request, context) => {
|
|
144
|
+
// Add auth header
|
|
145
|
+
const token = cookies().get('token')?.value;
|
|
146
|
+
if (token)
|
|
147
|
+
{
|
|
148
|
+
request.headers.set('Authorization', `Bearer ${token}`);
|
|
149
|
+
}
|
|
150
|
+
return request;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Response Interceptor
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
interceptors: {
|
|
160
|
+
response: async (response, context) => {
|
|
161
|
+
// Handle Set-Cookie from API
|
|
162
|
+
const setCookie = response.headers.get('set-cookie');
|
|
163
|
+
if (setCookie)
|
|
164
|
+
{
|
|
165
|
+
cookies().set(parseCookie(setCookie));
|
|
166
|
+
}
|
|
167
|
+
return response;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Cookie Handling
|
|
173
|
+
|
|
174
|
+
The RPC proxy automatically handles HttpOnly cookies:
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
// Server sets cookie
|
|
178
|
+
c.header('Set-Cookie', 'session=abc; HttpOnly; Secure');
|
|
179
|
+
|
|
180
|
+
// Proxy forwards to browser
|
|
181
|
+
// Browser stores HttpOnly cookie
|
|
182
|
+
// Subsequent requests include cookie automatically
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Error Handling
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
try
|
|
189
|
+
{
|
|
190
|
+
const user = await api.getUser.call({ params: { id: '123' } });
|
|
191
|
+
}
|
|
192
|
+
catch (error)
|
|
193
|
+
{
|
|
194
|
+
if (error.status === 404)
|
|
195
|
+
{
|
|
196
|
+
// Not found
|
|
197
|
+
}
|
|
198
|
+
else if (error.status === 401)
|
|
199
|
+
{
|
|
200
|
+
// Unauthorized
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Environment Variables
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
# API server URL
|
|
209
|
+
SPFN_API_URL=http://localhost:8790
|
|
210
|
+
|
|
211
|
+
# For production
|
|
212
|
+
SPFN_API_URL=https://api.example.com
|
|
213
|
+
|
|
214
|
+
# RPC proxy timeout (AbortController, default: 120s)
|
|
215
|
+
# Should be shorter than FETCH_HEADERS_TIMEOUT for meaningful 504 responses
|
|
216
|
+
RPC_PROXY_TIMEOUT=120000
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Best Practices
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
// 1. Create single api instance
|
|
223
|
+
// src/lib/api.ts
|
|
224
|
+
export const api = createApi<AppRouter>();
|
|
225
|
+
|
|
226
|
+
// 2. Use in Server Components for SSR
|
|
227
|
+
export default async function Page() {
|
|
228
|
+
const data = await api.getData.call({}); // SSR
|
|
229
|
+
return <div>{data}</div>;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// 3. Handle loading states in Client Components
|
|
233
|
+
const [loading, setLoading] = useState(false);
|
|
234
|
+
|
|
235
|
+
// 4. Use Server Actions for mutations
|
|
236
|
+
'use server';
|
|
237
|
+
export async function createItem(formData: FormData) {
|
|
238
|
+
return api.createItem.call({ body: { ... } });
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// 5. Type-safe error handling
|
|
242
|
+
try {
|
|
243
|
+
await api.getUser.call({ params: { id } });
|
|
244
|
+
} catch (e) {
|
|
245
|
+
if (e.status === 404) redirect('/not-found');
|
|
246
|
+
}
|
|
247
|
+
```
|