@navios/di 0.7.0 → 0.8.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.
Files changed (35) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/lib/browser/index.d.mts +62 -2
  3. package/lib/browser/index.d.mts.map +1 -1
  4. package/lib/browser/index.mjs +132 -15
  5. package/lib/browser/index.mjs.map +1 -1
  6. package/lib/{container-CXDYDJSM.d.mts → container-Bp1W-pWJ.d.mts} +63 -3
  7. package/lib/container-Bp1W-pWJ.d.mts.map +1 -0
  8. package/lib/{container-Bv6PZZLJ.mjs → container-DAKOvAgr.mjs} +137 -18
  9. package/lib/container-DAKOvAgr.mjs.map +1 -0
  10. package/lib/{container-BCv3XS6m.cjs → container-DENMeJ87.cjs} +137 -18
  11. package/lib/container-DENMeJ87.cjs.map +1 -0
  12. package/lib/{container-b6mDUdGq.d.cts → container-YPwvmlK2.d.cts} +63 -3
  13. package/lib/container-YPwvmlK2.d.cts.map +1 -0
  14. package/lib/index.cjs +1 -1
  15. package/lib/index.d.cts +1 -1
  16. package/lib/index.d.cts.map +1 -1
  17. package/lib/index.d.mts +1 -1
  18. package/lib/index.d.mts.map +1 -1
  19. package/lib/index.mjs +1 -1
  20. package/lib/testing/index.cjs +1 -1
  21. package/lib/testing/index.d.cts +1 -1
  22. package/lib/testing/index.d.mts +1 -1
  23. package/lib/testing/index.mjs +1 -1
  24. package/package.json +1 -1
  25. package/src/internal/context/request-context.mts +11 -0
  26. package/src/internal/core/instance-resolver.mts +11 -0
  27. package/src/internal/core/token-processor.mts +60 -1
  28. package/src/internal/holder/base-holder-manager.mts +106 -10
  29. package/src/internal/holder/request-storage.mts +7 -14
  30. package/src/internal/holder/singleton-storage.mts +3 -16
  31. package/src/token/registry.mts +21 -0
  32. package/lib/container-BCv3XS6m.cjs.map +0 -1
  33. package/lib/container-Bv6PZZLJ.mjs.map +0 -1
  34. package/lib/container-CXDYDJSM.d.mts.map +0 -1
  35. package/lib/container-b6mDUdGq.d.cts.map +0 -1
@@ -19,9 +19,15 @@ export type HolderReadyResult<T> = [undefined, InstanceHolder<T>] | [DIError]
19
19
  */
20
20
  export abstract class BaseHolderManager {
21
21
  protected readonly _holders: Map<string, InstanceHolder>
22
+ /**
23
+ * Reverse dependency index: maps a dependency name to the set of holder names that depend on it.
24
+ * This allows O(1) lookup of dependents instead of O(n) iteration.
25
+ */
26
+ protected readonly _dependents: Map<string, Set<string>>
22
27
 
23
28
  constructor(protected readonly logger: Console | null = null) {
24
29
  this._holders = new Map()
30
+ this._dependents = new Map()
25
31
  }
26
32
 
27
33
  /**
@@ -48,32 +54,118 @@ export abstract class BaseHolderManager {
48
54
  abstract has(name: string): any
49
55
 
50
56
  /**
51
- * Deletes a holder by name.
57
+ * Deletes a holder by name and cleans up the reverse dependency index.
52
58
  * @param name The name of the holder to delete
53
59
  * @returns true if the holder was deleted, false if it didn't exist
54
60
  */
55
61
  delete(name: string): boolean {
62
+ const holder = this._holders.get(name)
63
+ if (holder) {
64
+ // Remove this holder from the reverse index for all its dependencies
65
+ this.removeFromDependentsIndex(name, holder.deps)
66
+ }
56
67
  return this._holders.delete(name)
57
68
  }
58
69
 
70
+ /**
71
+ * Registers a holder's dependencies in the reverse index.
72
+ * Call this after creating a holder with dependencies.
73
+ * @param holderName The name of the holder that has dependencies
74
+ * @param deps The set of dependency names
75
+ */
76
+ registerDependencies(holderName: string, deps: Set<string>): void {
77
+ for (const dep of deps) {
78
+ let dependents = this._dependents.get(dep)
79
+ if (!dependents) {
80
+ dependents = new Set()
81
+ this._dependents.set(dep, dependents)
82
+ }
83
+ dependents.add(holderName)
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Removes a holder from the reverse dependency index.
89
+ * @param holderName The name of the holder to remove
90
+ * @param deps The set of dependency names to clean up
91
+ */
92
+ protected removeFromDependentsIndex(holderName: string, deps: Set<string>): void {
93
+ for (const dep of deps) {
94
+ const dependents = this._dependents.get(dep)
95
+ if (dependents) {
96
+ dependents.delete(holderName)
97
+ if (dependents.size === 0) {
98
+ this._dependents.delete(dep)
99
+ }
100
+ }
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Gets all holder names that depend on the given instance name.
106
+ * O(1) lookup using the reverse dependency index.
107
+ * @param instanceName The instance name to find dependents for
108
+ * @returns Array of holder names that depend on this instance
109
+ */
110
+ getDependents(instanceName: string): string[] {
111
+ const dependents = this._dependents.get(instanceName)
112
+ return dependents ? Array.from(dependents) : []
113
+ }
114
+
59
115
  /**
60
116
  * Filters holders based on a predicate function.
61
117
  * @param predicate Function to test each holder
62
118
  * @returns A new Map containing only the holders that match the predicate
119
+ * @deprecated Use forEachHolder() for iteration to avoid allocations
63
120
  */
64
121
  filter(
65
122
  predicate: (value: InstanceHolder<any>, key: string) => boolean,
66
123
  ): Map<string, InstanceHolder> {
67
- return new Map(
68
- [...this._holders].filter(([key, value]) => predicate(value, key)),
69
- )
124
+ const result = new Map<string, InstanceHolder>()
125
+ for (const [key, value] of this._holders) {
126
+ if (predicate(value, key)) {
127
+ result.set(key, value)
128
+ }
129
+ }
130
+ return result
70
131
  }
71
132
 
72
133
  /**
73
- * Clears all holders from this manager.
134
+ * Iterates over holders with a callback. More efficient than filter() as it
135
+ * avoids creating intermediate arrays and Maps.
136
+ * @param callback Function called for each holder with (holder, name)
137
+ */
138
+ forEachHolder(
139
+ callback: (holder: InstanceHolder<any>, name: string) => void,
140
+ ): void {
141
+ for (const [name, holder] of this._holders) {
142
+ callback(holder, name)
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Finds the first holder matching a predicate. More efficient than filter()
148
+ * when only one result is needed.
149
+ * @param predicate Function to test each holder
150
+ * @returns The first matching holder or undefined
151
+ */
152
+ findHolder(
153
+ predicate: (holder: InstanceHolder<any>, name: string) => boolean,
154
+ ): InstanceHolder | undefined {
155
+ for (const [name, holder] of this._holders) {
156
+ if (predicate(holder, name)) {
157
+ return holder
158
+ }
159
+ }
160
+ return undefined
161
+ }
162
+
163
+ /**
164
+ * Clears all holders from this manager and the reverse dependency index.
74
165
  */
75
166
  clear(): void {
76
167
  this._holders.clear()
168
+ this._dependents.clear()
77
169
  }
78
170
 
79
171
  /**
@@ -202,16 +294,20 @@ export abstract class BaseHolderManager {
202
294
  return [DIError.circularDependency(cycle)]
203
295
  }
204
296
 
205
- // Track the waiting relationship
206
- waiterHolder.waitingFor.add(holder.name)
297
+ if (process.env.NODE_ENV !== 'production') {
298
+ // Track the waiting relationship
299
+ waiterHolder.waitingFor.add(holder.name)
300
+ }
207
301
  }
208
302
 
209
303
  try {
210
304
  await holder.creationPromise
211
305
  } finally {
212
- // Clean up the waiting relationship
213
- if (waiterHolder) {
214
- waiterHolder.waitingFor.delete(holder.name)
306
+ if (process.env.NODE_ENV !== 'production') {
307
+ // Clean up the waiting relationship
308
+ if (waiterHolder) {
309
+ waiterHolder.waitingFor.delete(holder.name)
310
+ }
215
311
  }
216
312
  }
217
313
 
@@ -113,22 +113,15 @@ export class RequestStorage implements IHolderStorage {
113
113
  }
114
114
 
115
115
  findDependents(instanceName: string): string[] {
116
- const dependents: string[] = []
117
-
118
- // Check request-scoped holders
119
- for (const [name, holder] of this.contextHolder.holders) {
120
- if (holder.deps.has(instanceName)) {
121
- dependents.push(name)
122
- }
123
- }
116
+ // Get dependents from request context (O(1) using reverse index)
117
+ const requestDependents = this.contextHolder.getDependents(instanceName)
124
118
 
125
119
  // Also check singleton holders - a singleton may depend on this request-scoped service
126
- for (const [name, holder] of this.holderManager.filter(() => true)) {
127
- if (holder.deps.has(instanceName)) {
128
- dependents.push(name)
129
- }
130
- }
120
+ const singletonDependents = this.holderManager.getDependents(instanceName)
131
121
 
132
- return dependents
122
+ // Combine both arrays (avoid allocations if one is empty)
123
+ if (requestDependents.length === 0) return singletonDependents
124
+ if (singletonDependents.length === 0) return requestDependents
125
+ return [...requestDependents, ...singletonDependents]
133
126
  }
134
127
  }
@@ -79,27 +79,14 @@ export class SingletonStorage implements IHolderStorage {
79
79
  forEach(
80
80
  callback: (name: string, holder: InstanceHolder) => void,
81
81
  ): void {
82
- for (const [name, holder] of this.manager.filter(() => true)) {
83
- callback(name, holder)
84
- }
82
+ this.manager.forEachHolder((holder, name) => callback(name, holder))
85
83
  }
86
84
 
87
85
  findByInstance(instance: unknown): InstanceHolder | null {
88
- for (const [, holder] of this.manager.filter(
89
- (h) => h.instance === instance,
90
- )) {
91
- return holder
92
- }
93
- return null
86
+ return this.manager.findHolder((h) => h.instance === instance) ?? null
94
87
  }
95
88
 
96
89
  findDependents(instanceName: string): string[] {
97
- const dependents: string[] = []
98
- for (const [name, holder] of this.manager.filter(() => true)) {
99
- if (holder.deps.has(instanceName)) {
100
- dependents.push(name)
101
- }
102
- }
103
- return dependents
90
+ return this.manager.getDependents(instanceName)
104
91
  }
105
92
  }
@@ -49,6 +49,27 @@ export class Registry {
49
49
  delete(token: InjectionToken<any, any>) {
50
50
  this.factories.delete(token.id)
51
51
  }
52
+
53
+ /**
54
+ * Updates the scope of an already registered factory.
55
+ * This is useful when you need to dynamically change a service's scope
56
+ * (e.g., when a singleton controller has request-scoped dependencies).
57
+ *
58
+ * @param token The injection token to update
59
+ * @param scope The new scope to set
60
+ * @returns true if the scope was updated, false if the token was not found
61
+ */
62
+ updateScope(token: InjectionToken<any, any>, scope: InjectableScope): boolean {
63
+ const factory = this.factories.get(token.id)
64
+ if (factory) {
65
+ factory.scope = scope
66
+ return true
67
+ }
68
+ if (this.parent) {
69
+ return this.parent.updateScope(token, scope)
70
+ }
71
+ return false
72
+ }
52
73
  }
53
74
 
54
75
  export const globalRegistry = new Registry()