@spfn/core 0.1.0-alpha.88 → 0.2.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 (71) hide show
  1. package/README.md +1046 -384
  2. package/dist/boss-D-fGtVgM.d.ts +187 -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 +167 -17
  7. package/dist/codegen/index.js +76 -1419
  8. package/dist/codegen/index.js.map +1 -1
  9. package/dist/config/index.d.ts +1191 -0
  10. package/dist/config/index.js +264 -0
  11. package/dist/config/index.js.map +1 -0
  12. package/dist/db/index.d.ts +728 -59
  13. package/dist/db/index.js +1028 -1225
  14. package/dist/db/index.js.map +1 -1
  15. package/dist/env/index.d.ts +579 -308
  16. package/dist/env/index.js +438 -930
  17. package/dist/env/index.js.map +1 -1
  18. package/dist/errors/index.d.ts +417 -29
  19. package/dist/errors/index.js +359 -98
  20. package/dist/errors/index.js.map +1 -1
  21. package/dist/event/index.d.ts +108 -0
  22. package/dist/event/index.js +122 -0
  23. package/dist/event/index.js.map +1 -0
  24. package/dist/job/index.d.ts +172 -0
  25. package/dist/job/index.js +361 -0
  26. package/dist/job/index.js.map +1 -0
  27. package/dist/logger/index.d.ts +20 -79
  28. package/dist/logger/index.js +82 -387
  29. package/dist/logger/index.js.map +1 -1
  30. package/dist/middleware/index.d.ts +2 -11
  31. package/dist/middleware/index.js +49 -703
  32. package/dist/middleware/index.js.map +1 -1
  33. package/dist/nextjs/index.d.ts +120 -0
  34. package/dist/nextjs/index.js +416 -0
  35. package/dist/nextjs/index.js.map +1 -0
  36. package/dist/{client/nextjs/index.d.ts → nextjs/server.d.ts} +288 -262
  37. package/dist/nextjs/server.js +568 -0
  38. package/dist/nextjs/server.js.map +1 -0
  39. package/dist/route/index.d.ts +686 -25
  40. package/dist/route/index.js +440 -1287
  41. package/dist/route/index.js.map +1 -1
  42. package/dist/route/types.d.ts +38 -0
  43. package/dist/route/types.js +3 -0
  44. package/dist/route/types.js.map +1 -0
  45. package/dist/server/index.d.ts +201 -67
  46. package/dist/server/index.js +921 -3182
  47. package/dist/server/index.js.map +1 -1
  48. package/dist/types-BGl4QL1w.d.ts +77 -0
  49. package/dist/types-DRG2XMTR.d.ts +157 -0
  50. package/package.json +52 -47
  51. package/dist/auto-loader-JFaZ9gON.d.ts +0 -80
  52. package/dist/client/index.d.ts +0 -358
  53. package/dist/client/index.js +0 -357
  54. package/dist/client/index.js.map +0 -1
  55. package/dist/client/nextjs/index.js +0 -371
  56. package/dist/client/nextjs/index.js.map +0 -1
  57. package/dist/codegen/generators/index.d.ts +0 -19
  58. package/dist/codegen/generators/index.js +0 -1404
  59. package/dist/codegen/generators/index.js.map +0 -1
  60. package/dist/database-errors-BNNmLTJE.d.ts +0 -86
  61. package/dist/events/index.d.ts +0 -183
  62. package/dist/events/index.js +0 -77
  63. package/dist/events/index.js.map +0 -1
  64. package/dist/index-DHiAqhKv.d.ts +0 -101
  65. package/dist/index.d.ts +0 -8
  66. package/dist/index.js +0 -3674
  67. package/dist/index.js.map +0 -1
  68. package/dist/types/index.d.ts +0 -121
  69. package/dist/types/index.js +0 -38
  70. package/dist/types/index.js.map +0 -1
  71. package/dist/types-BXibIEyj.d.ts +0 -60
package/README.md CHANGED
@@ -1,494 +1,1086 @@
1
- # @spfn/core
1
+ # @spfn/core - Technical Architecture Documentation
2
2
 
3
- > Core framework for building type-safe backend APIs with Next.js and Hono
3
+ Full-stack type-safe framework for building Next.js + Node.js applications with end-to-end type inference.
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
+ > **Alpha Release**: SPFN is currently in alpha. APIs may change. Use `@alpha` tag for installation.
10
10
 
11
- ## Installation
11
+ ---
12
12
 
13
- **Recommended: Create New Project**
14
- ```bash
15
- npx spfn@alpha create my-app
13
+ ## Table of Contents
14
+
15
+ - [Overview & Philosophy](#overview--philosophy)
16
+ - [System Architecture](#system-architecture)
17
+ - [Module Architecture](#module-architecture)
18
+ - [Type System](#type-system)
19
+ - [Integration Points](#integration-points)
20
+ - [Design Decisions](#design-decisions)
21
+ - [Extension Points](#extension-points)
22
+ - [Migration Guides](#migration-guides)
23
+ - [Module Exports](#module-exports)
24
+ - [Quick Reference](#quick-reference)
25
+
26
+ ---
27
+
28
+ ## Overview & Philosophy
29
+
30
+ SPFN (Superfunction) is a full-stack TypeScript framework that provides **end-to-end type safety** from database to frontend with a **tRPC-inspired developer experience**.
31
+
32
+ ### Core Principles
33
+
34
+ 1. **Type Safety First**: Types flow from database schema → server routes → client API
35
+ 2. **Developer Experience**: tRPC-style API with method chaining (`.params().query().call()`)
36
+ 3. **Explicit over Magic**: No file-based routing, explicit imports for tree-shaking
37
+ 4. **Security by Default**: HttpOnly cookies, API Route Proxy, environment isolation
38
+ 5. **Production Ready**: Transaction management, read/write separation, graceful shutdown
39
+
40
+ ### Design Philosophy
41
+
42
+ **Inspired by tRPC, Built for Production:**
43
+
44
+ ```typescript
45
+ // tRPC-style API calls with structured input
46
+ const user = await api.getUser.call({
47
+ params: { id: '123' },
48
+ query: { include: 'posts' }
49
+ });
50
+ // ^? { id: string; name: string; email: string; posts: Post[] }
16
51
  ```
17
52
 
18
- **Add to Existing Next.js Project**
19
- ```bash
20
- cd your-nextjs-project
21
- npx spfn@alpha init
53
+ **But with production features:**
54
+ - Cookie handling via API Route Proxy
55
+ - Transaction management with AsyncLocalStorage
56
+ - Read/Write database separation
57
+ - Graceful shutdown and health checks
58
+ - Lifecycle hooks for extensibility
59
+
60
+ ---
61
+
62
+ ## System Architecture
63
+
64
+ ### High-Level Overview
65
+
66
+ ```
67
+ +---------------------------------------------------------------+
68
+ | Next.js Application |
69
+ | +----------------------------------------------------------+ |
70
+ | | Frontend (React) | |
71
+ | | - Server Components: SSR, ISR, Static | |
72
+ | | - Client Components: Interactive UI | |
73
+ | +---------------------------+------------------------------+ |
74
+ | | |
75
+ | | import { api } from '@spfn/...' |
76
+ | | api.getUser.call({ params }) |
77
+ | v |
78
+ | +----------------------------------------------------------+ |
79
+ | | RPC Proxy (Edge/Node.js) | |
80
+ | | app/api/rpc/[routeName]/route.ts | |
81
+ | | | |
82
+ | | 1. Resolve routeName → method/path from router | |
83
+ | | | |
84
+ | | 2. Request Interceptors | |
85
+ | | - Auth token injection | |
86
+ | | - Cookie forwarding | |
87
+ | | - Header manipulation | |
88
+ | | | |
89
+ | | 3. Forward to SPFN Server | |
90
+ | | fetch(SPFN_API_URL + resolvedPath) | |
91
+ | | | |
92
+ | | 4. Response Interceptors | |
93
+ | | - Set HttpOnly cookies | |
94
+ | | - Transform response | |
95
+ | | - Error handling | |
96
+ | +---------------------------+------------------------------+ |
97
+ +-------------------------------+--------------------------------+
98
+ |
99
+ | HTTP Request
100
+ v
101
+ +---------------------------------------------------------------+
102
+ | SPFN API Server (Node.js) |
103
+ | +----------------------------------------------------------+ |
104
+ | | Hono Web Framework | |
105
+ | | | |
106
+ | | 12-Step Middleware Pipeline: | |
107
+ | | 1. Logger | |
108
+ | | 2. CORS | |
109
+ | | 3. Global Middlewares | |
110
+ | | 4. Route-specific Middlewares | |
111
+ | | 5. Request Validation (TypeBox) | |
112
+ | | 6. Route Handler | |
113
+ | | 7-12. Response processing, error handling | |
114
+ | +---------------------------+------------------------------+ |
115
+ | | |
116
+ | | define-route System |
117
+ | v |
118
+ | +----------------------------------------------------------+ |
119
+ | | Route Handlers | |
120
+ | | - Type-safe input validation | |
121
+ | | - Transaction middleware | |
122
+ | | - Business logic | |
123
+ | +---------------------------+------------------------------+ |
124
+ | | |
125
+ | | Database Queries |
126
+ | v |
127
+ | +----------------------------------------------------------+ |
128
+ | | Database Layer (Drizzle ORM) | |
129
+ | | - Helper functions (findOne, create, etc.) | |
130
+ | | - Transaction propagation (AsyncLocalStorage) | |
131
+ | | - Read/Write separation | |
132
+ | +---------------------------+------------------------------+ |
133
+ +-------------------------------+--------------------------------+
134
+ |
135
+ | SQL Queries
136
+ v
137
+ +---------------------------------------------------------------+
138
+ | PostgreSQL Database |
139
+ | - Primary (Read/Write) |
140
+ | - Replica (Read-only) [optional] |
141
+ +---------------------------------------------------------------+
22
142
  ```
23
143
 
24
- **Manual Installation**
25
- ```bash
26
- npm install @spfn/core hono drizzle-orm postgres @sinclair/typebox
144
+ ### Request Flow Example
145
+
146
+ ```typescript
147
+ // 1. Client Call (Next.js Server Component)
148
+ // app/users/[id]/page.tsx
149
+ import { createApi } from '@spfn/core/nextjs';
150
+ import type { AppRouter } from '@/server/router';
151
+
152
+ const api = createApi<AppRouter>();
153
+ const user = await api.getUser.call({ params: { id: params.id } });
154
+ // → GET /api/rpc/getUser?input={"params":{"id":"123"}}
155
+
156
+ // 2. RPC Proxy
157
+ // app/api/rpc/[routeName]/route.ts
158
+ import { appRouter } from '@/server/router';
159
+ import { createRpcProxy } from '@spfn/core/nextjs/server';
160
+
161
+ export const { GET, POST } = createRpcProxy({ router: appRouter });
162
+ // - Resolves routeName → method/path from router
163
+ // - Forwards to http://localhost:8790/users/123
164
+ // - Applies interceptors (auth, cookies)
165
+
166
+ // 3. SPFN Server Route Handler
167
+ // src/server/routes/users.ts
168
+ export const getUser = route.get('/users/:id')
169
+ .input({
170
+ params: Type.Object({ id: Type.String() }),
171
+ })
172
+ .handler(async (c) => {
173
+ const { params } = await c.data();
174
+ const user = await userRepo.findById(params.id);
175
+ return user;
176
+ });
177
+
178
+ // 4. Database Query
179
+ // Drizzle ORM with helper function
180
+ // SELECT * FROM users WHERE id = $1
181
+
182
+ // 5. Response flows back through interceptors → proxy → client
27
183
  ```
28
184
 
29
- ## Quick Start
185
+ ---
30
186
 
31
- ### 1. Define a Contract
187
+ ## Module Architecture
32
188
 
33
- ```typescript
34
- // src/server/routes/users/contract.ts
35
- import { Type } from '@sinclair/typebox';
189
+ ### 1. Route System (`src/route/`)
36
190
 
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
- };
191
+ **Purpose**: Type-safe routing with automatic validation
192
+
193
+ **Architecture**:
194
+
195
+ ```
196
+ route.get('/users/:id')
197
+ .input({ params, query, body })
198
+ .handler(async (c) => { ... })
199
+ |
200
+ |-- Type Inference: RouteDef<TInput, TResponse>
201
+ |-- Validation: TypeBox schema
202
+ |-- Middleware: Skip control per route
203
+ |-- Response: c.success() / c.error()
53
204
  ```
54
205
 
55
- ### 2. Create a Route
206
+ **Key Components**:
56
207
 
57
- ```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';
208
+ - `defineRouter()`: Combines route definitions into typed router
209
+ - `route.get/post/put/patch/delete()`: Route builder with method chaining
210
+ - Input validation: Automatic TypeBox validation
211
+ - Middleware control: Per-route middleware skip
63
212
 
64
- const app = createApp();
213
+ **Design Pattern**: Builder pattern with type inference
65
214
 
66
- app.bind(getUsersContract, async (c) => {
67
- const { page = 1, limit = 10 } = c.query;
215
+ **[→ Full Documentation](./src/route/README.md)**
68
216
 
69
- // Use helper function directly - no Repository needed
70
- const offset = (page - 1) * limit;
71
- const result = await findMany(users, { limit, offset });
217
+ ---
72
218
 
73
- return c.json({ users: result, total: result.length });
74
- });
219
+ ### 2. Server System (`src/server/`)
75
220
 
76
- export default app;
221
+ **Purpose**: HTTP server with lifecycle management
222
+
223
+ **Architecture**:
224
+
225
+ ```
226
+ Configuration Sources (Priority Order):
227
+ 1. Runtime config (startServer({ port: 3000 }))
228
+ 2. server.config.ts (defineServerConfig().build())
229
+ 3. Environment variables (PORT, DATABASE_URL)
230
+ 4. Defaults
231
+
232
+ |
233
+ v
234
+ Middleware Pipeline:
235
+ 1. Request Logger
236
+ 2. CORS
237
+ 3. Global Middlewares
238
+ 4. Named Middlewares
239
+ 5. beforeRoutes hook
240
+ 6. Route Registration
241
+ 7. afterRoutes hook
242
+ 8. Route-specific Middlewares
243
+ 9. Request Validation
244
+ 10. Route Handler Execution
245
+ 11. Response Serialization
246
+ 12. Error Handler
247
+
248
+ |
249
+ v
250
+ Lifecycle Hooks:
251
+ - beforeInfrastructure
252
+ - afterInfrastructure
253
+ - beforeRoutes
254
+ - afterRoutes
255
+ - afterStart
256
+ - beforeShutdown
77
257
  ```
78
258
 
79
- ### 3. Start Server
259
+ **Key Components**:
80
260
 
81
- ```bash
82
- npm run spfn:dev
83
- # Server starts on http://localhost:8790
84
- ```
85
-
86
- ## Architecture Pattern
87
-
88
- Superfunction follows a **layered architecture** that separates concerns and keeps code maintainable:
89
-
90
- ```
91
- ┌─────────────────────────────────────────┐
92
- │ Routes Layer │ HTTP handlers, contracts
93
- │ - Define API contracts (TypeBox)
94
- │ - Handle requests/responses │
95
- │ - Thin handlers │
96
- └──────────────┬──────────────────────────┘
97
-
98
- ┌──────────────▼──────────────────────────┐
99
- │ Service Layer │ Business logic
100
- │ - Orchestrate operations │
101
- │ - Implement business rules │
102
- │ - Use helper functions or custom logic │
103
- └──────────────┬──────────────────────────┘
104
-
105
- ┌──────────────▼──────────────────────────┐
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 │
117
- └─────────────────────────────────────────┘
118
- ```
119
-
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
- });
261
+ - `defineServerConfig()`: Fluent configuration builder
262
+ - `createServer()`: Creates Hono app with routes
263
+ - `startServer()`: Starts HTTP server with lifecycle
264
+ - Lifecycle hooks: beforeInfrastructure, afterInfrastructure, beforeRoutes, afterRoutes, afterStart, beforeShutdown
265
+ - Graceful shutdown: SIGTERM/SIGINT handling
266
+
267
+ **Design Pattern**: Builder + Lifecycle hooks
268
+
269
+ **[→ Full Documentation](./src/server/README.md)**
270
+
271
+ ---
272
+
273
+ ### 3. Database System (`src/db/`)
139
274
 
140
- export type Post = typeof posts.$inferSelect;
141
- export type NewPost = typeof posts.$inferInsert;
275
+ **Purpose**: Type-safe database operations with transactions
276
+
277
+ **Architecture**:
278
+
279
+ ```
280
+ Application Code
281
+ |
282
+ | findOne(users, { id: 1 })
283
+ v
284
+ Helper Functions (Facade)
285
+ |
286
+ | Check AsyncLocalStorage for transaction
287
+ v
288
+ Transaction Context?
289
+ |-- Yes: Use transaction instance
290
+ |-- No: Use default database connection
291
+ |
292
+ v
293
+ Drizzle ORM Query Builder
294
+ |
295
+ v
296
+ PostgreSQL (Primary or Replica)
142
297
  ```
143
298
 
144
- **2. Data Access Layer** - Helper functions with domain-specific wrappers
299
+ **Key Components**:
300
+
301
+ - Helper functions: `findOne`, `findMany`, `create`, `update`, `delete`, `count`
302
+ - Transaction middleware: `Transactional()` with AsyncLocalStorage propagation
303
+ - Read/Write separation: Automatic routing based on operation
304
+ - Schema helpers: `id()`, `timestamps()`, `foreignKey()`, `enumText()`
305
+
306
+ **Design Pattern**: Facade + AsyncLocalStorage for transaction propagation
307
+
308
+ **[→ Full Documentation](./src/db/README.md)**
309
+
310
+ **Transaction Flow**:
145
311
 
146
312
  ```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';
313
+ // Route with Transactional middleware
314
+ app.bind(createUserContract, [Transactional()], async (c) => {
315
+ // All database operations use the same transaction
316
+ const user = await create(users, { name: 'John' });
317
+ const profile = await create(profiles, { userId: user.id });
318
+
319
+ // Auto-commit on success
320
+ // Auto-rollback on error
321
+ return c.json(user);
322
+ });
323
+ ```
151
324
 
152
- // Domain-specific wrappers using helper functions
153
- export async function findPostBySlug(slug: string): Promise<Post | null> {
154
- return findOne(posts, { slug });
155
- }
325
+ ---
156
326
 
157
- export async function findPublishedPosts(): Promise<Post[]> {
158
- return findMany(posts, {
159
- where: { status: 'published' },
160
- orderBy: desc(posts.createdAt)
161
- });
162
- }
327
+ ### 4. Client System (`src/nextjs/`)
163
328
 
164
- export async function createPost(data: NewPost): Promise<Post> {
165
- return createHelper(posts, data);
166
- }
329
+ **Purpose**: Type-safe API client for Next.js with tRPC-style DX
330
+
331
+ **Architecture**:
167
332
 
168
- // Or use helper functions directly in routes for simple cases
169
- // const post = await findOne(posts, { id: 1 });
170
333
  ```
334
+ Client Code
335
+ |
336
+ | api.getUser.call({ params: { id: '123' } })
337
+ v
338
+ ApiClient (Proxy)
339
+ |
340
+ | body 유무로 GET/POST 결정
341
+ | GET /api/rpc/getUser?input={...}
342
+ | POST /api/rpc/createUser body: {...}
343
+ v
344
+ fetch() to Next.js API Route
345
+ |
346
+ v
347
+ RpcProxy (API Route Handler)
348
+ |
349
+ | 1. Extract routeName from URL
350
+ | 2. Lookup method/path from appRouter
351
+ | 3. Execute request interceptors
352
+ | 4. Forward to SPFN_API_URL with resolved path
353
+ | 5. Execute response interceptors
354
+ | 6. Set cookies from ctx.setCookies
355
+ v
356
+ Return NextResponse
357
+ ```
358
+
359
+ **Key Components**:
360
+
361
+ - `createApi()`: tRPC-style client with type inference (no metadata needed)
362
+ - `createRpcProxy()`: RPC-style API Route handler with route resolution
363
+ - `registerInterceptors()`: Registry for plugin interceptors
364
+ - Path matching: Wildcard and param support (`/_auth/*`, `/users/:id`)
365
+ - Cookie handling: `setCookies` array in response interceptors
366
+
367
+ **Design Pattern**: Proxy for client, RPC resolution for proxy
368
+
369
+ **[→ Full Documentation](./src/nextjs/README.md)**
370
+
371
+ ---
372
+
373
+ ### 5. Error System (`src/errors/`)
374
+
375
+ **Purpose**: Structured error handling with HTTP status codes
376
+
377
+ **Key Components**:
378
+
379
+ - `ApiError`: Base error class
380
+ - `ValidationError`: Input validation failures (400)
381
+ - `NotFoundError`: Resource not found (404)
382
+ - `ConflictError`: Duplicate resources (409)
383
+ - `UnauthorizedError`: Authentication required (401)
384
+
385
+ **[→ Full Documentation](./src/errors/README.md)**
386
+
387
+ ---
388
+
389
+ ### 6. Logger System (`src/logger/`)
390
+
391
+ **Purpose**: Structured logging with transports and masking
392
+
393
+ **Key Components**:
394
+
395
+ - Adapter pattern: Pino (default) or custom
396
+ - Sensitive data masking: Passwords, tokens, API keys
397
+ - File rotation: Date and size-based with cleanup
398
+ - Multiple transports: Console, File, Slack, Email
399
+
400
+ **[→ Full Documentation](./src/logger/README.md)**
401
+
402
+ ---
403
+
404
+ ### 7. Cache System (`src/cache/`)
405
+
406
+ **Purpose**: Valkey/Redis integration with master-replica support
407
+
408
+ **Key Components**:
409
+
410
+ - `initCache()`: Initialize connections
411
+ - `getCache()`: Write operations (master)
412
+ - `getCacheRead()`: Read operations (replica or master)
413
+ - `isCacheDisabled()`: Check if cache is disabled
414
+ - Graceful degradation when cache unavailable
415
+
416
+ **[→ Full Documentation](./src/cache/README.md)**
417
+
418
+ ---
419
+
420
+ ### 8. Middleware System (`src/middleware/`)
421
+
422
+ **Purpose**: Named middleware with skip control
423
+
424
+ **Key Components**:
425
+
426
+ - `defineMiddleware()`: Create named middleware
427
+ - Skip control: Routes can skip specific middlewares
428
+ - Built-in: Logger, CORS, Error handler
171
429
 
172
- **3. Routes Layer** - HTTP API
430
+ **[→ Full Documentation](./src/middleware/README.md)**
431
+
432
+ ---
433
+
434
+ ## Type System
435
+
436
+ ### End-to-End Type Safety Flow
173
437
 
174
438
  ```typescript
175
- // src/server/routes/posts/contracts.ts
439
+ // 1. Database Schema (Source of Truth)
440
+ // src/server/entities/users.ts
441
+ export const users = pgTable('users', {
442
+ id: id(),
443
+ name: text('name').notNull(),
444
+ email: text('email').notNull().unique(),
445
+ ...timestamps(),
446
+ });
447
+
448
+ export type User = typeof users.$inferSelect;
449
+ // ^? { id: string; name: string; email: string; createdAt: Date; updatedAt: Date }
450
+
451
+ // 2. Server Route Definition
452
+ // src/server/routes/users.ts
453
+ import { route } from '@spfn/core/route';
176
454
  import { Type } from '@sinclair/typebox';
177
455
 
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
- };
456
+ export const getUser = route.get('/users/:id')
457
+ .input({
458
+ params: Type.Object({
459
+ id: Type.String(),
460
+ }),
461
+ query: Type.Object({
462
+ include: Type.Optional(Type.String()),
463
+ }),
464
+ })
465
+ .handler(async (c) => {
466
+ const { params, query } = await c.data();
467
+ // ^? { params: { id: string }, query: { include?: string } }
468
+
469
+ const user = await findOne(users, { id: params.id });
470
+ // ^? User | null
471
+
472
+ return c.success(user);
473
+ // ^? Response inferred from return value
474
+ });
475
+
476
+ // 3. Router Type Export
477
+ // src/server/router.ts
478
+ export const appRouter = defineRouter({
479
+ getUser,
480
+ // ... other routes
481
+ });
191
482
 
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
- };
483
+ export type AppRouter = typeof appRouter;
484
+ // ^? Router<{ getUser: RouteDef<..., ...>, ... }>
485
+
486
+ // 4. Client API Call (Next.js)
487
+ // app/users/[id]/page.tsx
488
+ import { api } from '@spfn/core/nextjs/server';
489
+
490
+ const user = await api.getUser
491
+ .params({ id: '123' }) // Type-checked: must be { id: string }
492
+ .query({ include: 'posts' }) // Type-checked: { include?: string }
493
+ .call();
494
+ // ^? User (inferred from server handler return type)
495
+
496
+ // Full type inference chain:
497
+ // User type → RouteDef → Router → Client API → return type
201
498
  ```
202
499
 
500
+ ### Type Inference Utilities
501
+
203
502
  ```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';
503
+ // Extract input type from route
504
+ type GetUserInput = InferRouteInput<typeof getUser>;
505
+ // ^? { params: { id: string }; query: { include?: string } }
506
+
507
+ // Extract output type from route
508
+ type GetUserOutput = InferRouteOutput<typeof getUser>;
509
+ // ^? User
510
+
511
+ // Client type inference
512
+ type ApiClient<TRouter> = {
513
+ [K in keyof TRouter['routes']]: TRouter['routes'][K] extends RouteDef<infer TInput, infer TOutput>
514
+ ? RouteClient<TInput, TOutput>
515
+ : never;
516
+ };
517
+ ```
210
518
 
211
- const app = createApp();
519
+ ---
212
520
 
213
- // POST /posts - Create new post (with transaction)
521
+ ## Integration Points
522
+
523
+ ### 1. Server ↔ Database: Transaction Context
524
+
525
+ **Mechanism**: AsyncLocalStorage propagates transaction across async calls
526
+
527
+ ```typescript
528
+ // Route handler
214
529
  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
- });
530
+ // AsyncLocalStorage stores transaction
531
+ const post = await create(posts, { title: 'Hello' });
238
532
 
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);
533
+ // Same transaction is used automatically
534
+ const tag = await create(tags, { postId: post.id, name: 'news' });
535
+
536
+ // Auto-commit on success, auto-rollback on error
537
+ return c.json(post);
243
538
  });
539
+ ```
244
540
 
245
- export default app;
541
+ **Why**: No need to pass transaction object through function calls
542
+
543
+ ---
544
+
545
+ ### 2. Server ↔ Client: Type Inference
546
+
547
+ **Mechanism**: `typeof appRouter` captures all route types
548
+
549
+ ```typescript
550
+ // Server: Export router type
551
+ export const appRouter = defineRouter({ getUser, createUser });
552
+ export type AppRouter = typeof appRouter;
553
+
554
+ // Client: Import type (not value!)
555
+ import type { AppRouter } from '@/server/router';
556
+ const api = createApi<AppRouter>(/* ... */);
557
+
558
+ // Automatic type inference for all routes
559
+ const user = await api.getUser.params({ id: '123' }).call();
560
+ // ^? Full type safety without manual type definitions
246
561
  ```
247
562
 
248
- ### Why This Architecture?
563
+ **Why**: Single source of truth (server) → client types automatically sync
249
564
 
250
- **✅ Separation of Concerns**
251
- - Each layer has a single responsibility
252
- - Easy to locate and modify code
565
+ ---
253
566
 
254
- **✅ Testability**
255
- - Test each layer independently
256
- - Mock dependencies easily
567
+ ### 3. Next.js ↔ SPFN: RPC Proxy
257
568
 
258
- **✅ Reusability**
259
- - Services can be used by multiple routes
260
- - Data access functions can be shared across services
569
+ **Mechanism**: Next.js API Route resolves routeName and forwards to SPFN server
261
570
 
262
- **✅ Type Safety**
263
- - Types flow from Entity → Data Access → Service → Route
264
- - Full IDE autocomplete and error checking
571
+ ```typescript
572
+ // app/api/rpc/[routeName]/route.ts
573
+ import { appRouter } from '@/server/router';
574
+ import { createRpcProxy } from '@spfn/core/nextjs/server';
265
575
 
266
- **✅ Maintainability**
267
- - Add features without breaking existing code
268
- - Clear boundaries prevent coupling
576
+ export const { GET, POST } = createRpcProxy({ router: appRouter });
269
577
 
270
- ### Layer Responsibilities
578
+ // Automatically:
579
+ // 1. Resolves routeName → method/path from router
580
+ // 2. Forwards to http://localhost:8790/{resolved-path}
581
+ // 3. Applies interceptors (auth, cookies)
582
+ // 4. Returns NextResponse
583
+ ```
271
584
 
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 |
585
+ **Why**:
586
+ - HttpOnly cookie support (browser → Next.js includes cookies automatically)
587
+ - Security (SPFN_API_URL hidden from browser)
588
+ - No CORS (same-origin requests)
589
+ - No metadata codegen required
278
590
 
279
- ### Best Practices
591
+ ---
592
+
593
+ ### 4. Packages: Registry System
594
+
595
+ **Mechanism**: Packages register interceptors on import
596
+
597
+ ```typescript
598
+ // @spfn/auth package
599
+ import { registerInterceptors } from '@spfn/core/nextjs/server';
600
+
601
+ registerInterceptors('auth', [
602
+ {
603
+ pathPattern: '/_auth/*',
604
+ response: async (ctx, next) => {
605
+ // Set session cookie
606
+ ctx.setCookies.push({ name: 'session', value: token });
607
+ await next();
608
+ },
609
+ },
610
+ ]);
611
+
612
+ // App: Auto-discovery
613
+ import '@spfn/auth/adapters/nextjs'; // Registers interceptors
614
+ export { GET, POST } from '@spfn/core/nextjs/server'; // Uses registered interceptors
615
+ ```
616
+
617
+ **Why**: Zero-config integration for plugin packages
618
+
619
+ ---
280
620
 
281
- **Entity Layer:**
282
- - ✅ Use schema helpers: `id()`, `timestamps()`
283
- - ✅ Export inferred types: `Post`, `NewPost`
284
- - ✅ Use TEXT with enum for status fields
621
+ ## Design Decisions
285
622
 
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
623
+ ### 1. Why tRPC-Style API over REST Client?
293
624
 
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
625
+ **Decision**: Method chaining with `.params().query().call()`
300
626
 
301
- ## Core Modules
627
+ **Reasons**:
628
+ - Better DX: Fluent API guides usage
629
+ - Type safety: Each method is type-checked
630
+ - Flexibility: Optional parameters can be omitted
631
+ - Discovery: IDE autocomplete shows available options
302
632
 
303
- ### 📁 Routing
304
- File-based routing with contract validation and type safety.
633
+ **Example**:
634
+ ```typescript
635
+ // tRPC-style (SPFN)
636
+ const user = await api.getUser
637
+ .params({ id: '123' })
638
+ .query({ include: 'posts' })
639
+ .call();
640
+
641
+ // vs Traditional REST client
642
+ const user = await client.get('/users/123', {
643
+ params: { include: 'posts' } // No type safety
644
+ });
645
+ ```
305
646
 
306
- **[→ Read Routing Documentation](./src/route/README.md)**
647
+ ---
307
648
 
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)
649
+ ### 2. Why API Route Proxy Pattern?
313
650
 
314
- ### 🗄️ Database
315
- Drizzle ORM integration with type-safe helper functions and automatic transaction handling.
651
+ **Decision**: Next.js API Route forwards to SPFN server
316
652
 
317
- **[→ Read Database Documentation](./src/db/README.md)**
653
+ **Reasons**:
654
+ - **HttpOnly Cookies**: Browser automatically sends cookies to same-origin
655
+ - **Security**: SPFN API URL hidden from browser
656
+ - **No CORS**: Same-origin requests (localhost:3000 → localhost:3000)
657
+ - **Unified Path**: Server Components and Client Components use same API
318
658
 
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)
659
+ **Alternative Rejected**: Direct calls from browser to SPFN server
660
+ - Would expose SPFN_API_URL to browser
661
+ - CORS configuration required
662
+ - Cannot use HttpOnly cookies from browser
325
663
 
326
- ### 📦 Function Schema Discovery
327
- Automatic discovery of database schemas from Superfunction ecosystem functions.
664
+ ---
328
665
 
329
- **[→ Read Database Manager Documentation](./src/db/manager/README.md)**
666
+ ### 3. Why define-route over File-Based Routing?
330
667
 
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
668
+ **Decision**: Explicit route imports with `defineRouter()`
337
669
 
338
- **How it works:**
670
+ **Reasons**:
671
+ - **Explicit Imports**: Better tree-shaking, clear dependencies
672
+ - **Type Safety**: `typeof appRouter` captures all routes
673
+ - **Flexibility**: Routes can be defined anywhere
674
+ - **No Magic**: No file system scanning at runtime
675
+
676
+ **Alternative Rejected**: File-based routing (like Next.js)
677
+ - Runtime file system scanning
678
+ - Implicit route registration
679
+ - Harder to trace route definitions
680
+ - Less flexible for monorepo setups
681
+
682
+ **Migration Path**: Deprecated contract-based and file-based routing systems
683
+
684
+ ---
339
685
 
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
- }
686
+ ### 4. Why AsyncLocalStorage for Transactions?
687
+
688
+ **Decision**: Transaction propagation without explicit passing
689
+
690
+ **Reasons**:
691
+ - **Clean API**: No need to pass `tx` object through all functions
692
+ - **Automatic**: Works across async boundaries
693
+ - **Safe**: Isolated per request
694
+ - **Compatible**: Works with existing code
695
+
696
+ **Example**:
697
+ ```typescript
698
+ // With AsyncLocalStorage (SPFN)
699
+ async function createPostWithTags(data) {
700
+ const post = await create(posts, data);
701
+ const tags = await createMany(tags, data.tags.map(t => ({ postId: post.id, ...t })));
702
+ // Same transaction automatically
703
+ }
704
+
705
+ // vs Explicit transaction passing
706
+ async function createPostWithTags(tx, data) {
707
+ const post = await tx.insert(posts).values(data);
708
+ const tags = await tx.insert(tags).values(...);
709
+ // Must pass tx everywhere
348
710
  }
349
711
  ```
350
712
 
351
- Superfunction automatically discovers and merges these schemas during migration generation:
713
+ ---
714
+
715
+ ### 5. Why Hono over Express?
716
+
717
+ **Decision**: Hono as the underlying web framework
718
+
719
+ **Reasons**:
720
+ - **TypeScript First**: Better type inference
721
+ - **Performance**: Faster than Express
722
+ - **Modern**: Built for Edge/Serverless
723
+ - **Lightweight**: Smaller bundle size
724
+
725
+ ---
726
+
727
+ ### 6. Why TypeBox over Zod?
728
+
729
+ **Decision**: TypeBox for schema validation
730
+
731
+ **Reasons**:
732
+ - **JSON Schema**: Standard, interoperable
733
+ - **Performance**: Faster validation (JIT compilation)
734
+ - **OpenAPI**: Easy OpenAPI generation
735
+ - **Smaller**: Smaller bundle size
736
+
737
+ **Note**: Both are supported, TypeBox is the default
738
+
739
+ ---
740
+
741
+ ## Extension Points
742
+
743
+ ### 1. Custom Middleware
744
+
745
+ Add global or route-specific middleware:
746
+
352
747
  ```typescript
353
- import { getDrizzleConfig } from '@spfn/core'
748
+ // server.config.ts
749
+ import { defineServerConfig } from '@spfn/core/server';
750
+ import { defineMiddleware } from '@spfn/core/route';
751
+
752
+ const rateLimitMiddleware = defineMiddleware('rateLimit', async (c, next) => {
753
+ const ip = c.req.header('x-forwarded-for');
754
+ if (await isRateLimited(ip)) {
755
+ return c.json({ error: 'Rate limit exceeded' }, 429);
756
+ }
757
+ await next();
758
+ });
759
+
760
+ export default defineServerConfig()
761
+ .middlewares([rateLimitMiddleware])
762
+ .build();
354
763
 
355
- // Auto-discovers all function schemas
356
- const config = getDrizzleConfig()
764
+ // Route can skip middleware
765
+ export const publicRoute = route.get('/public')
766
+ .middleware({ skip: ['rateLimit'] })
767
+ .handler(async (c) => { ... });
357
768
  ```
358
769
 
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
770
+ ---
771
+
772
+ ### 2. Custom Interceptors
773
+
774
+ Add request/response interceptors:
775
+
776
+ ```typescript
777
+ // app/api/rpc/[routeName]/route.ts
778
+ import { appRouter } from '@/server/router';
779
+ import { createRpcProxy } from '@spfn/core/nextjs/server';
780
+
781
+ export const { GET, POST } = createRpcProxy({
782
+ router: appRouter,
783
+ interceptors: [
784
+ {
785
+ pathPattern: '/admin/*',
786
+ request: async (ctx, next) => {
787
+ // Check admin role
788
+ const isAdmin = await checkAdminRole(ctx.cookies);
789
+ if (!isAdmin) {
790
+ throw new Error('Unauthorized');
791
+ }
792
+ await next();
793
+ },
794
+ },
795
+ ],
796
+ });
366
797
  ```
367
798
 
368
- **Create your own Superfunction packages:**
799
+ ---
800
+
801
+ ### 3. Plugin System
802
+
803
+ Create SPFN packages with auto-discovery:
804
+
369
805
  ```typescript
370
- // 1. Define entities
371
- export const myTable = pgTable('my_table', { ... })
806
+ // @yourcompany/spfn-analytics package
807
+ import { registerInterceptors } from '@spfn/core/nextjs/server';
808
+
809
+ registerInterceptors('analytics', [
810
+ {
811
+ pathPattern: '*',
812
+ response: async (ctx, next) => {
813
+ await analytics.track({
814
+ path: ctx.path,
815
+ status: ctx.response.status,
816
+ duration: Date.now() - ctx.metadata.startTime,
817
+ });
818
+ await next();
819
+ },
820
+ },
821
+ ]);
822
+
823
+ // App: Auto-discovery
824
+ import '@yourcompany/spfn-analytics/adapters/nextjs';
825
+ export { GET, POST } from '@spfn/core/nextjs/server';
826
+ ```
827
+
828
+ ---
372
829
 
373
- // 2. Add to package.json
374
- {
375
- "spfn": {
376
- "schemas": ["./dist/entities/*.js"]
377
- }
830
+ ### 4. Custom Database Helpers
831
+
832
+ Extend database layer with domain-specific functions:
833
+
834
+ ```typescript
835
+ // src/server/repositories/users.repository.ts
836
+ import { findOne, create } from '@spfn/core/db';
837
+ import { users } from '../entities/users';
838
+
839
+ export async function findUserByEmail(email: string) {
840
+ return findOne(users, { email });
378
841
  }
379
842
 
380
- // 3. Users install with one command
381
- // pnpm spfn add @yourcompany/spfn-plugin
843
+ export async function createUserWithProfile(data) {
844
+ const user = await create(users, data.user);
845
+ const profile = await create(profiles, {
846
+ ...data.profile,
847
+ userId: user.id,
848
+ });
849
+ return { user, profile };
850
+ }
382
851
  ```
383
852
 
384
- ### 🔄 Transactions
385
- Automatic transaction management with async context propagation.
853
+ ---
386
854
 
387
- **[→ Read Transaction Documentation](./src/db/docs/transactions.md)**
855
+ ## Migration Guides
388
856
 
389
- **Key Features:**
390
- - Auto-commit on success, auto-rollback on error
391
- - AsyncLocalStorage-based context
392
- - Transaction logging
857
+ ### From Contract-Based to define-route
393
858
 
394
- ### 💾 Cache
395
- Redis integration with master-replica support.
859
+ **Before** (Deprecated):
860
+ ```typescript
861
+ // contract.ts
862
+ export const getUserContract = {
863
+ method: 'GET' as const,
864
+ path: '/users/:id',
865
+ params: Type.Object({ id: Type.String() }),
866
+ response: Type.Object({ id: Type.String(), name: Type.String() }),
867
+ } as const satisfies RouteContract;
868
+
869
+ // route.ts
870
+ import { createApp } from '@spfn/core/route';
871
+ const app = createApp();
872
+ app.bind(getUserContract, async (c) => { ... });
873
+ export default app;
874
+ ```
396
875
 
397
- **[→ Read Cache Documentation](./src/cache/README.md)**
876
+ **After** (Current):
877
+ ```typescript
878
+ // routes/users.ts
879
+ import { route } from '@spfn/core/route';
880
+
881
+ export const getUser = route.get('/users/:id')
882
+ .input({
883
+ params: Type.Object({ id: Type.String() }),
884
+ })
885
+ .handler(async (c) => {
886
+ const { params } = await c.data();
887
+ return c.success({ id: params.id, name: 'John' });
888
+ });
889
+
890
+ // router.ts
891
+ import { defineRouter } from '@spfn/core/route';
892
+ export const appRouter = defineRouter({ getUser });
893
+ export type AppRouter = typeof appRouter;
894
+ ```
398
895
 
399
- ### ⚠️ Error Handling
400
- Custom error classes with unified HTTP responses.
896
+ **Benefits**:
897
+ - Better type inference
898
+ - Explicit imports (tree-shaking)
899
+ - No separate contract files
401
900
 
402
- **[→ Read Error Documentation](./src/errors/README.md)**
901
+ ---
403
902
 
404
- ### 🔐 Middleware
405
- Request logging, CORS, and error handling middleware.
903
+ ### From File-Based to define-route
406
904
 
407
- **[→ Read Middleware Documentation](./src/middleware/README.md)**
905
+ **Before** (Deprecated):
906
+ ```typescript
907
+ // routes/users/[id].ts
908
+ export default async function handler(c) { ... }
408
909
 
409
- ### 🖥️ Server
410
- Server configuration and lifecycle management.
910
+ // Automatic file system scanning
911
+ ```
411
912
 
412
- **[→ Read Server Documentation](./src/server/README.md)**
913
+ **After** (Current):
914
+ ```typescript
915
+ // routes/users.ts
916
+ export const getUser = route.get('/users/:id')...
413
917
 
414
- ### 📝 Logger
415
- High-performance logging with multiple transports, sensitive data masking, and automatic validation.
918
+ // router.ts
919
+ export const appRouter = defineRouter({ getUser });
416
920
 
417
- **[→ Read Logger Documentation](./src/logger/README.md)**
921
+ // server.config.ts
922
+ export default defineServerConfig()
923
+ .routes(appRouter)
924
+ .build();
925
+ ```
418
926
 
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)
927
+ **Benefits**:
928
+ - Explicit route registration
929
+ - Better IDE support
930
+ - No runtime file system scanning
425
931
 
426
- ### ⚙️ Code Generation
427
- Automatic code generation with pluggable generators and centralized file watching.
932
+ ---
428
933
 
429
- **[→ Read Codegen Documentation](./src/codegen/README.md)**
934
+ ### From ContractClient to tRPC-Style API
430
935
 
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
936
+ **Before** (Old Client):
937
+ ```typescript
938
+ import { ContractClient } from '@spfn/core/client';
939
+ import { getUserContract } from '@/contracts/users';
940
+
941
+ const client = new ContractClient({ baseUrl: 'http://localhost:8790' });
942
+ const user = await client.call(getUserContract, {
943
+ params: { id: '123' },
944
+ });
945
+ ```
946
+
947
+ **After** (Current):
948
+ ```typescript
949
+ import { api } from '@spfn/core/nextjs/server';
950
+
951
+ const user = await api.getUser
952
+ .params({ id: '123' })
953
+ .call();
954
+ ```
955
+
956
+ **Benefits**:
957
+ - tRPC-style DX
958
+ - Method chaining
959
+ - Better type inference
960
+ - Automatic cookie handling
961
+
962
+ ---
437
963
 
438
964
  ## Module Exports
439
965
 
440
- ### Main Export
966
+ ### Main Server Exports
967
+
441
968
  ```typescript
442
- import { startServer, createServer } from '@spfn/core';
969
+ import {
970
+ startServer,
971
+ createServer,
972
+ defineServerConfig,
973
+ } from '@spfn/core';
443
974
  ```
444
975
 
445
- ### Routing
976
+ ### Route System
977
+
446
978
  ```typescript
447
- import { createApp, bind, loadRoutes } from '@spfn/core/route';
448
- import type { RouteContext, RouteContract } from '@spfn/core/route';
979
+ import {
980
+ route,
981
+ defineRouter,
982
+ } from '@spfn/core/route';
983
+
984
+ import type {
985
+ RouteDef,
986
+ Router,
987
+ RouteInput,
988
+ } from '@spfn/core/route';
449
989
  ```
450
990
 
451
- ### Database
991
+ ### Database System
992
+
452
993
  ```typescript
453
994
  import {
454
- getDatabase,
455
- findOne,
456
- findMany,
457
- create,
458
- createMany,
459
- updateOne,
460
- updateMany,
461
- deleteOne,
462
- deleteMany,
463
- count
995
+ getDatabase,
996
+ findOne,
997
+ findMany,
998
+ create,
999
+ createMany,
1000
+ updateOne,
1001
+ updateMany,
1002
+ deleteOne,
1003
+ deleteMany,
1004
+ count,
1005
+ } from '@spfn/core/db';
1006
+
1007
+ import {
1008
+ Transactional,
1009
+ getTransaction,
1010
+ runWithTransaction,
464
1011
  } from '@spfn/core/db';
465
1012
  ```
466
1013
 
467
- ### Transactions
1014
+ ### Client System
1015
+
468
1016
  ```typescript
1017
+ // Client-safe exports (works in Client Components)
469
1018
  import {
470
- Transactional,
471
- getTransaction,
472
- runWithTransaction
473
- } from '@spfn/core/db';
1019
+ createApi,
1020
+ ApiError,
1021
+ } from '@spfn/core/nextjs';
1022
+
1023
+ // Server-only exports (API Routes)
1024
+ import {
1025
+ createRpcProxy,
1026
+ registerInterceptors,
1027
+ } from '@spfn/core/nextjs/server';
474
1028
  ```
475
1029
 
476
- ### Cache
1030
+ ### Error System
1031
+
477
1032
  ```typescript
478
- import { initRedis, getRedis, getRedisRead } from '@spfn/core';
1033
+ import {
1034
+ ApiError,
1035
+ ValidationError,
1036
+ NotFoundError,
1037
+ ConflictError,
1038
+ UnauthorizedError,
1039
+ } from '@spfn/core/errors';
479
1040
  ```
480
1041
 
481
- ### Logger
1042
+ ### Logger System
1043
+
482
1044
  ```typescript
483
1045
  import { logger } from '@spfn/core';
484
1046
  ```
485
1047
 
486
- ### Client (for frontend)
1048
+ ### Cache System
1049
+
487
1050
  ```typescript
488
- import { ContractClient, createClient } from '@spfn/core/client';
1051
+ import {
1052
+ initCache,
1053
+ getCache,
1054
+ getCacheRead,
1055
+ isCacheDisabled,
1056
+ closeCache,
1057
+ } from '@spfn/core/cache';
489
1058
  ```
490
1059
 
491
- ## Environment Variables
1060
+ ### Environment System
1061
+
1062
+ ```typescript
1063
+ import {
1064
+ defineEnvSchema,
1065
+ createEnvRegistry,
1066
+ envString,
1067
+ envNumber,
1068
+ envBoolean,
1069
+ envEnum,
1070
+ } from '@spfn/core/env';
1071
+ ```
1072
+
1073
+ ### Configuration System
1074
+
1075
+ ```typescript
1076
+ import { env, envSchema } from '@spfn/core/config';
1077
+ ```
1078
+
1079
+ ---
1080
+
1081
+ ## Quick Reference
1082
+
1083
+ ### Environment Variables
492
1084
 
493
1085
  ```bash
494
1086
  # Database (required)
@@ -497,60 +1089,130 @@ DATABASE_URL=postgresql://user:pass@localhost:5432/db
497
1089
  # Database Read Replica (optional)
498
1090
  DATABASE_READ_URL=postgresql://user:pass@replica:5432/db
499
1091
 
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
1092
+ # Cache - Valkey/Redis (optional)
1093
+ CACHE_URL=redis://localhost:6379
1094
+ CACHE_WRITE_URL=redis://master:6379
1095
+ CACHE_READ_URL=redis://replica:6379
1096
+
1097
+ # Next.js App URL (for Server Components calling API Routes)
1098
+ SPFN_APP_URL=http://localhost:3000
1099
+
1100
+ # SPFN API Server URL (for API Route Proxy)
1101
+ SPFN_API_URL=http://localhost:8790
504
1102
 
505
1103
  # Server
506
1104
  PORT=8790
507
1105
  HOST=localhost
508
1106
  NODE_ENV=development
509
1107
 
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)
1108
+ # Server Timeouts (optional, milliseconds)
1109
+ SERVER_TIMEOUT=120000
1110
+ SERVER_KEEPALIVE_TIMEOUT=65000
1111
+ SERVER_HEADERS_TIMEOUT=60000
1112
+ SHUTDOWN_TIMEOUT=30000
515
1113
 
516
1114
  # 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)
1115
+ LOGGER_ADAPTER=pino
1116
+ LOGGER_FILE_ENABLED=true
1117
+ LOG_DIR=/var/log/myapp
520
1118
  ```
521
1119
 
522
- ## Requirements
523
-
524
- - Node.js >= 18
525
- - Next.js 15+ with App Router (when using with CLI)
526
- - PostgreSQL
527
- - Redis (optional)
1120
+ ---
528
1121
 
529
- ## Testing
1122
+ ### Basic Setup
530
1123
 
1124
+ **1. Install**
531
1125
  ```bash
532
- npm test # Run all tests
533
- npm test -- route # Run route tests only
534
- npm test -- --coverage # With coverage
1126
+ npm install @spfn/core hono drizzle-orm postgres @sinclair/typebox
1127
+ ```
1128
+
1129
+ **2. Define Routes**
1130
+ ```typescript
1131
+ // src/server/routes/users.ts
1132
+ export const getUser = route.get('/users/:id')
1133
+ .input({ params: Type.Object({ id: Type.String() }) })
1134
+ .handler(async (c) => {
1135
+ const { params } = await c.data();
1136
+ return c.success({ id: params.id, name: 'John' });
1137
+ });
535
1138
  ```
536
1139
 
537
- **Test Coverage:** 120+ tests across all modules
1140
+ **3. Create Router**
1141
+ ```typescript
1142
+ // src/server/router.ts
1143
+ export const appRouter = defineRouter({ getUser });
1144
+ export type AppRouter = typeof appRouter;
1145
+ ```
1146
+
1147
+ **4. Configure Server**
1148
+ ```typescript
1149
+ // src/server/server.config.ts
1150
+ export default defineServerConfig()
1151
+ .port(8790)
1152
+ .routes(appRouter)
1153
+ .build();
1154
+ ```
1155
+
1156
+ **5. Create RPC Proxy (Next.js)**
1157
+ ```typescript
1158
+ // app/api/rpc/[routeName]/route.ts
1159
+ import { appRouter } from '@/server/router';
1160
+ import { createRpcProxy } from '@spfn/core/nextjs/server';
1161
+
1162
+ export const { GET, POST } = createRpcProxy({ router: appRouter });
1163
+ ```
1164
+
1165
+ **6. Use Client**
1166
+ ```typescript
1167
+ // app/users/[id]/page.tsx
1168
+ import { createApi } from '@spfn/core/nextjs';
1169
+ import type { AppRouter } from '@/server/router';
1170
+
1171
+ const api = createApi<AppRouter>();
1172
+ const user = await api.getUser.call({ params: { id: '123' } });
1173
+ ```
1174
+
1175
+ ---
538
1176
 
539
1177
  ## Documentation
540
1178
 
1179
+ ### Technical Architecture
1180
+ - [Route System](./src/route/README.md) - define-route system, type inference
1181
+ - [Server System](./src/server/README.md) - Configuration, middleware pipeline, lifecycle
1182
+ - [Database System](./src/db/README.md) - Helper functions, transactions, schema helpers
1183
+ - [Client System](./src/nextjs/README.md) - tRPC-style API, TypedProxy, interceptors
1184
+
541
1185
  ### Guides
542
- - [File-based Routing](./src/route/README.md)
543
- - [Database & Helper Functions](./src/db/README.md)
544
1186
  - [Transaction Management](./src/db/docs/transactions.md)
545
- - [Redis Cache](./src/cache/README.md)
546
1187
  - [Error Handling](./src/errors/README.md)
547
- - [Middleware](./src/middleware/README.md)
548
- - [Server Configuration](./src/server/README.md)
549
1188
  - [Logger](./src/logger/README.md)
1189
+ - [Cache](./src/cache/README.md)
1190
+ - [Middleware](./src/middleware/README.md)
550
1191
  - [Code Generation](./src/codegen/README.md)
551
1192
 
552
- ### API Reference
553
- - See module-specific README files linked above
1193
+ ---
1194
+
1195
+ ## Requirements
1196
+
1197
+ - Node.js >= 18
1198
+ - Next.js 15+ with App Router (when using client integration)
1199
+ - PostgreSQL
1200
+ - Valkey/Redis (optional)
1201
+ - TypeScript >= 5.0
1202
+
1203
+ ---
1204
+
1205
+ ## Testing
1206
+
1207
+ ```bash
1208
+ npm test # Run all tests
1209
+ npm test -- route # Run route tests only
1210
+ npm test -- --coverage # With coverage
1211
+ ```
1212
+
1213
+ **Test Coverage**: 650+ tests across all modules
1214
+
1215
+ ---
554
1216
 
555
1217
  ## License
556
1218