@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,786 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* IoC Container for Dependency Resolution
|
|
3
|
-
*
|
|
4
|
-
* Comprehensive dependency injection container with automatic resolution,
|
|
5
|
-
* lifecycle management, and contextual binding support
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { CircularDependencyException } from '../errors/router-errors'
|
|
9
|
-
|
|
10
|
-
// Extend Reflect with metadata methods
|
|
11
|
-
declare global {
|
|
12
|
-
// eslint-disable-next-line ts/no-namespace
|
|
13
|
-
namespace Reflect {
|
|
14
|
-
function getMetadata(_metadataKey: any, _target: any, _propertyKey?: string | symbol): any
|
|
15
|
-
function defineMetadata(_metadataKey: any, _metadataValue: any, _target: any, _propertyKey?: string | symbol): void
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// Container binding types
|
|
20
|
-
export type BindingScope = 'singleton' | 'transient' | 'scoped' | 'request'
|
|
21
|
-
|
|
22
|
-
// Token can be a string, symbol, function, constructor, or generic Function
|
|
23
|
-
// eslint-disable-next-line ts/no-unsafe-function-type
|
|
24
|
-
export type Token = string | symbol | ((...args: any[]) => any) | (new (...args: any[]) => any) | Function
|
|
25
|
-
|
|
26
|
-
export interface BindingMetadata {
|
|
27
|
-
scope: BindingScope
|
|
28
|
-
tags?: string[]
|
|
29
|
-
when?: (context: ResolutionContext) => boolean
|
|
30
|
-
factory?: (..._args: any[]) => any
|
|
31
|
-
dependencies?: Token[]
|
|
32
|
-
lazy?: boolean
|
|
33
|
-
decorators?: ((..._args: any[]) => any)[]
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface Binding<T = any> {
|
|
37
|
-
token: Token
|
|
38
|
-
implementation?: new (...args: any[]) => T
|
|
39
|
-
instance?: T
|
|
40
|
-
factory?: (...args: any[]) => T
|
|
41
|
-
value?: T
|
|
42
|
-
metadata: BindingMetadata
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export interface ResolutionContext {
|
|
46
|
-
container: Container
|
|
47
|
-
token: Token
|
|
48
|
-
parent?: ResolutionContext
|
|
49
|
-
environment?: string
|
|
50
|
-
requestId?: string
|
|
51
|
-
depth: number
|
|
52
|
-
resolving: Set<Token>
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export interface ContainerOptions {
|
|
56
|
-
autoRegister?: boolean
|
|
57
|
-
strictMode?: boolean
|
|
58
|
-
maxDepth?: number
|
|
59
|
-
enableDecorators?: boolean
|
|
60
|
-
enableInterception?: boolean
|
|
61
|
-
defaultScope?: BindingScope
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Dependency injection decorators
|
|
65
|
-
export const INJECTABLE_METADATA_KEY: unique symbol = Symbol('injectable')
|
|
66
|
-
export const INJECT_METADATA_KEY: unique symbol = Symbol('inject')
|
|
67
|
-
export const OPTIONAL_METADATA_KEY: unique symbol = Symbol('optional')
|
|
68
|
-
export const TAGGED_METADATA_KEY: unique symbol = Symbol('tagged')
|
|
69
|
-
|
|
70
|
-
// Decorator metadata
|
|
71
|
-
export interface InjectableMetadata {
|
|
72
|
-
token?: string | symbol
|
|
73
|
-
scope?: BindingScope
|
|
74
|
-
tags?: string[]
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export interface InjectMetadata {
|
|
78
|
-
token: Token
|
|
79
|
-
optional?: boolean
|
|
80
|
-
tags?: string[]
|
|
81
|
-
when?: (context: ResolutionContext) => boolean
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Main IoC Container implementation
|
|
86
|
-
*/
|
|
87
|
-
export class Container {
|
|
88
|
-
private bindings = new Map<Token, Binding>()
|
|
89
|
-
// Tag → tokens index, maintained on register/unbind so tagged lookups
|
|
90
|
-
// are O(matches) instead of scanning every binding
|
|
91
|
-
private tagIndex = new Map<string, Set<Token>>()
|
|
92
|
-
private instances = new Map<Token, any>()
|
|
93
|
-
private scopedInstances = new Map<string, Map<Token, any>>()
|
|
94
|
-
private interceptors = new Map<Token, ((..._args: any[]) => any)[]>()
|
|
95
|
-
private parent?: Container
|
|
96
|
-
private children = new Set<Container>()
|
|
97
|
-
private options: Required<ContainerOptions>
|
|
98
|
-
|
|
99
|
-
constructor(options: ContainerOptions = {}) {
|
|
100
|
-
this.options = {
|
|
101
|
-
autoRegister: options.autoRegister ?? true,
|
|
102
|
-
strictMode: options.strictMode ?? false,
|
|
103
|
-
maxDepth: options.maxDepth ?? 50,
|
|
104
|
-
enableDecorators: options.enableDecorators ?? true,
|
|
105
|
-
enableInterception: options.enableInterception ?? false,
|
|
106
|
-
defaultScope: options.defaultScope ?? 'transient',
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Bind a token to an implementation
|
|
112
|
-
*/
|
|
113
|
-
bind<T>(token: Token): BindingBuilder<T> {
|
|
114
|
-
return new BindingBuilder<T>(this, token)
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Register a binding
|
|
119
|
-
*/
|
|
120
|
-
register<T>(binding: Binding<T>): this {
|
|
121
|
-
// Re-registering a token replaces its tag index entries
|
|
122
|
-
const existing = this.bindings.get(binding.token)
|
|
123
|
-
if (existing?.metadata?.tags) {
|
|
124
|
-
for (const tag of existing.metadata.tags) {
|
|
125
|
-
this.tagIndex.get(tag)?.delete(binding.token)
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
this.bindings.set(binding.token, binding)
|
|
130
|
-
|
|
131
|
-
if (binding.metadata?.tags) {
|
|
132
|
-
for (const tag of binding.metadata.tags) {
|
|
133
|
-
let tokens = this.tagIndex.get(tag)
|
|
134
|
-
if (!tokens) {
|
|
135
|
-
tokens = new Set()
|
|
136
|
-
this.tagIndex.set(tag, tokens)
|
|
137
|
-
}
|
|
138
|
-
tokens.add(binding.token)
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
return this
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Bind a singleton
|
|
146
|
-
*/
|
|
147
|
-
singleton<T>(
|
|
148
|
-
token: Token,
|
|
149
|
-
implementation: new (...args: any[]) => T,
|
|
150
|
-
): Container {
|
|
151
|
-
return this.bind<T>(token).to(implementation).inSingletonScope().build()
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Bind a transient
|
|
156
|
-
*/
|
|
157
|
-
transient<T>(
|
|
158
|
-
token: Token,
|
|
159
|
-
implementation: new (...args: any[]) => T,
|
|
160
|
-
): Container {
|
|
161
|
-
return this.bind<T>(token).to(implementation).inTransientScope().build()
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Bind a value
|
|
166
|
-
*/
|
|
167
|
-
value<T>(token: Token, value: T): Container {
|
|
168
|
-
return this.bind<T>(token).toValue(value).build()
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Bind a factory
|
|
173
|
-
*/
|
|
174
|
-
factory<T>(
|
|
175
|
-
token: Token,
|
|
176
|
-
factory: (...args: any[]) => T,
|
|
177
|
-
): Container {
|
|
178
|
-
return this.bind<T>(token).toFactory(factory).build()
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Resolve a dependency
|
|
183
|
-
*/
|
|
184
|
-
resolve<T>(token: Token, context?: Partial<ResolutionContext>): T {
|
|
185
|
-
const resolutionContext: ResolutionContext = {
|
|
186
|
-
container: this,
|
|
187
|
-
token,
|
|
188
|
-
environment: context?.environment || 'development',
|
|
189
|
-
requestId: context?.requestId || this.generateRequestId(),
|
|
190
|
-
depth: context?.depth || 0,
|
|
191
|
-
resolving: context?.resolving || new Set(),
|
|
192
|
-
parent: context?.parent,
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
return this.resolveInternal<T>(token, resolutionContext)
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Resolve all instances of a token (useful for arrays of services)
|
|
200
|
-
*/
|
|
201
|
-
resolveAll<T>(token: Token): T[] {
|
|
202
|
-
// Direct binding plus anything tagged with the token (when it's a
|
|
203
|
-
// string), deduplicated by token
|
|
204
|
-
const tokens = new Set<Token>()
|
|
205
|
-
if (this.bindings.has(token)) {
|
|
206
|
-
tokens.add(token)
|
|
207
|
-
}
|
|
208
|
-
if (typeof token === 'string') {
|
|
209
|
-
for (const tagged of this.tagIndex.get(token) ?? []) {
|
|
210
|
-
tokens.add(tagged)
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
return Array.from(tokens, t => this.resolve<T>(t))
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Resolve with tags
|
|
219
|
-
*/
|
|
220
|
-
resolveTagged<T>(tag: string): T[] {
|
|
221
|
-
const tokens = this.tagIndex.get(tag)
|
|
222
|
-
if (!tokens || tokens.size === 0) {
|
|
223
|
-
return []
|
|
224
|
-
}
|
|
225
|
-
return Array.from(tokens, token => this.resolve<T>(token))
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Get all bindings with a specific tag
|
|
230
|
-
*/
|
|
231
|
-
getTaggedBindings<T>(tag: string): Binding<T>[] {
|
|
232
|
-
const tokens = this.tagIndex.get(tag)
|
|
233
|
-
if (!tokens || tokens.size === 0) {
|
|
234
|
-
return []
|
|
235
|
-
}
|
|
236
|
-
const bindings: Binding<T>[] = []
|
|
237
|
-
for (const token of tokens) {
|
|
238
|
-
const binding = this.bindings.get(token)
|
|
239
|
-
if (binding) {
|
|
240
|
-
bindings.push(binding as Binding<T>)
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
return bindings
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Add interceptor for a service
|
|
248
|
-
*/
|
|
249
|
-
addInterceptor<T>(token: Token, interceptor: (service: T) => T): void {
|
|
250
|
-
const key = this.tokenToString(token)
|
|
251
|
-
if (!this.interceptors.has(key)) {
|
|
252
|
-
this.interceptors.set(key, [])
|
|
253
|
-
}
|
|
254
|
-
this.interceptors.get(key)!.push(interceptor as (_service: any) => any)
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Register controller (basic implementation for compatibility)
|
|
259
|
-
*/
|
|
260
|
-
registerController(controller: new (...args: any[]) => any): void {
|
|
261
|
-
// Basic implementation - just register as singleton
|
|
262
|
-
this.singleton(controller, controller)
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Get controller instance
|
|
267
|
-
*/
|
|
268
|
-
getController<T>(controller: new (...args: any[]) => T): T {
|
|
269
|
-
return this.resolve<T>(controller)
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Check if a token is bound
|
|
274
|
-
*/
|
|
275
|
-
isBound(token: Token): boolean {
|
|
276
|
-
return this.bindings.has(token) || (this.parent?.isBound(token) ?? false)
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Unbind a token
|
|
281
|
-
*/
|
|
282
|
-
unbind(token: Token): this {
|
|
283
|
-
const binding = this.bindings.get(token)
|
|
284
|
-
if (binding?.metadata?.tags) {
|
|
285
|
-
for (const tag of binding.metadata.tags) {
|
|
286
|
-
this.tagIndex.get(tag)?.delete(token)
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
this.bindings.delete(token)
|
|
290
|
-
this.instances.delete(token)
|
|
291
|
-
return this
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
* Create a child container
|
|
296
|
-
*/
|
|
297
|
-
createChild(options?: ContainerOptions): Container {
|
|
298
|
-
const child = new Container({ ...this.options, ...options })
|
|
299
|
-
child.parent = this
|
|
300
|
-
this.children.add(child)
|
|
301
|
-
return child
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
/**
|
|
305
|
-
* Create a scoped container
|
|
306
|
-
*/
|
|
307
|
-
createScope(scopeId: string): Container {
|
|
308
|
-
const scope = this.createChild()
|
|
309
|
-
scope.scopedInstances.set(scopeId, new Map())
|
|
310
|
-
return scope
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* Get all bindings
|
|
315
|
-
*/
|
|
316
|
-
getBindings(): Map<Token, Binding> {
|
|
317
|
-
return new Map(this.bindings)
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* Clear all bindings and instances
|
|
322
|
-
*/
|
|
323
|
-
clear(): this {
|
|
324
|
-
this.bindings.clear()
|
|
325
|
-
this.tagIndex.clear()
|
|
326
|
-
this.instances.clear()
|
|
327
|
-
this.scopedInstances.clear()
|
|
328
|
-
return this
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
/**
|
|
332
|
-
* Add interceptor
|
|
333
|
-
*/
|
|
334
|
-
intercept<T>(
|
|
335
|
-
token: string | symbol | ((..._args: any[]) => any),
|
|
336
|
-
interceptor: (instance: T, context: ResolutionContext) => T,
|
|
337
|
-
): this {
|
|
338
|
-
if (!this.interceptors.has(token)) {
|
|
339
|
-
this.interceptors.set(token, [])
|
|
340
|
-
}
|
|
341
|
-
this.interceptors.get(token)!.push(interceptor)
|
|
342
|
-
return this
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
/**
|
|
346
|
-
* Internal resolution logic
|
|
347
|
-
*/
|
|
348
|
-
private resolveInternal<T>(token: Token, context: ResolutionContext): T {
|
|
349
|
-
// Check for circular dependencies
|
|
350
|
-
if (context.resolving.has(token)) {
|
|
351
|
-
// Build the dependency chain for better error message
|
|
352
|
-
const chain = Array.from(context.resolving).map(t => this.tokenToString(t))
|
|
353
|
-
chain.push(this.tokenToString(token))
|
|
354
|
-
throw new CircularDependencyException(chain)
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// Check depth limit
|
|
358
|
-
if (context.depth > this.options.maxDepth) {
|
|
359
|
-
const chain = Array.from(context.resolving).map(t => this.tokenToString(t))
|
|
360
|
-
throw new Error(
|
|
361
|
-
`Maximum resolution depth exceeded (${this.options.maxDepth}).\n`
|
|
362
|
-
+ `This usually indicates a circular or very deep dependency chain.\n`
|
|
363
|
-
+ `Current resolution chain:\n${chain.map((t, i) => ` ${i + 1}. ${t}`).join('\n')}`,
|
|
364
|
-
)
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
context.resolving.add(token)
|
|
368
|
-
context.depth++
|
|
369
|
-
|
|
370
|
-
try {
|
|
371
|
-
// Try to resolve from current container
|
|
372
|
-
const binding = this.bindings.get(token)
|
|
373
|
-
|
|
374
|
-
if (binding) {
|
|
375
|
-
// Check contextual conditions
|
|
376
|
-
if (binding.metadata.when && !binding.metadata.when(context)) {
|
|
377
|
-
throw new Error(`Contextual binding condition not met for: ${this.tokenToString(token)}`)
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
return this.createInstance<T>(binding, context)
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
// Try parent container
|
|
384
|
-
if (this.parent) {
|
|
385
|
-
// Create a fresh context for parent resolution to avoid false circular dependency detection
|
|
386
|
-
const parentContext: ResolutionContext = {
|
|
387
|
-
resolving: new Set(),
|
|
388
|
-
depth: 0,
|
|
389
|
-
container: this.parent,
|
|
390
|
-
token,
|
|
391
|
-
}
|
|
392
|
-
return this.parent.resolveInternal<T>(token, parentContext)
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
// Auto-registration attempt
|
|
396
|
-
if (this.options.autoRegister && typeof token === 'function') {
|
|
397
|
-
return this.autoRegisterAndResolve<T>(token as new (..._args: any[]) => T, context)
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
// Strict mode check
|
|
401
|
-
if (this.options.strictMode) {
|
|
402
|
-
throw new Error(`No binding found for: ${this.tokenToString(token)}`)
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
// Try to create instance directly
|
|
406
|
-
if (typeof token === 'function') {
|
|
407
|
-
return this.createInstanceFromConstructor<T>(token as new (..._args: any[]) => T, context)
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
throw new Error(`Cannot resolve: ${this.tokenToString(token)}`)
|
|
411
|
-
}
|
|
412
|
-
finally {
|
|
413
|
-
context.resolving.delete(token)
|
|
414
|
-
context.depth--
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
/**
|
|
419
|
-
* Create instance from binding
|
|
420
|
-
*/
|
|
421
|
-
protected createInstance<T>(binding: Binding<T>, context: ResolutionContext): T {
|
|
422
|
-
let instance: T
|
|
423
|
-
|
|
424
|
-
// Check for existing singleton
|
|
425
|
-
if (binding.metadata.scope === 'singleton' && this.instances.has(binding.token)) {
|
|
426
|
-
instance = this.instances.get(binding.token)
|
|
427
|
-
}
|
|
428
|
-
// Check for scoped instance
|
|
429
|
-
else if (binding.metadata.scope === 'scoped' && context.requestId) {
|
|
430
|
-
const scopedMap = this.scopedInstances.get(context.requestId)
|
|
431
|
-
if (scopedMap?.has(binding.token)) {
|
|
432
|
-
instance = scopedMap.get(binding.token)
|
|
433
|
-
}
|
|
434
|
-
else {
|
|
435
|
-
instance = this.createNewInstance<T>(binding, context)
|
|
436
|
-
if (!scopedMap) {
|
|
437
|
-
this.scopedInstances.set(context.requestId, new Map())
|
|
438
|
-
}
|
|
439
|
-
this.scopedInstances.get(context.requestId)!.set(binding.token, instance)
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
// Create new instance
|
|
443
|
-
else {
|
|
444
|
-
instance = this.createNewInstance<T>(binding, context)
|
|
445
|
-
|
|
446
|
-
// Store singleton
|
|
447
|
-
if (binding.metadata.scope === 'singleton') {
|
|
448
|
-
this.instances.set(binding.token, instance)
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
// Apply interceptors
|
|
453
|
-
const tokenKey = this.tokenToString(binding.token)
|
|
454
|
-
if (this.interceptors.has(tokenKey)) {
|
|
455
|
-
const interceptors = this.interceptors.get(tokenKey)!
|
|
456
|
-
for (const interceptor of interceptors) {
|
|
457
|
-
instance = interceptor(instance)
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
return instance
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
/**
|
|
465
|
-
* Create new instance from binding
|
|
466
|
-
*/
|
|
467
|
-
private createNewInstance<T>(binding: Binding<T>, context: ResolutionContext): T {
|
|
468
|
-
if (binding.value !== undefined) {
|
|
469
|
-
return binding.value
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
if (binding.factory) {
|
|
473
|
-
const dependencies = this.resolveDependencies(binding.metadata.dependencies || [], context)
|
|
474
|
-
return binding.factory(...dependencies)
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
if (binding.instance) {
|
|
478
|
-
return binding.instance
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
if (binding.implementation) {
|
|
482
|
-
return this.createInstanceFromConstructor<T>(binding.implementation, context)
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
throw new Error(`Invalid binding configuration for: ${this.tokenToString(binding.token)}`)
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
/**
|
|
489
|
-
* Create instance from constructor
|
|
490
|
-
*/
|
|
491
|
-
private createInstanceFromConstructor<T>(
|
|
492
|
-
constructor: new (...args: any[]) => T,
|
|
493
|
-
context: ResolutionContext,
|
|
494
|
-
): T {
|
|
495
|
-
const dependencies = this.getConstructorDependencies(constructor)
|
|
496
|
-
const resolvedDependencies = this.resolveDependencies(dependencies, context)
|
|
497
|
-
|
|
498
|
-
const instance = new constructor(...resolvedDependencies)
|
|
499
|
-
|
|
500
|
-
// Apply decorators if enabled
|
|
501
|
-
if (this.options.enableDecorators) {
|
|
502
|
-
this.applyDecorators(instance, constructor)
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
return instance
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
/**
|
|
509
|
-
* Get constructor dependencies using reflection
|
|
510
|
-
*/
|
|
511
|
-
private getConstructorDependencies(constructor: ((...args: any[]) => any) | (new (...args: any[]) => any)): Token[] {
|
|
512
|
-
// Check for explicit metadata
|
|
513
|
-
let injectMetadata = Reflect.getMetadata?.(INJECT_METADATA_KEY, constructor) as InjectMetadata[]
|
|
514
|
-
|| (constructor as any)[INJECT_METADATA_KEY] as InjectMetadata[]
|
|
515
|
-
|
|
516
|
-
// If not found on the constructor itself, check the constructor property
|
|
517
|
-
// For classes, the metadata might be stored on the constructor property
|
|
518
|
-
if (!injectMetadata && (constructor as any).constructor) {
|
|
519
|
-
injectMetadata = (constructor as any).constructor[INJECT_METADATA_KEY] as InjectMetadata[]
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
// Also check if this is a class and the metadata is on the class itself
|
|
523
|
-
if (!injectMetadata && typeof constructor === 'function' && constructor.prototype) {
|
|
524
|
-
// This is a class constructor, check if metadata is stored on the class
|
|
525
|
-
const classMetadata = (constructor as any)[INJECT_METADATA_KEY] as InjectMetadata[]
|
|
526
|
-
if (classMetadata) {
|
|
527
|
-
injectMetadata = classMetadata
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
// Final check: if this is a class, look for metadata on the class itself
|
|
532
|
-
if (!injectMetadata && typeof constructor === 'function') {
|
|
533
|
-
const directMetadata = (constructor as any)[INJECT_METADATA_KEY] as InjectMetadata[]
|
|
534
|
-
if (directMetadata) {
|
|
535
|
-
injectMetadata = directMetadata
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
if (injectMetadata) {
|
|
540
|
-
return injectMetadata.map(meta => meta.token)
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
// Try to get parameter types from TypeScript metadata
|
|
544
|
-
const paramTypes = Reflect.getMetadata?.('design:paramtypes', constructor) as Token[]
|
|
545
|
-
if (paramTypes) {
|
|
546
|
-
return paramTypes
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
// Fallback to parsing constructor string (less reliable)
|
|
550
|
-
return this.parseConstructorDependencies(constructor)
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
/**
|
|
554
|
-
* Parse constructor dependencies from function string
|
|
555
|
-
*/
|
|
556
|
-
private parseConstructorDependencies(constructor: ((...args: any[]) => any) | (new (...args: any[]) => any)): string[] {
|
|
557
|
-
const constructorString = constructor.toString()
|
|
558
|
-
const match = constructorString.match(/constructor\s*\(([^)]*)\)/)
|
|
559
|
-
|
|
560
|
-
if (!match || !match[1]) {
|
|
561
|
-
return []
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
return match[1]
|
|
565
|
-
.split(',')
|
|
566
|
-
.map(param => param.trim().split(':')[0].trim())
|
|
567
|
-
.filter(param => param.length > 0)
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
/**
|
|
571
|
-
* Resolve array of dependencies
|
|
572
|
-
*/
|
|
573
|
-
private resolveDependencies(
|
|
574
|
-
dependencies: Token[],
|
|
575
|
-
context: ResolutionContext,
|
|
576
|
-
): any[] {
|
|
577
|
-
return dependencies.map(dep => this.resolveInternal(dep, context))
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
/**
|
|
581
|
-
* Auto-register and resolve
|
|
582
|
-
*/
|
|
583
|
-
private autoRegisterAndResolve<T>(
|
|
584
|
-
constructor: new (...args: any[]) => T,
|
|
585
|
-
context: ResolutionContext,
|
|
586
|
-
): T {
|
|
587
|
-
// Check for injectable metadata
|
|
588
|
-
const metadata = Reflect.getMetadata?.(INJECTABLE_METADATA_KEY, constructor) as InjectableMetadata
|
|
589
|
-
|
|
590
|
-
const binding: Binding<T> = {
|
|
591
|
-
token: constructor,
|
|
592
|
-
implementation: constructor,
|
|
593
|
-
metadata: {
|
|
594
|
-
scope: metadata?.scope || this.options.defaultScope,
|
|
595
|
-
tags: metadata?.tags,
|
|
596
|
-
},
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
this.register(binding)
|
|
600
|
-
return this.resolveInternal<T>(constructor, context)
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
/**
|
|
604
|
-
* Apply decorators to instance
|
|
605
|
-
*/
|
|
606
|
-
private applyDecorators(instance: any, constructor: ((..._args: any[]) => any) | (new (..._args: any[]) => any)): void {
|
|
607
|
-
const decorators = Reflect.getMetadata?.('decorators', constructor) as ((..._args: any[]) => any)[]
|
|
608
|
-
if (decorators) {
|
|
609
|
-
for (const decorator of decorators) {
|
|
610
|
-
decorator(instance)
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
/**
|
|
616
|
-
* Check if binding has tag
|
|
617
|
-
*/
|
|
618
|
-
private hasTag(binding: Binding, tag: string): boolean {
|
|
619
|
-
return binding.metadata.tags?.includes(tag) ?? false
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
/**
|
|
623
|
-
* Convert token to string for error messages
|
|
624
|
-
*/
|
|
625
|
-
private tokenToString(token: Token): string {
|
|
626
|
-
if (typeof token === 'string')
|
|
627
|
-
return token
|
|
628
|
-
if (typeof token === 'symbol')
|
|
629
|
-
return token.toString()
|
|
630
|
-
if (typeof token === 'function')
|
|
631
|
-
return token.name || 'anonymous'
|
|
632
|
-
return 'unknown'
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
/**
|
|
636
|
-
* Generate unique request ID
|
|
637
|
-
*/
|
|
638
|
-
private generateRequestId(): string {
|
|
639
|
-
return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
/**
|
|
644
|
-
* Fluent binding builder
|
|
645
|
-
*/
|
|
646
|
-
export class BindingBuilder<T> {
|
|
647
|
-
private binding: Partial<Binding<T>>
|
|
648
|
-
|
|
649
|
-
constructor(private container: Container, token: Token) {
|
|
650
|
-
this.binding = {
|
|
651
|
-
token,
|
|
652
|
-
metadata: {
|
|
653
|
-
scope: 'transient',
|
|
654
|
-
},
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
/**
|
|
659
|
-
* Bind to implementation
|
|
660
|
-
*/
|
|
661
|
-
to(implementation: new (...args: any[]) => T): this {
|
|
662
|
-
this.binding.implementation = implementation
|
|
663
|
-
return this
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
/**
|
|
667
|
-
* Bind to value
|
|
668
|
-
*/
|
|
669
|
-
toValue(value: T): this {
|
|
670
|
-
this.binding.value = value
|
|
671
|
-
this.binding.metadata!.scope = 'singleton'
|
|
672
|
-
return this
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
/**
|
|
676
|
-
* Bind to factory
|
|
677
|
-
*/
|
|
678
|
-
toFactory(factory: (...args: any[]) => T): this {
|
|
679
|
-
this.binding.factory = factory
|
|
680
|
-
return this
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
/**
|
|
684
|
-
* Bind to existing instance
|
|
685
|
-
*/
|
|
686
|
-
toInstance(instance: T): this {
|
|
687
|
-
this.binding.instance = instance
|
|
688
|
-
this.binding.metadata!.scope = 'singleton'
|
|
689
|
-
return this
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
/**
|
|
693
|
-
* Set singleton scope
|
|
694
|
-
*/
|
|
695
|
-
inSingletonScope(): this {
|
|
696
|
-
this.binding.metadata!.scope = 'singleton'
|
|
697
|
-
return this
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
/**
|
|
701
|
-
* Set transient scope
|
|
702
|
-
*/
|
|
703
|
-
inTransientScope(): this {
|
|
704
|
-
this.binding.metadata!.scope = 'transient'
|
|
705
|
-
return this
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
/**
|
|
709
|
-
* Set scoped scope
|
|
710
|
-
*/
|
|
711
|
-
inScopedScope(): this {
|
|
712
|
-
this.binding.metadata!.scope = 'scoped'
|
|
713
|
-
return this
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
/**
|
|
717
|
-
* Set request scope
|
|
718
|
-
*/
|
|
719
|
-
inRequestScope(): this {
|
|
720
|
-
this.binding.metadata!.scope = 'request'
|
|
721
|
-
return this
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
/**
|
|
725
|
-
* Add tags
|
|
726
|
-
*/
|
|
727
|
-
withTags(...tags: string[]): this {
|
|
728
|
-
this.binding.metadata!.tags = [...(this.binding.metadata!.tags || []), ...tags]
|
|
729
|
-
return this
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
/**
|
|
733
|
-
* Add contextual condition
|
|
734
|
-
*/
|
|
735
|
-
when(condition: (context: ResolutionContext) => boolean): this {
|
|
736
|
-
this.binding.metadata!.when = condition
|
|
737
|
-
return this
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
/**
|
|
741
|
-
* Set as lazy
|
|
742
|
-
*/
|
|
743
|
-
lazy(): this {
|
|
744
|
-
this.binding.metadata!.lazy = true
|
|
745
|
-
return this
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
/**
|
|
749
|
-
* Build and register binding
|
|
750
|
-
*/
|
|
751
|
-
build(): Container {
|
|
752
|
-
this.container.register(this.binding as Binding<T>)
|
|
753
|
-
return this.container
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
/**
|
|
758
|
-
* Global container instance
|
|
759
|
-
*/
|
|
760
|
-
let globalContainer: Container | null = null
|
|
761
|
-
|
|
762
|
-
/**
|
|
763
|
-
* Get or create global container
|
|
764
|
-
*/
|
|
765
|
-
export function getContainer(): Container {
|
|
766
|
-
if (!globalContainer) {
|
|
767
|
-
globalContainer = new Container()
|
|
768
|
-
}
|
|
769
|
-
return globalContainer
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
/**
|
|
773
|
-
* Set global container
|
|
774
|
-
*/
|
|
775
|
-
export function setContainer(container: Container): void {
|
|
776
|
-
globalContainer = container
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
/**
|
|
780
|
-
* Create new container
|
|
781
|
-
*/
|
|
782
|
-
export function createContainer(options?: ContainerOptions): Container {
|
|
783
|
-
return new Container(options)
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
export { Container as DIContainer }
|