@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.
Files changed (64) hide show
  1. package/README.md +262 -1092
  2. package/dist/{boss-D-fGtVgM.d.ts → boss-DI1r4kTS.d.ts} +68 -11
  3. package/dist/codegen/index.d.ts +55 -8
  4. package/dist/codegen/index.js +179 -5
  5. package/dist/codegen/index.js.map +1 -1
  6. package/dist/config/index.d.ts +204 -6
  7. package/dist/config/index.js +44 -11
  8. package/dist/config/index.js.map +1 -1
  9. package/dist/db/index.d.ts +13 -0
  10. package/dist/db/index.js +92 -33
  11. package/dist/db/index.js.map +1 -1
  12. package/dist/env/index.d.ts +83 -3
  13. package/dist/env/index.js +83 -15
  14. package/dist/env/index.js.map +1 -1
  15. package/dist/env/loader.d.ts +95 -0
  16. package/dist/env/loader.js +78 -0
  17. package/dist/env/loader.js.map +1 -0
  18. package/dist/event/index.d.ts +29 -70
  19. package/dist/event/index.js +15 -1
  20. package/dist/event/index.js.map +1 -1
  21. package/dist/event/sse/client.d.ts +157 -0
  22. package/dist/event/sse/client.js +169 -0
  23. package/dist/event/sse/client.js.map +1 -0
  24. package/dist/event/sse/index.d.ts +46 -0
  25. package/dist/event/sse/index.js +205 -0
  26. package/dist/event/sse/index.js.map +1 -0
  27. package/dist/job/index.d.ts +54 -8
  28. package/dist/job/index.js +61 -12
  29. package/dist/job/index.js.map +1 -1
  30. package/dist/middleware/index.d.ts +124 -11
  31. package/dist/middleware/index.js +41 -7
  32. package/dist/middleware/index.js.map +1 -1
  33. package/dist/nextjs/index.d.ts +2 -2
  34. package/dist/nextjs/index.js +37 -5
  35. package/dist/nextjs/index.js.map +1 -1
  36. package/dist/nextjs/server.d.ts +45 -24
  37. package/dist/nextjs/server.js +87 -66
  38. package/dist/nextjs/server.js.map +1 -1
  39. package/dist/route/index.d.ts +207 -14
  40. package/dist/route/index.js +304 -31
  41. package/dist/route/index.js.map +1 -1
  42. package/dist/route/types.d.ts +2 -31
  43. package/dist/router-Di7ENoah.d.ts +151 -0
  44. package/dist/server/index.d.ts +321 -10
  45. package/dist/server/index.js +798 -189
  46. package/dist/server/index.js.map +1 -1
  47. package/dist/{types-DRG2XMTR.d.ts → types-7Mhoxnnt.d.ts} +97 -4
  48. package/dist/types-DHQMQlcb.d.ts +305 -0
  49. package/docs/cache.md +133 -0
  50. package/docs/codegen.md +74 -0
  51. package/docs/database.md +346 -0
  52. package/docs/entity.md +539 -0
  53. package/docs/env.md +499 -0
  54. package/docs/errors.md +319 -0
  55. package/docs/event.md +432 -0
  56. package/docs/file-upload.md +717 -0
  57. package/docs/job.md +131 -0
  58. package/docs/logger.md +108 -0
  59. package/docs/middleware.md +337 -0
  60. package/docs/nextjs.md +247 -0
  61. package/docs/repository.md +496 -0
  62. package/docs/route.md +497 -0
  63. package/docs/server.md +429 -0
  64. package/package.json +19 -3
package/docs/errors.md ADDED
@@ -0,0 +1,319 @@
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
+ ```