@spfn/core 0.2.0-beta.4 → 0.2.0-beta.42

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 (68) hide show
  1. package/README.md +260 -1175
  2. package/dist/{boss-BO8ty33K.d.ts → boss-DI1r4kTS.d.ts} +24 -7
  3. package/dist/cache/index.js +32 -29
  4. package/dist/cache/index.js.map +1 -1
  5. package/dist/codegen/index.d.ts +55 -8
  6. package/dist/codegen/index.js +179 -5
  7. package/dist/codegen/index.js.map +1 -1
  8. package/dist/config/index.d.ts +168 -6
  9. package/dist/config/index.js +29 -5
  10. package/dist/config/index.js.map +1 -1
  11. package/dist/db/index.d.ts +128 -4
  12. package/dist/db/index.js +177 -50
  13. package/dist/db/index.js.map +1 -1
  14. package/dist/env/index.d.ts +55 -1
  15. package/dist/env/index.js +71 -3
  16. package/dist/env/index.js.map +1 -1
  17. package/dist/env/loader.d.ts +27 -19
  18. package/dist/env/loader.js +33 -25
  19. package/dist/env/loader.js.map +1 -1
  20. package/dist/event/index.d.ts +27 -1
  21. package/dist/event/index.js +6 -1
  22. package/dist/event/index.js.map +1 -1
  23. package/dist/event/sse/client.d.ts +77 -2
  24. package/dist/event/sse/client.js +87 -24
  25. package/dist/event/sse/client.js.map +1 -1
  26. package/dist/event/sse/index.d.ts +10 -4
  27. package/dist/event/sse/index.js +158 -12
  28. package/dist/event/sse/index.js.map +1 -1
  29. package/dist/job/index.d.ts +23 -8
  30. package/dist/job/index.js +96 -20
  31. package/dist/job/index.js.map +1 -1
  32. package/dist/logger/index.d.ts +5 -0
  33. package/dist/logger/index.js +14 -0
  34. package/dist/logger/index.js.map +1 -1
  35. package/dist/middleware/index.d.ts +23 -1
  36. package/dist/middleware/index.js +58 -5
  37. package/dist/middleware/index.js.map +1 -1
  38. package/dist/nextjs/index.d.ts +2 -2
  39. package/dist/nextjs/index.js +77 -31
  40. package/dist/nextjs/index.js.map +1 -1
  41. package/dist/nextjs/server.d.ts +44 -23
  42. package/dist/nextjs/server.js +83 -65
  43. package/dist/nextjs/server.js.map +1 -1
  44. package/dist/route/index.d.ts +158 -4
  45. package/dist/route/index.js +253 -17
  46. package/dist/route/index.js.map +1 -1
  47. package/dist/server/index.d.ts +251 -16
  48. package/dist/server/index.js +774 -228
  49. package/dist/server/index.js.map +1 -1
  50. package/dist/{types-D_N_U-Py.d.ts → types-7Mhoxnnt.d.ts} +21 -1
  51. package/dist/types-DKQ90YL7.d.ts +372 -0
  52. package/docs/cache.md +133 -0
  53. package/docs/codegen.md +74 -0
  54. package/docs/database.md +370 -0
  55. package/docs/entity.md +539 -0
  56. package/docs/env.md +499 -0
  57. package/docs/errors.md +319 -0
  58. package/docs/event.md +443 -0
  59. package/docs/file-upload.md +717 -0
  60. package/docs/job.md +131 -0
  61. package/docs/logger.md +108 -0
  62. package/docs/middleware.md +337 -0
  63. package/docs/nextjs.md +247 -0
  64. package/docs/repository.md +496 -0
  65. package/docs/route.md +497 -0
  66. package/docs/server.md +429 -0
  67. package/package.json +3 -2
  68. package/dist/types-B-e_f2dQ.d.ts +0 -121
@@ -0,0 +1,370 @@
1
+ # Database
2
+
3
+ PostgreSQL database layer with Drizzle ORM, automatic transaction management, and read/write separation.
4
+
5
+ ## Setup
6
+
7
+ ### Environment Variables
8
+
9
+ ```bash
10
+ # Single database
11
+ DATABASE_URL=postgresql://localhost:5432/mydb
12
+
13
+ # Primary + Replica (recommended for production)
14
+ DATABASE_WRITE_URL=postgresql://primary:5432/mydb
15
+ DATABASE_READ_URL=postgresql://replica:5432/mydb
16
+ ```
17
+
18
+ ### Initialize
19
+
20
+ ```typescript
21
+ import { initDatabase } from '@spfn/core/db';
22
+
23
+ // Called automatically by startServer()
24
+ // Manual call only needed for scripts
25
+ await initDatabase();
26
+ ```
27
+
28
+ ---
29
+
30
+ ## Helper Functions
31
+
32
+ Standalone functions for simple database operations.
33
+
34
+ ```typescript
35
+ import {
36
+ findOne,
37
+ findMany,
38
+ create,
39
+ createMany,
40
+ upsert,
41
+ updateOne,
42
+ updateMany,
43
+ deleteOne,
44
+ deleteMany,
45
+ count
46
+ } from '@spfn/core/db';
47
+ ```
48
+
49
+ ### findOne
50
+
51
+ Find a single record.
52
+
53
+ ```typescript
54
+ // Object-based where (simple equality)
55
+ const user = await findOne(users, { id: '1' });
56
+ const user = await findOne(users, { email: 'test@example.com' });
57
+
58
+ // SQL-based where (complex conditions)
59
+ import { eq, and, gt } from 'drizzle-orm';
60
+ const user = await findOne(users, and(
61
+ eq(users.email, 'test@example.com'),
62
+ eq(users.isActive, true)
63
+ ));
64
+ ```
65
+
66
+ ### findMany
67
+
68
+ Find multiple records with filtering, ordering, and pagination.
69
+
70
+ ```typescript
71
+ // Simple
72
+ const allUsers = await findMany(users);
73
+
74
+ // With options
75
+ const activeUsers = await findMany(users, {
76
+ where: { isActive: true },
77
+ orderBy: desc(users.createdAt),
78
+ limit: 10,
79
+ offset: 0
80
+ });
81
+
82
+ // Complex where
83
+ const recentAdmins = await findMany(users, {
84
+ where: and(
85
+ eq(users.role, 'admin'),
86
+ gt(users.createdAt, lastWeek)
87
+ ),
88
+ orderBy: [desc(users.createdAt), asc(users.name)],
89
+ limit: 20
90
+ });
91
+ ```
92
+
93
+ ### create
94
+
95
+ Create a single record.
96
+
97
+ ```typescript
98
+ const user = await create(users, {
99
+ email: 'new@example.com',
100
+ name: 'New User'
101
+ });
102
+ ```
103
+
104
+ ### createMany
105
+
106
+ Create multiple records.
107
+
108
+ ```typescript
109
+ const newUsers = await createMany(users, [
110
+ { email: 'user1@example.com', name: 'User 1' },
111
+ { email: 'user2@example.com', name: 'User 2' }
112
+ ]);
113
+ ```
114
+
115
+ ### upsert
116
+
117
+ Insert or update on conflict.
118
+
119
+ ```typescript
120
+ const cache = await upsert(cmsCache, data, {
121
+ target: [cmsCache.section, cmsCache.locale],
122
+ set: {
123
+ content: data.content,
124
+ updatedAt: new Date()
125
+ }
126
+ });
127
+ ```
128
+
129
+ ### updateOne
130
+
131
+ Update a single record. Returns updated record or null.
132
+
133
+ ```typescript
134
+ const updated = await updateOne(users, { id: '1' }, { name: 'Updated Name' });
135
+ if (!updated)
136
+ {
137
+ throw new Error('User not found');
138
+ }
139
+ ```
140
+
141
+ ### updateMany
142
+
143
+ Update multiple records. Returns array of updated records.
144
+
145
+ ```typescript
146
+ const updated = await updateMany(
147
+ users,
148
+ { role: 'guest' },
149
+ { isActive: false }
150
+ );
151
+ ```
152
+
153
+ ### deleteOne
154
+
155
+ Delete a single record. Returns deleted record or null.
156
+
157
+ ```typescript
158
+ const deleted = await deleteOne(users, { id: '1' });
159
+ ```
160
+
161
+ ### deleteMany
162
+
163
+ Delete multiple records. Returns array of deleted records.
164
+
165
+ ```typescript
166
+ const deleted = await deleteMany(users, { isActive: false });
167
+ ```
168
+
169
+ ### count
170
+
171
+ Count records.
172
+
173
+ ```typescript
174
+ const total = await count(users);
175
+ const activeCount = await count(users, { isActive: true });
176
+ const adminCount = await count(users, eq(users.role, 'admin'));
177
+ ```
178
+
179
+ ---
180
+
181
+ ## Transaction
182
+
183
+ ### Transactional Middleware
184
+
185
+ Use in routes for automatic commit/rollback.
186
+
187
+ ```typescript
188
+ import { Transactional } from '@spfn/core/db';
189
+
190
+ route.post('/users')
191
+ .use([Transactional()])
192
+ .handler(async (c) => {
193
+ // Auto commit on success
194
+ // Auto rollback on error
195
+ return userRepo.create(body);
196
+ });
197
+ ```
198
+
199
+ **With options:**
200
+
201
+ ```typescript
202
+ Transactional({
203
+ timeout: 30000, // Transaction timeout (ms)
204
+ logSuccess: false, // Log successful transactions
205
+ logErrors: true // Log failed transactions
206
+ })
207
+ ```
208
+
209
+ ### Manual Transaction
210
+
211
+ For complex multi-operation scenarios.
212
+
213
+ ```typescript
214
+ import { runWithTransaction } from '@spfn/core/db';
215
+
216
+ await runWithTransaction(async () => {
217
+ const user = await userRepo.create(userData);
218
+ await profileRepo.create({ userId: user.id, ...profileData });
219
+ await emailService.sendWelcome(user.email);
220
+ // All succeed or all rollback
221
+ });
222
+ ```
223
+
224
+ ### After-Commit Hooks
225
+
226
+ Schedule side effects to run only after the transaction commits.
227
+
228
+ ```typescript
229
+ import { onAfterCommit } from '@spfn/core/db';
230
+
231
+ async function submitRequest(spaceId: string, chatId: string)
232
+ {
233
+ const publication = await publicationRepo.create({ spaceId, chatId });
234
+ await requestRepo.updateStatusAtomically(requestId, 'submitted');
235
+
236
+ // Runs after commit, fire-and-forget
237
+ onAfterCommit(() => generateArticle(spaceId, chatId, publication.id));
238
+
239
+ return publication;
240
+ }
241
+ ```
242
+
243
+ - Inside transaction: queued, executed after root commit
244
+ - Outside transaction: executed immediately
245
+ - Nested transactions: callbacks bubble up to root
246
+ - Errors are logged, never thrown
247
+
248
+ ### Get Current Transaction
249
+
250
+ Access the current transaction context.
251
+
252
+ ```typescript
253
+ import { getTransaction } from '@spfn/core/db';
254
+
255
+ async function customDbOperation()
256
+ {
257
+ const tx = getTransaction();
258
+ if (tx)
259
+ {
260
+ // Inside transaction
261
+ await tx.insert(users).values(data);
262
+ }
263
+ else
264
+ {
265
+ // Not in transaction
266
+ const db = getDatabase('write');
267
+ await db.insert(users).values(data);
268
+ }
269
+ }
270
+ ```
271
+
272
+ ---
273
+
274
+ ## Direct Database Access
275
+
276
+ For complex queries not covered by helpers.
277
+
278
+ ```typescript
279
+ import { getDatabase } from '@spfn/core/db';
280
+
281
+ // Read operations (uses replica if available)
282
+ const db = getDatabase('read');
283
+ const results = await db
284
+ .select({
285
+ user: users,
286
+ postsCount: sql`count(${posts.id})`
287
+ })
288
+ .from(users)
289
+ .leftJoin(posts, eq(users.id, posts.authorId))
290
+ .groupBy(users.id);
291
+
292
+ // Write operations (always uses primary)
293
+ const db = getDatabase('write');
294
+ await db.insert(users).values(data);
295
+ ```
296
+
297
+ ---
298
+
299
+ ## Connection Info
300
+
301
+ ```typescript
302
+ import { getDatabaseInfo, checkConnection } from '@spfn/core/db';
303
+
304
+ // Get connection status
305
+ const info = getDatabaseInfo();
306
+ // { hasWriteDb: true, hasReadDb: true, pattern: 'write-read' }
307
+
308
+ // Health check
309
+ const isHealthy = await checkConnection(getDatabase('write'));
310
+ ```
311
+
312
+ ---
313
+
314
+ ## Cleanup
315
+
316
+ ```typescript
317
+ import { closeDatabase } from '@spfn/core/db';
318
+
319
+ // Called automatically on graceful shutdown
320
+ // Manual call for scripts/tests
321
+ await closeDatabase();
322
+ ```
323
+
324
+ ---
325
+
326
+ ## Best Practices
327
+
328
+ ### Do
329
+
330
+ ```typescript
331
+ // 1. Use Transactional for write routes
332
+ route.post('/users')
333
+ .use([Transactional()])
334
+ .handler(...)
335
+
336
+ // 2. Use repository pattern for data access
337
+ const user = await userRepo.findById(id);
338
+
339
+ // 3. Use read database for read operations
340
+ async findAll()
341
+ {
342
+ return this._findMany(users); // BaseRepository uses readDb
343
+ }
344
+
345
+ // 4. Close connections in tests
346
+ afterAll(async () => {
347
+ await closeDatabase();
348
+ });
349
+ ```
350
+
351
+ ### Don't
352
+
353
+ ```typescript
354
+ // 1. Don't forget Transactional for writes
355
+ route.post('/users')
356
+ .handler(async (c) => { // Missing Transactional!
357
+ await userRepo.create(body);
358
+ });
359
+
360
+ // 2. Don't bypass repository in routes
361
+ route.get('/users')
362
+ .handler(async (c) => {
363
+ // Bad - use repository
364
+ return getDatabase('read').select().from(users);
365
+ });
366
+
367
+ // 3. Don't use write database for reads
368
+ const db = getDatabase('write'); // Bad for read queries
369
+ await db.select().from(users);
370
+ ```