@navios/di 0.2.1 → 0.3.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 (62) hide show
  1. package/README.md +299 -38
  2. package/docs/README.md +121 -48
  3. package/docs/api-reference.md +763 -0
  4. package/docs/container.md +274 -0
  5. package/docs/examples/basic-usage.mts +97 -0
  6. package/docs/examples/factory-pattern.mts +318 -0
  7. package/docs/examples/injection-tokens.mts +225 -0
  8. package/docs/examples/request-scope-example.mts +254 -0
  9. package/docs/examples/service-lifecycle.mts +359 -0
  10. package/docs/factory.md +584 -0
  11. package/docs/getting-started.md +308 -0
  12. package/docs/injectable.md +496 -0
  13. package/docs/injection-tokens.md +400 -0
  14. package/docs/lifecycle.md +539 -0
  15. package/docs/scopes.md +749 -0
  16. package/lib/_tsup-dts-rollup.d.mts +494 -145
  17. package/lib/_tsup-dts-rollup.d.ts +494 -145
  18. package/lib/index.d.mts +26 -12
  19. package/lib/index.d.ts +26 -12
  20. package/lib/index.js +1021 -470
  21. package/lib/index.js.map +1 -1
  22. package/lib/index.mjs +1011 -461
  23. package/lib/index.mjs.map +1 -1
  24. package/package.json +2 -2
  25. package/project.json +10 -2
  26. package/src/__tests__/container.spec.mts +1301 -0
  27. package/src/__tests__/factory.spec.mts +137 -0
  28. package/src/__tests__/injectable.spec.mts +32 -88
  29. package/src/__tests__/injection-token.spec.mts +333 -17
  30. package/src/__tests__/request-scope.spec.mts +427 -0
  31. package/src/__type-tests__/factory.spec-d.mts +65 -0
  32. package/src/__type-tests__/inject.spec-d.mts +27 -28
  33. package/src/__type-tests__/injectable.spec-d.mts +42 -206
  34. package/src/container.mts +167 -0
  35. package/src/decorators/factory.decorator.mts +79 -0
  36. package/src/decorators/index.mts +1 -0
  37. package/src/decorators/injectable.decorator.mts +6 -56
  38. package/src/enums/injectable-scope.enum.mts +5 -1
  39. package/src/event-emitter.mts +18 -20
  40. package/src/factory-context.mts +2 -10
  41. package/src/index.mts +3 -2
  42. package/src/injection-token.mts +19 -4
  43. package/src/injector.mts +8 -20
  44. package/src/interfaces/factory.interface.mts +3 -3
  45. package/src/interfaces/index.mts +2 -0
  46. package/src/interfaces/on-service-destroy.interface.mts +3 -0
  47. package/src/interfaces/on-service-init.interface.mts +3 -0
  48. package/src/registry.mts +7 -16
  49. package/src/request-context-holder.mts +174 -0
  50. package/src/service-instantiator.mts +158 -0
  51. package/src/service-locator-event-bus.mts +0 -28
  52. package/src/service-locator-instance-holder.mts +27 -16
  53. package/src/service-locator-manager.mts +84 -0
  54. package/src/service-locator.mts +548 -393
  55. package/src/utils/defer.mts +73 -0
  56. package/src/utils/get-injectors.mts +91 -78
  57. package/src/utils/index.mts +2 -0
  58. package/src/utils/types.mts +52 -0
  59. package/docs/concepts/injectable.md +0 -182
  60. package/docs/concepts/injection-token.md +0 -145
  61. package/src/proxy-service-locator.mts +0 -83
  62. package/src/resolve-service.mts +0 -41
@@ -0,0 +1,158 @@
1
+ import type { FactoryContext } from './factory-context.mjs'
2
+ import type { FactoryRecord } from './registry.mjs'
3
+ import type { Injectors } from './utils/get-injectors.mjs'
4
+
5
+ import { InjectableType } from './enums/index.mjs'
6
+
7
+ /**
8
+ * ServiceInstantiator handles the instantiation of services based on registry records.
9
+ * It replaces the hard-coded logic in Injectable and Factory decorators.
10
+ */
11
+ export class ServiceInstantiator {
12
+ constructor(private readonly injectors: Injectors) {}
13
+
14
+ /**
15
+ * Instantiates a service based on its registry record.
16
+ * @param ctx The factory context for dependency injection
17
+ * @param record The factory record from the registry
18
+ * @param args Optional arguments for the service
19
+ * @returns Promise resolving to [undefined, instance] or [error]
20
+ */
21
+ async instantiateService<T>(
22
+ ctx: FactoryContext,
23
+ record: FactoryRecord<T, any>,
24
+ args: any = undefined,
25
+ ): Promise<[undefined, T] | [Error]> {
26
+ try {
27
+ switch (record.type) {
28
+ case InjectableType.Class:
29
+ return this.instantiateClass(ctx, record, args)
30
+ case InjectableType.Factory:
31
+ return this.instantiateFactory(ctx, record, args)
32
+ default:
33
+ throw new Error(
34
+ `[ServiceInstantiator] Unknown service type: ${record.type}`,
35
+ )
36
+ }
37
+ } catch (error) {
38
+ return [error instanceof Error ? error : new Error(String(error))]
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Instantiates a class-based service (Injectable decorator).
44
+ * @param ctx The factory context for dependency injection
45
+ * @param record The factory record from the registry
46
+ * @param args Optional arguments for the service constructor
47
+ * @returns Promise resolving to [undefined, instance] or [error]
48
+ */
49
+ private async instantiateClass<T>(
50
+ ctx: FactoryContext,
51
+ record: FactoryRecord<T, any>,
52
+ args: any,
53
+ ): Promise<[undefined, T] | [Error]> {
54
+ try {
55
+ const tryLoad = this.injectors.wrapSyncInit(() => {
56
+ const original = this.injectors.provideFactoryContext(ctx)
57
+ let result = new record.target(...(args ? [args] : []))
58
+ this.injectors.provideFactoryContext(original)
59
+ return result
60
+ })
61
+
62
+ let [instance, promises, injectState] = tryLoad()
63
+ if (promises.length > 0) {
64
+ const results = await Promise.allSettled(promises)
65
+ if (results.some((result) => result.status === 'rejected')) {
66
+ throw new Error(
67
+ `[ServiceInstantiator] Service ${record.target.name} cannot be instantiated.`,
68
+ )
69
+ }
70
+ const newRes = tryLoad(injectState)
71
+ instance = newRes[0]
72
+ promises = newRes[1]
73
+ }
74
+
75
+ if (promises.length > 0) {
76
+ console.error(`[ServiceInstantiator] ${record.target.name} has problem with it's definition.
77
+
78
+ One or more of the dependencies are registered as a InjectableScope.Instance and are used with inject.
79
+
80
+ Please use inject asyncInject of inject to load those dependencies.`)
81
+ throw new Error(
82
+ `[ServiceInstantiator] Service ${record.target.name} cannot be instantiated.`,
83
+ )
84
+ }
85
+
86
+ // Handle lifecycle hooks
87
+ if ('onServiceInit' in instance) {
88
+ await (instance as any).onServiceInit()
89
+ }
90
+ if ('onServiceDestroy' in instance) {
91
+ ctx.addDestroyListener(async () => {
92
+ await (instance as any).onServiceDestroy()
93
+ })
94
+ }
95
+
96
+ return [undefined, instance]
97
+ } catch (error) {
98
+ return [error instanceof Error ? error : new Error(String(error))]
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Instantiates a factory-based service (Factory decorator).
104
+ * @param ctx The factory context for dependency injection
105
+ * @param record The factory record from the registry
106
+ * @param args Optional arguments for the factory
107
+ * @returns Promise resolving to [undefined, instance] or [error]
108
+ */
109
+ private async instantiateFactory<T>(
110
+ ctx: FactoryContext,
111
+ record: FactoryRecord<T, any>,
112
+ args: any,
113
+ ): Promise<[undefined, T] | [Error]> {
114
+ try {
115
+ const tryLoad = this.injectors.wrapSyncInit(() => {
116
+ const original = this.injectors.provideFactoryContext(ctx)
117
+ let result = new record.target()
118
+ this.injectors.provideFactoryContext(original)
119
+ return result
120
+ })
121
+
122
+ let [builder, promises, injectState] = tryLoad()
123
+ if (promises.length > 0) {
124
+ const results = await Promise.allSettled(promises)
125
+ if (results.some((result) => result.status === 'rejected')) {
126
+ throw new Error(
127
+ `[ServiceInstantiator] Service ${record.target.name} cannot be instantiated.`,
128
+ )
129
+ }
130
+ const newRes = tryLoad(injectState)
131
+ builder = newRes[0]
132
+ promises = newRes[1]
133
+ }
134
+
135
+ if (promises.length > 0) {
136
+ console.error(`[ServiceInstantiator] ${record.target.name} has problem with it's definition.
137
+
138
+ One or more of the dependencies are registered as a InjectableScope.Instance and are used with inject.
139
+
140
+ Please use asyncInject instead of inject to load those dependencies.`)
141
+ throw new Error(
142
+ `[ServiceInstantiator] Service ${record.target.name} cannot be instantiated.`,
143
+ )
144
+ }
145
+
146
+ if (typeof builder.create !== 'function') {
147
+ throw new Error(
148
+ `[ServiceInstantiator] Factory ${record.target.name} does not implement the create method.`,
149
+ )
150
+ }
151
+
152
+ const instance = await builder.create(ctx, args)
153
+ return [undefined, instance]
154
+ } catch (error) {
155
+ return [error instanceof Error ? error : new Error(String(error))]
156
+ }
157
+ }
158
+ }
@@ -43,21 +43,6 @@ export class ServiceLocatorEventBus {
43
43
 
44
44
  const events = this.listeners.get(key)!
45
45
 
46
- const preEvent = `pre:${event}`
47
- const postEvent = `post:${event}`
48
- this.logger?.debug(`[ServiceLocatorEventBus]#emit(): ${key}:${preEvent}`)
49
- await Promise.allSettled(
50
- [...(events.get(preEvent) ?? [])].map((listener) => listener(preEvent)),
51
- ).then((results) => {
52
- results
53
- .filter((result) => result.status === 'rejected')
54
- .forEach((result: PromiseRejectedResult) => {
55
- this.logger?.warn(
56
- `[ServiceLocatorEventBus]#emit(): ${key}:${preEvent} rejected with`,
57
- result.reason,
58
- )
59
- })
60
- })
61
46
  this.logger?.debug(`[ServiceLocatorEventBus]#emit(): ${key}:${event}`)
62
47
 
63
48
  const res = await Promise.allSettled(
@@ -78,19 +63,6 @@ export class ServiceLocatorEventBus {
78
63
  }
79
64
  return results
80
65
  })
81
- this.logger?.debug(`[ServiceLocatorEventBus]#emit(): ${key}:${postEvent}`)
82
- await Promise.allSettled(
83
- [...(events.get(postEvent) ?? [])].map((listener) => listener(postEvent)),
84
- ).then((results) => {
85
- results
86
- .filter((result) => result.status === 'rejected')
87
- .forEach((result: PromiseRejectedResult) => {
88
- this.logger?.warn(
89
- `[ServiceLocatorEventBus]#emit(): ${key}:${postEvent} rejected with`,
90
- result.reason,
91
- )
92
- })
93
- })
94
66
  return res
95
67
  }
96
68
  }
@@ -1,15 +1,11 @@
1
- /* eslint-disable @typescript-eslint/no-empty-object-type */
2
-
3
- export enum ServiceLocatorInstanceHolderKind {
4
- Instance = 'instance',
5
- Factory = 'factory',
6
- AbstractFactory = 'abstractFactory',
7
- }
1
+ import type { InjectableScope, InjectableType } from './enums/index.mjs'
8
2
 
3
+ /* eslint-disable @typescript-eslint/no-empty-object-type */
9
4
  export enum ServiceLocatorInstanceHolderStatus {
10
5
  Created = 'created',
11
6
  Creating = 'creating',
12
7
  Destroying = 'destroying',
8
+ Error = 'error',
13
9
  }
14
10
 
15
11
  export type ServiceLocatorInstanceEffect = () => void
@@ -21,9 +17,9 @@ export interface ServiceLocatorInstanceHolderCreating<Instance> {
21
17
  instance: null
22
18
  creationPromise: Promise<[undefined, Instance]> | null
23
19
  destroyPromise: null
24
- kind: ServiceLocatorInstanceHolderKind
25
- effects: ServiceLocatorInstanceEffect[]
26
- deps: string[]
20
+ type: InjectableType
21
+ scope: InjectableScope
22
+ deps: Set<string>
27
23
  destroyListeners: ServiceLocatorInstanceDestroyListener[]
28
24
  createdAt: number
29
25
  ttl: number
@@ -35,9 +31,9 @@ export interface ServiceLocatorInstanceHolderCreated<Instance> {
35
31
  instance: Instance
36
32
  creationPromise: null
37
33
  destroyPromise: null
38
- kind: ServiceLocatorInstanceHolderKind
39
- effects: ServiceLocatorInstanceEffect[]
40
- deps: string[]
34
+ type: InjectableType
35
+ scope: InjectableScope
36
+ deps: Set<string>
41
37
  destroyListeners: ServiceLocatorInstanceDestroyListener[]
42
38
  createdAt: number
43
39
  ttl: number
@@ -49,9 +45,23 @@ export interface ServiceLocatorInstanceHolderDestroying<Instance> {
49
45
  instance: Instance | null
50
46
  creationPromise: null
51
47
  destroyPromise: Promise<void>
52
- kind: ServiceLocatorInstanceHolderKind
53
- effects: ServiceLocatorInstanceEffect[]
54
- deps: string[]
48
+ type: InjectableType
49
+ scope: InjectableScope
50
+ deps: Set<string>
51
+ destroyListeners: ServiceLocatorInstanceDestroyListener[]
52
+ createdAt: number
53
+ ttl: number
54
+ }
55
+
56
+ export interface ServiceLocatorInstanceHolderError {
57
+ status: ServiceLocatorInstanceHolderStatus.Error
58
+ name: string
59
+ instance: Error
60
+ creationPromise: null
61
+ destroyPromise: null
62
+ type: InjectableType
63
+ scope: InjectableScope
64
+ deps: Set<string>
55
65
  destroyListeners: ServiceLocatorInstanceDestroyListener[]
56
66
  createdAt: number
57
67
  ttl: number
@@ -61,3 +71,4 @@ export type ServiceLocatorInstanceHolder<Instance = unknown> =
61
71
  | ServiceLocatorInstanceHolderCreating<Instance>
62
72
  | ServiceLocatorInstanceHolderCreated<Instance>
63
73
  | ServiceLocatorInstanceHolderDestroying<Instance>
74
+ | ServiceLocatorInstanceHolderError
@@ -1,6 +1,7 @@
1
1
  /* eslint-disable @typescript-eslint/no-empty-object-type */
2
2
  import type { ServiceLocatorInstanceHolder } from './service-locator-instance-holder.mjs'
3
3
 
4
+ import { InjectableScope, InjectableType } from './enums/index.mjs'
4
5
  import {
5
6
  ErrorsEnum,
6
7
  InstanceDestroying,
@@ -8,6 +9,7 @@ import {
8
9
  InstanceNotFound,
9
10
  } from './errors/index.mjs'
10
11
  import { ServiceLocatorInstanceHolderStatus } from './service-locator-instance-holder.mjs'
12
+ import { createDeferred } from './utils/defer.mjs'
11
13
 
12
14
  export class ServiceLocatorManager {
13
15
  private readonly instancesHolders: Map<string, ServiceLocatorInstanceHolder> =
@@ -38,6 +40,11 @@ export class ServiceLocatorManager {
38
40
  `[ServiceLocatorManager]#getInstanceHolder() Instance ${holder.name} is destroying`,
39
41
  )
40
42
  return [new InstanceDestroying(holder.name), holder]
43
+ } else if (holder.status === ServiceLocatorInstanceHolderStatus.Error) {
44
+ this.logger?.log(
45
+ `[ServiceLocatorManager]#getInstanceHolder() Instance ${holder.name} is in error state`,
46
+ )
47
+ return [holder.instance as InstanceNotFound, holder]
41
48
  }
42
49
 
43
50
  return [undefined, holder]
@@ -86,4 +93,81 @@ export class ServiceLocatorManager {
86
93
  ),
87
94
  )
88
95
  }
96
+
97
+ /**
98
+ * Creates a new holder with Creating status and a deferred creation promise.
99
+ * This is useful for creating placeholder holders that can be fulfilled later.
100
+ * @param name The name of the instance
101
+ * @param type The injectable type
102
+ * @param scope The injectable scope
103
+ * @param deps Optional set of dependencies
104
+ * @param ttl Optional time-to-live in milliseconds (defaults to Infinity)
105
+ * @returns A tuple containing the deferred promise and the holder
106
+ */
107
+ createCreatingHolder<Instance>(
108
+ name: string,
109
+ type: InjectableType,
110
+ scope: InjectableScope,
111
+ deps: Set<string> = new Set(),
112
+ ttl: number = Infinity,
113
+ ): [
114
+ ReturnType<typeof createDeferred<[undefined, Instance]>>,
115
+ ServiceLocatorInstanceHolder<Instance>,
116
+ ] {
117
+ const deferred = createDeferred<[undefined, Instance]>()
118
+
119
+ const holder: ServiceLocatorInstanceHolder<Instance> = {
120
+ status: ServiceLocatorInstanceHolderStatus.Creating,
121
+ name,
122
+ instance: null,
123
+ creationPromise: deferred.promise,
124
+ destroyPromise: null,
125
+ type,
126
+ scope,
127
+ deps,
128
+ destroyListeners: [],
129
+ createdAt: Date.now(),
130
+ ttl,
131
+ }
132
+
133
+ return [deferred, holder]
134
+ }
135
+
136
+ /**
137
+ * Creates a new holder with Created status and an actual instance.
138
+ * This is useful for creating holders that already have their instance ready.
139
+ * @param name The name of the instance
140
+ * @param instance The actual instance to store
141
+ * @param type The injectable type
142
+ * @param scope The injectable scope
143
+ * @param deps Optional set of dependencies
144
+ * @param ttl Optional time-to-live in milliseconds (defaults to Infinity)
145
+ * @returns The created holder
146
+ */
147
+ storeCreatedHolder<Instance>(
148
+ name: string,
149
+ instance: Instance,
150
+ type: InjectableType,
151
+ scope: InjectableScope,
152
+ deps: Set<string> = new Set(),
153
+ ttl: number = Infinity,
154
+ ): ServiceLocatorInstanceHolder<Instance> {
155
+ const holder: ServiceLocatorInstanceHolder<Instance> = {
156
+ status: ServiceLocatorInstanceHolderStatus.Created,
157
+ name,
158
+ instance,
159
+ creationPromise: null,
160
+ destroyPromise: null,
161
+ type,
162
+ scope,
163
+ deps,
164
+ destroyListeners: [],
165
+ createdAt: Date.now(),
166
+ ttl,
167
+ }
168
+
169
+ this.instancesHolders.set(name, holder)
170
+
171
+ return holder
172
+ }
89
173
  }