@spfn/core 0.1.0-alpha.88 → 0.2.0-beta.10

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 (97) hide show
  1. package/README.md +298 -466
  2. package/dist/boss-DI1r4kTS.d.ts +244 -0
  3. package/dist/cache/index.d.ts +13 -33
  4. package/dist/cache/index.js +14 -703
  5. package/dist/cache/index.js.map +1 -1
  6. package/dist/codegen/index.d.ts +214 -17
  7. package/dist/codegen/index.js +231 -1420
  8. package/dist/codegen/index.js.map +1 -1
  9. package/dist/config/index.d.ts +1227 -0
  10. package/dist/config/index.js +273 -0
  11. package/dist/config/index.js.map +1 -0
  12. package/dist/db/index.d.ts +741 -59
  13. package/dist/db/index.js +1063 -1226
  14. package/dist/db/index.js.map +1 -1
  15. package/dist/env/index.d.ts +658 -308
  16. package/dist/env/index.js +503 -928
  17. package/dist/env/index.js.map +1 -1
  18. package/dist/env/loader.d.ts +87 -0
  19. package/dist/env/loader.js +70 -0
  20. package/dist/env/loader.js.map +1 -0
  21. package/dist/errors/index.d.ts +417 -29
  22. package/dist/errors/index.js +359 -98
  23. package/dist/errors/index.js.map +1 -1
  24. package/dist/event/index.d.ts +41 -0
  25. package/dist/event/index.js +131 -0
  26. package/dist/event/index.js.map +1 -0
  27. package/dist/event/sse/client.d.ts +82 -0
  28. package/dist/event/sse/client.js +115 -0
  29. package/dist/event/sse/client.js.map +1 -0
  30. package/dist/event/sse/index.d.ts +40 -0
  31. package/dist/event/sse/index.js +92 -0
  32. package/dist/event/sse/index.js.map +1 -0
  33. package/dist/job/index.d.ts +218 -0
  34. package/dist/job/index.js +410 -0
  35. package/dist/job/index.js.map +1 -0
  36. package/dist/logger/index.d.ts +20 -79
  37. package/dist/logger/index.js +82 -387
  38. package/dist/logger/index.js.map +1 -1
  39. package/dist/middleware/index.d.ts +102 -20
  40. package/dist/middleware/index.js +51 -705
  41. package/dist/middleware/index.js.map +1 -1
  42. package/dist/nextjs/index.d.ts +120 -0
  43. package/dist/nextjs/index.js +448 -0
  44. package/dist/nextjs/index.js.map +1 -0
  45. package/dist/{client/nextjs/index.d.ts → nextjs/server.d.ts} +335 -262
  46. package/dist/nextjs/server.js +637 -0
  47. package/dist/nextjs/server.js.map +1 -0
  48. package/dist/route/index.d.ts +879 -25
  49. package/dist/route/index.js +697 -1271
  50. package/dist/route/index.js.map +1 -1
  51. package/dist/route/types.d.ts +9 -0
  52. package/dist/route/types.js +3 -0
  53. package/dist/route/types.js.map +1 -0
  54. package/dist/router-Di7ENoah.d.ts +151 -0
  55. package/dist/server/index.d.ts +345 -64
  56. package/dist/server/index.js +1174 -3233
  57. package/dist/server/index.js.map +1 -1
  58. package/dist/types-B-e_f2dQ.d.ts +121 -0
  59. package/dist/types-BGl4QL1w.d.ts +77 -0
  60. package/dist/types-BOPTApC2.d.ts +245 -0
  61. package/docs/cache.md +133 -0
  62. package/docs/codegen.md +74 -0
  63. package/docs/database.md +346 -0
  64. package/docs/entity.md +539 -0
  65. package/docs/env.md +477 -0
  66. package/docs/errors.md +319 -0
  67. package/docs/event.md +116 -0
  68. package/docs/file-upload.md +717 -0
  69. package/docs/job.md +131 -0
  70. package/docs/logger.md +108 -0
  71. package/docs/middleware.md +337 -0
  72. package/docs/nextjs.md +241 -0
  73. package/docs/repository.md +496 -0
  74. package/docs/route.md +497 -0
  75. package/docs/server.md +307 -0
  76. package/package.json +68 -48
  77. package/dist/auto-loader-JFaZ9gON.d.ts +0 -80
  78. package/dist/client/index.d.ts +0 -358
  79. package/dist/client/index.js +0 -357
  80. package/dist/client/index.js.map +0 -1
  81. package/dist/client/nextjs/index.js +0 -371
  82. package/dist/client/nextjs/index.js.map +0 -1
  83. package/dist/codegen/generators/index.d.ts +0 -19
  84. package/dist/codegen/generators/index.js +0 -1404
  85. package/dist/codegen/generators/index.js.map +0 -1
  86. package/dist/database-errors-BNNmLTJE.d.ts +0 -86
  87. package/dist/events/index.d.ts +0 -183
  88. package/dist/events/index.js +0 -77
  89. package/dist/events/index.js.map +0 -1
  90. package/dist/index-DHiAqhKv.d.ts +0 -101
  91. package/dist/index.d.ts +0 -8
  92. package/dist/index.js +0 -3674
  93. package/dist/index.js.map +0 -1
  94. package/dist/types/index.d.ts +0 -121
  95. package/dist/types/index.js +0 -38
  96. package/dist/types/index.js.map +0 -1
  97. package/dist/types-BXibIEyj.d.ts +0 -60
package/README.md CHANGED
@@ -1,561 +1,393 @@
1
1
  # @spfn/core
2
2
 
3
- > Core framework for building type-safe backend APIs with Next.js and Hono
3
+ Type-safe Node.js backend framework built on Hono + Drizzle ORM.
4
4
 
5
5
  [![npm version](https://badge.fury.io/js/@spfn%2Fcore.svg)](https://www.npmjs.com/package/@spfn/core)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.3-blue)](https://www.typescriptlang.org/)
8
8
 
9
- > ⚠️ **Alpha Release**: SPFN is currently in alpha. APIs may change. Use `@alpha` tag for installation.
9
+ > **Beta Release**: Core APIs are stable but may have minor changes before 1.0.
10
10
 
11
11
  ## Installation
12
12
 
13
- **Recommended: Create New Project**
14
13
  ```bash
15
- npx spfn@alpha create my-app
14
+ pnpm add @spfn/core
16
15
  ```
17
16
 
18
- **Add to Existing Next.js Project**
19
- ```bash
20
- cd your-nextjs-project
21
- npx spfn@alpha init
22
- ```
17
+ ## Quick Start
23
18
 
24
- **Manual Installation**
25
- ```bash
26
- npm install @spfn/core hono drizzle-orm postgres @sinclair/typebox
19
+ ### 1. Define Entity
20
+
21
+ ```typescript
22
+ // src/server/entities/users.ts
23
+ import { pgTable, text, boolean } from 'drizzle-orm/pg-core';
24
+ import { id, timestamps } from '@spfn/core/db';
25
+
26
+ export const users = pgTable('users', {
27
+ id: id(),
28
+ email: text('email').notNull().unique(),
29
+ name: text('name').notNull(),
30
+ isActive: boolean('is_active').notNull().default(true),
31
+ ...timestamps()
32
+ });
33
+
34
+ export type User = typeof users.$inferSelect;
35
+ export type NewUser = typeof users.$inferInsert;
27
36
  ```
28
37
 
29
- ## Quick Start
38
+ ### 2. Create Repository
39
+
40
+ ```typescript
41
+ // src/server/repositories/user.repository.ts
42
+ import { BaseRepository } from '@spfn/core/db';
43
+ import { users, type User, type NewUser } from '../entities/users';
44
+
45
+ export class UserRepository extends BaseRepository
46
+ {
47
+ async findById(id: string): Promise<User | null>
48
+ {
49
+ return this._findOne(users, { id });
50
+ }
51
+
52
+ async findByEmail(email: string): Promise<User | null>
53
+ {
54
+ return this._findOne(users, { email });
55
+ }
56
+
57
+ async findAll(): Promise<User[]>
58
+ {
59
+ return this._findMany(users);
60
+ }
61
+
62
+ async create(data: NewUser): Promise<User>
63
+ {
64
+ return this._create(users, data);
65
+ }
66
+
67
+ async update(id: string, data: Partial<NewUser>): Promise<User | null>
68
+ {
69
+ return this._updateOne(users, { id }, data);
70
+ }
71
+
72
+ async delete(id: string): Promise<User | null>
73
+ {
74
+ return this._deleteOne(users, { id });
75
+ }
76
+ }
77
+ ```
30
78
 
31
- ### 1. Define a Contract
79
+ ### 3. Define Routes
32
80
 
33
81
  ```typescript
34
- // src/server/routes/users/contract.ts
82
+ // src/server/routes/users.ts
83
+ import { route } from '@spfn/core/route';
84
+ import { Transactional } from '@spfn/core/db';
35
85
  import { Type } from '@sinclair/typebox';
36
-
37
- export const getUsersContract = {
38
- method: 'GET' as const,
39
- path: '/',
40
- query: Type.Object({
41
- page: Type.Optional(Type.Number()),
42
- limit: Type.Optional(Type.Number()),
43
- }),
44
- response: Type.Object({
45
- users: Type.Array(Type.Object({
46
- id: Type.Number(),
47
- name: Type.String(),
48
- email: Type.String(),
49
- })),
50
- total: Type.Number(),
51
- }),
52
- };
86
+ import { UserRepository } from '../repositories/user.repository';
87
+
88
+ const userRepo = new UserRepository();
89
+
90
+ export const getUsers = route.get('/users')
91
+ .handler(async () => {
92
+ return userRepo.findAll();
93
+ });
94
+
95
+ export const getUser = route.get('/users/:id')
96
+ .input({
97
+ params: Type.Object({ id: Type.String() })
98
+ })
99
+ .handler(async (c) => {
100
+ const { params } = await c.data();
101
+ const user = await userRepo.findById(params.id);
102
+
103
+ if (!user)
104
+ {
105
+ throw new Error('User not found');
106
+ }
107
+
108
+ return user;
109
+ });
110
+
111
+ export const createUser = route.post('/users')
112
+ .input({
113
+ body: Type.Object({
114
+ email: Type.String({ format: 'email' }),
115
+ name: Type.String({ minLength: 1 })
116
+ })
117
+ })
118
+ .use([Transactional()])
119
+ .handler(async (c) => {
120
+ const { body } = await c.data();
121
+ return userRepo.create(body);
122
+ });
123
+
124
+ export const updateUser = route.patch('/users/:id')
125
+ .input({
126
+ params: Type.Object({ id: Type.String() }),
127
+ body: Type.Object({
128
+ email: Type.Optional(Type.String({ format: 'email' })),
129
+ name: Type.Optional(Type.String({ minLength: 1 }))
130
+ })
131
+ })
132
+ .use([Transactional()])
133
+ .handler(async (c) => {
134
+ const { params, body } = await c.data();
135
+ const user = await userRepo.update(params.id, body);
136
+
137
+ if (!user)
138
+ {
139
+ throw new Error('User not found');
140
+ }
141
+
142
+ return user;
143
+ });
144
+
145
+ export const deleteUser = route.delete('/users/:id')
146
+ .input({
147
+ params: Type.Object({ id: Type.String() })
148
+ })
149
+ .use([Transactional()])
150
+ .handler(async (c) => {
151
+ const { params } = await c.data();
152
+ const user = await userRepo.delete(params.id);
153
+
154
+ if (!user)
155
+ {
156
+ throw new Error('User not found');
157
+ }
158
+
159
+ return { success: true };
160
+ });
53
161
  ```
54
162
 
55
- ### 2. Create a Route
163
+ ### 4. Configure Server
56
164
 
57
165
  ```typescript
58
- // src/server/routes/users/index.ts
59
- import { createApp } from '@spfn/core/route';
60
- import { getUsersContract } from './contract.js';
61
- import { findMany } from '@spfn/core/db';
62
- import { users } from '../../entities/users.js';
166
+ // src/server/server.config.ts
167
+ import { defineServerConfig, defineRouter } from '@spfn/core/server';
168
+ import * as userRoutes from './routes/users';
63
169
 
64
- const app = createApp();
170
+ const appRouter = defineRouter({
171
+ ...userRoutes
172
+ });
65
173
 
66
- app.bind(getUsersContract, async (c) => {
67
- const { page = 1, limit = 10 } = c.query;
174
+ export default defineServerConfig()
175
+ .port(8790)
176
+ .routes(appRouter)
177
+ .build();
68
178
 
69
- // Use helper function directly - no Repository needed
70
- const offset = (page - 1) * limit;
71
- const result = await findMany(users, { limit, offset });
179
+ export type AppRouter = typeof appRouter;
180
+ ```
72
181
 
73
- return c.json({ users: result, total: result.length });
74
- });
182
+ ### 5. Start Server
75
183
 
76
- export default app;
184
+ ```typescript
185
+ // src/server/index.ts
186
+ import { startServer } from '@spfn/core/server';
187
+
188
+ await startServer();
77
189
  ```
78
190
 
79
- ### 3. Start Server
191
+ ### 6. Environment Variables
80
192
 
81
193
  ```bash
82
- npm run spfn:dev
83
- # Server starts on http://localhost:8790
194
+ # .env
195
+ DATABASE_URL=postgresql://localhost:5432/mydb
196
+ PORT=8790
84
197
  ```
85
198
 
86
- ## Architecture Pattern
199
+ ---
87
200
 
88
- Superfunction follows a **layered architecture** that separates concerns and keeps code maintainable:
201
+ ## Architecture Overview
89
202
 
90
203
  ```
91
204
  ┌─────────────────────────────────────────┐
92
- │ Routes Layer │ HTTP handlers, contracts
93
- - Define API contracts (TypeBox)
94
- │ - Handle requests/responses │
95
- │ - Thin handlers │
205
+ │ Routes Layer │ API endpoints + validation
206
+ route.get('/users/:id').handler(...)
96
207
  └──────────────┬──────────────────────────┘
97
208
 
98
209
  ┌──────────────▼──────────────────────────┐
99
- Service Layer │ Business logic
100
- - Orchestrate operations
101
- │ - Implement business rules │
102
- │ - Use helper functions or custom logic │
210
+ Repository Layer │ Business logic + data access
211
+ class UserRepository extends Base...
103
212
  └──────────────┬──────────────────────────┘
104
213
 
105
214
  ┌──────────────▼──────────────────────────┐
106
- Data Access Layer │ Database operations
107
- - Use helper functions (findOne, etc)
108
- │ - Custom queries with Drizzle │
109
- │ - Domain-specific wrappers │
110
- └──────────────┬──────────────────────────┘
111
-
112
- ┌──────────────▼──────────────────────────┐
113
- │ Entity Layer │ Database schema
114
- │ - Table definitions (Drizzle) │
115
- │ - Type inference │
116
- │ - Schema helpers │
215
+ Entity Layer │ Database schema (Drizzle)
216
+ pgTable('users', { id, email, ... })
117
217
  └─────────────────────────────────────────┘
118
218
  ```
119
219
 
120
- ### Complete Example: Blog Post System
121
-
122
- **1. Entity Layer** - Define database schema
123
-
124
- ```typescript
125
- // src/server/entities/posts.ts
126
- import { pgTable, text } from 'drizzle-orm/pg-core';
127
- import { id, timestamps } from '@spfn/core/db';
128
-
129
- export const posts = pgTable('posts', {
130
- id: id(),
131
- title: text('title').notNull(),
132
- slug: text('slug').notNull().unique(),
133
- content: text('content').notNull(),
134
- status: text('status', {
135
- enum: ['draft', 'published']
136
- }).notNull().default('draft'),
137
- ...timestamps(),
138
- });
139
-
140
- export type Post = typeof posts.$inferSelect;
141
- export type NewPost = typeof posts.$inferInsert;
142
- ```
143
-
144
- **2. Data Access Layer** - Helper functions with domain-specific wrappers
220
+ **Key Principles:**
221
+ - **Route**: Thin layer, input validation only
222
+ - **Repository**: All business logic and DB access
223
+ - **Entity**: Schema definition only, no logic
145
224
 
146
- ```typescript
147
- // src/server/repositories/posts.repository.ts
148
- import { findOne, findMany, create as createHelper } from '@spfn/core/db';
149
- import { eq, desc } from 'drizzle-orm';
150
- import { posts, type Post, type NewPost } from '../entities/posts';
151
-
152
- // Domain-specific wrappers using helper functions
153
- export async function findPostBySlug(slug: string): Promise<Post | null> {
154
- return findOne(posts, { slug });
155
- }
156
-
157
- export async function findPublishedPosts(): Promise<Post[]> {
158
- return findMany(posts, {
159
- where: { status: 'published' },
160
- orderBy: desc(posts.createdAt)
161
- });
162
- }
163
-
164
- export async function createPost(data: NewPost): Promise<Post> {
165
- return createHelper(posts, data);
166
- }
167
-
168
- // Or use helper functions directly in routes for simple cases
169
- // const post = await findOne(posts, { id: 1 });
170
- ```
171
-
172
- **3. Routes Layer** - HTTP API
225
+ ---
173
226
 
174
- ```typescript
175
- // src/server/routes/posts/contracts.ts
176
- import { Type } from '@sinclair/typebox';
227
+ ## Directory Structure
177
228
 
178
- export const createPostContract = {
179
- method: 'POST' as const,
180
- path: '/',
181
- body: Type.Object({
182
- title: Type.String(),
183
- content: Type.String(),
184
- }),
185
- response: Type.Object({
186
- id: Type.String(),
187
- title: Type.String(),
188
- slug: Type.String(),
189
- }),
190
- };
191
-
192
- export const listPostsContract = {
193
- method: 'GET' as const,
194
- path: '/',
195
- response: Type.Array(Type.Object({
196
- id: Type.String(),
197
- title: Type.String(),
198
- slug: Type.String(),
199
- })),
200
- };
201
229
  ```
202
-
203
- ```typescript
204
- // src/server/routes/posts/index.ts
205
- import { createApp } from '@spfn/core/route';
206
- import { Transactional } from '@spfn/core/db';
207
- import { ConflictError } from '@spfn/core/errors';
208
- import { findPostBySlug, createPost, findPublishedPosts } from '../../repositories/posts.repository';
209
- import { createPostContract, listPostsContract } from './contracts';
210
-
211
- const app = createApp();
212
-
213
- // POST /posts - Create new post (with transaction)
214
- app.bind(createPostContract, [Transactional()], async (c) => {
215
- const body = await c.data();
216
-
217
- // Generate slug from title
218
- const slug = body.title.toLowerCase()
219
- .replace(/[^a-z0-9]+/g, '-')
220
- .replace(/(^-|-$)/g, '');
221
-
222
- // Check if slug exists
223
- const existing = await findPostBySlug(slug);
224
- if (existing) {
225
- throw new ConflictError('Post with this title already exists', { slug });
226
- }
227
-
228
- // Create post
229
- const post = await createPost({
230
- ...body,
231
- slug,
232
- status: 'draft'
233
- });
234
-
235
- // ✅ Auto-commit on success, auto-rollback on error
236
- return c.json(post, 201);
237
- });
238
-
239
- // GET /posts - List published posts (no transaction needed)
240
- app.bind(listPostsContract, async (c) => {
241
- const posts = await findPublishedPosts();
242
- return c.json(posts);
243
- });
244
-
245
- export default app;
230
+ src/server/
231
+ ├── entities/ # Database schema
232
+ │ ├── users.ts
233
+ │ └── index.ts
234
+ ├── repositories/ # Data access + business logic
235
+ │ ├── user.repository.ts
236
+ │ └── index.ts
237
+ ├── routes/ # API routes
238
+ │ ├── users.ts
239
+ │ └── index.ts
240
+ ├── server.config.ts # Server configuration
241
+ └── index.ts # Entry point
246
242
  ```
247
243
 
248
- ### Why This Architecture?
249
-
250
- **✅ Separation of Concerns**
251
- - Each layer has a single responsibility
252
- - Easy to locate and modify code
253
-
254
- **✅ Testability**
255
- - Test each layer independently
256
- - Mock dependencies easily
257
-
258
- **✅ Reusability**
259
- - Services can be used by multiple routes
260
- - Data access functions can be shared across services
261
-
262
- **✅ Type Safety**
263
- - Types flow from Entity → Data Access → Service → Route
264
- - Full IDE autocomplete and error checking
265
-
266
- **✅ Maintainability**
267
- - Add features without breaking existing code
268
- - Clear boundaries prevent coupling
269
-
270
- ### Layer Responsibilities
271
-
272
- | Layer | Responsibility | Examples |
273
- |-------|---------------|----------|
274
- | **Entity** | Define data structure | Schema, types, constraints |
275
- | **Data Access** | Database operations | Helper functions, custom queries, joins |
276
- | **Service** | Business logic | Validation, orchestration, rules |
277
- | **Routes** | HTTP interface | Contracts, request handling |
278
-
279
- ### Best Practices
280
-
281
- **Entity Layer:**
282
- - ✅ Use schema helpers: `id()`, `timestamps()`
283
- - ✅ Export inferred types: `Post`, `NewPost`
284
- - ✅ Use TEXT with enum for status fields
285
-
286
- **Data Access Layer:**
287
- - ✅ Use helper functions for simple CRUD: `findOne()`, `create()`, etc.
288
- - ✅ Create domain-specific wrappers in `src/server/repositories/*.repository.ts`
289
- - ✅ Export functions (not classes): `export async function findPostBySlug()`
290
- - ✅ Use object-based where for simple queries: `{ id: 1 }`
291
- - ✅ Use SQL-based where for complex queries: `and(eq(...), gt(...))`
292
- - ✅ Full TypeScript type inference from table schemas
293
-
294
- **Routes Layer:**
295
- - ✅ Keep handlers thin (delegate to services/data access)
296
- - ✅ Define contracts with TypeBox
297
- - ✅ Use `Transactional()` middleware for write operations
298
- - ✅ Use `c.data()` for validated input
299
- - ✅ Return `c.json()` responses
300
-
301
- ## Core Modules
302
-
303
- ### 📁 Routing
304
- File-based routing with contract validation and type safety.
305
-
306
- **[→ Read Routing Documentation](./src/route/README.md)**
307
-
308
- **Key Features:**
309
- - Automatic route discovery (`index.ts`, `[id].ts`, `[...slug].ts`)
310
- - Contract-based validation with TypeBox
311
- - Type-safe request/response handling
312
- - Method-level middleware control (skip auth per HTTP method)
313
-
314
- ### 🗄️ Database
315
- Drizzle ORM integration with type-safe helper functions and automatic transaction handling.
316
-
317
- **[→ Read Database Documentation](./src/db/README.md)**
318
-
319
- **Key Features:**
320
- - Helper functions for type-safe CRUD operations
321
- - Automatic transaction handling and read/write separation
322
- - Schema helpers: `id()`, `timestamps()`, `foreignKey()`
323
- - Hybrid where clause support (objects or SQL)
324
- - **Function schema auto-discovery** (see below)
325
-
326
- ### 📦 Function Schema Discovery
327
- Automatic discovery of database schemas from Superfunction ecosystem functions.
328
-
329
- **[→ Read Database Manager Documentation](./src/db/manager/README.md)**
330
-
331
- **Key Features:**
332
- - Zero-config schema discovery from `@spfn/*` functions
333
- - Functions declare schemas via `package.json`
334
- - No hard dependencies between functions
335
- - Efficient scanning (direct dependencies only)
336
- - Function-specific migration support
244
+ ---
337
245
 
338
- **How it works:**
246
+ ## Core Concepts
339
247
 
340
- Functions declare their schemas in `package.json`:
341
- ```json
342
- {
343
- "name": "@spfn/cms",
344
- "spfn": {
345
- "schemas": ["./dist/entities/*.js"],
346
- "setupMessage": "📚 Setup guide..."
347
- }
348
- }
349
- ```
248
+ ### Route Definition
350
249
 
351
- Superfunction automatically discovers and merges these schemas during migration generation:
352
250
  ```typescript
353
- import { getDrizzleConfig } from '@spfn/core'
251
+ import { route } from '@spfn/core/route';
252
+ import { Type } from '@sinclair/typebox';
354
253
 
355
- // Auto-discovers all function schemas
356
- const config = getDrizzleConfig()
254
+ // GET with params and query
255
+ route.get('/users/:id')
256
+ .input({
257
+ params: Type.Object({ id: Type.String() }),
258
+ query: Type.Object({ include: Type.Optional(Type.String()) })
259
+ })
260
+ .handler(async (c) => {
261
+ const { params, query } = await c.data();
262
+ // params.id, query.include are fully typed
263
+ });
264
+
265
+ // POST with body
266
+ route.post('/users')
267
+ .input({
268
+ body: Type.Object({
269
+ email: Type.String(),
270
+ name: Type.String()
271
+ })
272
+ })
273
+ .handler(async (c) => {
274
+ const { body } = await c.data();
275
+ // body.email, body.name are fully typed
276
+ });
357
277
  ```
358
278
 
359
- **Install functions with automatic DB setup:**
360
- ```bash
361
- pnpm spfn add @spfn/cms
362
- # ✅ Installs function
363
- # ✅ Generates migrations
364
- # ✅ Applies migrations
365
- # ✅ Shows setup guide
366
- ```
279
+ ### Repository Pattern
367
280
 
368
- **Create your own Superfunction packages:**
369
281
  ```typescript
370
- // 1. Define entities
371
- export const myTable = pgTable('my_table', { ... })
282
+ import { BaseRepository } from '@spfn/core/db';
372
283
 
373
- // 2. Add to package.json
284
+ export class UserRepository extends BaseRepository
374
285
  {
375
- "spfn": {
376
- "schemas": ["./dist/entities/*.js"]
377
- }
286
+ // Protected helpers available:
287
+ // _findOne, _findMany, _create, _createMany
288
+ // _updateOne, _updateMany, _deleteOne, _deleteMany
289
+ // _count, _upsert
290
+
291
+ async findActive()
292
+ {
293
+ return this._findMany(users, {
294
+ where: { isActive: true },
295
+ orderBy: desc(users.createdAt),
296
+ limit: 10
297
+ });
298
+ }
378
299
  }
379
-
380
- // 3. Users install with one command
381
- // pnpm spfn add @yourcompany/spfn-plugin
382
300
  ```
383
301
 
384
- ### 🔄 Transactions
385
- Automatic transaction management with async context propagation.
386
-
387
- **[→ Read Transaction Documentation](./src/db/docs/transactions.md)**
388
-
389
- **Key Features:**
390
- - Auto-commit on success, auto-rollback on error
391
- - AsyncLocalStorage-based context
392
- - Transaction logging
393
-
394
- ### 💾 Cache
395
- Redis integration with master-replica support.
396
-
397
- **[→ Read Cache Documentation](./src/cache/README.md)**
398
-
399
- ### ⚠️ Error Handling
400
- Custom error classes with unified HTTP responses.
401
-
402
- **[→ Read Error Documentation](./src/errors/README.md)**
403
-
404
- ### 🔐 Middleware
405
- Request logging, CORS, and error handling middleware.
302
+ ### Transaction
406
303
 
407
- **[→ Read Middleware Documentation](./src/middleware/README.md)**
408
-
409
- ### 🖥️ Server
410
- Server configuration and lifecycle management.
411
-
412
- **[→ Read Server Documentation](./src/server/README.md)**
413
-
414
- ### 📝 Logger
415
- High-performance logging with multiple transports, sensitive data masking, and automatic validation.
416
-
417
- **[→ Read Logger Documentation](./src/logger/README.md)**
418
-
419
- **Key Features:**
420
- - Adapter pattern (Pino for production, custom for full control)
421
- - Sensitive data masking (passwords, tokens, API keys)
422
- - File rotation (date and size-based) with automatic cleanup
423
- - Configuration validation with clear error messages
424
- - Multiple transports (Console, File, Slack, Email)
425
-
426
- ### ⚙️ Code Generation
427
- Automatic code generation with pluggable generators and centralized file watching.
428
-
429
- **[→ Read Codegen Documentation](./src/codegen/README.md)**
304
+ ```typescript
305
+ import { Transactional } from '@spfn/core/db';
430
306
 
431
- **Key Features:**
432
- - Orchestrator pattern for managing multiple generators
433
- - Built-in contract generator for type-safe API clients
434
- - Configuration-based setup (`.spfnrc.json` or `package.json`)
435
- - Watch mode integrated into `spfn dev`
436
- - Extensible with custom generators
307
+ // Middleware-based (recommended)
308
+ route.post('/users')
309
+ .use([Transactional()])
310
+ .handler(async (c) => {
311
+ // Auto commit on success
312
+ // Auto rollback on error
313
+ });
437
314
 
438
- ## Module Exports
315
+ // Manual control
316
+ import { runWithTransaction } from '@spfn/core/db';
439
317
 
440
- ### Main Export
441
- ```typescript
442
- import { startServer, createServer } from '@spfn/core';
318
+ await runWithTransaction(async () => {
319
+ await userRepo.create(userData);
320
+ await profileRepo.create(profileData);
321
+ });
443
322
  ```
444
323
 
445
- ### Routing
446
- ```typescript
447
- import { createApp, bind, loadRoutes } from '@spfn/core/route';
448
- import type { RouteContext, RouteContract } from '@spfn/core/route';
449
- ```
324
+ ### Schema Helpers
450
325
 
451
- ### Database
452
326
  ```typescript
453
327
  import {
454
- getDatabase,
455
- findOne,
456
- findMany,
457
- create,
458
- createMany,
459
- updateOne,
460
- updateMany,
461
- deleteOne,
462
- deleteMany,
463
- count
328
+ id, // bigserial primary key
329
+ uuid, // uuid primary key
330
+ timestamps, // createdAt, updatedAt
331
+ foreignKey, // required FK with cascade
332
+ optionalForeignKey, // nullable FK
333
+ softDelete, // deletedAt, deletedBy
334
+ enumText, // type-safe enum
335
+ typedJsonb // type-safe JSONB
464
336
  } from '@spfn/core/db';
465
- ```
466
337
 
467
- ### Transactions
468
- ```typescript
469
- import {
470
- Transactional,
471
- getTransaction,
472
- runWithTransaction
473
- } from '@spfn/core/db';
338
+ export const posts = pgTable('posts', {
339
+ id: id(),
340
+ title: text('title').notNull(),
341
+ authorId: foreignKey('author', () => users.id),
342
+ status: enumText('status', ['draft', 'published']).default('draft'),
343
+ metadata: typedJsonb<{ views: number }>('metadata'),
344
+ ...timestamps(),
345
+ ...softDelete()
346
+ });
474
347
  ```
475
348
 
476
- ### Cache
477
- ```typescript
478
- import { initRedis, getRedis, getRedisRead } from '@spfn/core';
479
- ```
349
+ ---
480
350
 
481
- ### Logger
482
- ```typescript
483
- import { logger } from '@spfn/core';
484
- ```
351
+ ## Module Documentation
352
+
353
+ | Module | Description |
354
+ |--------|-------------|
355
+ | [Route](./docs/route.md) | Route definition, validation, response patterns |
356
+ | [Database](./docs/database.md) | Connection, helpers, transactions |
357
+ | [Entity](./docs/entity.md) | Schema definition, column helpers |
358
+ | [Repository](./docs/repository.md) | BaseRepository, CRUD patterns |
359
+ | [Middleware](./docs/middleware.md) | Named middleware, skip control |
360
+ | [Server](./docs/server.md) | Configuration, lifecycle hooks |
361
+ | [Errors](./docs/errors.md) | Error types, handling patterns |
362
+ | [Environment](./docs/env.md) | Type-safe environment variables |
363
+ | [Codegen](./docs/codegen.md) | API client generation |
364
+ | [Cache](./docs/cache.md) | Redis caching |
365
+ | [Event](./docs/event.md) | Event system |
366
+ | [Job](./docs/job.md) | Background jobs |
367
+ | [Logger](./docs/logger.md) | Logging |
368
+ | [Next.js](./docs/nextjs.md) | Next.js integration, RPC proxy |
485
369
 
486
- ### Client (for frontend)
487
- ```typescript
488
- import { ContractClient, createClient } from '@spfn/core/client';
489
- ```
370
+ ---
490
371
 
491
- ## Environment Variables
372
+ ## CLI Commands
492
373
 
493
374
  ```bash
494
- # Database (required)
495
- DATABASE_URL=postgresql://user:pass@localhost:5432/db
496
-
497
- # Database Read Replica (optional)
498
- DATABASE_READ_URL=postgresql://user:pass@replica:5432/db
499
-
500
- # Redis (optional)
501
- REDIS_URL=redis://localhost:6379
502
- REDIS_WRITE_URL=redis://master:6379 # Master-replica setup
503
- REDIS_READ_URL=redis://replica:6379
504
-
505
- # Server
506
- PORT=8790
507
- HOST=localhost
508
- NODE_ENV=development
509
-
510
- # Server Timeouts (optional, in milliseconds)
511
- SERVER_TIMEOUT=120000 # Request timeout (default: 120000)
512
- SERVER_KEEPALIVE_TIMEOUT=65000 # Keep-alive timeout (default: 65000)
513
- SERVER_HEADERS_TIMEOUT=60000 # Headers timeout (default: 60000)
514
- SHUTDOWN_TIMEOUT=30000 # Graceful shutdown timeout (default: 30000)
515
-
516
- # Logger (optional)
517
- LOGGER_ADAPTER=pino # pino | custom (default: pino)
518
- LOGGER_FILE_ENABLED=true # Enable file logging (production only)
519
- LOG_DIR=/var/log/myapp # Log directory (required when file logging enabled)
520
- ```
521
-
522
- ## Requirements
375
+ # Migration
376
+ npx spfn db generate # Generate migration
377
+ npx spfn db migrate # Apply migration
523
378
 
524
- - Node.js >= 18
525
- - Next.js 15+ with App Router (when using with CLI)
526
- - PostgreSQL
527
- - Redis (optional)
379
+ # Development
380
+ pnpm spfn:dev # Start dev server (auto codegen)
528
381
 
529
- ## Testing
382
+ # API Client Generation
383
+ pnpm spfn codegen run
530
384
 
531
- ```bash
532
- npm test # Run all tests
533
- npm test -- route # Run route tests only
534
- npm test -- --coverage # With coverage
385
+ # Database Studio
386
+ pnpm drizzle-kit studio
535
387
  ```
536
388
 
537
- **Test Coverage:** 120+ tests across all modules
538
-
539
- ## Documentation
540
-
541
- ### Guides
542
- - [File-based Routing](./src/route/README.md)
543
- - [Database & Helper Functions](./src/db/README.md)
544
- - [Transaction Management](./src/db/docs/transactions.md)
545
- - [Redis Cache](./src/cache/README.md)
546
- - [Error Handling](./src/errors/README.md)
547
- - [Middleware](./src/middleware/README.md)
548
- - [Server Configuration](./src/server/README.md)
549
- - [Logger](./src/logger/README.md)
550
- - [Code Generation](./src/codegen/README.md)
551
-
552
- ### API Reference
553
- - See module-specific README files linked above
389
+ ---
554
390
 
555
391
  ## License
556
392
 
557
393
  MIT
558
-
559
- ---
560
-
561
- Part of the [Superfunction Framework](https://github.com/spfn/spfn)