@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
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
- ```