create-fluxstack 1.19.0 → 1.20.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/LLMD/INDEX.md +1 -1
- package/LLMD/MAINTENANCE.md +197 -197
- package/LLMD/MIGRATION.md +44 -1
- package/LLMD/agent.md +20 -7
- package/LLMD/config/declarative-system.md +268 -268
- package/LLMD/config/environment-vars.md +3 -6
- package/LLMD/config/runtime-reload.md +401 -401
- package/LLMD/core/build-system.md +599 -599
- package/LLMD/core/framework-lifecycle.md +249 -229
- package/LLMD/core/plugin-system.md +154 -100
- package/LLMD/patterns/anti-patterns.md +397 -397
- package/LLMD/patterns/project-structure.md +264 -264
- package/LLMD/patterns/type-safety.md +61 -5
- package/LLMD/reference/cli-commands.md +31 -7
- package/LLMD/reference/plugin-hooks.md +4 -2
- package/LLMD/reference/troubleshooting.md +364 -364
- package/LLMD/resources/controllers.md +465 -465
- package/LLMD/resources/live-auth.md +178 -1
- package/LLMD/resources/live-binary-delta.md +3 -1
- package/LLMD/resources/live-components.md +1192 -1041
- package/LLMD/resources/live-logging.md +3 -1
- package/LLMD/resources/live-rooms.md +1 -1
- package/LLMD/resources/live-upload.md +228 -181
- package/LLMD/resources/plugins-external.md +8 -7
- package/LLMD/resources/rest-auth.md +290 -290
- package/LLMD/resources/routes-eden.md +254 -254
- package/app/client/.live-stubs/LiveAdminPanel.js +15 -0
- package/app/client/.live-stubs/LiveCounter.js +9 -0
- package/app/client/.live-stubs/LiveForm.js +11 -0
- package/app/client/.live-stubs/LiveLocalCounter.js +8 -0
- package/app/client/.live-stubs/LivePingPong.js +10 -0
- package/app/client/.live-stubs/LiveRoomChat.js +11 -0
- package/app/client/.live-stubs/LiveSharedCounter.js +10 -0
- package/app/client/.live-stubs/LiveUpload.js +15 -0
- package/app/server/live/auto-generated-components.ts +1 -1
- package/core/utils/version.ts +6 -6
- package/package.json +108 -108
|
@@ -1,397 +1,397 @@
|
|
|
1
|
-
# Anti-Patterns
|
|
2
|
-
|
|
3
|
-
**Version:** 1.
|
|
4
|
-
|
|
5
|
-
## Quick Facts
|
|
6
|
-
|
|
7
|
-
- FluxStack has strict rules to maintain type safety and stability
|
|
8
|
-
- Violations break type inference, cause runtime errors, or introduce security issues
|
|
9
|
-
- Most issues stem from ignoring the core/app separation
|
|
10
|
-
|
|
11
|
-
## Core Directory Violations
|
|
12
|
-
|
|
13
|
-
### Never Modify `core/`
|
|
14
|
-
|
|
15
|
-
```typescript
|
|
16
|
-
// ❌ NEVER do this
|
|
17
|
-
// Editing core/server/framework.ts
|
|
18
|
-
// Editing core/plugins/manager.ts
|
|
19
|
-
// Editing core/utils/config-schema.ts
|
|
20
|
-
|
|
21
|
-
// ✅ Use extension points instead
|
|
22
|
-
// Create plugins in plugins/
|
|
23
|
-
// Override configs in config/
|
|
24
|
-
// Add business logic in app/
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
**Why**: `core/` is framework code. Changes break on updates and can't be merged upstream.
|
|
28
|
-
|
|
29
|
-
## Eden Treaty Anti-Patterns
|
|
30
|
-
|
|
31
|
-
### Never Wrap Eden Treaty
|
|
32
|
-
|
|
33
|
-
```typescript
|
|
34
|
-
// ❌ WRONG - Wrapping breaks type inference
|
|
35
|
-
async function apiCall<T>(fn: () => Promise<any>): Promise<T> {
|
|
36
|
-
try {
|
|
37
|
-
const result = await fn()
|
|
38
|
-
return result.data as T // Type cast = lost inference
|
|
39
|
-
} catch (error) {
|
|
40
|
-
throw error
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const user = await apiCall<User>(() => api.users({ id: 1 }).get())
|
|
45
|
-
// user type is manually cast, not inferred
|
|
46
|
-
|
|
47
|
-
// ✅ CORRECT - Use Eden Treaty directly
|
|
48
|
-
const { data, error } = await api.users({ id: 1 }).get()
|
|
49
|
-
// data is automatically typed as UserResponse
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
**Why**: Eden Treaty's power is automatic type inference. Wrappers destroy this.
|
|
53
|
-
|
|
54
|
-
### Never Omit Response Schemas
|
|
55
|
-
|
|
56
|
-
```typescript
|
|
57
|
-
// ❌ WRONG - No response schema
|
|
58
|
-
export const usersRoutes = new Elysia({ prefix: '/users' })
|
|
59
|
-
.get('/', () => {
|
|
60
|
-
return { users: [] } // Response type is 'unknown' in Eden
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
// ✅ CORRECT - Always define response schema
|
|
64
|
-
export const usersRoutes = new Elysia({ prefix: '/users' })
|
|
65
|
-
.get('/', () => {
|
|
66
|
-
return { users: [] }
|
|
67
|
-
}, {
|
|
68
|
-
response: t.Object({
|
|
69
|
-
users: t.Array(t.Object({
|
|
70
|
-
id: t.Number(),
|
|
71
|
-
name: t.String()
|
|
72
|
-
}))
|
|
73
|
-
})
|
|
74
|
-
})
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
**Why**: Response schemas enable type inference AND generate Swagger docs.
|
|
78
|
-
|
|
79
|
-
### Never Define Types Manually for API Responses
|
|
80
|
-
|
|
81
|
-
```typescript
|
|
82
|
-
// ❌ WRONG - Manual type definitions
|
|
83
|
-
interface UserResponse {
|
|
84
|
-
id: number
|
|
85
|
-
name: string
|
|
86
|
-
}
|
|
87
|
-
const { data } = await api.users.get()
|
|
88
|
-
const users = data as UserResponse[] // Type assertion
|
|
89
|
-
|
|
90
|
-
// ✅ CORRECT - Let Eden Treaty infer types
|
|
91
|
-
const { data, error } = await api.users.get()
|
|
92
|
-
// TypeScript automatically knows data.users is User[]
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
## Configuration Anti-Patterns
|
|
96
|
-
|
|
97
|
-
### Never Use process.env Directly
|
|
98
|
-
|
|
99
|
-
```typescript
|
|
100
|
-
// ❌ WRONG - No validation, no type safety
|
|
101
|
-
const port = process.env.PORT || 3000
|
|
102
|
-
const debug = process.env.DEBUG === 'true'
|
|
103
|
-
|
|
104
|
-
// ✅ CORRECT - Use config system
|
|
105
|
-
import { appConfig } from '@config/app.config'
|
|
106
|
-
const port = appConfig.port // number, validated
|
|
107
|
-
const debug = appConfig.debug // boolean, validated
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
### Never Hardcode Configuration
|
|
111
|
-
|
|
112
|
-
```typescript
|
|
113
|
-
// ❌ WRONG - Hardcoded values
|
|
114
|
-
const corsOrigins = ['http://localhost:5173', 'https://myapp.com']
|
|
115
|
-
|
|
116
|
-
// ✅ CORRECT - Use environment-based config
|
|
117
|
-
import { serverConfig } from '@config/server.config'
|
|
118
|
-
const corsOrigins = serverConfig.cors.origins
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
### Never Mix Config Layers
|
|
122
|
-
|
|
123
|
-
```typescript
|
|
124
|
-
// ❌ WRONG - Accessing system config from app code
|
|
125
|
-
import { systemConfig } from '@config/system.config'
|
|
126
|
-
console.log(systemConfig.framework.name) // Framework details in app
|
|
127
|
-
|
|
128
|
-
// ✅ CORRECT - Use appropriate config layer
|
|
129
|
-
import { appConfig } from '@config/app.config'
|
|
130
|
-
console.log(appConfig.name)
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
## Import Path Anti-Patterns
|
|
134
|
-
|
|
135
|
-
### Never Use Deep Relative Imports
|
|
136
|
-
|
|
137
|
-
```typescript
|
|
138
|
-
// ❌ WRONG - Brittle, hard to refactor
|
|
139
|
-
import { api } from '../../../lib/eden-api'
|
|
140
|
-
import type { User } from '../../../../shared/types'
|
|
141
|
-
|
|
142
|
-
// ✅ CORRECT - Use path aliases
|
|
143
|
-
import { api } from '@client/lib/eden-api'
|
|
144
|
-
import type { User } from '@shared/types'
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
### Never Import Core Internals
|
|
148
|
-
|
|
149
|
-
```typescript
|
|
150
|
-
// ❌ WRONG - Internal implementation details
|
|
151
|
-
import { internalHelper } from '@core/framework/internal/utils'
|
|
152
|
-
|
|
153
|
-
// ✅ CORRECT - Use public exports only
|
|
154
|
-
import { publicUtil } from '@core/utils'
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
## Plugin Security Anti-Patterns
|
|
158
|
-
|
|
159
|
-
### Never Enable NPM Discovery Without Whitelist
|
|
160
|
-
|
|
161
|
-
```bash
|
|
162
|
-
# ❌ WRONG - All NPM plugins auto-loaded (dangerous!)
|
|
163
|
-
PLUGINS_DISCOVER_NPM=true
|
|
164
|
-
# No PLUGINS_ALLOWED set
|
|
165
|
-
|
|
166
|
-
# ✅ CORRECT - Whitelist required packages
|
|
167
|
-
PLUGINS_DISCOVER_NPM=true
|
|
168
|
-
PLUGINS_ALLOWED=fluxstack-plugin-auth,@acme/fplugin-payments
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
### Never Skip Plugin Auditing
|
|
172
|
-
|
|
173
|
-
```bash
|
|
174
|
-
# ❌ WRONG - Installing without audit
|
|
175
|
-
bun add some-random-plugin
|
|
176
|
-
|
|
177
|
-
# ✅ CORRECT - Use plugin:add with audit
|
|
178
|
-
bun run fluxstack plugin:add some-random-plugin
|
|
179
|
-
# Automatically audits before install
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
### Never Trust Plugin Config Blindly
|
|
183
|
-
|
|
184
|
-
```typescript
|
|
185
|
-
// ❌ WRONG - Using unvalidated plugin config
|
|
186
|
-
const pluginConfig = await loadPluginConfig(pluginName)
|
|
187
|
-
database.connect(pluginConfig.connectionString) // Potential injection
|
|
188
|
-
|
|
189
|
-
// ✅ CORRECT - Validate with schema
|
|
190
|
-
const schema = {
|
|
191
|
-
connectionString: config.string('DB_URL', '', true)
|
|
192
|
-
}
|
|
193
|
-
const validatedConfig = defineConfig(schema)
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
## Route Definition Anti-Patterns
|
|
197
|
-
|
|
198
|
-
### Never Mix Business Logic in Routes
|
|
199
|
-
|
|
200
|
-
```typescript
|
|
201
|
-
// ❌ WRONG - Database logic in route
|
|
202
|
-
export const usersRoutes = new Elysia({ prefix: '/users' })
|
|
203
|
-
.get('/', async () => {
|
|
204
|
-
const db = await connectDB()
|
|
205
|
-
const users = await db.query('SELECT * FROM users')
|
|
206
|
-
await db.close()
|
|
207
|
-
return { users }
|
|
208
|
-
})
|
|
209
|
-
|
|
210
|
-
// ✅ CORRECT - Use controller/service pattern
|
|
211
|
-
export const usersRoutes = new Elysia({ prefix: '/users' })
|
|
212
|
-
.get('/', async () => {
|
|
213
|
-
return await userController.list()
|
|
214
|
-
})
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
### Never Forget Error Handling
|
|
218
|
-
|
|
219
|
-
```typescript
|
|
220
|
-
// ❌ WRONG - Unhandled errors
|
|
221
|
-
.post('/', async ({ body }) => {
|
|
222
|
-
const user = await createUser(body) // May throw
|
|
223
|
-
return { user }
|
|
224
|
-
})
|
|
225
|
-
|
|
226
|
-
// ✅ CORRECT - Handle errors properly
|
|
227
|
-
.post('/', async ({ body, error }) => {
|
|
228
|
-
try {
|
|
229
|
-
const user = await createUser(body)
|
|
230
|
-
return { success: true, user }
|
|
231
|
-
} catch (e) {
|
|
232
|
-
return error(400, { success: false, message: e.message })
|
|
233
|
-
}
|
|
234
|
-
})
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
## Testing Anti-Patterns
|
|
238
|
-
|
|
239
|
-
### Never Test Against Real API
|
|
240
|
-
|
|
241
|
-
```typescript
|
|
242
|
-
// ❌ WRONG - Real API calls in tests
|
|
243
|
-
it('should fetch users', async () => {
|
|
244
|
-
const { data } = await api.users.get() // Hits real backend
|
|
245
|
-
expect(data.users).toBeDefined()
|
|
246
|
-
})
|
|
247
|
-
|
|
248
|
-
// ✅ CORRECT - Mock Eden Treaty
|
|
249
|
-
vi.mock('@client/lib/eden-api', () => ({
|
|
250
|
-
api: {
|
|
251
|
-
users: {
|
|
252
|
-
get: vi.fn().mockResolvedValue({
|
|
253
|
-
data: { users: [{ id: 1, name: 'Test' }] },
|
|
254
|
-
error: undefined
|
|
255
|
-
})
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}))
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
## Build Anti-Patterns
|
|
262
|
-
|
|
263
|
-
### Never Import Dev Dependencies in Production
|
|
264
|
-
|
|
265
|
-
```typescript
|
|
266
|
-
// ❌ WRONG - Conditional import that still bundles
|
|
267
|
-
import { DevTools } from 'react-devtools' // Always bundled
|
|
268
|
-
|
|
269
|
-
if (process.env.NODE_ENV === 'development') {
|
|
270
|
-
DevTools.init()
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// ✅ CORRECT - Dynamic import for dev-only
|
|
274
|
-
if (import.meta.env.DEV) {
|
|
275
|
-
const { DevTools } = await import('react-devtools')
|
|
276
|
-
DevTools.init()
|
|
277
|
-
}
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
## Live Component Security Anti-Patterns
|
|
281
|
-
|
|
282
|
-
### Never Omit publicActions
|
|
283
|
-
|
|
284
|
-
```typescript
|
|
285
|
-
// ❌ WRONG - No publicActions = ALL remote actions blocked (secure by default)
|
|
286
|
-
export class MyComponent extends LiveComponent<State> {
|
|
287
|
-
static componentName = 'MyComponent'
|
|
288
|
-
static defaultState = { count: 0 }
|
|
289
|
-
|
|
290
|
-
async increment() { this.state.count++ } // Client CANNOT call this!
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// ✅ CORRECT - Explicitly whitelist callable methods
|
|
294
|
-
export class MyComponent extends LiveComponent<State> {
|
|
295
|
-
static componentName = 'MyComponent'
|
|
296
|
-
static publicActions = ['increment'] as const // Only increment is callable
|
|
297
|
-
static defaultState = { count: 0 }
|
|
298
|
-
|
|
299
|
-
async increment() { this.state.count++ }
|
|
300
|
-
|
|
301
|
-
// Internal helper - not in publicActions, so not callable from client
|
|
302
|
-
private _recalculate() { /* ... */ }
|
|
303
|
-
}
|
|
304
|
-
```
|
|
305
|
-
|
|
306
|
-
**Why**: Components without `publicActions` deny ALL remote actions. This is secure by default - if you forget, nothing is exposed rather than everything.
|
|
307
|
-
|
|
308
|
-
### Never Include setValue Without Careful Consideration
|
|
309
|
-
|
|
310
|
-
```typescript
|
|
311
|
-
// ❌ DANGEROUS - setValue allows client to set ANY state key
|
|
312
|
-
static publicActions = ['sendMessage', 'setValue'] as const
|
|
313
|
-
// Client can now do: component.setValue({ key: 'isAdmin', value: true })
|
|
314
|
-
|
|
315
|
-
// ✅ CORRECT - Only expose specific, safe actions
|
|
316
|
-
static publicActions = ['sendMessage', 'deleteMessage'] as const
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
**Why**: `setValue` is a generic action that allows the client to modify any state property. Only include it if all state fields are safe for clients to modify.
|
|
320
|
-
|
|
321
|
-
### Never Trust MIME Types Alone for Uploads
|
|
322
|
-
|
|
323
|
-
```typescript
|
|
324
|
-
// ❌ WRONG - Only checking MIME type header (easily spoofed)
|
|
325
|
-
if (file.type === 'image/jpeg') {
|
|
326
|
-
// Accept file - but it could be an EXE with a fake MIME header!
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// ✅ CORRECT - Framework validates magic bytes automatically
|
|
330
|
-
// FileUploadManager.validateContentMagicBytes() runs on completeUpload()
|
|
331
|
-
// No manual code needed - the framework handles this
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
**Why**: MIME types come from the client and can be spoofed. The framework validates actual file content (magic bytes) against the claimed type.
|
|
335
|
-
|
|
336
|
-
### Never Store Sensitive Data in State
|
|
337
|
-
|
|
338
|
-
```typescript
|
|
339
|
-
// ❌ WRONG - Token goes to the client via STATE_UPDATE/STATE_DELTA
|
|
340
|
-
export class Chat extends LiveComponent<State> {
|
|
341
|
-
static defaultState = { messages: [], token: '' } // token synced to client!
|
|
342
|
-
static publicActions = ['connect'] as const
|
|
343
|
-
|
|
344
|
-
async connect(payload: { token: string }) {
|
|
345
|
-
this.state.token = payload.token // 💀 Visible in browser DevTools!
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
// ✅ CORRECT - Use $private for server-only data
|
|
350
|
-
export class Chat extends LiveComponent<State> {
|
|
351
|
-
static defaultState = { messages: [] as string[] }
|
|
352
|
-
static publicActions = ['connect'] as const
|
|
353
|
-
|
|
354
|
-
async connect(payload: { token: string }) {
|
|
355
|
-
this.$private.token = payload.token // 🔒 Never leaves the server
|
|
356
|
-
this.state.messages = await fetch(this.$private.token)
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
```
|
|
360
|
-
|
|
361
|
-
**Why**: Everything in `state` is serialized and sent to the client via WebSocket. Use `$private` for tokens, API keys, internal IDs, or any data the client should not see.
|
|
362
|
-
|
|
363
|
-
### Never Ignore Double Extensions
|
|
364
|
-
|
|
365
|
-
```typescript
|
|
366
|
-
// ❌ WRONG - Only checking last extension
|
|
367
|
-
const ext = filename.split('.').pop() // Returns 'jpg' for 'malware.exe.jpg'
|
|
368
|
-
|
|
369
|
-
// ✅ CORRECT - Framework checks all intermediate extensions automatically
|
|
370
|
-
// FileUploadManager blocks files like 'malware.exe.jpg'
|
|
371
|
-
// No manual code needed - handled at framework level
|
|
372
|
-
```
|
|
373
|
-
|
|
374
|
-
## Summary Table
|
|
375
|
-
|
|
376
|
-
| Anti-Pattern | Impact | Solution |
|
|
377
|
-
|-------------|--------|----------|
|
|
378
|
-
| Modifying `core/` | Update conflicts | Use plugins/app |
|
|
379
|
-
| Wrapping Eden Treaty | Lost type inference | Use directly |
|
|
380
|
-
| Missing response schemas | Unknown types | Always define schemas |
|
|
381
|
-
| Direct process.env | No validation | Use config system |
|
|
382
|
-
| Deep relative imports | Fragile paths | Use aliases |
|
|
383
|
-
| NPM plugins without whitelist | Security risk | Set PLUGINS_ALLOWED |
|
|
384
|
-
| Business logic in routes | Unmaintainable | Use controllers |
|
|
385
|
-
| Missing `publicActions` | All actions blocked | Always define whitelist |
|
|
386
|
-
| Including `setValue` carelessly | Privilege escalation | Use specific actions |
|
|
387
|
-
| Sensitive data in `state` | Data leak to client | Use `$private` instead |
|
|
388
|
-
| Trusting MIME types alone | File disguise attacks | Framework validates magic bytes |
|
|
389
|
-
|
|
390
|
-
## Related
|
|
391
|
-
|
|
392
|
-
- [Project Structure](./project-structure.md)
|
|
393
|
-
- [Type Safety](./type-safety.md)
|
|
394
|
-
- [Plugin Security](../core/plugin-system.md)
|
|
395
|
-
- [Routes with Eden Treaty](../resources/routes-eden.md)
|
|
396
|
-
- [Live Components](../resources/live-components.md)
|
|
397
|
-
- [Live Upload](../resources/live-upload.md)
|
|
1
|
+
# Anti-Patterns
|
|
2
|
+
|
|
3
|
+
**Version:** 1.19.0 | **Updated:** 2026-04-14
|
|
4
|
+
|
|
5
|
+
## Quick Facts
|
|
6
|
+
|
|
7
|
+
- FluxStack has strict rules to maintain type safety and stability
|
|
8
|
+
- Violations break type inference, cause runtime errors, or introduce security issues
|
|
9
|
+
- Most issues stem from ignoring the core/app separation
|
|
10
|
+
|
|
11
|
+
## Core Directory Violations
|
|
12
|
+
|
|
13
|
+
### Never Modify `core/`
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
// ❌ NEVER do this
|
|
17
|
+
// Editing core/server/framework.ts
|
|
18
|
+
// Editing core/plugins/manager.ts
|
|
19
|
+
// Editing core/utils/config-schema.ts
|
|
20
|
+
|
|
21
|
+
// ✅ Use extension points instead
|
|
22
|
+
// Create plugins in plugins/
|
|
23
|
+
// Override configs in config/
|
|
24
|
+
// Add business logic in app/
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**Why**: `core/` is framework code. Changes break on updates and can't be merged upstream.
|
|
28
|
+
|
|
29
|
+
## Eden Treaty Anti-Patterns
|
|
30
|
+
|
|
31
|
+
### Never Wrap Eden Treaty
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
// ❌ WRONG - Wrapping breaks type inference
|
|
35
|
+
async function apiCall<T>(fn: () => Promise<any>): Promise<T> {
|
|
36
|
+
try {
|
|
37
|
+
const result = await fn()
|
|
38
|
+
return result.data as T // Type cast = lost inference
|
|
39
|
+
} catch (error) {
|
|
40
|
+
throw error
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const user = await apiCall<User>(() => api.users({ id: 1 }).get())
|
|
45
|
+
// user type is manually cast, not inferred
|
|
46
|
+
|
|
47
|
+
// ✅ CORRECT - Use Eden Treaty directly
|
|
48
|
+
const { data, error } = await api.users({ id: 1 }).get()
|
|
49
|
+
// data is automatically typed as UserResponse
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Why**: Eden Treaty's power is automatic type inference. Wrappers destroy this.
|
|
53
|
+
|
|
54
|
+
### Never Omit Response Schemas
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
// ❌ WRONG - No response schema
|
|
58
|
+
export const usersRoutes = new Elysia({ prefix: '/users' })
|
|
59
|
+
.get('/', () => {
|
|
60
|
+
return { users: [] } // Response type is 'unknown' in Eden
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
// ✅ CORRECT - Always define response schema
|
|
64
|
+
export const usersRoutes = new Elysia({ prefix: '/users' })
|
|
65
|
+
.get('/', () => {
|
|
66
|
+
return { users: [] }
|
|
67
|
+
}, {
|
|
68
|
+
response: t.Object({
|
|
69
|
+
users: t.Array(t.Object({
|
|
70
|
+
id: t.Number(),
|
|
71
|
+
name: t.String()
|
|
72
|
+
}))
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Why**: Response schemas enable type inference AND generate Swagger docs.
|
|
78
|
+
|
|
79
|
+
### Never Define Types Manually for API Responses
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
// ❌ WRONG - Manual type definitions
|
|
83
|
+
interface UserResponse {
|
|
84
|
+
id: number
|
|
85
|
+
name: string
|
|
86
|
+
}
|
|
87
|
+
const { data } = await api.users.get()
|
|
88
|
+
const users = data as UserResponse[] // Type assertion
|
|
89
|
+
|
|
90
|
+
// ✅ CORRECT - Let Eden Treaty infer types
|
|
91
|
+
const { data, error } = await api.users.get()
|
|
92
|
+
// TypeScript automatically knows data.users is User[]
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Configuration Anti-Patterns
|
|
96
|
+
|
|
97
|
+
### Never Use process.env Directly
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
// ❌ WRONG - No validation, no type safety
|
|
101
|
+
const port = process.env.PORT || 3000
|
|
102
|
+
const debug = process.env.DEBUG === 'true'
|
|
103
|
+
|
|
104
|
+
// ✅ CORRECT - Use config system
|
|
105
|
+
import { appConfig } from '@config/app.config'
|
|
106
|
+
const port = appConfig.port // number, validated
|
|
107
|
+
const debug = appConfig.debug // boolean, validated
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Never Hardcode Configuration
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
// ❌ WRONG - Hardcoded values
|
|
114
|
+
const corsOrigins = ['http://localhost:5173', 'https://myapp.com']
|
|
115
|
+
|
|
116
|
+
// ✅ CORRECT - Use environment-based config
|
|
117
|
+
import { serverConfig } from '@config/server.config'
|
|
118
|
+
const corsOrigins = serverConfig.cors.origins
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Never Mix Config Layers
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
// ❌ WRONG - Accessing system config from app code
|
|
125
|
+
import { systemConfig } from '@config/system.config'
|
|
126
|
+
console.log(systemConfig.framework.name) // Framework details in app
|
|
127
|
+
|
|
128
|
+
// ✅ CORRECT - Use appropriate config layer
|
|
129
|
+
import { appConfig } from '@config/app.config'
|
|
130
|
+
console.log(appConfig.name)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Import Path Anti-Patterns
|
|
134
|
+
|
|
135
|
+
### Never Use Deep Relative Imports
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
// ❌ WRONG - Brittle, hard to refactor
|
|
139
|
+
import { api } from '../../../lib/eden-api'
|
|
140
|
+
import type { User } from '../../../../shared/types'
|
|
141
|
+
|
|
142
|
+
// ✅ CORRECT - Use path aliases
|
|
143
|
+
import { api } from '@client/lib/eden-api'
|
|
144
|
+
import type { User } from '@shared/types'
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Never Import Core Internals
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
// ❌ WRONG - Internal implementation details
|
|
151
|
+
import { internalHelper } from '@core/framework/internal/utils'
|
|
152
|
+
|
|
153
|
+
// ✅ CORRECT - Use public exports only
|
|
154
|
+
import { publicUtil } from '@core/utils'
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Plugin Security Anti-Patterns
|
|
158
|
+
|
|
159
|
+
### Never Enable NPM Discovery Without Whitelist
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
# ❌ WRONG - All NPM plugins auto-loaded (dangerous!)
|
|
163
|
+
PLUGINS_DISCOVER_NPM=true
|
|
164
|
+
# No PLUGINS_ALLOWED set
|
|
165
|
+
|
|
166
|
+
# ✅ CORRECT - Whitelist required packages
|
|
167
|
+
PLUGINS_DISCOVER_NPM=true
|
|
168
|
+
PLUGINS_ALLOWED=fluxstack-plugin-auth,@acme/fplugin-payments
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Never Skip Plugin Auditing
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
# ❌ WRONG - Installing without audit
|
|
175
|
+
bun add some-random-plugin
|
|
176
|
+
|
|
177
|
+
# ✅ CORRECT - Use plugin:add with audit
|
|
178
|
+
bun run fluxstack plugin:add some-random-plugin
|
|
179
|
+
# Automatically audits before install
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Never Trust Plugin Config Blindly
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
// ❌ WRONG - Using unvalidated plugin config
|
|
186
|
+
const pluginConfig = await loadPluginConfig(pluginName)
|
|
187
|
+
database.connect(pluginConfig.connectionString) // Potential injection
|
|
188
|
+
|
|
189
|
+
// ✅ CORRECT - Validate with schema
|
|
190
|
+
const schema = {
|
|
191
|
+
connectionString: config.string('DB_URL', '', true)
|
|
192
|
+
}
|
|
193
|
+
const validatedConfig = defineConfig(schema)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Route Definition Anti-Patterns
|
|
197
|
+
|
|
198
|
+
### Never Mix Business Logic in Routes
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
// ❌ WRONG - Database logic in route
|
|
202
|
+
export const usersRoutes = new Elysia({ prefix: '/users' })
|
|
203
|
+
.get('/', async () => {
|
|
204
|
+
const db = await connectDB()
|
|
205
|
+
const users = await db.query('SELECT * FROM users')
|
|
206
|
+
await db.close()
|
|
207
|
+
return { users }
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
// ✅ CORRECT - Use controller/service pattern
|
|
211
|
+
export const usersRoutes = new Elysia({ prefix: '/users' })
|
|
212
|
+
.get('/', async () => {
|
|
213
|
+
return await userController.list()
|
|
214
|
+
})
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Never Forget Error Handling
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
// ❌ WRONG - Unhandled errors
|
|
221
|
+
.post('/', async ({ body }) => {
|
|
222
|
+
const user = await createUser(body) // May throw
|
|
223
|
+
return { user }
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
// ✅ CORRECT - Handle errors properly
|
|
227
|
+
.post('/', async ({ body, error }) => {
|
|
228
|
+
try {
|
|
229
|
+
const user = await createUser(body)
|
|
230
|
+
return { success: true, user }
|
|
231
|
+
} catch (e) {
|
|
232
|
+
return error(400, { success: false, message: e.message })
|
|
233
|
+
}
|
|
234
|
+
})
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Testing Anti-Patterns
|
|
238
|
+
|
|
239
|
+
### Never Test Against Real API
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
// ❌ WRONG - Real API calls in tests
|
|
243
|
+
it('should fetch users', async () => {
|
|
244
|
+
const { data } = await api.users.get() // Hits real backend
|
|
245
|
+
expect(data.users).toBeDefined()
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
// ✅ CORRECT - Mock Eden Treaty
|
|
249
|
+
vi.mock('@client/lib/eden-api', () => ({
|
|
250
|
+
api: {
|
|
251
|
+
users: {
|
|
252
|
+
get: vi.fn().mockResolvedValue({
|
|
253
|
+
data: { users: [{ id: 1, name: 'Test' }] },
|
|
254
|
+
error: undefined
|
|
255
|
+
})
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}))
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Build Anti-Patterns
|
|
262
|
+
|
|
263
|
+
### Never Import Dev Dependencies in Production
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
// ❌ WRONG - Conditional import that still bundles
|
|
267
|
+
import { DevTools } from 'react-devtools' // Always bundled
|
|
268
|
+
|
|
269
|
+
if (process.env.NODE_ENV === 'development') {
|
|
270
|
+
DevTools.init()
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ✅ CORRECT - Dynamic import for dev-only
|
|
274
|
+
if (import.meta.env.DEV) {
|
|
275
|
+
const { DevTools } = await import('react-devtools')
|
|
276
|
+
DevTools.init()
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Live Component Security Anti-Patterns
|
|
281
|
+
|
|
282
|
+
### Never Omit publicActions
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
// ❌ WRONG - No publicActions = ALL remote actions blocked (secure by default)
|
|
286
|
+
export class MyComponent extends LiveComponent<State> {
|
|
287
|
+
static componentName = 'MyComponent'
|
|
288
|
+
static defaultState = { count: 0 }
|
|
289
|
+
|
|
290
|
+
async increment() { this.state.count++ } // Client CANNOT call this!
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ✅ CORRECT - Explicitly whitelist callable methods
|
|
294
|
+
export class MyComponent extends LiveComponent<State> {
|
|
295
|
+
static componentName = 'MyComponent'
|
|
296
|
+
static publicActions = ['increment'] as const // Only increment is callable
|
|
297
|
+
static defaultState = { count: 0 }
|
|
298
|
+
|
|
299
|
+
async increment() { this.state.count++ }
|
|
300
|
+
|
|
301
|
+
// Internal helper - not in publicActions, so not callable from client
|
|
302
|
+
private _recalculate() { /* ... */ }
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
**Why**: Components without `publicActions` deny ALL remote actions. This is secure by default - if you forget, nothing is exposed rather than everything.
|
|
307
|
+
|
|
308
|
+
### Never Include setValue Without Careful Consideration
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
// ❌ DANGEROUS - setValue allows client to set ANY state key
|
|
312
|
+
static publicActions = ['sendMessage', 'setValue'] as const
|
|
313
|
+
// Client can now do: component.setValue({ key: 'isAdmin', value: true })
|
|
314
|
+
|
|
315
|
+
// ✅ CORRECT - Only expose specific, safe actions
|
|
316
|
+
static publicActions = ['sendMessage', 'deleteMessage'] as const
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
**Why**: `setValue` is a generic action that allows the client to modify any state property. Only include it if all state fields are safe for clients to modify.
|
|
320
|
+
|
|
321
|
+
### Never Trust MIME Types Alone for Uploads
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
// ❌ WRONG - Only checking MIME type header (easily spoofed)
|
|
325
|
+
if (file.type === 'image/jpeg') {
|
|
326
|
+
// Accept file - but it could be an EXE with a fake MIME header!
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// ✅ CORRECT - Framework validates magic bytes automatically
|
|
330
|
+
// FileUploadManager.validateContentMagicBytes() runs on completeUpload()
|
|
331
|
+
// No manual code needed - the framework handles this
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
**Why**: MIME types come from the client and can be spoofed. The framework validates actual file content (magic bytes) against the claimed type.
|
|
335
|
+
|
|
336
|
+
### Never Store Sensitive Data in State
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
// ❌ WRONG - Token goes to the client via STATE_UPDATE/STATE_DELTA
|
|
340
|
+
export class Chat extends LiveComponent<State> {
|
|
341
|
+
static defaultState = { messages: [], token: '' } // token synced to client!
|
|
342
|
+
static publicActions = ['connect'] as const
|
|
343
|
+
|
|
344
|
+
async connect(payload: { token: string }) {
|
|
345
|
+
this.state.token = payload.token // 💀 Visible in browser DevTools!
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// ✅ CORRECT - Use $private for server-only data
|
|
350
|
+
export class Chat extends LiveComponent<State> {
|
|
351
|
+
static defaultState = { messages: [] as string[] }
|
|
352
|
+
static publicActions = ['connect'] as const
|
|
353
|
+
|
|
354
|
+
async connect(payload: { token: string }) {
|
|
355
|
+
this.$private.token = payload.token // 🔒 Never leaves the server
|
|
356
|
+
this.state.messages = await fetch(this.$private.token)
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
**Why**: Everything in `state` is serialized and sent to the client via WebSocket. Use `$private` for tokens, API keys, internal IDs, or any data the client should not see.
|
|
362
|
+
|
|
363
|
+
### Never Ignore Double Extensions
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
// ❌ WRONG - Only checking last extension
|
|
367
|
+
const ext = filename.split('.').pop() // Returns 'jpg' for 'malware.exe.jpg'
|
|
368
|
+
|
|
369
|
+
// ✅ CORRECT - Framework checks all intermediate extensions automatically
|
|
370
|
+
// FileUploadManager blocks files like 'malware.exe.jpg'
|
|
371
|
+
// No manual code needed - handled at framework level
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
## Summary Table
|
|
375
|
+
|
|
376
|
+
| Anti-Pattern | Impact | Solution |
|
|
377
|
+
|-------------|--------|----------|
|
|
378
|
+
| Modifying `core/` | Update conflicts | Use plugins/app |
|
|
379
|
+
| Wrapping Eden Treaty | Lost type inference | Use directly |
|
|
380
|
+
| Missing response schemas | Unknown types | Always define schemas |
|
|
381
|
+
| Direct process.env | No validation | Use config system |
|
|
382
|
+
| Deep relative imports | Fragile paths | Use aliases |
|
|
383
|
+
| NPM plugins without whitelist | Security risk | Set PLUGINS_ALLOWED |
|
|
384
|
+
| Business logic in routes | Unmaintainable | Use controllers |
|
|
385
|
+
| Missing `publicActions` | All actions blocked | Always define whitelist |
|
|
386
|
+
| Including `setValue` carelessly | Privilege escalation | Use specific actions |
|
|
387
|
+
| Sensitive data in `state` | Data leak to client | Use `$private` instead |
|
|
388
|
+
| Trusting MIME types alone | File disguise attacks | Framework validates magic bytes |
|
|
389
|
+
|
|
390
|
+
## Related
|
|
391
|
+
|
|
392
|
+
- [Project Structure](./project-structure.md)
|
|
393
|
+
- [Type Safety](./type-safety.md)
|
|
394
|
+
- [Plugin Security](../core/plugin-system.md)
|
|
395
|
+
- [Routes with Eden Treaty](../resources/routes-eden.md)
|
|
396
|
+
- [Live Components](../resources/live-components.md)
|
|
397
|
+
- [Live Upload](../resources/live-upload.md)
|