@stacksjs/bun-router 0.0.17 → 0.0.18
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/dist/cli.js +1 -1
- package/dist/container/service-provider.d.ts +3 -3
- package/dist/development/performance-profiler.d.ts +3 -3
- package/dist/development/route-debugger.d.ts +1 -1
- package/dist/development/route-inspector.d.ts +2 -2
- package/dist/observability/integration.d.ts +1 -1
- package/dist/response/macros.d.ts +1 -1
- package/dist/router/index.d.ts +7 -7
- package/dist/router/router.d.ts +0 -32
- package/dist/testing/performance-testing.d.ts +2 -2
- package/dist/testing/test-client.d.ts +1 -1
- package/dist/testing/websocket-testing.d.ts +1 -1
- package/package.json +2 -3
- package/src/auth.ts +0 -489
- package/src/cache/lru-cache.ts +0 -457
- package/src/cache/middleware-memoization.ts +0 -531
- package/src/cache/route-cache-warmer.ts +0 -486
- package/src/cache/sqlite-cache.ts +0 -783
- package/src/cache/streaming-cache.ts +0 -572
- package/src/cli/colors.ts +0 -29
- package/src/cli/index.ts +0 -287
- package/src/cli/middleware.ts +0 -291
- package/src/cli/openapi.ts +0 -407
- package/src/cli/router.ts +0 -188
- package/src/cli/routes.ts +0 -265
- package/src/cli/utils.ts +0 -531
- package/src/cli.ts +0 -5
- package/src/config.ts +0 -322
- package/src/container/container.ts +0 -786
- package/src/container/contextual-binding.ts +0 -606
- package/src/container/decorators.ts +0 -368
- package/src/container/index.ts +0 -49
- package/src/container/service-provider.ts +0 -596
- package/src/development/hot-reload.ts +0 -673
- package/src/development/index.ts +0 -499
- package/src/development/performance-profiler.ts +0 -717
- package/src/development/route-debugger.ts +0 -527
- package/src/development/route-inspector.ts +0 -749
- package/src/development/typescript-utilities.ts +0 -682
- package/src/docs.ts +0 -397
- package/src/errors/circuit-breaker.ts +0 -733
- package/src/errors/error-handler.ts +0 -569
- package/src/errors/error-reporting.ts +0 -672
- package/src/errors/exceptions.ts +0 -536
- package/src/errors/graceful-degradation.ts +0 -623
- package/src/errors/index.ts +0 -21
- package/src/errors/router-errors.ts +0 -632
- package/src/file-serving/static-files.ts +0 -718
- package/src/index.ts +0 -66
- package/src/middleware/auth.ts +0 -220
- package/src/middleware/content_security_policy.ts +0 -215
- package/src/middleware/cors.ts +0 -142
- package/src/middleware/csrf.ts +0 -166
- package/src/middleware/ddos_protection.ts +0 -279
- package/src/middleware/file_security.ts +0 -191
- package/src/middleware/file_upload.ts +0 -275
- package/src/middleware/helmet.ts +0 -268
- package/src/middleware/index.ts +0 -117
- package/src/middleware/input_validation.ts +0 -453
- package/src/middleware/json_body.ts +0 -37
- package/src/middleware/performance_alerting.ts +0 -540
- package/src/middleware/performance_dashboard.ts +0 -661
- package/src/middleware/performance_monitor.ts +0 -945
- package/src/middleware/pipeline.ts +0 -489
- package/src/middleware/rate_limit.ts +0 -251
- package/src/middleware/request_id.ts +0 -36
- package/src/middleware/request_signing.ts +0 -638
- package/src/middleware/request_tracer.ts +0 -653
- package/src/middleware/response_cache.ts +0 -778
- package/src/middleware/security.ts +0 -482
- package/src/middleware/security_suite.ts +0 -257
- package/src/middleware/session.ts +0 -91
- package/src/model-binding/index.ts +0 -18
- package/src/model-binding/model-middleware.ts +0 -425
- package/src/model-binding/model-registry.ts +0 -550
- package/src/model-binding.ts +0 -370
- package/src/model-resolver-factory.ts +0 -106
- package/src/observability/correlation.ts +0 -691
- package/src/observability/health-checks.ts +0 -729
- package/src/observability/index.ts +0 -184
- package/src/observability/integration.ts +0 -548
- package/src/observability/metrics.ts +0 -753
- package/src/observability/tracing.ts +0 -638
- package/src/optimization/bun-utilities.ts +0 -778
- package/src/query-builder-integration.ts +0 -137
- package/src/request/context.ts +0 -109
- package/src/request/enhanced-request.ts +0 -876
- package/src/request/macros.ts +0 -822
- package/src/response/macros.ts +0 -665
- package/src/response/response-factory.ts +0 -596
- package/src/router/api-routes.ts +0 -243
- package/src/router/file-based-routing.ts +0 -697
- package/src/router/file-streaming.ts +0 -383
- package/src/router/fluent-routing.ts +0 -866
- package/src/router/group-organization.ts +0 -229
- package/src/router/handler-resolver.ts +0 -327
- package/src/router/http-methods.ts +0 -407
- package/src/router/index.ts +0 -193
- package/src/router/middleware-groups.ts +0 -222
- package/src/router/middleware-integration.ts +0 -399
- package/src/router/middleware.ts +0 -234
- package/src/router/model-binding.ts +0 -215
- package/src/router/optimized-route-matching.ts +0 -302
- package/src/router/route-building.ts +0 -221
- package/src/router/route-compiler.ts +0 -691
- package/src/router/route-matching.ts +0 -368
- package/src/router/route-trie.ts +0 -450
- package/src/router/router.ts +0 -1616
- package/src/router/server.ts +0 -684
- package/src/router/validation-integration.ts +0 -449
- package/src/router/view-rendering.ts +0 -233
- package/src/router/websocket.ts +0 -100
- package/src/routing/route-caching.ts +0 -402
- package/src/routing/route-throttling.ts +0 -469
- package/src/routing/subdomain-routing.ts +0 -492
- package/src/session/database-store.ts +0 -109
- package/src/session/file-store.ts +0 -148
- package/src/session/index.ts +0 -244
- package/src/session/memory-store.ts +0 -88
- package/src/session/redis-store.ts +0 -93
- package/src/streaming/index.ts +0 -17
- package/src/streaming/sse-handler.ts +0 -482
- package/src/streaming/stream-handler.ts +0 -552
- package/src/testing/auth-testing.ts +0 -448
- package/src/testing/file-upload-testing.ts +0 -543
- package/src/testing/index.ts +0 -10
- package/src/testing/middleware-testing.ts +0 -322
- package/src/testing/model-binding-testing.ts +0 -645
- package/src/testing/performance-testing.ts +0 -738
- package/src/testing/test-client.ts +0 -310
- package/src/testing/test-request.ts +0 -315
- package/src/testing/test-response.ts +0 -332
- package/src/testing/types.ts +0 -224
- package/src/testing/websocket-testing.ts +0 -590
- package/src/types/controller-types.ts +0 -385
- package/src/types/core.ts +0 -683
- package/src/types/middleware-types.ts +0 -422
- package/src/types/request-response-augmentation.ts +0 -489
- package/src/types/route-inference.ts +0 -372
- package/src/types.ts +0 -2123
- package/src/url.ts +0 -131
- package/src/utils/index.ts +0 -1
- package/src/utils/query-preservation.ts +0 -201
- package/src/utils.ts +0 -395
- package/src/validation/validator.ts +0 -688
- package/src/websocket/clustering.ts +0 -769
|
@@ -1,550 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
EnhancedRequest,
|
|
3
|
-
ModelResolver,
|
|
4
|
-
ModelTransformerFn,
|
|
5
|
-
ModelValidatorFn,
|
|
6
|
-
} from '../types'
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Model resolution result
|
|
10
|
-
*/
|
|
11
|
-
export interface ModelResolutionResult<T = any> {
|
|
12
|
-
model: T | null
|
|
13
|
-
error?: string
|
|
14
|
-
status?: number
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Model binding configuration for a specific model
|
|
19
|
-
*/
|
|
20
|
-
export interface ModelBindingConfig<T = any> {
|
|
21
|
-
resolver: ModelResolver<T>
|
|
22
|
-
validator?: ModelValidatorFn<T>
|
|
23
|
-
transformer?: ModelTransformerFn<T>
|
|
24
|
-
cache?: {
|
|
25
|
-
enabled: boolean
|
|
26
|
-
ttl: number
|
|
27
|
-
key?: (params: Record<string, string>) => string
|
|
28
|
-
}
|
|
29
|
-
errorHandling?: {
|
|
30
|
-
notFound?: (params: Record<string, string>) => Response
|
|
31
|
-
validation?: (model: T, error: string) => Response
|
|
32
|
-
transform?: (error: string) => Response
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Model cache entry
|
|
38
|
-
*/
|
|
39
|
-
interface ModelCacheEntry<T> {
|
|
40
|
-
model: T
|
|
41
|
-
timestamp: number
|
|
42
|
-
ttl: number
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Model registry for managing model bindings
|
|
47
|
-
*/
|
|
48
|
-
export class ModelRegistry {
|
|
49
|
-
private models = new Map<string, ModelBindingConfig>()
|
|
50
|
-
private cache = new Map<string, ModelCacheEntry<any>>()
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Register a model binding
|
|
54
|
-
*/
|
|
55
|
-
register<T>(name: string, config: ModelBindingConfig<T>): void {
|
|
56
|
-
this.models.set(name, config as ModelBindingConfig)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Get model binding configuration
|
|
61
|
-
*/
|
|
62
|
-
get(name: string): ModelBindingConfig | undefined {
|
|
63
|
-
return this.models.get(name)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Check if model is registered
|
|
68
|
-
*/
|
|
69
|
-
has(name: string): boolean {
|
|
70
|
-
return this.models.has(name)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Resolve a model using its binding
|
|
75
|
-
*/
|
|
76
|
-
async resolve<T>(
|
|
77
|
-
modelName: string,
|
|
78
|
-
params: Record<string, string>,
|
|
79
|
-
req?: EnhancedRequest,
|
|
80
|
-
): Promise<ModelResolutionResult<T>> {
|
|
81
|
-
const config = this.models.get(modelName)
|
|
82
|
-
if (!config) {
|
|
83
|
-
return {
|
|
84
|
-
model: null,
|
|
85
|
-
error: `Model binding '${modelName}' not found`,
|
|
86
|
-
status: 500,
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
try {
|
|
91
|
-
// Check cache first
|
|
92
|
-
if (config.cache?.enabled) {
|
|
93
|
-
const cached = this.getCachedModel<T>(modelName, params, config)
|
|
94
|
-
if (cached) {
|
|
95
|
-
return { model: cached }
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Resolve the model
|
|
100
|
-
const model = await config.resolver(params, req!)
|
|
101
|
-
|
|
102
|
-
if (model === null || model === undefined) {
|
|
103
|
-
return {
|
|
104
|
-
model: null,
|
|
105
|
-
error: `${modelName} not found`,
|
|
106
|
-
status: 404,
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Validate the model
|
|
111
|
-
if (config.validator) {
|
|
112
|
-
const validationResult = await config.validator(model, params, req)
|
|
113
|
-
if (!validationResult.valid) {
|
|
114
|
-
return {
|
|
115
|
-
model: null,
|
|
116
|
-
error: validationResult.error || 'Model validation failed',
|
|
117
|
-
status: validationResult.status || 422,
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Transform the model
|
|
123
|
-
let finalModel = model
|
|
124
|
-
if (config.transformer) {
|
|
125
|
-
finalModel = await config.transformer(model, params, req)
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Cache the result
|
|
129
|
-
if (config.cache?.enabled) {
|
|
130
|
-
this.cacheModel(modelName, params, finalModel, config)
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return { model: finalModel }
|
|
134
|
-
}
|
|
135
|
-
catch (error) {
|
|
136
|
-
return {
|
|
137
|
-
model: null,
|
|
138
|
-
error: error instanceof Error ? error.message : 'Model resolution failed',
|
|
139
|
-
status: 500,
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Create error response for model binding failure
|
|
146
|
-
*/
|
|
147
|
-
createErrorResponse(
|
|
148
|
-
modelName: string,
|
|
149
|
-
result: ModelResolutionResult,
|
|
150
|
-
params: Record<string, string>,
|
|
151
|
-
): Response {
|
|
152
|
-
const config = this.models.get(modelName)
|
|
153
|
-
const status = result.status || 404
|
|
154
|
-
const error = result.error || 'Model not found'
|
|
155
|
-
|
|
156
|
-
// Use custom error handler if available
|
|
157
|
-
if (config?.errorHandling) {
|
|
158
|
-
if (status === 404 && config.errorHandling.notFound) {
|
|
159
|
-
return config.errorHandling.notFound(params)
|
|
160
|
-
}
|
|
161
|
-
if (status === 422 && config.errorHandling.validation) {
|
|
162
|
-
return config.errorHandling.validation(null, error)
|
|
163
|
-
}
|
|
164
|
-
if (config.errorHandling.transform) {
|
|
165
|
-
return config.errorHandling.transform(error)
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Default error response
|
|
170
|
-
return new Response(
|
|
171
|
-
JSON.stringify({
|
|
172
|
-
error,
|
|
173
|
-
status,
|
|
174
|
-
message: `Failed to resolve ${modelName}`,
|
|
175
|
-
}),
|
|
176
|
-
{
|
|
177
|
-
status,
|
|
178
|
-
headers: { 'Content-Type': 'application/json' },
|
|
179
|
-
},
|
|
180
|
-
)
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Clear cache for a specific model
|
|
185
|
-
*/
|
|
186
|
-
clearCache(modelName: string, params?: Record<string, string>): void {
|
|
187
|
-
if (params) {
|
|
188
|
-
const config = this.models.get(modelName)
|
|
189
|
-
if (config?.cache) {
|
|
190
|
-
const cacheKey = this.generateCacheKey(modelName, params, config)
|
|
191
|
-
this.cache.delete(cacheKey)
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
else {
|
|
195
|
-
// Clear all cache entries for this model
|
|
196
|
-
const keysToDelete: string[] = []
|
|
197
|
-
for (const key of this.cache.keys()) {
|
|
198
|
-
if (key.startsWith(`${modelName}:`)) {
|
|
199
|
-
keysToDelete.push(key)
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
for (const key of keysToDelete) {
|
|
203
|
-
this.cache.delete(key)
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Clear all cache
|
|
210
|
-
*/
|
|
211
|
-
clearAllCache(): void {
|
|
212
|
-
this.cache.clear()
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Get cache statistics
|
|
217
|
-
*/
|
|
218
|
-
getCacheStats(): { totalEntries: number, validEntries: number, expiredEntries: number, models: number } {
|
|
219
|
-
const now = Date.now()
|
|
220
|
-
let validEntries = 0
|
|
221
|
-
let expiredEntries = 0
|
|
222
|
-
|
|
223
|
-
for (const entry of this.cache.values()) {
|
|
224
|
-
if (now - entry.timestamp > entry.ttl) {
|
|
225
|
-
expiredEntries++
|
|
226
|
-
}
|
|
227
|
-
else {
|
|
228
|
-
validEntries++
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
return {
|
|
233
|
-
totalEntries: this.cache.size,
|
|
234
|
-
validEntries,
|
|
235
|
-
expiredEntries,
|
|
236
|
-
models: this.models.size,
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Cleanup expired cache entries
|
|
242
|
-
*/
|
|
243
|
-
cleanupCache(): number {
|
|
244
|
-
const now = Date.now()
|
|
245
|
-
const keysToDelete: string[] = []
|
|
246
|
-
|
|
247
|
-
for (const [key, entry] of this.cache.entries()) {
|
|
248
|
-
if (now - entry.timestamp > entry.ttl) {
|
|
249
|
-
keysToDelete.push(key)
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
for (const key of keysToDelete) {
|
|
254
|
-
this.cache.delete(key)
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
return keysToDelete.length
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Get cached model if available and valid
|
|
262
|
-
*/
|
|
263
|
-
private getCachedModel<T>(
|
|
264
|
-
modelName: string,
|
|
265
|
-
params: Record<string, string>,
|
|
266
|
-
config: ModelBindingConfig,
|
|
267
|
-
): T | null {
|
|
268
|
-
if (!config.cache?.enabled)
|
|
269
|
-
return null
|
|
270
|
-
|
|
271
|
-
const cacheKey = this.generateCacheKey(modelName, params, config)
|
|
272
|
-
const cached = this.cache.get(cacheKey)
|
|
273
|
-
|
|
274
|
-
if (!cached)
|
|
275
|
-
return null
|
|
276
|
-
|
|
277
|
-
const now = Date.now()
|
|
278
|
-
if (now - cached.timestamp > cached.ttl) {
|
|
279
|
-
this.cache.delete(cacheKey)
|
|
280
|
-
return null
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
return cached.model
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* Cache a model
|
|
288
|
-
*/
|
|
289
|
-
private cacheModel<T>(
|
|
290
|
-
modelName: string,
|
|
291
|
-
params: Record<string, string>,
|
|
292
|
-
model: T,
|
|
293
|
-
config: ModelBindingConfig,
|
|
294
|
-
): void {
|
|
295
|
-
if (!config.cache?.enabled)
|
|
296
|
-
return
|
|
297
|
-
|
|
298
|
-
const cacheKey = this.generateCacheKey(modelName, params, config)
|
|
299
|
-
this.cache.set(cacheKey, {
|
|
300
|
-
model,
|
|
301
|
-
timestamp: Date.now(),
|
|
302
|
-
ttl: config.cache.ttl,
|
|
303
|
-
})
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
/**
|
|
307
|
-
* Generate cache key for model
|
|
308
|
-
*/
|
|
309
|
-
private generateCacheKey(
|
|
310
|
-
modelName: string,
|
|
311
|
-
params: Record<string, string>,
|
|
312
|
-
config: ModelBindingConfig,
|
|
313
|
-
): string {
|
|
314
|
-
if (config.cache?.key) {
|
|
315
|
-
return `${modelName}:${config.cache.key(params)}`
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
const paramString = Object.entries(params)
|
|
319
|
-
.sort(([a], [b]) => a.localeCompare(b))
|
|
320
|
-
.map(([key, value]) => `${key}=${value}`)
|
|
321
|
-
.join('&')
|
|
322
|
-
|
|
323
|
-
return `${modelName}:${paramString}`
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
/**
|
|
328
|
-
* Global model registry instance
|
|
329
|
-
*/
|
|
330
|
-
export const modelRegistry: ModelRegistry = new ModelRegistry()
|
|
331
|
-
|
|
332
|
-
/**
|
|
333
|
-
* Model binding utilities
|
|
334
|
-
*/
|
|
335
|
-
export const ModelUtils = {
|
|
336
|
-
/**
|
|
337
|
-
* Create a simple resolver for database models
|
|
338
|
-
*/
|
|
339
|
-
dbResolver<T>(
|
|
340
|
-
tableName: string,
|
|
341
|
-
idField: string = 'id',
|
|
342
|
-
db?: any,
|
|
343
|
-
): ModelResolver<T> {
|
|
344
|
-
return async (params) => {
|
|
345
|
-
const id = params[idField]
|
|
346
|
-
if (!id)
|
|
347
|
-
return null
|
|
348
|
-
|
|
349
|
-
// This is a placeholder - replace with your actual database logic
|
|
350
|
-
if (db && typeof db.query === 'function') {
|
|
351
|
-
const result = await db.query(`SELECT * FROM ${tableName} WHERE ${idField} = ?`, [id])
|
|
352
|
-
return result[0] || null
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// Fallback for demo purposes
|
|
356
|
-
console.warn('No database connection provided to dbResolver')
|
|
357
|
-
return null
|
|
358
|
-
}
|
|
359
|
-
},
|
|
360
|
-
|
|
361
|
-
/**
|
|
362
|
-
* Create a validator that checks required fields
|
|
363
|
-
*/
|
|
364
|
-
requiredFieldsValidator<T extends Record<string, any>>(
|
|
365
|
-
fields: (keyof T)[],
|
|
366
|
-
): ModelValidatorFn<T> {
|
|
367
|
-
return async (model) => {
|
|
368
|
-
for (const field of fields) {
|
|
369
|
-
if (model[field] === undefined || model[field] === null) {
|
|
370
|
-
return {
|
|
371
|
-
valid: false,
|
|
372
|
-
error: `Required field '${String(field)}' is missing`,
|
|
373
|
-
status: 422,
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
return { valid: true }
|
|
378
|
-
}
|
|
379
|
-
},
|
|
380
|
-
|
|
381
|
-
/**
|
|
382
|
-
* Create a transformer that picks specific fields
|
|
383
|
-
*/
|
|
384
|
-
pickFieldsTransformer<T, K extends keyof T>(
|
|
385
|
-
fields: K[],
|
|
386
|
-
): ModelTransformerFn<T> {
|
|
387
|
-
return async (model) => {
|
|
388
|
-
const result = {} as Pick<T, K>
|
|
389
|
-
for (const field of fields) {
|
|
390
|
-
result[field] = model[field]
|
|
391
|
-
}
|
|
392
|
-
return result as T
|
|
393
|
-
}
|
|
394
|
-
},
|
|
395
|
-
|
|
396
|
-
/**
|
|
397
|
-
* Create a transformer that excludes specific fields
|
|
398
|
-
*/
|
|
399
|
-
omitFieldsTransformer<T, K extends keyof T>(
|
|
400
|
-
fields: K[],
|
|
401
|
-
): ModelTransformerFn<T> {
|
|
402
|
-
return async (model) => {
|
|
403
|
-
const result = { ...model }
|
|
404
|
-
for (const field of fields) {
|
|
405
|
-
delete result[field]
|
|
406
|
-
}
|
|
407
|
-
return result
|
|
408
|
-
}
|
|
409
|
-
},
|
|
410
|
-
|
|
411
|
-
/**
|
|
412
|
-
* Create a UUID validator
|
|
413
|
-
*/
|
|
414
|
-
uuidValidator<T extends { id: string }>(): ModelValidatorFn<T> {
|
|
415
|
-
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
|
|
416
|
-
|
|
417
|
-
return async (model) => {
|
|
418
|
-
if (!uuidRegex.test(model.id)) {
|
|
419
|
-
return {
|
|
420
|
-
valid: false,
|
|
421
|
-
error: 'Invalid UUID format',
|
|
422
|
-
status: 422,
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
return { valid: true }
|
|
426
|
-
}
|
|
427
|
-
},
|
|
428
|
-
|
|
429
|
-
/**
|
|
430
|
-
* Create ownership validator (checks if user owns the model)
|
|
431
|
-
*/
|
|
432
|
-
ownershipValidator<T extends { userId?: string | number }>(
|
|
433
|
-
userIdField: string = 'userId',
|
|
434
|
-
): ModelValidatorFn<T> {
|
|
435
|
-
return async (model, _params, req) => {
|
|
436
|
-
const userId = req?.user?.id
|
|
437
|
-
if (!userId) {
|
|
438
|
-
return {
|
|
439
|
-
valid: false,
|
|
440
|
-
error: 'Authentication required',
|
|
441
|
-
status: 401,
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
const modelUserId = (model as any)[userIdField]
|
|
446
|
-
if (modelUserId && modelUserId.toString() !== userId.toString()) {
|
|
447
|
-
return {
|
|
448
|
-
valid: false,
|
|
449
|
-
error: 'Access denied',
|
|
450
|
-
status: 403,
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
return { valid: true }
|
|
455
|
-
}
|
|
456
|
-
},
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
/**
|
|
460
|
-
* Common model binding factories
|
|
461
|
-
*/
|
|
462
|
-
export const ModelFactory = {
|
|
463
|
-
/**
|
|
464
|
-
* Create a simple model binding for database entities
|
|
465
|
-
*/
|
|
466
|
-
entity<T>(
|
|
467
|
-
tableName: string,
|
|
468
|
-
options: {
|
|
469
|
-
idField?: string
|
|
470
|
-
cache?: boolean
|
|
471
|
-
cacheTtl?: number
|
|
472
|
-
validator?: ModelValidatorFn<T>
|
|
473
|
-
transformer?: ModelTransformerFn<T>
|
|
474
|
-
db?: any
|
|
475
|
-
} = {},
|
|
476
|
-
): ModelBindingConfig<T> {
|
|
477
|
-
const {
|
|
478
|
-
idField = 'id',
|
|
479
|
-
cache = false,
|
|
480
|
-
cacheTtl = 300000, // 5 minutes
|
|
481
|
-
validator,
|
|
482
|
-
transformer,
|
|
483
|
-
db,
|
|
484
|
-
} = options
|
|
485
|
-
|
|
486
|
-
return {
|
|
487
|
-
resolver: ModelUtils.dbResolver<T>(tableName, idField, db),
|
|
488
|
-
validator,
|
|
489
|
-
transformer,
|
|
490
|
-
cache: cache ? { enabled: true, ttl: cacheTtl } : { enabled: false, ttl: 0 },
|
|
491
|
-
}
|
|
492
|
-
},
|
|
493
|
-
|
|
494
|
-
/**
|
|
495
|
-
* Create a user model binding with ownership validation
|
|
496
|
-
*/
|
|
497
|
-
user<T extends { id: string, userId?: string }>(
|
|
498
|
-
tableName: string = 'users',
|
|
499
|
-
options: {
|
|
500
|
-
cache?: boolean
|
|
501
|
-
cacheTtl?: number
|
|
502
|
-
db?: any
|
|
503
|
-
} = {},
|
|
504
|
-
): ModelBindingConfig<T> {
|
|
505
|
-
const { cache = true, cacheTtl = 600000, db } = options // 10 minutes cache
|
|
506
|
-
|
|
507
|
-
return {
|
|
508
|
-
resolver: ModelUtils.dbResolver<T>(tableName, 'id', db),
|
|
509
|
-
validator: ModelUtils.uuidValidator<T>(),
|
|
510
|
-
transformer: ModelUtils.omitFieldsTransformer(['password', 'passwordHash'] as any),
|
|
511
|
-
cache: { enabled: cache, ttl: cacheTtl },
|
|
512
|
-
}
|
|
513
|
-
},
|
|
514
|
-
|
|
515
|
-
/**
|
|
516
|
-
* Create a resource model binding with ownership check
|
|
517
|
-
*/
|
|
518
|
-
ownedResource<T extends { id: string, userId: string }>(
|
|
519
|
-
tableName: string,
|
|
520
|
-
options: {
|
|
521
|
-
idField?: string
|
|
522
|
-
userIdField?: string
|
|
523
|
-
cache?: boolean
|
|
524
|
-
cacheTtl?: number
|
|
525
|
-
db?: any
|
|
526
|
-
} = {},
|
|
527
|
-
): ModelBindingConfig<T> {
|
|
528
|
-
const {
|
|
529
|
-
idField = 'id',
|
|
530
|
-
userIdField = 'userId',
|
|
531
|
-
cache = true,
|
|
532
|
-
cacheTtl = 300000,
|
|
533
|
-
db,
|
|
534
|
-
} = options
|
|
535
|
-
|
|
536
|
-
return {
|
|
537
|
-
resolver: ModelUtils.dbResolver<T>(tableName, idField, db),
|
|
538
|
-
validator: async (model, params, req) => {
|
|
539
|
-
// First validate UUID
|
|
540
|
-
const uuidResult = await ModelUtils.uuidValidator<T>()(model, params, req)
|
|
541
|
-
if (!uuidResult.valid)
|
|
542
|
-
return uuidResult
|
|
543
|
-
|
|
544
|
-
// Then validate ownership
|
|
545
|
-
return ModelUtils.ownershipValidator<T>(userIdField)(model, params, req)
|
|
546
|
-
},
|
|
547
|
-
cache: { enabled: cache, ttl: cacheTtl },
|
|
548
|
-
}
|
|
549
|
-
},
|
|
550
|
-
}
|