@spfn/core 0.2.0-beta.9 → 0.3.0-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.
Files changed (95) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +467 -305
  3. package/dist/authz/index.d.ts +34 -0
  4. package/dist/authz/index.js +810 -0
  5. package/dist/authz/index.js.map +1 -0
  6. package/dist/{boss-DI1r4kTS.d.ts → boss-D16fO2oG.d.ts} +41 -1
  7. package/dist/cache/index.js +42 -30
  8. package/dist/cache/index.js.map +1 -1
  9. package/dist/codegen/index.d.ts +121 -13
  10. package/dist/codegen/index.js +212 -15
  11. package/dist/codegen/index.js.map +1 -1
  12. package/dist/config/index.d.ts +615 -6
  13. package/dist/config/index.js +124 -5
  14. package/dist/config/index.js.map +1 -1
  15. package/dist/contract/index.d.ts +220 -0
  16. package/dist/contract/index.js +558 -0
  17. package/dist/contract/index.js.map +1 -0
  18. package/dist/db/index.d.ts +528 -85
  19. package/dist/db/index.js +831 -122
  20. package/dist/db/index.js.map +1 -1
  21. package/dist/define-middleware-DfDP39Nq.d.ts +167 -0
  22. package/dist/env/index.d.ts +26 -2
  23. package/dist/env/index.js +15 -5
  24. package/dist/env/index.js.map +1 -1
  25. package/dist/env/loader.d.ts +26 -19
  26. package/dist/env/loader.js +32 -25
  27. package/dist/env/loader.js.map +1 -1
  28. package/dist/errors/index.d.ts +10 -0
  29. package/dist/errors/index.js +418 -5
  30. package/dist/errors/index.js.map +1 -1
  31. package/dist/event/index.d.ts +33 -3
  32. package/dist/event/index.js +24 -3
  33. package/dist/event/index.js.map +1 -1
  34. package/dist/event/sse/client.d.ts +42 -3
  35. package/dist/event/sse/client.js +128 -45
  36. package/dist/event/sse/client.js.map +1 -1
  37. package/dist/event/sse/index.d.ts +12 -5
  38. package/dist/event/sse/index.js +280 -32
  39. package/dist/event/sse/index.js.map +1 -1
  40. package/dist/event/ws/client.d.ts +59 -0
  41. package/dist/event/ws/client.js +273 -0
  42. package/dist/event/ws/client.js.map +1 -0
  43. package/dist/event/ws/index.d.ts +94 -0
  44. package/dist/event/ws/index.js +272 -0
  45. package/dist/event/ws/index.js.map +1 -0
  46. package/dist/job/index.d.ts +2 -2
  47. package/dist/job/index.js +155 -42
  48. package/dist/job/index.js.map +1 -1
  49. package/dist/logger/index.d.ts +5 -0
  50. package/dist/logger/index.js +14 -0
  51. package/dist/logger/index.js.map +1 -1
  52. package/dist/middleware/index.d.ts +347 -9
  53. package/dist/middleware/index.js +1462 -15
  54. package/dist/middleware/index.js.map +1 -1
  55. package/dist/nextjs/index.d.ts +2 -2
  56. package/dist/nextjs/index.js +42 -28
  57. package/dist/nextjs/index.js.map +1 -1
  58. package/dist/nextjs/server.d.ts +35 -51
  59. package/dist/nextjs/server.js +126 -60
  60. package/dist/nextjs/server.js.map +1 -1
  61. package/dist/ops/index.d.ts +152 -0
  62. package/dist/ops/index.js +500 -0
  63. package/dist/ops/index.js.map +1 -0
  64. package/dist/route/index.d.ts +8 -694
  65. package/dist/route/index.js +111 -22
  66. package/dist/route/index.js.map +1 -1
  67. package/dist/router-Qbssr11H.d.ts +676 -0
  68. package/dist/security/index.d.ts +83 -0
  69. package/dist/security/index.js +173 -0
  70. package/dist/security/index.js.map +1 -0
  71. package/dist/server/index.d.ts +491 -22
  72. package/dist/server/index.js +1887 -308
  73. package/dist/server/index.js.map +1 -1
  74. package/dist/token-manager-BT5EnUAR.d.ts +278 -0
  75. package/dist/types-2AbaW4Ie.d.ts +205 -0
  76. package/dist/{types-BOPTApC2.d.ts → types-9oszaJqp.d.ts} +7 -2
  77. package/dist/types-D1c57Ko-.d.ts +115 -0
  78. package/dist/types-ZQODsBft.d.ts +282 -0
  79. package/package.json +244 -208
  80. package/dist/router-Di7ENoah.d.ts +0 -151
  81. package/dist/types-B-e_f2dQ.d.ts +0 -121
  82. package/docs/cache.md +0 -133
  83. package/docs/codegen.md +0 -74
  84. package/docs/database.md +0 -346
  85. package/docs/entity.md +0 -539
  86. package/docs/env.md +0 -477
  87. package/docs/errors.md +0 -319
  88. package/docs/event.md +0 -116
  89. package/docs/job.md +0 -131
  90. package/docs/logger.md +0 -108
  91. package/docs/middleware.md +0 -337
  92. package/docs/nextjs.md +0 -241
  93. package/docs/repository.md +0 -496
  94. package/docs/route.md +0 -497
  95. package/docs/server.md +0 -307
@@ -1,337 +0,0 @@
1
- # Middleware
2
-
3
- Named middleware system with route-level skip control.
4
-
5
- ## Define Middleware
6
-
7
- ### Regular Middleware
8
-
9
- ```typescript
10
- import { defineMiddleware } from '@spfn/core/route';
11
-
12
- export const authMiddleware = defineMiddleware('auth', async (c, next) => {
13
- const token = c.req.header('authorization');
14
-
15
- if (!token)
16
- {
17
- return c.json({ error: 'Unauthorized' }, 401);
18
- }
19
-
20
- const user = await verifyToken(token);
21
- c.set('user', user);
22
-
23
- await next();
24
- });
25
- ```
26
-
27
- ### Factory Middleware
28
-
29
- Middleware with parameters:
30
-
31
- ```typescript
32
- export const requireRole = defineMiddleware('role',
33
- (...roles: string[]) => async (c, next) => {
34
- const user = c.get('user');
35
-
36
- if (!roles.includes(user.role))
37
- {
38
- return c.json({ error: 'Forbidden' }, 403);
39
- }
40
-
41
- await next();
42
- }
43
- );
44
-
45
- // Usage
46
- route.get('/admin')
47
- .use([requireRole('admin', 'superadmin')])
48
- .handler(...)
49
- ```
50
-
51
- ### Two-Parameter Factory
52
-
53
- For factory with exactly 2 parameters, use `defineMiddlewareFactory`:
54
-
55
- ```typescript
56
- import { defineMiddlewareFactory } from '@spfn/core/route';
57
-
58
- export const rateLimiter = defineMiddlewareFactory('rateLimit',
59
- (limit: number, windowMs: number) => async (c, next) => {
60
- // Rate limit logic
61
- await next();
62
- }
63
- );
64
-
65
- // Usage
66
- route.get('/api')
67
- .use([rateLimiter(100, 60000)]) // 100 requests per minute
68
- .handler(...)
69
- ```
70
-
71
- ---
72
-
73
- ## Register Global Middleware
74
-
75
- ```typescript
76
- // src/server/server.config.ts
77
- import { defineServerConfig } from '@spfn/core/server';
78
- import { authMiddleware, loggerMiddleware, corsMiddleware } from './middlewares';
79
-
80
- export default defineServerConfig()
81
- .middlewares([
82
- loggerMiddleware,
83
- corsMiddleware,
84
- authMiddleware
85
- ])
86
- .routes(appRouter)
87
- .build();
88
- ```
89
-
90
- **Execution order:**
91
- 1. Global middlewares (in registration order)
92
- 2. Route-level middlewares (from `.use()`)
93
- 3. Validation middleware (automatic)
94
- 4. Route handler
95
-
96
- ---
97
-
98
- ## Use in Routes
99
-
100
- ### Add Middleware
101
-
102
- ```typescript
103
- import { Transactional } from '@spfn/core/db';
104
- import { authMiddleware, requireRole } from './middlewares';
105
-
106
- route.post('/admin/users')
107
- .use([authMiddleware, requireRole('admin'), Transactional()])
108
- .handler(async (c) => {
109
- const user = c.raw.get('user'); // From authMiddleware
110
- // ...
111
- });
112
- ```
113
-
114
- ### Skip Global Middleware
115
-
116
- ```typescript
117
- // Skip specific middlewares by name
118
- route.get('/public/health')
119
- .skip(['auth', 'rateLimit'])
120
- .handler(async (c) => {
121
- return { status: 'ok' };
122
- });
123
-
124
- // Skip all global middlewares
125
- route.get('/webhooks/stripe')
126
- .skip('*')
127
- .handler(async (c) => {
128
- // No global middleware applied
129
- });
130
- ```
131
-
132
- **Note:** Route-level middlewares (`.use()`) are never skipped.
133
-
134
- ---
135
-
136
- ## Common Middleware Patterns
137
-
138
- ### Authentication
139
-
140
- ```typescript
141
- export const authMiddleware = defineMiddleware('auth', async (c, next) => {
142
- const token = c.req.header('authorization')?.replace('Bearer ', '');
143
-
144
- if (!token)
145
- {
146
- return c.json({ error: 'Missing token' }, 401);
147
- }
148
-
149
- try
150
- {
151
- const payload = await verifyJWT(token);
152
- c.set('userId', payload.sub);
153
- c.set('user', await userRepo.findById(payload.sub));
154
- await next();
155
- }
156
- catch
157
- {
158
- return c.json({ error: 'Invalid token' }, 401);
159
- }
160
- });
161
- ```
162
-
163
- ### Role-based Access
164
-
165
- ```typescript
166
- export const requirePermissions = defineMiddleware('permission',
167
- (...permissions: string[]) => async (c, next) => {
168
- const user = c.get('user');
169
-
170
- const hasPermission = permissions.every(p =>
171
- user.permissions.includes(p)
172
- );
173
-
174
- if (!hasPermission)
175
- {
176
- return c.json({ error: 'Insufficient permissions' }, 403);
177
- }
178
-
179
- await next();
180
- }
181
- );
182
-
183
- // Usage
184
- route.delete('/posts/:id')
185
- .use([requirePermissions('posts:delete')])
186
- .handler(...)
187
- ```
188
-
189
- ### Rate Limiting
190
-
191
- ```typescript
192
- const requestCounts = new Map<string, { count: number; resetAt: number }>();
193
-
194
- export const rateLimiter = defineMiddlewareFactory('rateLimit',
195
- (limit: number, windowMs: number) => async (c, next) => {
196
- const key = c.req.header('x-forwarded-for') || 'unknown';
197
- const now = Date.now();
198
-
199
- const record = requestCounts.get(key);
200
-
201
- if (!record || now > record.resetAt)
202
- {
203
- requestCounts.set(key, { count: 1, resetAt: now + windowMs });
204
- }
205
- else if (record.count >= limit)
206
- {
207
- return c.json({ error: 'Too many requests' }, 429);
208
- }
209
- else
210
- {
211
- record.count++;
212
- }
213
-
214
- await next();
215
- }
216
- );
217
- ```
218
-
219
- ### Request Logging
220
-
221
- ```typescript
222
- export const requestLogger = defineMiddleware('requestLogger', async (c, next) => {
223
- const start = Date.now();
224
- const method = c.req.method;
225
- const path = c.req.path;
226
-
227
- await next();
228
-
229
- const duration = Date.now() - start;
230
- const status = c.res.status;
231
-
232
- console.log(`${method} ${path} ${status} ${duration}ms`);
233
- });
234
- ```
235
-
236
- ### CORS
237
-
238
- ```typescript
239
- export const corsMiddleware = defineMiddleware('cors', async (c, next) => {
240
- const origin = c.req.header('origin');
241
-
242
- if (origin && allowedOrigins.includes(origin))
243
- {
244
- c.header('Access-Control-Allow-Origin', origin);
245
- c.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
246
- c.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
247
- }
248
-
249
- if (c.req.method === 'OPTIONS')
250
- {
251
- return c.body(null, 204);
252
- }
253
-
254
- await next();
255
- });
256
- ```
257
-
258
- ---
259
-
260
- ## Middleware Deduplication
261
-
262
- When the same middleware is registered both globally and in a route, it's automatically deduplicated:
263
-
264
- ```typescript
265
- // Registered globally
266
- .middlewares([authMiddleware])
267
-
268
- // Also used in route
269
- route.get('/users')
270
- .use([authMiddleware]) // Skipped (already applied globally)
271
- .handler(...)
272
- ```
273
-
274
- ---
275
-
276
- ## Access Context Data
277
-
278
- ### Set Data
279
-
280
- ```typescript
281
- defineMiddleware('auth', async (c, next) => {
282
- c.set('user', user);
283
- c.set('sessionId', sessionId);
284
- await next();
285
- });
286
- ```
287
-
288
- ### Get Data in Handler
289
-
290
- ```typescript
291
- route.get('/profile')
292
- .handler(async (c) => {
293
- const user = c.raw.get('user');
294
- const sessionId = c.raw.get('sessionId');
295
- // ...
296
- });
297
- ```
298
-
299
- ---
300
-
301
- ## Best Practices
302
-
303
- ### Do
304
-
305
- ```typescript
306
- // 1. Use meaningful names for skip control
307
- export const authMiddleware = defineMiddleware('auth', ...);
308
- export const rateLimiter = defineMiddleware('rateLimit', ...);
309
-
310
- // 2. Set context data for downstream use
311
- c.set('user', user);
312
-
313
- // 3. Return early for unauthorized requests
314
- if (!token) return c.json({ error: 'Unauthorized' }, 401);
315
-
316
- // 4. Always call next() for successful middleware
317
- await next();
318
- ```
319
-
320
- ### Don't
321
-
322
- ```typescript
323
- // 1. Don't forget to call next()
324
- defineMiddleware('logger', async (c, next) => {
325
- console.log(c.req.path);
326
- // Missing next() - request hangs!
327
- });
328
-
329
- // 2. Don't use generic names
330
- defineMiddleware('middleware1', ...); // Bad
331
-
332
- // 3. Don't throw errors - return responses
333
- defineMiddleware('auth', async (c, next) => {
334
- if (!token) throw new Error('No token'); // Bad
335
- if (!token) return c.json({ error: 'No token' }, 401); // Good
336
- });
337
- ```
package/docs/nextjs.md DELETED
@@ -1,241 +0,0 @@
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 { appRouter } from '@/server/server.config';
12
- import { createRpcProxy } from '@spfn/core/nextjs/server';
13
-
14
- export const { GET, POST, PUT, PATCH, DELETE } = createRpcProxy({
15
- router: appRouter,
16
- apiUrl: process.env.SPFN_API_URL || 'http://localhost:8790'
17
- });
18
- ```
19
-
20
- ### 2. Create API Client
21
-
22
- ```typescript
23
- // src/lib/api.ts
24
- import { createApi } from '@spfn/core/nextjs';
25
- import type { AppRouter } from '@/server/server.config';
26
-
27
- export const api = createApi<AppRouter>();
28
- ```
29
-
30
- ## Usage
31
-
32
- ### Server Components
33
-
34
- ```typescript
35
- // app/users/[id]/page.tsx
36
- import { api } from '@/lib/api';
37
-
38
- export default async function UserPage({ params }: { params: { id: string } })
39
- {
40
- const user = await api.getUser.call({
41
- params: { id: params.id }
42
- });
43
-
44
- return <div>{user.name}</div>;
45
- }
46
- ```
47
-
48
- ### Client Components
49
-
50
- ```typescript
51
- 'use client';
52
-
53
- import { api } from '@/lib/api';
54
- import { useState } from 'react';
55
-
56
- export function CreateUserForm()
57
- {
58
- const [loading, setLoading] = useState(false);
59
-
60
- async function handleSubmit(formData: FormData)
61
- {
62
- setLoading(true);
63
- try
64
- {
65
- await api.createUser.call({
66
- body: {
67
- email: formData.get('email') as string,
68
- name: formData.get('name') as string
69
- }
70
- });
71
- }
72
- finally
73
- {
74
- setLoading(false);
75
- }
76
- }
77
-
78
- return (
79
- <form action={handleSubmit}>
80
- {/* ... */}
81
- </form>
82
- );
83
- }
84
- ```
85
-
86
- ### Server Actions
87
-
88
- ```typescript
89
- // app/actions.ts
90
- 'use server';
91
-
92
- import { api } from '@/lib/api';
93
-
94
- export async function createUser(formData: FormData)
95
- {
96
- const user = await api.createUser.call({
97
- body: {
98
- email: formData.get('email') as string,
99
- name: formData.get('name') as string
100
- }
101
- });
102
-
103
- return user;
104
- }
105
- ```
106
-
107
- ## API Client Methods
108
-
109
- ```typescript
110
- // Call with params
111
- const user = await api.getUser.call({
112
- params: { id: '123' }
113
- });
114
-
115
- // Call with query
116
- const users = await api.getUsers.call({
117
- query: { page: 1, limit: 20, search: 'john' }
118
- });
119
-
120
- // Call with body
121
- const created = await api.createUser.call({
122
- body: { email: 'user@example.com', name: 'User' }
123
- });
124
-
125
- // Call with multiple inputs
126
- const updated = await api.updateUser.call({
127
- params: { id: '123' },
128
- body: { name: 'Updated Name' }
129
- });
130
- ```
131
-
132
- ## Interceptors
133
-
134
- ### Request Interceptor
135
-
136
- ```typescript
137
- export const { GET, POST } = createRpcProxy({
138
- router: appRouter,
139
- apiUrl: process.env.SPFN_API_URL,
140
- interceptors: {
141
- request: async (request, context) => {
142
- // Add auth header
143
- const token = cookies().get('token')?.value;
144
- if (token)
145
- {
146
- request.headers.set('Authorization', `Bearer ${token}`);
147
- }
148
- return request;
149
- }
150
- }
151
- });
152
- ```
153
-
154
- ### Response Interceptor
155
-
156
- ```typescript
157
- interceptors: {
158
- response: async (response, context) => {
159
- // Handle Set-Cookie from API
160
- const setCookie = response.headers.get('set-cookie');
161
- if (setCookie)
162
- {
163
- cookies().set(parseCookie(setCookie));
164
- }
165
- return response;
166
- }
167
- }
168
- ```
169
-
170
- ## Cookie Handling
171
-
172
- The RPC proxy automatically handles HttpOnly cookies:
173
-
174
- ```typescript
175
- // Server sets cookie
176
- c.header('Set-Cookie', 'session=abc; HttpOnly; Secure');
177
-
178
- // Proxy forwards to browser
179
- // Browser stores HttpOnly cookie
180
- // Subsequent requests include cookie automatically
181
- ```
182
-
183
- ## Error Handling
184
-
185
- ```typescript
186
- try
187
- {
188
- const user = await api.getUser.call({ params: { id: '123' } });
189
- }
190
- catch (error)
191
- {
192
- if (error.status === 404)
193
- {
194
- // Not found
195
- }
196
- else if (error.status === 401)
197
- {
198
- // Unauthorized
199
- }
200
- }
201
- ```
202
-
203
- ## Environment Variables
204
-
205
- ```bash
206
- # API server URL
207
- SPFN_API_URL=http://localhost:8790
208
-
209
- # For production
210
- SPFN_API_URL=https://api.example.com
211
- ```
212
-
213
- ## Best Practices
214
-
215
- ```typescript
216
- // 1. Create single api instance
217
- // src/lib/api.ts
218
- export const api = createApi<AppRouter>();
219
-
220
- // 2. Use in Server Components for SSR
221
- export default async function Page() {
222
- const data = await api.getData.call({}); // SSR
223
- return <div>{data}</div>;
224
- }
225
-
226
- // 3. Handle loading states in Client Components
227
- const [loading, setLoading] = useState(false);
228
-
229
- // 4. Use Server Actions for mutations
230
- 'use server';
231
- export async function createItem(formData: FormData) {
232
- return api.createItem.call({ body: { ... } });
233
- }
234
-
235
- // 5. Type-safe error handling
236
- try {
237
- await api.getUser.call({ params: { id } });
238
- } catch (e) {
239
- if (e.status === 404) redirect('/not-found');
240
- }
241
- ```