@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.
- package/README.md +299 -38
- package/docs/README.md +121 -48
- package/docs/api-reference.md +763 -0
- package/docs/container.md +274 -0
- package/docs/examples/basic-usage.mts +97 -0
- package/docs/examples/factory-pattern.mts +318 -0
- package/docs/examples/injection-tokens.mts +225 -0
- package/docs/examples/request-scope-example.mts +254 -0
- package/docs/examples/service-lifecycle.mts +359 -0
- package/docs/factory.md +584 -0
- package/docs/getting-started.md +308 -0
- package/docs/injectable.md +496 -0
- package/docs/injection-tokens.md +400 -0
- package/docs/lifecycle.md +539 -0
- package/docs/scopes.md +749 -0
- package/lib/_tsup-dts-rollup.d.mts +494 -145
- package/lib/_tsup-dts-rollup.d.ts +494 -145
- package/lib/index.d.mts +26 -12
- package/lib/index.d.ts +26 -12
- package/lib/index.js +1021 -470
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +1011 -461
- package/lib/index.mjs.map +1 -1
- package/package.json +2 -2
- package/project.json +10 -2
- package/src/__tests__/container.spec.mts +1301 -0
- package/src/__tests__/factory.spec.mts +137 -0
- package/src/__tests__/injectable.spec.mts +32 -88
- package/src/__tests__/injection-token.spec.mts +333 -17
- package/src/__tests__/request-scope.spec.mts +427 -0
- package/src/__type-tests__/factory.spec-d.mts +65 -0
- package/src/__type-tests__/inject.spec-d.mts +27 -28
- package/src/__type-tests__/injectable.spec-d.mts +42 -206
- package/src/container.mts +167 -0
- package/src/decorators/factory.decorator.mts +79 -0
- package/src/decorators/index.mts +1 -0
- package/src/decorators/injectable.decorator.mts +6 -56
- package/src/enums/injectable-scope.enum.mts +5 -1
- package/src/event-emitter.mts +18 -20
- package/src/factory-context.mts +2 -10
- package/src/index.mts +3 -2
- package/src/injection-token.mts +19 -4
- package/src/injector.mts +8 -20
- package/src/interfaces/factory.interface.mts +3 -3
- package/src/interfaces/index.mts +2 -0
- package/src/interfaces/on-service-destroy.interface.mts +3 -0
- package/src/interfaces/on-service-init.interface.mts +3 -0
- package/src/registry.mts +7 -16
- package/src/request-context-holder.mts +174 -0
- package/src/service-instantiator.mts +158 -0
- package/src/service-locator-event-bus.mts +0 -28
- package/src/service-locator-instance-holder.mts +27 -16
- package/src/service-locator-manager.mts +84 -0
- package/src/service-locator.mts +548 -393
- package/src/utils/defer.mts +73 -0
- package/src/utils/get-injectors.mts +91 -78
- package/src/utils/index.mts +2 -0
- package/src/utils/types.mts +52 -0
- package/docs/concepts/injectable.md +0 -182
- package/docs/concepts/injection-token.md +0 -145
- package/src/proxy-service-locator.mts +0 -83
- 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
|
-
|
|
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
|
-
|
|
25
|
-
|
|
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
|
-
|
|
39
|
-
|
|
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
|
-
|
|
53
|
-
|
|
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
|
}
|