@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.
- package/LICENSE +1 -1
- package/README.md +467 -305
- package/dist/authz/index.d.ts +34 -0
- package/dist/authz/index.js +810 -0
- package/dist/authz/index.js.map +1 -0
- package/dist/{boss-DI1r4kTS.d.ts → boss-D16fO2oG.d.ts} +41 -1
- package/dist/cache/index.js +42 -30
- package/dist/cache/index.js.map +1 -1
- package/dist/codegen/index.d.ts +121 -13
- package/dist/codegen/index.js +212 -15
- package/dist/codegen/index.js.map +1 -1
- package/dist/config/index.d.ts +615 -6
- package/dist/config/index.js +124 -5
- package/dist/config/index.js.map +1 -1
- package/dist/contract/index.d.ts +220 -0
- package/dist/contract/index.js +558 -0
- package/dist/contract/index.js.map +1 -0
- package/dist/db/index.d.ts +528 -85
- package/dist/db/index.js +831 -122
- package/dist/db/index.js.map +1 -1
- package/dist/define-middleware-DfDP39Nq.d.ts +167 -0
- package/dist/env/index.d.ts +26 -2
- package/dist/env/index.js +15 -5
- package/dist/env/index.js.map +1 -1
- package/dist/env/loader.d.ts +26 -19
- package/dist/env/loader.js +32 -25
- package/dist/env/loader.js.map +1 -1
- package/dist/errors/index.d.ts +10 -0
- package/dist/errors/index.js +418 -5
- package/dist/errors/index.js.map +1 -1
- package/dist/event/index.d.ts +33 -3
- package/dist/event/index.js +24 -3
- package/dist/event/index.js.map +1 -1
- package/dist/event/sse/client.d.ts +42 -3
- package/dist/event/sse/client.js +128 -45
- package/dist/event/sse/client.js.map +1 -1
- package/dist/event/sse/index.d.ts +12 -5
- package/dist/event/sse/index.js +280 -32
- package/dist/event/sse/index.js.map +1 -1
- package/dist/event/ws/client.d.ts +59 -0
- package/dist/event/ws/client.js +273 -0
- package/dist/event/ws/client.js.map +1 -0
- package/dist/event/ws/index.d.ts +94 -0
- package/dist/event/ws/index.js +272 -0
- package/dist/event/ws/index.js.map +1 -0
- package/dist/job/index.d.ts +2 -2
- package/dist/job/index.js +155 -42
- package/dist/job/index.js.map +1 -1
- package/dist/logger/index.d.ts +5 -0
- package/dist/logger/index.js +14 -0
- package/dist/logger/index.js.map +1 -1
- package/dist/middleware/index.d.ts +347 -9
- package/dist/middleware/index.js +1462 -15
- package/dist/middleware/index.js.map +1 -1
- package/dist/nextjs/index.d.ts +2 -2
- package/dist/nextjs/index.js +42 -28
- package/dist/nextjs/index.js.map +1 -1
- package/dist/nextjs/server.d.ts +35 -51
- package/dist/nextjs/server.js +126 -60
- package/dist/nextjs/server.js.map +1 -1
- package/dist/ops/index.d.ts +152 -0
- package/dist/ops/index.js +500 -0
- package/dist/ops/index.js.map +1 -0
- package/dist/route/index.d.ts +8 -694
- package/dist/route/index.js +111 -22
- package/dist/route/index.js.map +1 -1
- package/dist/router-Qbssr11H.d.ts +676 -0
- package/dist/security/index.d.ts +83 -0
- package/dist/security/index.js +173 -0
- package/dist/security/index.js.map +1 -0
- package/dist/server/index.d.ts +491 -22
- package/dist/server/index.js +1887 -308
- package/dist/server/index.js.map +1 -1
- package/dist/token-manager-BT5EnUAR.d.ts +278 -0
- package/dist/types-2AbaW4Ie.d.ts +205 -0
- package/dist/{types-BOPTApC2.d.ts → types-9oszaJqp.d.ts} +7 -2
- package/dist/types-D1c57Ko-.d.ts +115 -0
- package/dist/types-ZQODsBft.d.ts +282 -0
- package/package.json +244 -208
- package/dist/router-Di7ENoah.d.ts +0 -151
- package/dist/types-B-e_f2dQ.d.ts +0 -121
- package/docs/cache.md +0 -133
- package/docs/codegen.md +0 -74
- package/docs/database.md +0 -346
- package/docs/entity.md +0 -539
- package/docs/env.md +0 -477
- package/docs/errors.md +0 -319
- package/docs/event.md +0 -116
- package/docs/job.md +0 -131
- package/docs/logger.md +0 -108
- package/docs/middleware.md +0 -337
- package/docs/nextjs.md +0 -241
- package/docs/repository.md +0 -496
- package/docs/route.md +0 -497
- package/docs/server.md +0 -307
package/docs/errors.md
DELETED
|
@@ -1,319 +0,0 @@
|
|
|
1
|
-
# Errors
|
|
2
|
-
|
|
3
|
-
Error types and handling patterns.
|
|
4
|
-
|
|
5
|
-
## Error Types
|
|
6
|
-
|
|
7
|
-
### HttpError
|
|
8
|
-
|
|
9
|
-
General HTTP error with status code.
|
|
10
|
-
|
|
11
|
-
```typescript
|
|
12
|
-
import { HttpError } from '@spfn/core/errors';
|
|
13
|
-
|
|
14
|
-
throw new HttpError(404, 'User not found');
|
|
15
|
-
throw new HttpError(403, 'Access denied');
|
|
16
|
-
throw new HttpError(500, 'Internal server error');
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
### ValidationError
|
|
20
|
-
|
|
21
|
-
Input validation error with field details.
|
|
22
|
-
|
|
23
|
-
```typescript
|
|
24
|
-
import { ValidationError } from '@spfn/core/errors';
|
|
25
|
-
|
|
26
|
-
throw new ValidationError({
|
|
27
|
-
message: 'Validation failed',
|
|
28
|
-
fields: [
|
|
29
|
-
{ path: '/email', message: 'Invalid email format' },
|
|
30
|
-
{ path: '/name', message: 'Name is required' }
|
|
31
|
-
]
|
|
32
|
-
});
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
**Response format:**
|
|
36
|
-
|
|
37
|
-
```json
|
|
38
|
-
{
|
|
39
|
-
"error": "Validation failed",
|
|
40
|
-
"fields": [
|
|
41
|
-
{ "path": "/email", "message": "Invalid email format" },
|
|
42
|
-
{ "path": "/name", "message": "Name is required" }
|
|
43
|
-
]
|
|
44
|
-
}
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
### NotFoundError
|
|
48
|
-
|
|
49
|
-
Resource not found error.
|
|
50
|
-
|
|
51
|
-
```typescript
|
|
52
|
-
import { NotFoundError } from '@spfn/core/errors';
|
|
53
|
-
|
|
54
|
-
throw new NotFoundError('User');
|
|
55
|
-
// → 404: "User not found"
|
|
56
|
-
|
|
57
|
-
throw new NotFoundError('Post', '123');
|
|
58
|
-
// → 404: "Post with id 123 not found"
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### UnauthorizedError
|
|
62
|
-
|
|
63
|
-
Authentication required error.
|
|
64
|
-
|
|
65
|
-
```typescript
|
|
66
|
-
import { UnauthorizedError } from '@spfn/core/errors';
|
|
67
|
-
|
|
68
|
-
throw new UnauthorizedError();
|
|
69
|
-
// → 401: "Unauthorized"
|
|
70
|
-
|
|
71
|
-
throw new UnauthorizedError('Invalid token');
|
|
72
|
-
// → 401: "Invalid token"
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
### ForbiddenError
|
|
76
|
-
|
|
77
|
-
Permission denied error.
|
|
78
|
-
|
|
79
|
-
```typescript
|
|
80
|
-
import { ForbiddenError } from '@spfn/core/errors';
|
|
81
|
-
|
|
82
|
-
throw new ForbiddenError();
|
|
83
|
-
// → 403: "Forbidden"
|
|
84
|
-
|
|
85
|
-
throw new ForbiddenError('Admin access required');
|
|
86
|
-
// → 403: "Admin access required"
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
### ConflictError
|
|
90
|
-
|
|
91
|
-
Resource conflict error.
|
|
92
|
-
|
|
93
|
-
```typescript
|
|
94
|
-
import { ConflictError } from '@spfn/core/errors';
|
|
95
|
-
|
|
96
|
-
throw new ConflictError('Email already exists');
|
|
97
|
-
// → 409: "Email already exists"
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
### BadRequestError
|
|
101
|
-
|
|
102
|
-
Invalid request error.
|
|
103
|
-
|
|
104
|
-
```typescript
|
|
105
|
-
import { BadRequestError } from '@spfn/core/errors';
|
|
106
|
-
|
|
107
|
-
throw new BadRequestError('Invalid date format');
|
|
108
|
-
// → 400: "Invalid date format"
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
---
|
|
112
|
-
|
|
113
|
-
## Database Errors
|
|
114
|
-
|
|
115
|
-
### RepositoryError
|
|
116
|
-
|
|
117
|
-
Error from repository operations with context.
|
|
118
|
-
|
|
119
|
-
```typescript
|
|
120
|
-
import { RepositoryError } from '@spfn/core/db';
|
|
121
|
-
|
|
122
|
-
// Automatically thrown by BaseRepository.withContext()
|
|
123
|
-
// Contains: repository name, method, table, original error
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
### PostgreSQL Error Conversion
|
|
127
|
-
|
|
128
|
-
```typescript
|
|
129
|
-
import { fromPostgresError } from '@spfn/core/db';
|
|
130
|
-
|
|
131
|
-
try
|
|
132
|
-
{
|
|
133
|
-
await db.insert(users).values(data);
|
|
134
|
-
}
|
|
135
|
-
catch (error)
|
|
136
|
-
{
|
|
137
|
-
const customError = fromPostgresError(error);
|
|
138
|
-
// 23505 → DuplicateEntryError
|
|
139
|
-
// 23503 → ConstraintViolationError
|
|
140
|
-
// 40P01 → DeadlockError
|
|
141
|
-
throw customError;
|
|
142
|
-
}
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
---
|
|
146
|
-
|
|
147
|
-
## Error Handling in Routes
|
|
148
|
-
|
|
149
|
-
### Simple Throw
|
|
150
|
-
|
|
151
|
-
```typescript
|
|
152
|
-
route.get('/users/:id')
|
|
153
|
-
.handler(async (c) => {
|
|
154
|
-
const user = await userRepo.findById(id);
|
|
155
|
-
|
|
156
|
-
if (!user)
|
|
157
|
-
{
|
|
158
|
-
throw new NotFoundError('User');
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return user;
|
|
162
|
-
});
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
### With HttpError
|
|
166
|
-
|
|
167
|
-
```typescript
|
|
168
|
-
route.post('/login')
|
|
169
|
-
.handler(async (c) => {
|
|
170
|
-
const { body } = await c.data();
|
|
171
|
-
const user = await userRepo.findByEmail(body.email);
|
|
172
|
-
|
|
173
|
-
if (!user || !await verifyPassword(body.password, user.password))
|
|
174
|
-
{
|
|
175
|
-
throw new UnauthorizedError('Invalid credentials');
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return { token: generateToken(user) };
|
|
179
|
-
});
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
### Validation in Repository
|
|
183
|
-
|
|
184
|
-
```typescript
|
|
185
|
-
// repository
|
|
186
|
-
async createUser(data: NewUser)
|
|
187
|
-
{
|
|
188
|
-
const existing = await this._findOne(users, { email: data.email });
|
|
189
|
-
if (existing)
|
|
190
|
-
{
|
|
191
|
-
throw new ConflictError('Email already exists');
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return this._create(users, data);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// route
|
|
198
|
-
route.post('/users')
|
|
199
|
-
.handler(async (c) => {
|
|
200
|
-
const { body } = await c.data();
|
|
201
|
-
return userRepo.createUser(body); // Throws ConflictError if exists
|
|
202
|
-
});
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
---
|
|
206
|
-
|
|
207
|
-
## Error Response Format
|
|
208
|
-
|
|
209
|
-
All errors are converted to JSON response:
|
|
210
|
-
|
|
211
|
-
```json
|
|
212
|
-
{
|
|
213
|
-
"error": "Error message",
|
|
214
|
-
"code": "ERROR_CODE",
|
|
215
|
-
"statusCode": 404
|
|
216
|
-
}
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
**Validation errors:**
|
|
220
|
-
|
|
221
|
-
```json
|
|
222
|
-
{
|
|
223
|
-
"error": "Validation failed",
|
|
224
|
-
"fields": [
|
|
225
|
-
{ "path": "/email", "message": "Invalid format" }
|
|
226
|
-
],
|
|
227
|
-
"statusCode": 400
|
|
228
|
-
}
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
---
|
|
232
|
-
|
|
233
|
-
## Global Error Handler
|
|
234
|
-
|
|
235
|
-
Errors are caught by global error middleware:
|
|
236
|
-
|
|
237
|
-
```typescript
|
|
238
|
-
// Automatic - no setup needed
|
|
239
|
-
// Standard Error → 500 Internal Server Error
|
|
240
|
-
// HttpError → Custom status code
|
|
241
|
-
// ValidationError → 400 with field details
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
---
|
|
245
|
-
|
|
246
|
-
## Custom Error Classes
|
|
247
|
-
|
|
248
|
-
```typescript
|
|
249
|
-
import { HttpError } from '@spfn/core/errors';
|
|
250
|
-
|
|
251
|
-
export class PaymentRequiredError extends HttpError
|
|
252
|
-
{
|
|
253
|
-
constructor(message = 'Payment required')
|
|
254
|
-
{
|
|
255
|
-
super(402, message);
|
|
256
|
-
this.name = 'PaymentRequiredError';
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
export class TooManyRequestsError extends HttpError
|
|
261
|
-
{
|
|
262
|
-
constructor(retryAfter?: number)
|
|
263
|
-
{
|
|
264
|
-
super(429, 'Too many requests');
|
|
265
|
-
this.name = 'TooManyRequestsError';
|
|
266
|
-
if (retryAfter)
|
|
267
|
-
{
|
|
268
|
-
this.headers = { 'Retry-After': String(retryAfter) };
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
---
|
|
275
|
-
|
|
276
|
-
## Best Practices
|
|
277
|
-
|
|
278
|
-
### Do
|
|
279
|
-
|
|
280
|
-
```typescript
|
|
281
|
-
// 1. Use specific error types
|
|
282
|
-
throw new NotFoundError('User'); // Not: throw new Error('User not found');
|
|
283
|
-
|
|
284
|
-
// 2. Provide meaningful messages
|
|
285
|
-
throw new ForbiddenError('Only admins can delete users');
|
|
286
|
-
|
|
287
|
-
// 3. Throw errors from repository for business logic
|
|
288
|
-
async createUser(data) {
|
|
289
|
-
if (await this.emailExists(data.email)) {
|
|
290
|
-
throw new ConflictError('Email already exists');
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// 4. Let errors propagate - don't catch and re-throw
|
|
295
|
-
route.handler(async (c) => {
|
|
296
|
-
return userRepo.create(data); // Let errors propagate
|
|
297
|
-
});
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
### Don't
|
|
301
|
-
|
|
302
|
-
```typescript
|
|
303
|
-
// 1. Don't use generic Error for HTTP errors
|
|
304
|
-
throw new Error('Not found'); // Use NotFoundError
|
|
305
|
-
|
|
306
|
-
// 2. Don't catch errors just to log
|
|
307
|
-
try {
|
|
308
|
-
await userRepo.create(data);
|
|
309
|
-
} catch (e) {
|
|
310
|
-
console.log(e); // Bad - error handling does this
|
|
311
|
-
throw e;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// 3. Don't return error objects
|
|
315
|
-
return { error: 'Not found' }; // Throw instead
|
|
316
|
-
|
|
317
|
-
// 4. Don't expose internal error details
|
|
318
|
-
throw new HttpError(500, error.stack); // Bad - security risk
|
|
319
|
-
```
|
package/docs/event.md
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
# Event
|
|
2
|
-
|
|
3
|
-
In-process event system for decoupled communication.
|
|
4
|
-
|
|
5
|
-
## Define Events
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
// src/server/events/index.ts
|
|
9
|
-
import { defineEvent, defineEventHandler } from '@spfn/core/event';
|
|
10
|
-
|
|
11
|
-
// Define event types
|
|
12
|
-
export const userCreated = defineEvent<{
|
|
13
|
-
userId: string;
|
|
14
|
-
email: string;
|
|
15
|
-
}>('user.created');
|
|
16
|
-
|
|
17
|
-
export const userUpdated = defineEvent<{
|
|
18
|
-
userId: string;
|
|
19
|
-
changes: Record<string, any>;
|
|
20
|
-
}>('user.updated');
|
|
21
|
-
|
|
22
|
-
export const userDeleted = defineEvent<{
|
|
23
|
-
userId: string;
|
|
24
|
-
}>('user.deleted');
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## Emit Events
|
|
28
|
-
|
|
29
|
-
```typescript
|
|
30
|
-
import { emit } from '@spfn/core/event';
|
|
31
|
-
import { userCreated } from './events';
|
|
32
|
-
|
|
33
|
-
// In repository or service
|
|
34
|
-
async function createUser(data: NewUser)
|
|
35
|
-
{
|
|
36
|
-
const user = await this._create(users, data);
|
|
37
|
-
|
|
38
|
-
await emit(userCreated, {
|
|
39
|
-
userId: user.id,
|
|
40
|
-
email: user.email
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
return user;
|
|
44
|
-
}
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
## Handle Events
|
|
48
|
-
|
|
49
|
-
```typescript
|
|
50
|
-
import { on } from '@spfn/core/event';
|
|
51
|
-
import { userCreated, userDeleted } from './events';
|
|
52
|
-
|
|
53
|
-
// Register handlers
|
|
54
|
-
on(userCreated, async (payload) => {
|
|
55
|
-
// Send welcome email
|
|
56
|
-
await emailService.sendWelcome(payload.email);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
on(userCreated, async (payload) => {
|
|
60
|
-
// Create default settings
|
|
61
|
-
await settingsRepo.createDefaults(payload.userId);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
on(userDeleted, async (payload) => {
|
|
65
|
-
// Cleanup related data
|
|
66
|
-
await cleanupUserData(payload.userId);
|
|
67
|
-
});
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
## Handler Registration
|
|
71
|
-
|
|
72
|
-
```typescript
|
|
73
|
-
// src/server/events/handlers.ts
|
|
74
|
-
import { on } from '@spfn/core/event';
|
|
75
|
-
import { userCreated, userUpdated, userDeleted } from './index';
|
|
76
|
-
|
|
77
|
-
// Register all handlers
|
|
78
|
-
export function registerEventHandlers()
|
|
79
|
-
{
|
|
80
|
-
on(userCreated, handleUserCreated);
|
|
81
|
-
on(userUpdated, handleUserUpdated);
|
|
82
|
-
on(userDeleted, handleUserDeleted);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Call in server startup
|
|
86
|
-
import { registerEventHandlers } from './events/handlers';
|
|
87
|
-
registerEventHandlers();
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
## Best Practices
|
|
91
|
-
|
|
92
|
-
```typescript
|
|
93
|
-
// 1. Define events in a central location
|
|
94
|
-
// src/server/events/index.ts
|
|
95
|
-
|
|
96
|
-
// 2. Use descriptive event names
|
|
97
|
-
defineEvent('user.created')
|
|
98
|
-
defineEvent('order.completed')
|
|
99
|
-
defineEvent('payment.failed')
|
|
100
|
-
|
|
101
|
-
// 3. Keep payloads minimal
|
|
102
|
-
defineEvent<{ userId: string }>('user.deleted') // Just ID, not full user
|
|
103
|
-
|
|
104
|
-
// 4. Handle errors in handlers
|
|
105
|
-
on(userCreated, async (payload) => {
|
|
106
|
-
try {
|
|
107
|
-
await sendEmail(payload.email);
|
|
108
|
-
} catch (error) {
|
|
109
|
-
logger.error('Failed to send email', { error });
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
// 5. Use events for side effects, not core logic
|
|
114
|
-
// Core: await userRepo.create(data);
|
|
115
|
-
// Side effect: emit(userCreated, { ... });
|
|
116
|
-
```
|
package/docs/job.md
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
# Job
|
|
2
|
-
|
|
3
|
-
Background job processing.
|
|
4
|
-
|
|
5
|
-
## Define Jobs
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
// src/server/jobs/send-email.job.ts
|
|
9
|
-
import { defineJob } from '@spfn/core/job';
|
|
10
|
-
|
|
11
|
-
export const sendEmailJob = defineJob<{
|
|
12
|
-
to: string;
|
|
13
|
-
subject: string;
|
|
14
|
-
body: string;
|
|
15
|
-
}>({
|
|
16
|
-
name: 'send-email',
|
|
17
|
-
handler: async (payload) => {
|
|
18
|
-
await emailService.send({
|
|
19
|
-
to: payload.to,
|
|
20
|
-
subject: payload.subject,
|
|
21
|
-
body: payload.body
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## Enqueue Jobs
|
|
28
|
-
|
|
29
|
-
```typescript
|
|
30
|
-
import { enqueue } from '@spfn/core/job';
|
|
31
|
-
import { sendEmailJob } from './jobs/send-email.job';
|
|
32
|
-
|
|
33
|
-
// Enqueue for immediate processing
|
|
34
|
-
await enqueue(sendEmailJob, {
|
|
35
|
-
to: 'user@example.com',
|
|
36
|
-
subject: 'Welcome',
|
|
37
|
-
body: 'Welcome to our app!'
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
// Enqueue with delay
|
|
41
|
-
await enqueue(sendEmailJob, payload, {
|
|
42
|
-
delay: 60000 // 1 minute
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
// Enqueue with options
|
|
46
|
-
await enqueue(sendEmailJob, payload, {
|
|
47
|
-
priority: 'high',
|
|
48
|
-
attempts: 3,
|
|
49
|
-
backoff: 'exponential'
|
|
50
|
-
});
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
## Job Options
|
|
54
|
-
|
|
55
|
-
```typescript
|
|
56
|
-
defineJob({
|
|
57
|
-
name: 'process-image',
|
|
58
|
-
concurrency: 5, // Max concurrent jobs
|
|
59
|
-
attempts: 3, // Retry attempts
|
|
60
|
-
backoff: 'exponential', // Backoff strategy
|
|
61
|
-
timeout: 30000, // Job timeout (ms)
|
|
62
|
-
handler: async (payload) => {
|
|
63
|
-
// ...
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
## Scheduled Jobs
|
|
69
|
-
|
|
70
|
-
```typescript
|
|
71
|
-
import { schedule } from '@spfn/core/job';
|
|
72
|
-
|
|
73
|
-
// Run every hour
|
|
74
|
-
schedule('cleanup', '0 * * * *', async () => {
|
|
75
|
-
await cleanupExpiredSessions();
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
// Run daily at midnight
|
|
79
|
-
schedule('daily-report', '0 0 * * *', async () => {
|
|
80
|
-
await generateDailyReport();
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
// Run every 5 minutes
|
|
84
|
-
schedule('health-check', '*/5 * * * *', async () => {
|
|
85
|
-
await checkExternalServices();
|
|
86
|
-
});
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
## Job Registration
|
|
90
|
-
|
|
91
|
-
```typescript
|
|
92
|
-
// src/server/jobs/index.ts
|
|
93
|
-
import { registerJobs } from '@spfn/core/job';
|
|
94
|
-
import { sendEmailJob } from './send-email.job';
|
|
95
|
-
import { processImageJob } from './process-image.job';
|
|
96
|
-
|
|
97
|
-
export function initializeJobs()
|
|
98
|
-
{
|
|
99
|
-
registerJobs([
|
|
100
|
-
sendEmailJob,
|
|
101
|
-
processImageJob
|
|
102
|
-
]);
|
|
103
|
-
}
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
## Best Practices
|
|
107
|
-
|
|
108
|
-
```typescript
|
|
109
|
-
// 1. Keep jobs idempotent
|
|
110
|
-
handler: async (payload) => {
|
|
111
|
-
// Check if already processed
|
|
112
|
-
const existing = await db.findProcessed(payload.id);
|
|
113
|
-
if (existing) return;
|
|
114
|
-
|
|
115
|
-
await processItem(payload);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// 2. Use appropriate timeout
|
|
119
|
-
timeout: 30000 // Don't set too high
|
|
120
|
-
|
|
121
|
-
// 3. Handle failures gracefully
|
|
122
|
-
attempts: 3,
|
|
123
|
-
backoff: 'exponential'
|
|
124
|
-
|
|
125
|
-
// 4. Log job progress
|
|
126
|
-
handler: async (payload) => {
|
|
127
|
-
logger.info('Processing job', { jobId: payload.id });
|
|
128
|
-
// ...
|
|
129
|
-
logger.info('Job completed', { jobId: payload.id });
|
|
130
|
-
}
|
|
131
|
-
```
|
package/docs/logger.md
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
# Logger
|
|
2
|
-
|
|
3
|
-
Structured logging with context support.
|
|
4
|
-
|
|
5
|
-
## Basic Usage
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
import { logger } from '@spfn/core/logger';
|
|
9
|
-
|
|
10
|
-
logger.info('User created', { userId: '123' });
|
|
11
|
-
logger.warn('Rate limit approaching', { remaining: 10 });
|
|
12
|
-
logger.error('Failed to process', { error: err.message });
|
|
13
|
-
logger.debug('Processing request', { path: '/api/users' });
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
## Log Levels
|
|
17
|
-
|
|
18
|
-
| Level | Usage |
|
|
19
|
-
|-------|-------|
|
|
20
|
-
| `debug` | Development debugging |
|
|
21
|
-
| `info` | General information |
|
|
22
|
-
| `warn` | Warning conditions |
|
|
23
|
-
| `error` | Error conditions |
|
|
24
|
-
|
|
25
|
-
## Structured Logging
|
|
26
|
-
|
|
27
|
-
```typescript
|
|
28
|
-
// Good - structured data
|
|
29
|
-
logger.info('User login', {
|
|
30
|
-
userId: user.id,
|
|
31
|
-
email: user.email,
|
|
32
|
-
ip: request.ip
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
// Bad - string concatenation
|
|
36
|
-
logger.info(`User ${user.id} logged in from ${request.ip}`);
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
## Create Scoped Logger
|
|
40
|
-
|
|
41
|
-
```typescript
|
|
42
|
-
import { createLogger } from '@spfn/core/logger';
|
|
43
|
-
|
|
44
|
-
const userLogger = createLogger('user-service');
|
|
45
|
-
|
|
46
|
-
userLogger.info('Created user');
|
|
47
|
-
// Output: [user-service] Created user
|
|
48
|
-
|
|
49
|
-
const paymentLogger = createLogger('payment');
|
|
50
|
-
paymentLogger.error('Payment failed', { orderId: '123' });
|
|
51
|
-
// Output: [payment] Payment failed { orderId: '123' }
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
## Context Logging
|
|
55
|
-
|
|
56
|
-
```typescript
|
|
57
|
-
import { withLogContext } from '@spfn/core/logger';
|
|
58
|
-
|
|
59
|
-
// Add context to all logs in scope
|
|
60
|
-
await withLogContext({ requestId: '123', userId: 'abc' }, async () => {
|
|
61
|
-
logger.info('Processing request');
|
|
62
|
-
// Output includes: { requestId: '123', userId: 'abc' }
|
|
63
|
-
|
|
64
|
-
await doSomething();
|
|
65
|
-
logger.info('Request complete');
|
|
66
|
-
// Also includes context
|
|
67
|
-
});
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
## Log Format
|
|
71
|
-
|
|
72
|
-
Development (pretty):
|
|
73
|
-
```
|
|
74
|
-
2024-01-15 10:30:45 INFO User created { userId: '123', email: 'user@example.com' }
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
Production (JSON):
|
|
78
|
-
```json
|
|
79
|
-
{"timestamp":"2024-01-15T10:30:45.123Z","level":"info","message":"User created","userId":"123","email":"user@example.com"}
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
## Best Practices
|
|
83
|
-
|
|
84
|
-
```typescript
|
|
85
|
-
// 1. Use structured data
|
|
86
|
-
logger.info('Operation complete', { duration: 150, result: 'success' });
|
|
87
|
-
|
|
88
|
-
// 2. Include error details
|
|
89
|
-
logger.error('Request failed', {
|
|
90
|
-
error: err.message,
|
|
91
|
-
stack: err.stack,
|
|
92
|
-
path: req.path
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
// 3. Use appropriate levels
|
|
96
|
-
logger.debug(...) // Development only
|
|
97
|
-
logger.info(...) // Normal operations
|
|
98
|
-
logger.warn(...) // Potential issues
|
|
99
|
-
logger.error(...) // Errors requiring attention
|
|
100
|
-
|
|
101
|
-
// 4. Create scoped loggers for modules
|
|
102
|
-
const dbLogger = createLogger('database');
|
|
103
|
-
const authLogger = createLogger('auth');
|
|
104
|
-
|
|
105
|
-
// 5. Don't log sensitive data
|
|
106
|
-
logger.info('User login', { userId: '123' }); // Good
|
|
107
|
-
logger.info('User login', { password: '...' }); // Bad!
|
|
108
|
-
```
|