create-fluxstack 1.10.1 → 1.12.0
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.
- package/.dockerignore +1 -2
- package/Dockerfile +8 -8
- package/LLMD/INDEX.md +64 -0
- package/LLMD/MAINTENANCE.md +197 -0
- package/LLMD/MIGRATION.md +156 -0
- package/LLMD/config/.gitkeep +1 -0
- package/LLMD/config/declarative-system.md +268 -0
- package/LLMD/config/environment-vars.md +327 -0
- package/LLMD/config/runtime-reload.md +401 -0
- package/LLMD/core/.gitkeep +1 -0
- package/LLMD/core/build-system.md +599 -0
- package/LLMD/core/framework-lifecycle.md +229 -0
- package/LLMD/core/plugin-system.md +451 -0
- package/LLMD/patterns/.gitkeep +1 -0
- package/LLMD/patterns/anti-patterns.md +297 -0
- package/LLMD/patterns/project-structure.md +264 -0
- package/LLMD/patterns/type-safety.md +440 -0
- package/LLMD/reference/.gitkeep +1 -0
- package/LLMD/reference/cli-commands.md +250 -0
- package/LLMD/reference/plugin-hooks.md +357 -0
- package/LLMD/reference/routing.md +39 -0
- package/LLMD/reference/troubleshooting.md +364 -0
- package/LLMD/resources/.gitkeep +1 -0
- package/LLMD/resources/controllers.md +465 -0
- package/LLMD/resources/live-components.md +703 -0
- package/LLMD/resources/live-rooms.md +482 -0
- package/LLMD/resources/live-upload.md +130 -0
- package/LLMD/resources/plugins-external.md +617 -0
- package/LLMD/resources/routes-eden.md +254 -0
- package/README.md +37 -17
- package/app/client/index.html +0 -1
- package/app/client/src/App.tsx +107 -150
- package/app/client/src/components/AppLayout.tsx +68 -0
- package/app/client/src/components/BackButton.tsx +13 -0
- package/app/client/src/components/DemoPage.tsx +20 -0
- package/app/client/src/components/LiveUploadWidget.tsx +204 -0
- package/app/client/src/lib/eden-api.ts +85 -60
- package/app/client/src/live/ChatDemo.tsx +107 -0
- package/app/client/src/live/CounterDemo.tsx +206 -0
- package/app/client/src/live/FormDemo.tsx +119 -0
- package/app/client/src/live/RoomChatDemo.tsx +242 -0
- package/app/client/src/live/UploadDemo.tsx +21 -0
- package/app/client/src/main.tsx +4 -1
- package/app/client/src/pages/ApiTestPage.tsx +108 -0
- package/app/client/src/pages/HomePage.tsx +76 -0
- package/app/server/app.ts +1 -4
- package/app/server/controllers/users.controller.ts +36 -44
- package/app/server/index.ts +25 -35
- package/app/server/live/LiveChat.ts +77 -0
- package/app/server/live/LiveCounter.ts +67 -0
- package/app/server/live/LiveForm.ts +63 -0
- package/app/server/live/LiveLocalCounter.ts +32 -0
- package/app/server/live/LiveRoomChat.ts +285 -0
- package/app/server/live/LiveUpload.ts +81 -0
- package/app/server/routes/index.ts +3 -1
- package/app/server/routes/room.routes.ts +117 -0
- package/app/server/routes/users.routes.ts +35 -27
- package/app/shared/types/index.ts +14 -2
- package/config/app.config.ts +2 -62
- package/config/client.config.ts +2 -95
- package/config/database.config.ts +2 -99
- package/config/fluxstack.config.ts +25 -45
- package/config/index.ts +57 -38
- package/config/monitoring.config.ts +2 -114
- package/config/plugins.config.ts +2 -80
- package/config/server.config.ts +2 -68
- package/config/services.config.ts +2 -130
- package/config/system/app.config.ts +29 -0
- package/config/system/build.config.ts +49 -0
- package/config/system/client.config.ts +68 -0
- package/config/system/database.config.ts +17 -0
- package/config/system/fluxstack.config.ts +114 -0
- package/config/{logger.config.ts → system/logger.config.ts} +3 -1
- package/config/system/monitoring.config.ts +114 -0
- package/config/system/plugins.config.ts +84 -0
- package/config/{runtime.config.ts → system/runtime.config.ts} +1 -1
- package/config/system/server.config.ts +68 -0
- package/config/system/services.config.ts +46 -0
- package/config/{system.config.ts → system/system.config.ts} +1 -1
- package/core/build/flux-plugins-generator.ts +325 -325
- package/core/build/index.ts +39 -27
- package/core/build/live-components-generator.ts +3 -3
- package/core/build/optimizer.ts +235 -235
- package/core/cli/command-registry.ts +6 -4
- package/core/cli/commands/build.ts +79 -0
- package/core/cli/commands/create.ts +54 -0
- package/core/cli/commands/dev.ts +101 -0
- package/core/cli/commands/help.ts +34 -0
- package/core/cli/commands/index.ts +34 -0
- package/core/cli/commands/make-plugin.ts +90 -0
- package/core/cli/commands/plugin-add.ts +197 -0
- package/core/cli/commands/plugin-deps.ts +2 -2
- package/core/cli/commands/plugin-list.ts +208 -0
- package/core/cli/commands/plugin-remove.ts +170 -0
- package/core/cli/generators/component.ts +769 -769
- package/core/cli/generators/controller.ts +1 -1
- package/core/cli/generators/index.ts +146 -146
- package/core/cli/generators/interactive.ts +227 -227
- package/core/cli/generators/plugin.ts +2 -2
- package/core/cli/generators/prompts.ts +82 -82
- package/core/cli/generators/route.ts +6 -6
- package/core/cli/generators/service.ts +2 -2
- package/core/cli/generators/template-engine.ts +4 -3
- package/core/cli/generators/types.ts +2 -2
- package/core/cli/generators/utils.ts +191 -191
- package/core/cli/index.ts +115 -686
- package/core/cli/plugin-discovery.ts +2 -2
- package/core/client/LiveComponentsProvider.tsx +60 -8
- package/core/client/api/eden.ts +183 -0
- package/core/client/api/index.ts +11 -0
- package/core/client/components/Live.tsx +104 -0
- package/core/client/fluxstack.ts +1 -9
- package/core/client/hooks/AdaptiveChunkSizer.ts +215 -215
- package/core/client/hooks/state-validator.ts +1 -1
- package/core/client/hooks/useAuth.ts +48 -48
- package/core/client/hooks/useChunkedUpload.ts +85 -35
- package/core/client/hooks/useLiveChunkedUpload.ts +87 -0
- package/core/client/hooks/useLiveComponent.ts +800 -0
- package/core/client/hooks/useLiveUpload.ts +71 -0
- package/core/client/hooks/useRoom.ts +409 -0
- package/core/client/hooks/useRoomProxy.ts +382 -0
- package/core/client/index.ts +17 -68
- package/core/client/standalone-entry.ts +8 -0
- package/core/client/standalone.ts +74 -53
- package/core/client/state/createStore.ts +192 -192
- package/core/client/state/index.ts +14 -14
- package/core/config/index.ts +70 -291
- package/core/config/schema.ts +42 -723
- package/core/framework/client.ts +131 -131
- package/core/framework/index.ts +7 -7
- package/core/framework/server.ts +47 -40
- package/core/framework/types.ts +2 -2
- package/core/index.ts +23 -4
- package/core/live/ComponentRegistry.ts +3 -3
- package/core/live/types.ts +77 -0
- package/core/plugins/built-in/index.ts +134 -134
- package/core/plugins/built-in/live-components/commands/create-live-component.ts +242 -1066
- package/core/plugins/built-in/live-components/index.ts +1 -1
- package/core/plugins/built-in/monitoring/index.ts +111 -47
- package/core/plugins/built-in/static/index.ts +1 -1
- package/core/plugins/built-in/swagger/index.ts +68 -265
- package/core/plugins/built-in/vite/index.ts +85 -185
- package/core/plugins/built-in/vite/vite-dev.ts +10 -16
- package/core/plugins/config.ts +9 -7
- package/core/plugins/dependency-manager.ts +31 -1
- package/core/plugins/discovery.ts +19 -7
- package/core/plugins/executor.ts +2 -2
- package/core/plugins/index.ts +203 -203
- package/core/plugins/manager.ts +27 -39
- package/core/plugins/module-resolver.ts +19 -8
- package/core/plugins/registry.ts +255 -19
- package/core/plugins/types.ts +20 -53
- package/core/server/framework.ts +66 -43
- package/core/server/index.ts +15 -15
- package/core/server/live/ComponentRegistry.ts +78 -71
- package/core/server/live/FileUploadManager.ts +23 -10
- package/core/server/live/LiveComponentPerformanceMonitor.ts +1 -1
- package/core/server/live/LiveRoomManager.ts +261 -0
- package/core/server/live/RoomEventBus.ts +234 -0
- package/core/server/live/RoomStateManager.ts +172 -0
- package/core/server/live/StateSignature.ts +643 -643
- package/core/server/live/WebSocketConnectionManager.ts +30 -19
- package/core/server/live/auto-generated-components.ts +21 -9
- package/core/server/live/index.ts +14 -0
- package/core/server/live/websocket-plugin.ts +214 -67
- package/core/server/middleware/elysia-helpers.ts +7 -2
- package/core/server/middleware/errorHandling.ts +1 -1
- package/core/server/middleware/index.ts +31 -31
- package/core/server/plugins/database.ts +180 -180
- package/core/server/plugins/static-files-plugin.ts +69 -69
- package/core/server/plugins/swagger.ts +1 -1
- package/core/server/rooms/RoomBroadcaster.ts +357 -0
- package/core/server/rooms/RoomSystem.ts +463 -0
- package/core/server/rooms/index.ts +13 -0
- package/core/server/services/BaseService.ts +1 -1
- package/core/server/services/ServiceContainer.ts +1 -1
- package/core/server/services/index.ts +8 -8
- package/core/templates/create-project.ts +12 -12
- package/core/testing/index.ts +9 -9
- package/core/testing/setup.ts +73 -73
- package/core/types/api.ts +168 -168
- package/core/types/build.ts +219 -219
- package/core/types/config.ts +56 -26
- package/core/types/index.ts +4 -4
- package/core/types/plugin.ts +107 -107
- package/core/types/types.ts +353 -14
- package/core/utils/build-logger.ts +324 -324
- package/core/utils/config-schema.ts +480 -480
- package/core/utils/env.ts +2 -8
- package/core/utils/errors/codes.ts +114 -114
- package/core/utils/errors/handlers.ts +36 -1
- package/core/utils/errors/index.ts +49 -5
- package/core/utils/errors/middleware.ts +113 -113
- package/core/utils/helpers.ts +6 -16
- package/core/utils/index.ts +17 -17
- package/core/utils/logger/colors.ts +114 -114
- package/core/utils/logger/config.ts +13 -9
- package/core/utils/logger/formatter.ts +82 -82
- package/core/utils/logger/group-logger.ts +101 -101
- package/core/utils/logger/index.ts +6 -1
- package/core/utils/logger/stack-trace.ts +3 -1
- package/core/utils/logger/startup-banner.ts +82 -82
- package/core/utils/logger/winston-logger.ts +152 -152
- package/core/utils/monitoring/index.ts +211 -211
- package/core/utils/sync-version.ts +66 -66
- package/core/utils/version.ts +1 -1
- package/create-fluxstack.ts +8 -7
- package/package.json +12 -13
- package/plugins/crypto-auth/cli/make-protected-route.command.ts +1 -1
- package/plugins/crypto-auth/client/CryptoAuthClient.ts +302 -302
- package/plugins/crypto-auth/client/components/index.ts +11 -11
- package/plugins/crypto-auth/client/index.ts +11 -11
- package/plugins/crypto-auth/config/index.ts +1 -1
- package/plugins/crypto-auth/index.ts +4 -4
- package/plugins/crypto-auth/package.json +65 -65
- package/plugins/crypto-auth/server/AuthMiddleware.ts +1 -1
- package/plugins/crypto-auth/server/CryptoAuthService.ts +185 -185
- package/plugins/crypto-auth/server/index.ts +21 -21
- package/plugins/crypto-auth/server/middlewares/cryptoAuthAdmin.ts +3 -3
- package/plugins/crypto-auth/server/middlewares/cryptoAuthOptional.ts +1 -1
- package/plugins/crypto-auth/server/middlewares/cryptoAuthPermissions.ts +2 -2
- package/plugins/crypto-auth/server/middlewares/cryptoAuthRequired.ts +2 -2
- package/plugins/crypto-auth/server/middlewares/helpers.ts +1 -1
- package/plugins/crypto-auth/server/middlewares/index.ts +22 -22
- package/tsconfig.api-strict.json +16 -0
- package/tsconfig.json +48 -52
- package/{app/client/tsconfig.node.json → tsconfig.node.json} +25 -25
- package/types/global.d.ts +29 -29
- package/types/vitest.d.ts +8 -8
- package/vite.config.ts +38 -62
- package/vitest.config.live.ts +10 -9
- package/vitest.config.ts +29 -17
- package/app/client/README.md +0 -69
- package/app/client/SIMPLIFICATION.md +0 -140
- package/app/client/frontend-only.ts +0 -12
- package/app/client/src/live/FileUploadExample.tsx +0 -359
- package/app/client/src/live/MinimalLiveClock.tsx +0 -47
- package/app/client/src/live/QuickUploadTest.tsx +0 -193
- package/app/client/tsconfig.app.json +0 -45
- package/app/client/tsconfig.json +0 -7
- package/app/client/zustand-setup.md +0 -65
- package/app/server/backend-only.ts +0 -18
- package/app/server/live/LiveClockComponent.ts +0 -215
- package/app/server/live/LiveFileUploadComponent.ts +0 -77
- package/app/server/routes/env-test.ts +0 -110
- package/core/client/hooks/index.ts +0 -7
- package/core/client/hooks/useHybridLiveComponent.ts +0 -685
- package/core/client/hooks/useTypedLiveComponent.ts +0 -133
- package/core/client/hooks/useWebSocket.ts +0 -361
- package/core/config/env.ts +0 -546
- package/core/config/loader.ts +0 -522
- package/core/config/runtime-config.ts +0 -327
- package/core/config/validator.ts +0 -540
- package/core/server/backend-entry.ts +0 -51
- package/core/server/standalone.ts +0 -106
- package/core/utils/regenerate-files.ts +0 -69
- package/fluxstack.config.ts +0 -354
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
# Controllers & Services
|
|
2
|
+
|
|
3
|
+
**Version:** 1.11.0 | **Updated:** 2025-02-08
|
|
4
|
+
|
|
5
|
+
## Quick Facts
|
|
6
|
+
|
|
7
|
+
- Controllers handle business logic, separate from routes
|
|
8
|
+
- Use static methods for stateless operations
|
|
9
|
+
- Return structured responses: `{ success: boolean, data?, error? }`
|
|
10
|
+
- Service layer optional for complex business logic
|
|
11
|
+
- Error handling uses FluxStackError classes
|
|
12
|
+
- Database integration via controllers or separate services
|
|
13
|
+
|
|
14
|
+
## Controller Pattern
|
|
15
|
+
|
|
16
|
+
Controllers separate business logic from route definitions:
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
// app/server/controllers/users.controller.ts
|
|
20
|
+
import type { CreateUserRequest } from '@app/shared/types'
|
|
21
|
+
|
|
22
|
+
export class UsersController {
|
|
23
|
+
static async getUsers() {
|
|
24
|
+
return {
|
|
25
|
+
success: true as const,
|
|
26
|
+
users: this.users,
|
|
27
|
+
count: this.users.length
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static async getUserById(id: number) {
|
|
32
|
+
const user = await findUser(id)
|
|
33
|
+
if (!user) {
|
|
34
|
+
return {
|
|
35
|
+
success: false as const,
|
|
36
|
+
error: 'User not found'
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
success: true as const,
|
|
41
|
+
user
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static async createUser(data: CreateUserRequest) {
|
|
46
|
+
// Validation
|
|
47
|
+
const existingUser = await findByEmail(data.email)
|
|
48
|
+
if (existingUser) {
|
|
49
|
+
return {
|
|
50
|
+
success: false as const,
|
|
51
|
+
error: 'Email already in use'
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Business logic
|
|
56
|
+
const newUser = await saveUser(data)
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
success: true as const,
|
|
60
|
+
user: newUser,
|
|
61
|
+
message: 'User created successfully'
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Route Integration
|
|
68
|
+
|
|
69
|
+
Routes call controllers, handle HTTP concerns:
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
// app/server/routes/users.routes.ts
|
|
73
|
+
import { Elysia, t } from 'elysia'
|
|
74
|
+
import { UsersController } from '@app/server/controllers/users.controller'
|
|
75
|
+
|
|
76
|
+
export const usersRoutes = new Elysia({ prefix: '/users' })
|
|
77
|
+
.get('/', async () => UsersController.getUsers(), {
|
|
78
|
+
response: GetUsersResponseSchema
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
.get('/:id', async ({ params, set }) => {
|
|
82
|
+
const id = Number(params.id)
|
|
83
|
+
|
|
84
|
+
if (!Number.isFinite(id)) {
|
|
85
|
+
set.status = 400
|
|
86
|
+
return { success: false, error: 'Invalid ID' }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const result = await UsersController.getUserById(id)
|
|
90
|
+
|
|
91
|
+
if (!result.success) {
|
|
92
|
+
set.status = 404
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return result
|
|
96
|
+
}, {
|
|
97
|
+
params: t.Object({ id: t.String() }),
|
|
98
|
+
response: GetUserResponseSchema
|
|
99
|
+
})
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Response Structure
|
|
103
|
+
|
|
104
|
+
Consistent response format across all controllers:
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
// Success response
|
|
108
|
+
{
|
|
109
|
+
success: true,
|
|
110
|
+
data: any,
|
|
111
|
+
message?: string
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Error response
|
|
115
|
+
{
|
|
116
|
+
success: false,
|
|
117
|
+
error: string,
|
|
118
|
+
details?: any
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Use `as const` for literal types in responses:
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
return {
|
|
126
|
+
success: true as const, // Type: true (not boolean)
|
|
127
|
+
user: newUser
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Service Layer Pattern
|
|
132
|
+
|
|
133
|
+
For complex business logic, separate into services:
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
// app/server/services/user.service.ts
|
|
137
|
+
export class UserService {
|
|
138
|
+
static async validateUserData(data: CreateUserRequest) {
|
|
139
|
+
// Complex validation logic
|
|
140
|
+
if (!this.isValidEmail(data.email)) {
|
|
141
|
+
throw new ValidationError('Invalid email format')
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const exists = await this.checkEmailExists(data.email)
|
|
145
|
+
if (exists) {
|
|
146
|
+
throw new ConflictError('Email already registered')
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
static async createUserWithProfile(data: CreateUserRequest) {
|
|
151
|
+
// Multi-step business logic
|
|
152
|
+
const user = await this.createUser(data)
|
|
153
|
+
const profile = await this.createProfile(user.id)
|
|
154
|
+
await this.sendWelcomeEmail(user.email)
|
|
155
|
+
|
|
156
|
+
return { user, profile }
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private static isValidEmail(email: string): boolean {
|
|
160
|
+
// Validation logic
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Controller uses service:
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
// app/server/controllers/users.controller.ts
|
|
169
|
+
import { UserService } from '@app/server/services/user.service'
|
|
170
|
+
|
|
171
|
+
export class UsersController {
|
|
172
|
+
static async createUser(data: CreateUserRequest) {
|
|
173
|
+
try {
|
|
174
|
+
await UserService.validateUserData(data)
|
|
175
|
+
const result = await UserService.createUserWithProfile(data)
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
success: true as const,
|
|
179
|
+
user: result.user,
|
|
180
|
+
profile: result.profile
|
|
181
|
+
}
|
|
182
|
+
} catch (error) {
|
|
183
|
+
if (error instanceof FluxStackError) {
|
|
184
|
+
return {
|
|
185
|
+
success: false as const,
|
|
186
|
+
error: error.message
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
throw error
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Error Handling
|
|
196
|
+
|
|
197
|
+
Use FluxStackError classes for structured errors:
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
import {
|
|
201
|
+
ValidationError,
|
|
202
|
+
NotFoundError,
|
|
203
|
+
ConflictError,
|
|
204
|
+
UnauthorizedError,
|
|
205
|
+
DatabaseError
|
|
206
|
+
} from '@core/utils/errors'
|
|
207
|
+
|
|
208
|
+
export class UsersController {
|
|
209
|
+
static async updateUser(id: number, data: UpdateUserRequest) {
|
|
210
|
+
// Validation errors (400)
|
|
211
|
+
if (!data.name || data.name.length < 2) {
|
|
212
|
+
throw new ValidationError('Name must be at least 2 characters')
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Not found errors (404)
|
|
216
|
+
const user = await findUser(id)
|
|
217
|
+
if (!user) {
|
|
218
|
+
throw new NotFoundError('User', { id })
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Conflict errors (409)
|
|
222
|
+
if (data.email !== user.email) {
|
|
223
|
+
const emailExists = await checkEmailExists(data.email)
|
|
224
|
+
if (emailExists) {
|
|
225
|
+
throw new ConflictError('Email already in use', { email: data.email })
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Database errors (500)
|
|
230
|
+
try {
|
|
231
|
+
const updated = await updateUserInDb(id, data)
|
|
232
|
+
return { success: true, user: updated }
|
|
233
|
+
} catch (error) {
|
|
234
|
+
throw new DatabaseError('update', { id, error })
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## Available Error Classes
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
// Validation (400)
|
|
244
|
+
ValidationError
|
|
245
|
+
InvalidInputError
|
|
246
|
+
MissingRequiredFieldError
|
|
247
|
+
|
|
248
|
+
// Authentication (401)
|
|
249
|
+
UnauthorizedError
|
|
250
|
+
InvalidTokenError
|
|
251
|
+
TokenExpiredError
|
|
252
|
+
|
|
253
|
+
// Authorization (403)
|
|
254
|
+
ForbiddenError
|
|
255
|
+
InsufficientPermissionsError
|
|
256
|
+
|
|
257
|
+
// Not Found (404)
|
|
258
|
+
NotFoundError
|
|
259
|
+
ResourceNotFoundError
|
|
260
|
+
EndpointNotFoundError
|
|
261
|
+
|
|
262
|
+
// Conflict (409)
|
|
263
|
+
ConflictError
|
|
264
|
+
ResourceAlreadyExistsError
|
|
265
|
+
|
|
266
|
+
// Rate Limiting (429)
|
|
267
|
+
RateLimitExceededError
|
|
268
|
+
|
|
269
|
+
// Server Errors (500)
|
|
270
|
+
InternalServerError
|
|
271
|
+
DatabaseError
|
|
272
|
+
ExternalServiceError
|
|
273
|
+
|
|
274
|
+
// Service Unavailable (503)
|
|
275
|
+
ServiceUnavailableError
|
|
276
|
+
MaintenanceModeError
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## Database Integration
|
|
280
|
+
|
|
281
|
+
Example with in-memory store (replace with real database):
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
export class UsersController {
|
|
285
|
+
private static users: User[] = []
|
|
286
|
+
private static nextId = 1
|
|
287
|
+
|
|
288
|
+
static async getUsers() {
|
|
289
|
+
// In production: const users = await db.select().from(usersTable)
|
|
290
|
+
return {
|
|
291
|
+
success: true as const,
|
|
292
|
+
users: this.users,
|
|
293
|
+
count: this.users.length
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
static async createUser(data: CreateUserRequest) {
|
|
298
|
+
// In production: const user = await db.insert(usersTable).values(data)
|
|
299
|
+
const newUser: User = {
|
|
300
|
+
id: this.nextId++,
|
|
301
|
+
...data,
|
|
302
|
+
createdAt: new Date()
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
this.users.push(newUser)
|
|
306
|
+
|
|
307
|
+
return {
|
|
308
|
+
success: true as const,
|
|
309
|
+
user: newUser
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
With Drizzle ORM:
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
import { db } from '@app/server/db'
|
|
319
|
+
import { users } from '@app/server/db/schema'
|
|
320
|
+
import { eq } from 'drizzle-orm'
|
|
321
|
+
|
|
322
|
+
export class UsersController {
|
|
323
|
+
static async getUsers() {
|
|
324
|
+
const userList = await db.select().from(users)
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
success: true as const,
|
|
328
|
+
users: userList,
|
|
329
|
+
count: userList.length
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
static async getUserById(id: number) {
|
|
334
|
+
const [user] = await db
|
|
335
|
+
.select()
|
|
336
|
+
.from(users)
|
|
337
|
+
.where(eq(users.id, id))
|
|
338
|
+
|
|
339
|
+
if (!user) {
|
|
340
|
+
return {
|
|
341
|
+
success: false as const,
|
|
342
|
+
error: 'User not found'
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return {
|
|
347
|
+
success: true as const,
|
|
348
|
+
user
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
static async createUser(data: CreateUserRequest) {
|
|
353
|
+
try {
|
|
354
|
+
const [newUser] = await db
|
|
355
|
+
.insert(users)
|
|
356
|
+
.values(data)
|
|
357
|
+
.returning()
|
|
358
|
+
|
|
359
|
+
return {
|
|
360
|
+
success: true as const,
|
|
361
|
+
user: newUser
|
|
362
|
+
}
|
|
363
|
+
} catch (error) {
|
|
364
|
+
throw new DatabaseError('insert', { error })
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
## Organization Patterns
|
|
371
|
+
|
|
372
|
+
### Simple Apps
|
|
373
|
+
```
|
|
374
|
+
app/server/
|
|
375
|
+
├── controllers/
|
|
376
|
+
│ ├── users.controller.ts
|
|
377
|
+
│ ├── posts.controller.ts
|
|
378
|
+
│ └── auth.controller.ts
|
|
379
|
+
└── routes/
|
|
380
|
+
├── users.routes.ts
|
|
381
|
+
├── posts.routes.ts
|
|
382
|
+
└── auth.routes.ts
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### Complex Apps
|
|
386
|
+
```
|
|
387
|
+
app/server/
|
|
388
|
+
├── controllers/
|
|
389
|
+
│ ├── users.controller.ts
|
|
390
|
+
│ └── posts.controller.ts
|
|
391
|
+
├── services/
|
|
392
|
+
│ ├── user.service.ts
|
|
393
|
+
│ ├── email.service.ts
|
|
394
|
+
│ └── storage.service.ts
|
|
395
|
+
├── repositories/
|
|
396
|
+
│ ├── user.repository.ts
|
|
397
|
+
│ └── post.repository.ts
|
|
398
|
+
└── routes/
|
|
399
|
+
├── users.routes.ts
|
|
400
|
+
└── posts.routes.ts
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
## Testing Controllers
|
|
404
|
+
|
|
405
|
+
```typescript
|
|
406
|
+
// tests/unit/controllers/users.controller.test.ts
|
|
407
|
+
import { describe, it, expect, beforeEach } from 'vitest'
|
|
408
|
+
import { UsersController } from '@app/server/controllers/users.controller'
|
|
409
|
+
|
|
410
|
+
describe('UsersController', () => {
|
|
411
|
+
beforeEach(() => {
|
|
412
|
+
UsersController.resetForTesting()
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
it('should create a user', async () => {
|
|
416
|
+
const result = await UsersController.createUser({
|
|
417
|
+
name: 'John Doe',
|
|
418
|
+
email: 'john@example.com'
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
expect(result.success).toBe(true)
|
|
422
|
+
expect(result.user).toMatchObject({
|
|
423
|
+
name: 'John Doe',
|
|
424
|
+
email: 'john@example.com'
|
|
425
|
+
})
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
it('should return error for duplicate email', async () => {
|
|
429
|
+
await UsersController.createUser({
|
|
430
|
+
name: 'John',
|
|
431
|
+
email: 'john@example.com'
|
|
432
|
+
})
|
|
433
|
+
|
|
434
|
+
const result = await UsersController.createUser({
|
|
435
|
+
name: 'Jane',
|
|
436
|
+
email: 'john@example.com'
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
expect(result.success).toBe(false)
|
|
440
|
+
expect(result.error).toContain('already in use')
|
|
441
|
+
})
|
|
442
|
+
})
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
## Critical Rules
|
|
446
|
+
|
|
447
|
+
**ALWAYS:**
|
|
448
|
+
- Separate business logic from routes
|
|
449
|
+
- Return structured `{ success, data?, error? }` responses
|
|
450
|
+
- Use `as const` for literal types
|
|
451
|
+
- Handle errors with FluxStackError classes
|
|
452
|
+
- Validate input in controllers or services
|
|
453
|
+
|
|
454
|
+
**NEVER:**
|
|
455
|
+
- Put business logic directly in routes
|
|
456
|
+
- Return raw data without success/error structure
|
|
457
|
+
- Ignore error handling
|
|
458
|
+
- Mix database queries with route handlers
|
|
459
|
+
- Forget to set HTTP status codes in routes
|
|
460
|
+
|
|
461
|
+
## Related
|
|
462
|
+
|
|
463
|
+
- [Routes with Eden Treaty](./routes-eden.md)
|
|
464
|
+
- [Project Structure](../patterns/project-structure.md)
|
|
465
|
+
- [Anti-Patterns](../patterns/anti-patterns.md)
|