@navios/di 0.5.1 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/CHANGELOG.md +145 -0
  2. package/README.md +196 -219
  3. package/docs/README.md +69 -11
  4. package/docs/api-reference.md +281 -117
  5. package/docs/container.md +220 -56
  6. package/docs/examples/request-scope-example.mts +2 -2
  7. package/docs/factory.md +3 -8
  8. package/docs/getting-started.md +37 -8
  9. package/docs/migration.md +318 -37
  10. package/docs/request-contexts.md +263 -175
  11. package/docs/scopes.md +79 -42
  12. package/lib/browser/index.d.mts +1577 -0
  13. package/lib/browser/index.d.mts.map +1 -0
  14. package/lib/browser/index.mjs +3013 -0
  15. package/lib/browser/index.mjs.map +1 -0
  16. package/lib/index-7jfWsiG4.d.mts +1211 -0
  17. package/lib/index-7jfWsiG4.d.mts.map +1 -0
  18. package/lib/index-DW3K5sOX.d.cts +1206 -0
  19. package/lib/index-DW3K5sOX.d.cts.map +1 -0
  20. package/lib/index.cjs +389 -0
  21. package/lib/index.cjs.map +1 -0
  22. package/lib/index.d.cts +376 -0
  23. package/lib/index.d.cts.map +1 -0
  24. package/lib/index.d.mts +371 -78
  25. package/lib/index.d.mts.map +1 -0
  26. package/lib/index.mjs +325 -63
  27. package/lib/index.mjs.map +1 -1
  28. package/lib/testing/index.cjs +9 -0
  29. package/lib/testing/index.d.cts +2 -0
  30. package/lib/testing/index.d.mts +2 -2
  31. package/lib/testing/index.mjs +2 -72
  32. package/lib/testing-BG_fa9TJ.mjs +2656 -0
  33. package/lib/testing-BG_fa9TJ.mjs.map +1 -0
  34. package/lib/testing-DIaIRiJz.cjs +2896 -0
  35. package/lib/testing-DIaIRiJz.cjs.map +1 -0
  36. package/package.json +29 -7
  37. package/project.json +2 -2
  38. package/src/__tests__/async-local-storage.browser.spec.mts +240 -0
  39. package/src/__tests__/async-local-storage.spec.mts +333 -0
  40. package/src/__tests__/container.spec.mts +30 -25
  41. package/src/__tests__/e2e.browser.spec.mts +790 -0
  42. package/src/__tests__/e2e.spec.mts +1222 -0
  43. package/src/__tests__/factory.spec.mts +1 -1
  44. package/src/__tests__/get-injectors.spec.mts +1 -1
  45. package/src/__tests__/injectable.spec.mts +1 -1
  46. package/src/__tests__/injection-token.spec.mts +1 -1
  47. package/src/__tests__/library-findings.spec.mts +563 -0
  48. package/src/__tests__/registry.spec.mts +2 -2
  49. package/src/__tests__/request-scope.spec.mts +266 -274
  50. package/src/__tests__/service-instantiator.spec.mts +18 -17
  51. package/src/__tests__/service-locator-event-bus.spec.mts +9 -9
  52. package/src/__tests__/service-locator-manager.spec.mts +15 -15
  53. package/src/__tests__/service-locator.spec.mts +167 -244
  54. package/src/__tests__/unified-api.spec.mts +27 -27
  55. package/src/__type-tests__/factory.spec-d.mts +2 -2
  56. package/src/__type-tests__/inject.spec-d.mts +2 -2
  57. package/src/__type-tests__/injectable.spec-d.mts +1 -1
  58. package/src/browser.mts +16 -0
  59. package/src/container/container.mts +319 -0
  60. package/src/container/index.mts +2 -0
  61. package/src/container/scoped-container.mts +350 -0
  62. package/src/decorators/factory.decorator.mts +4 -4
  63. package/src/decorators/injectable.decorator.mts +5 -5
  64. package/src/errors/di-error.mts +12 -5
  65. package/src/errors/index.mts +0 -8
  66. package/src/index.mts +156 -15
  67. package/src/interfaces/container.interface.mts +82 -0
  68. package/src/interfaces/factory.interface.mts +2 -2
  69. package/src/interfaces/index.mts +1 -0
  70. package/src/internal/context/async-local-storage.mts +120 -0
  71. package/src/internal/context/factory-context.mts +18 -0
  72. package/src/internal/context/index.mts +3 -0
  73. package/src/{request-context-holder.mts → internal/context/request-context.mts} +40 -27
  74. package/src/internal/context/resolution-context.mts +63 -0
  75. package/src/internal/context/sync-local-storage.mts +51 -0
  76. package/src/internal/core/index.mts +5 -0
  77. package/src/internal/core/instance-resolver.mts +641 -0
  78. package/src/{service-instantiator.mts → internal/core/instantiator.mts} +31 -27
  79. package/src/internal/core/invalidator.mts +437 -0
  80. package/src/internal/core/service-locator.mts +202 -0
  81. package/src/{token-processor.mts → internal/core/token-processor.mts} +79 -60
  82. package/src/{base-instance-holder-manager.mts → internal/holder/base-holder-manager.mts} +91 -21
  83. package/src/internal/holder/holder-manager.mts +85 -0
  84. package/src/internal/holder/holder-storage.interface.mts +116 -0
  85. package/src/internal/holder/index.mts +6 -0
  86. package/src/internal/holder/instance-holder.mts +109 -0
  87. package/src/internal/holder/request-storage.mts +134 -0
  88. package/src/internal/holder/singleton-storage.mts +105 -0
  89. package/src/internal/index.mts +4 -0
  90. package/src/internal/lifecycle/circular-detector.mts +77 -0
  91. package/src/internal/lifecycle/index.mts +2 -0
  92. package/src/{service-locator-event-bus.mts → internal/lifecycle/lifecycle-event-bus.mts} +11 -4
  93. package/src/testing/__tests__/test-container.spec.mts +2 -2
  94. package/src/testing/test-container.mts +4 -4
  95. package/src/token/index.mts +2 -0
  96. package/src/{injection-token.mts → token/injection-token.mts} +1 -1
  97. package/src/{registry.mts → token/registry.mts} +1 -1
  98. package/src/utils/get-injectable-token.mts +1 -1
  99. package/src/utils/get-injectors.mts +32 -15
  100. package/src/utils/types.mts +1 -1
  101. package/tsdown.config.mts +67 -0
  102. package/lib/_tsup-dts-rollup.d.mts +0 -1283
  103. package/lib/_tsup-dts-rollup.d.ts +0 -1283
  104. package/lib/chunk-2M576LCC.mjs +0 -2043
  105. package/lib/chunk-2M576LCC.mjs.map +0 -1
  106. package/lib/index.d.ts +0 -78
  107. package/lib/index.js +0 -2127
  108. package/lib/index.js.map +0 -1
  109. package/lib/testing/index.d.ts +0 -2
  110. package/lib/testing/index.js +0 -2060
  111. package/lib/testing/index.js.map +0 -1
  112. package/lib/testing/index.mjs.map +0 -1
  113. package/src/container.mts +0 -227
  114. package/src/factory-context.mts +0 -8
  115. package/src/instance-resolver.mts +0 -559
  116. package/src/request-context-manager.mts +0 -149
  117. package/src/service-invalidator.mts +0 -429
  118. package/src/service-locator-instance-holder.mts +0 -70
  119. package/src/service-locator-manager.mts +0 -85
  120. package/src/service-locator.mts +0 -246
  121. package/tsup.config.mts +0 -12
  122. /package/src/{injector.mts → injectors.mts} +0 -0
@@ -0,0 +1,82 @@
1
+ import type { z, ZodType } from 'zod/v4'
2
+
3
+ import type {
4
+ BoundInjectionToken,
5
+ ClassType,
6
+ ClassTypeWithArgument,
7
+ FactoryInjectionToken,
8
+ InjectionToken,
9
+ InjectionTokenSchemaType,
10
+ } from '../token/injection-token.mjs'
11
+ import type { Join, UnionToArray } from '../utils/types.mjs'
12
+ import type { Factorable } from './factory.interface.mjs'
13
+
14
+ /**
15
+ * Interface for dependency injection containers.
16
+ * Both Container and ScopedContainer implement this interface,
17
+ * allowing them to be used interchangeably in factory contexts.
18
+ */
19
+ export interface IContainer {
20
+ /**
21
+ * Gets an instance from the container.
22
+ */
23
+ // #1 Simple class
24
+ get<T extends ClassType>(
25
+ token: T,
26
+ ): InstanceType<T> extends Factorable<infer R>
27
+ ? Promise<R>
28
+ : Promise<InstanceType<T>>
29
+ // #1.1 Simple class with args
30
+ get<T extends ClassTypeWithArgument<R>, R>(
31
+ token: T,
32
+ args: R,
33
+ ): Promise<InstanceType<T>>
34
+ // #2 Token with required Schema
35
+ get<T, S extends InjectionTokenSchemaType>(
36
+ token: InjectionToken<T, S>,
37
+ args: z.input<S>,
38
+ ): Promise<T>
39
+ // #3 Token with optional Schema
40
+ get<T, S extends InjectionTokenSchemaType, R extends boolean>(
41
+ token: InjectionToken<T, S, R>,
42
+ ): R extends false
43
+ ? Promise<T>
44
+ : S extends ZodType<infer Type>
45
+ ? `Error: Your token requires args: ${Join<
46
+ UnionToArray<keyof Type>,
47
+ ', '
48
+ >}`
49
+ : 'Error: Your token requires args'
50
+ // #4 Token with no Schema
51
+ get<T>(token: InjectionToken<T, undefined>): Promise<T>
52
+ get<T>(token: BoundInjectionToken<T, any>): Promise<T>
53
+ get<T>(token: FactoryInjectionToken<T, any>): Promise<T>
54
+
55
+ /**
56
+ * Invalidates a service and its dependencies.
57
+ */
58
+ invalidate(service: unknown): Promise<void>
59
+
60
+ /**
61
+ * Checks if a service is registered in the container.
62
+ */
63
+ isRegistered(token: any): boolean
64
+
65
+ /**
66
+ * Disposes the container and cleans up all resources.
67
+ */
68
+ dispose(): Promise<void>
69
+
70
+ /**
71
+ * Waits for all pending operations to complete.
72
+ */
73
+ ready(): Promise<void>
74
+
75
+ /**
76
+ * @internal
77
+ * Attempts to get an instance synchronously if it already exists.
78
+ * Returns null if the instance doesn't exist or is not ready.
79
+ * Used internally by the inject system for synchronous property initialization.
80
+ */
81
+ tryGetSync<T>(token: any, args?: any): T | null
82
+ }
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod/v4'
2
2
 
3
- import type { FactoryContext } from '../factory-context.mjs'
4
- import type { InjectionTokenSchemaType } from '../injection-token.mjs'
3
+ import type { FactoryContext } from '../internal/context/factory-context.mjs'
4
+ import type { InjectionTokenSchemaType } from '../token/injection-token.mjs'
5
5
 
6
6
  export interface Factorable<T> {
7
7
  create(ctx?: FactoryContext): Promise<T> | T
@@ -1,3 +1,4 @@
1
+ export * from './container.interface.mjs'
1
2
  export * from './factory.interface.mjs'
2
3
  export * from './on-service-init.interface.mjs'
3
4
  export * from './on-service-destroy.interface.mjs'
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Cross-platform AsyncLocalStorage wrapper.
3
+ *
4
+ * Provides AsyncLocalStorage on Node.js/Bun and falls back to
5
+ * a synchronous-only polyfill in browser environments.
6
+ */
7
+
8
+ import { SyncLocalStorage } from './sync-local-storage.mjs'
9
+
10
+ /**
11
+ * Interface matching the subset of AsyncLocalStorage API we use.
12
+ */
13
+ export interface IAsyncLocalStorage<T> {
14
+ run<R>(store: T, fn: () => R): R
15
+ getStore(): T | undefined
16
+ }
17
+
18
+ /**
19
+ * Detects if we're running in a Node.js-like environment with async_hooks support.
20
+ */
21
+ function hasAsyncHooksSupport(): boolean {
22
+ // Check for Node.js
23
+ if (
24
+ typeof process !== 'undefined' &&
25
+ process.versions &&
26
+ process.versions.node
27
+ ) {
28
+ return true
29
+ }
30
+
31
+ // Check for Bun
32
+ if (typeof process !== 'undefined' && process.versions && 'bun' in process.versions) {
33
+ return true
34
+ }
35
+
36
+ // Check for Deno (also supports async_hooks via node compat)
37
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
38
+ if (typeof (globalThis as any).Deno !== 'undefined') {
39
+ return true
40
+ }
41
+
42
+ return false
43
+ }
44
+
45
+ // Cache for the AsyncLocalStorage class
46
+ let AsyncLocalStorageClass: (new <T>() => IAsyncLocalStorage<T>) | null = null
47
+ let initialized = false
48
+ let forceSyncMode = false
49
+
50
+ /**
51
+ * Gets the appropriate AsyncLocalStorage implementation for the current environment.
52
+ *
53
+ * - On Node.js/Bun/Deno: Returns the native AsyncLocalStorage
54
+ * - On browsers: Returns SyncLocalStorage polyfill
55
+ */
56
+ function getAsyncLocalStorageClass(): new <T>() => IAsyncLocalStorage<T> {
57
+ if (initialized) {
58
+ return AsyncLocalStorageClass!
59
+ }
60
+
61
+ initialized = true
62
+
63
+ if (!forceSyncMode && hasAsyncHooksSupport()) {
64
+ try {
65
+ // Dynamic require to avoid bundler issues
66
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
67
+ const asyncHooks = require('node:async_hooks')
68
+ AsyncLocalStorageClass = asyncHooks.AsyncLocalStorage
69
+ } catch {
70
+ // Fallback if require fails (shouldn't happen in Node/Bun)
71
+ AsyncLocalStorageClass = SyncLocalStorage as any
72
+ }
73
+ } else {
74
+ AsyncLocalStorageClass = SyncLocalStorage as any
75
+ }
76
+
77
+ return AsyncLocalStorageClass!
78
+ }
79
+
80
+ /**
81
+ * Creates a new AsyncLocalStorage instance appropriate for the current environment.
82
+ */
83
+ export function createAsyncLocalStorage<T>(): IAsyncLocalStorage<T> {
84
+ const StorageClass = getAsyncLocalStorageClass()
85
+ return new StorageClass<T>()
86
+ }
87
+
88
+ /**
89
+ * Returns true if we're using the real AsyncLocalStorage (Node/Bun/Deno).
90
+ * Returns false if we're using the sync-only polyfill (browser).
91
+ */
92
+ export function isUsingNativeAsyncLocalStorage(): boolean {
93
+ getAsyncLocalStorageClass() // Ensure initialized
94
+ return AsyncLocalStorageClass !== (SyncLocalStorage as any)
95
+ }
96
+
97
+ /**
98
+ * Testing utilities for forcing specific modes.
99
+ * Only exported for testing purposes.
100
+ */
101
+ export const __testing__ = {
102
+ /**
103
+ * Resets the initialization state and forces sync mode.
104
+ * Call this before creating new storage instances in tests.
105
+ */
106
+ forceSyncMode: () => {
107
+ initialized = false
108
+ forceSyncMode = true
109
+ AsyncLocalStorageClass = null
110
+ },
111
+
112
+ /**
113
+ * Resets to default behavior (auto-detect environment).
114
+ */
115
+ reset: () => {
116
+ initialized = false
117
+ forceSyncMode = false
118
+ AsyncLocalStorageClass = null
119
+ },
120
+ }
@@ -0,0 +1,18 @@
1
+ import type { IContainer } from '../../interfaces/container.interface.mjs'
2
+ import type { Injectors } from '../../utils/index.mjs'
3
+
4
+ /**
5
+ * Context provided to factory functions during service instantiation.
6
+ *
7
+ * Provides access to dependency injection (via inject), the container,
8
+ * and lifecycle hooks for cleanup.
9
+ */
10
+ export interface FactoryContext {
11
+ inject: Injectors['asyncInject']
12
+ /**
13
+ * The container instance for dependency resolution.
14
+ * This may be either a Container or ScopedContainer.
15
+ */
16
+ container: IContainer
17
+ addDestroyListener: (listener: () => void) => void
18
+ }
@@ -0,0 +1,3 @@
1
+ export * from './factory-context.mjs'
2
+ export * from './resolution-context.mjs'
3
+ export * from './request-context.mjs'
@@ -1,14 +1,16 @@
1
- import type { ServiceLocatorInstanceHolder } from './service-locator-instance-holder.mjs'
1
+ import type { InstanceHolder } from '../holder/instance-holder.mjs'
2
2
 
3
- import { BaseInstanceHolderManager } from './base-instance-holder-manager.mjs'
4
- import { InjectableScope, InjectableType } from './enums/index.mjs'
5
- import { InjectionToken } from './injection-token.mjs'
3
+ import { BaseHolderManager } from '../holder/base-holder-manager.mjs'
4
+ import { InjectableScope, InjectableType } from '../../enums/index.mjs'
5
+ import { InjectionToken } from '../../token/injection-token.mjs'
6
6
 
7
7
  /**
8
- * Request context holder that manages pre-prepared instances for a specific request.
9
- * This allows for efficient instantiation of request-scoped services.
8
+ * Interface for request context that manages pre-prepared instances for a specific request.
9
+ *
10
+ * Provides isolated storage for request-scoped services, enabling efficient
11
+ * instantiation and cleanup within the lifecycle of a single request.
10
12
  */
11
- export interface RequestContextHolder {
13
+ export interface RequestContext {
12
14
  /**
13
15
  * Unique identifier for this request context.
14
16
  */
@@ -17,7 +19,7 @@ export interface RequestContextHolder {
17
19
  /**
18
20
  * Instance holders for request-scoped services.
19
21
  */
20
- readonly holders: Map<string, ServiceLocatorInstanceHolder>
22
+ readonly holders: Map<string, InstanceHolder>
21
23
 
22
24
  /**
23
25
  * Priority for resolution in FactoryContext.inject method.
@@ -41,7 +43,7 @@ export interface RequestContextHolder {
41
43
  addInstance(
42
44
  instanceName: string,
43
45
  instance: any,
44
- holder: ServiceLocatorInstanceHolder,
46
+ holder: InstanceHolder,
45
47
  ): void
46
48
 
47
49
  /**
@@ -52,7 +54,12 @@ export interface RequestContextHolder {
52
54
  /**
53
55
  * Gets an instance holder from this context.
54
56
  */
55
- get(instanceName: string): ServiceLocatorInstanceHolder | undefined
57
+ get(instanceName: string): InstanceHolder | undefined
58
+
59
+ /**
60
+ * Sets an instance holder by name.
61
+ */
62
+ set(instanceName: string, holder: InstanceHolder): void
56
63
 
57
64
  /**
58
65
  * Checks if this context has a pre-prepared instance.
@@ -74,16 +81,16 @@ export interface RequestContextHolder {
74
81
  */
75
82
  setMetadata(key: string, value: any): void
76
83
 
77
- // Methods inherited from BaseInstanceHolderManager
84
+ // Methods inherited from BaseHolderManager
78
85
  /**
79
86
  * Filters holders based on a predicate function.
80
87
  */
81
88
  filter(
82
89
  predicate: (
83
- value: ServiceLocatorInstanceHolder<any>,
90
+ value: InstanceHolder<any>,
84
91
  key: string,
85
92
  ) => boolean,
86
- ): Map<string, ServiceLocatorInstanceHolder>
93
+ ): Map<string, InstanceHolder>
87
94
 
88
95
  /**
89
96
  * Deletes a holder by name.
@@ -101,12 +108,18 @@ export interface RequestContextHolder {
101
108
  isEmpty(): boolean
102
109
  }
103
110
 
111
+ /** @deprecated Use RequestContext instead */
112
+ export type RequestContextHolder = RequestContext
113
+
104
114
  /**
105
- * Default implementation of RequestContextHolder.
115
+ * Default implementation of RequestContext.
116
+ *
117
+ * Extends BaseHolderManager to provide holder management functionality
118
+ * with request-specific metadata and lifecycle support.
106
119
  */
107
- export class DefaultRequestContextHolder
108
- extends BaseInstanceHolderManager
109
- implements RequestContextHolder
120
+ export class DefaultRequestContext
121
+ extends BaseHolderManager
122
+ implements RequestContext
110
123
  {
111
124
  public readonly metadata = new Map<string, any>()
112
125
  public readonly createdAt = Date.now()
@@ -116,7 +129,7 @@ export class DefaultRequestContextHolder
116
129
  public readonly priority: number = 100,
117
130
  initialMetadata?: Record<string, any>,
118
131
  ) {
119
- super(null) // RequestContextHolder doesn't need logging
132
+ super(null) // RequestContext doesn't need logging
120
133
  if (initialMetadata) {
121
134
  Object.entries(initialMetadata).forEach(([key, value]) => {
122
135
  this.metadata.set(key, value)
@@ -127,21 +140,21 @@ export class DefaultRequestContextHolder
127
140
  /**
128
141
  * Public getter for holders to maintain interface compatibility.
129
142
  */
130
- get holders(): Map<string, ServiceLocatorInstanceHolder> {
143
+ get holders(): Map<string, InstanceHolder> {
131
144
  return this._holders
132
145
  }
133
146
 
134
147
  /**
135
- * Gets a holder by name. For RequestContextHolder, this is a simple lookup.
148
+ * Gets a holder by name. For RequestContext, this is a simple lookup.
136
149
  */
137
- get(name: string): ServiceLocatorInstanceHolder | undefined {
150
+ get(name: string): InstanceHolder | undefined {
138
151
  return this._holders.get(name)
139
152
  }
140
153
 
141
154
  /**
142
155
  * Sets a holder by name.
143
156
  */
144
- set(name: string, holder: ServiceLocatorInstanceHolder): void {
157
+ set(name: string, holder: InstanceHolder): void {
145
158
  this._holders.set(name, holder)
146
159
  }
147
160
 
@@ -155,7 +168,7 @@ export class DefaultRequestContextHolder
155
168
  addInstance(
156
169
  instanceName: string | InjectionToken<any, undefined>,
157
170
  instance: any,
158
- holder?: ServiceLocatorInstanceHolder,
171
+ holder?: InstanceHolder,
159
172
  ): void {
160
173
  if (instanceName instanceof InjectionToken) {
161
174
  const name = instanceName.toString()
@@ -190,12 +203,12 @@ export class DefaultRequestContextHolder
190
203
  }
191
204
 
192
205
  /**
193
- * Creates a new request context holder with the given parameters.
206
+ * Creates a new request context with the given parameters.
194
207
  */
195
- export function createRequestContextHolder(
208
+ export function createRequestContext(
196
209
  requestId: string,
197
210
  priority: number = 100,
198
211
  initialMetadata?: Record<string, any>,
199
- ): RequestContextHolder {
200
- return new DefaultRequestContextHolder(requestId, priority, initialMetadata)
212
+ ): RequestContext {
213
+ return new DefaultRequestContext(requestId, priority, initialMetadata)
201
214
  }
@@ -0,0 +1,63 @@
1
+ import type { InstanceHolder } from '../holder/instance-holder.mjs'
2
+
3
+ import { createAsyncLocalStorage } from './async-local-storage.mjs'
4
+
5
+ /**
6
+ * Data stored in the resolution context during service instantiation.
7
+ */
8
+ export interface ResolutionContextData {
9
+ /** The holder that is currently being instantiated */
10
+ waiterHolder: InstanceHolder
11
+ /** Function to get a holder by name (for cycle detection) */
12
+ getHolder: (name: string) => InstanceHolder | undefined
13
+ }
14
+
15
+ /**
16
+ * AsyncLocalStorage for tracking the current resolution context.
17
+ *
18
+ * This allows tracking which service is being instantiated even across
19
+ * async boundaries (like when inject() is called inside a constructor).
20
+ * Essential for circular dependency detection.
21
+ */
22
+ export const resolutionContext = createAsyncLocalStorage<ResolutionContextData>()
23
+
24
+ /**
25
+ * Runs a function within a resolution context.
26
+ *
27
+ * The context tracks which holder is currently being instantiated,
28
+ * allowing circular dependency detection to work correctly.
29
+ *
30
+ * @param waiterHolder The holder being instantiated
31
+ * @param getHolder Function to retrieve holders by name
32
+ * @param fn The function to run within the context
33
+ */
34
+ export function withResolutionContext<T>(
35
+ waiterHolder: InstanceHolder,
36
+ getHolder: (name: string) => InstanceHolder | undefined,
37
+ fn: () => T,
38
+ ): T {
39
+ return resolutionContext.run({ waiterHolder, getHolder }, fn)
40
+ }
41
+
42
+ /**
43
+ * Gets the current resolution context, if any.
44
+ *
45
+ * Returns undefined if we're not inside a resolution context
46
+ * (e.g., when resolving a top-level service that has no parent).
47
+ */
48
+ export function getCurrentResolutionContext(): ResolutionContextData | undefined {
49
+ return resolutionContext.getStore()
50
+ }
51
+
52
+ /**
53
+ * Runs a function outside any resolution context.
54
+ *
55
+ * This is useful for async injections that should not participate
56
+ * in circular dependency detection since they don't block.
57
+ *
58
+ * @param fn The function to run without resolution context
59
+ */
60
+ export function withoutResolutionContext<T>(fn: () => T): T {
61
+ // Run with undefined context to clear any current context
62
+ return resolutionContext.run(undefined as any, fn)
63
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * A synchronous-only polyfill for AsyncLocalStorage.
3
+ *
4
+ * This provides the same API as Node's AsyncLocalStorage but only works
5
+ * for synchronous code paths. It uses a simple stack-based approach.
6
+ *
7
+ * Limitations:
8
+ * - Context does NOT propagate across async boundaries (setTimeout, promises, etc.)
9
+ * - Only suitable for environments where DI resolution is synchronous
10
+ *
11
+ * This is acceptable for browser environments where:
12
+ * 1. Constructors are typically synchronous
13
+ * 2. Circular dependency detection mainly needs sync tracking
14
+ */
15
+ export class SyncLocalStorage<T> {
16
+ private stack: T[] = []
17
+
18
+ /**
19
+ * Runs a function within the given store context.
20
+ * The context is only available synchronously within the function.
21
+ */
22
+ run<R>(store: T, fn: () => R): R {
23
+ this.stack.push(store)
24
+ try {
25
+ return fn()
26
+ } finally {
27
+ this.stack.pop()
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Gets the current store value, or undefined if not in a context.
33
+ */
34
+ getStore(): T | undefined {
35
+ return this.stack.length > 0 ? this.stack[this.stack.length - 1] : undefined
36
+ }
37
+
38
+ /**
39
+ * Exits the current context and runs the function without any store.
40
+ * This matches AsyncLocalStorage.exit() behavior.
41
+ */
42
+ exit<R>(fn: () => R): R {
43
+ const savedStack = this.stack
44
+ this.stack = []
45
+ try {
46
+ return fn()
47
+ } finally {
48
+ this.stack = savedStack
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,5 @@
1
+ export * from './service-locator.mjs'
2
+ export * from './instance-resolver.mjs'
3
+ export * from './instantiator.mjs'
4
+ export * from './invalidator.mjs'
5
+ export * from './token-processor.mjs'