@navios/di 0.2.0 → 0.3.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.
- package/README.md +301 -39
- package/docs/README.md +122 -49
- 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 +495 -150
- package/lib/_tsup-dts-rollup.d.ts +495 -150
- package/lib/index.d.mts +26 -12
- package/lib/index.d.ts +26 -12
- package/lib/index.js +993 -462
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +983 -453
- 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 +263 -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 +24 -9
- 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 +145 -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 +550 -395
- package/src/utils/defer.mts +73 -0
- package/src/utils/get-injectors.mts +93 -80
- 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,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a deferred promise that can be resolved or rejected externally.
|
|
3
|
+
* This is useful for creating stub holders that can be fulfilled later.
|
|
4
|
+
*/
|
|
5
|
+
export class Deferred<T> {
|
|
6
|
+
public readonly promise: Promise<T>
|
|
7
|
+
private _resolve!: (value: T) => void
|
|
8
|
+
private _reject!: (reason?: any) => void
|
|
9
|
+
private _isResolved = false
|
|
10
|
+
private _isRejected = false
|
|
11
|
+
|
|
12
|
+
constructor() {
|
|
13
|
+
this.promise = new Promise<T>((resolve, reject) => {
|
|
14
|
+
this._resolve = resolve
|
|
15
|
+
this._reject = reject
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Resolves the deferred promise with the given value.
|
|
21
|
+
* @param value The value to resolve with
|
|
22
|
+
* @throws Error if the promise has already been resolved or rejected
|
|
23
|
+
*/
|
|
24
|
+
resolve(value: T): void {
|
|
25
|
+
if (this._isResolved || this._isRejected) {
|
|
26
|
+
throw new Error('Deferred promise has already been resolved or rejected')
|
|
27
|
+
}
|
|
28
|
+
this._isResolved = true
|
|
29
|
+
this._resolve(value)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Rejects the deferred promise with the given reason.
|
|
34
|
+
* @param reason The reason for rejection
|
|
35
|
+
* @throws Error if the promise has already been resolved or rejected
|
|
36
|
+
*/
|
|
37
|
+
reject(reason?: any): void {
|
|
38
|
+
if (this._isResolved || this._isRejected) {
|
|
39
|
+
throw new Error('Deferred promise has already been resolved or rejected')
|
|
40
|
+
}
|
|
41
|
+
this._isRejected = true
|
|
42
|
+
this._reject(reason)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Returns true if the promise has been resolved.
|
|
47
|
+
*/
|
|
48
|
+
get isResolved(): boolean {
|
|
49
|
+
return this._isResolved
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Returns true if the promise has been rejected.
|
|
54
|
+
*/
|
|
55
|
+
get isRejected(): boolean {
|
|
56
|
+
return this._isRejected
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Returns true if the promise has been settled (resolved or rejected).
|
|
61
|
+
*/
|
|
62
|
+
get isSettled(): boolean {
|
|
63
|
+
return this._isResolved || this._isRejected
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Creates a new deferred promise.
|
|
69
|
+
* @returns A new Deferred instance
|
|
70
|
+
*/
|
|
71
|
+
export function createDeferred<T>(): Deferred<T> {
|
|
72
|
+
return new Deferred<T>()
|
|
73
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { z, ZodObject, ZodType } from 'zod/v4'
|
|
2
2
|
|
|
3
|
+
import type { FactoryContext } from '../factory-context.mjs'
|
|
3
4
|
import type {
|
|
4
5
|
BoundInjectionToken,
|
|
5
6
|
ClassType,
|
|
@@ -7,52 +8,25 @@ import type {
|
|
|
7
8
|
InjectionToken,
|
|
8
9
|
InjectionTokenSchemaType,
|
|
9
10
|
} from '../injection-token.mjs'
|
|
10
|
-
import type {
|
|
11
|
+
import type { Factorable } from '../interfaces/factory.interface.mjs'
|
|
12
|
+
import type { InjectState, Join, UnionToArray } from './types.mjs'
|
|
11
13
|
|
|
12
14
|
import { InjectableTokenMeta } from '../symbols/index.mjs'
|
|
13
15
|
|
|
14
|
-
export interface CreateInjectorsOptions {
|
|
15
|
-
baseLocator: ServiceLocator
|
|
16
|
-
}
|
|
17
|
-
type Join<TElements, TSeparator extends string> =
|
|
18
|
-
TElements extends Readonly<[infer First, ...infer Rest]>
|
|
19
|
-
? Rest extends ReadonlyArray<string>
|
|
20
|
-
? First extends string
|
|
21
|
-
? `${First}${Rest extends [] ? '' : TSeparator}${Join<Rest, TSeparator>}`
|
|
22
|
-
: never
|
|
23
|
-
: never
|
|
24
|
-
: ''
|
|
25
|
-
// credits goes to https://stackoverflow.com/a/50375286
|
|
26
|
-
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
|
|
27
|
-
k: infer I,
|
|
28
|
-
) => void
|
|
29
|
-
? I
|
|
30
|
-
: never
|
|
31
|
-
|
|
32
|
-
// Converts union to overloaded function
|
|
33
|
-
type UnionToOvlds<U> = UnionToIntersection<
|
|
34
|
-
U extends any ? (f: U) => void : never
|
|
35
|
-
>
|
|
36
|
-
|
|
37
|
-
type PopUnion<U> = UnionToOvlds<U> extends (a: infer A) => void ? A : never
|
|
38
|
-
|
|
39
|
-
type IsUnion<T> = [T] extends [UnionToIntersection<T>] ? false : true
|
|
40
|
-
|
|
41
|
-
type UnionToArray<T, A extends unknown[] = []> =
|
|
42
|
-
IsUnion<T> extends true
|
|
43
|
-
? UnionToArray<Exclude<T, PopUnion<T>>, [PopUnion<T>, ...A]>
|
|
44
|
-
: [T, ...A]
|
|
45
|
-
|
|
46
16
|
export interface Injectors {
|
|
47
17
|
// #1 Simple class
|
|
48
|
-
|
|
18
|
+
asyncInject<T extends ClassType>(
|
|
19
|
+
token: T,
|
|
20
|
+
): InstanceType<T> extends Factorable<infer R>
|
|
21
|
+
? Promise<R>
|
|
22
|
+
: Promise<InstanceType<T>>
|
|
49
23
|
// #2 Token with required Schema
|
|
50
|
-
|
|
24
|
+
asyncInject<T, S extends InjectionTokenSchemaType>(
|
|
51
25
|
token: InjectionToken<T, S>,
|
|
52
26
|
args: z.input<S>,
|
|
53
27
|
): Promise<T>
|
|
54
28
|
// #3 Token with optional Schema
|
|
55
|
-
|
|
29
|
+
asyncInject<T, S extends InjectionTokenSchemaType, R extends boolean>(
|
|
56
30
|
token: InjectionToken<T, S, R>,
|
|
57
31
|
): R extends false
|
|
58
32
|
? Promise<T>
|
|
@@ -63,17 +37,19 @@ export interface Injectors {
|
|
|
63
37
|
>}`
|
|
64
38
|
: 'Error: Your token requires args'
|
|
65
39
|
// #4 Token with no Schema
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
40
|
+
asyncInject<T>(token: InjectionToken<T, undefined>): Promise<T>
|
|
41
|
+
asyncInject<T>(token: BoundInjectionToken<T, any>): Promise<T>
|
|
42
|
+
asyncInject<T>(token: FactoryInjectionToken<T, any>): Promise<T>
|
|
69
43
|
|
|
70
|
-
|
|
71
|
-
|
|
44
|
+
inject<T extends ClassType>(
|
|
45
|
+
token: T,
|
|
46
|
+
): InstanceType<T> extends Factorable<infer R> ? R : InstanceType<T>
|
|
47
|
+
inject<T, S extends InjectionTokenSchemaType>(
|
|
72
48
|
token: InjectionToken<T, S>,
|
|
73
49
|
args: z.input<S>,
|
|
74
50
|
): T
|
|
75
51
|
// #3 Token with optional Schema
|
|
76
|
-
|
|
52
|
+
inject<T, S extends InjectionTokenSchemaType, R extends boolean>(
|
|
77
53
|
token: InjectionToken<T, S, R>,
|
|
78
54
|
): R extends false
|
|
79
55
|
? T
|
|
@@ -83,38 +59,40 @@ export interface Injectors {
|
|
|
83
59
|
', '
|
|
84
60
|
>}`
|
|
85
61
|
: 'Error: Your token requires args'
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
62
|
+
inject<T>(token: InjectionToken<T, undefined>): T
|
|
63
|
+
inject<T>(token: BoundInjectionToken<T, any>): T
|
|
64
|
+
inject<T>(token: FactoryInjectionToken<T, any>): T
|
|
89
65
|
|
|
90
|
-
wrapSyncInit(
|
|
66
|
+
wrapSyncInit(
|
|
67
|
+
cb: () => any,
|
|
68
|
+
): (injectState?: InjectState) => [any, Promise<any>[], InjectState]
|
|
91
69
|
|
|
92
|
-
|
|
70
|
+
provideFactoryContext(context: FactoryContext | null): FactoryContext | null
|
|
93
71
|
}
|
|
94
72
|
|
|
95
|
-
export
|
|
73
|
+
export function getInjectors() {
|
|
74
|
+
let currentFactoryContext: FactoryContext | null = null
|
|
96
75
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
76
|
+
function provideFactoryContext(
|
|
77
|
+
context: FactoryContext,
|
|
78
|
+
): FactoryContext | null {
|
|
79
|
+
const original = currentFactoryContext
|
|
80
|
+
currentFactoryContext = context
|
|
81
|
+
return original
|
|
100
82
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
function getServiceLocator() {
|
|
104
|
-
if (!currentLocator) {
|
|
83
|
+
function getFactoryContext(): FactoryContext {
|
|
84
|
+
if (!currentFactoryContext) {
|
|
105
85
|
throw new Error(
|
|
106
|
-
'[Injector]
|
|
86
|
+
'[Injector] Trying to access injection context outside of a injectable context',
|
|
107
87
|
)
|
|
108
88
|
}
|
|
109
|
-
return
|
|
110
|
-
}
|
|
111
|
-
function provideServiceLocator(locator: ServiceLocator): ServiceLocator {
|
|
112
|
-
const original = currentLocator
|
|
113
|
-
currentLocator = locator
|
|
114
|
-
return original
|
|
89
|
+
return currentFactoryContext
|
|
115
90
|
}
|
|
116
91
|
|
|
117
|
-
|
|
92
|
+
let promiseCollector: null | ((promise: Promise<any>) => void) = null
|
|
93
|
+
let injectState: InjectState | null = null
|
|
94
|
+
|
|
95
|
+
function asyncInject(
|
|
118
96
|
token:
|
|
119
97
|
| ClassType
|
|
120
98
|
| InjectionToken<any>
|
|
@@ -122,43 +100,79 @@ export function getInjectors({ baseLocator }: CreateInjectorsOptions) {
|
|
|
122
100
|
| FactoryInjectionToken<any, any>,
|
|
123
101
|
args?: unknown,
|
|
124
102
|
) {
|
|
125
|
-
|
|
126
|
-
|
|
103
|
+
if (!injectState) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
'[Injector] Trying to access inject outside of a injectable context',
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
if (injectState.isFrozen) {
|
|
109
|
+
const idx = injectState.currentIndex++
|
|
110
|
+
const request = injectState.requests[idx]
|
|
111
|
+
if (request.token !== token) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
`[Injector] Wrong token order. Expected ${request.token.toString()} but got ${token.toString()}`,
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
return request.promise
|
|
117
|
+
}
|
|
127
118
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
119
|
+
const promise = getFactoryContext().inject(token as any, args as any)
|
|
120
|
+
injectState.requests.push({
|
|
121
|
+
token,
|
|
122
|
+
promise,
|
|
123
|
+
})
|
|
124
|
+
injectState.currentIndex++
|
|
131
125
|
|
|
132
|
-
|
|
126
|
+
return promise
|
|
127
|
+
}
|
|
133
128
|
|
|
134
129
|
function wrapSyncInit(cb: () => any) {
|
|
135
|
-
return () => {
|
|
130
|
+
return (previousState?: InjectState) => {
|
|
136
131
|
const promises: Promise<any>[] = []
|
|
137
132
|
const originalPromiseCollector = promiseCollector
|
|
133
|
+
const originalInjectState = injectState
|
|
134
|
+
injectState = previousState
|
|
135
|
+
? {
|
|
136
|
+
...previousState,
|
|
137
|
+
currentIndex: 0,
|
|
138
|
+
}
|
|
139
|
+
: {
|
|
140
|
+
currentIndex: 0,
|
|
141
|
+
isFrozen: false,
|
|
142
|
+
requests: [],
|
|
143
|
+
}
|
|
138
144
|
promiseCollector = (promise) => {
|
|
139
145
|
promises.push(promise)
|
|
140
146
|
}
|
|
141
147
|
const result = cb()
|
|
142
148
|
promiseCollector = originalPromiseCollector
|
|
143
|
-
|
|
149
|
+
const newInjectState = {
|
|
150
|
+
...injectState,
|
|
151
|
+
isFrozen: true,
|
|
152
|
+
}
|
|
153
|
+
injectState = originalInjectState
|
|
154
|
+
return [result, promises, newInjectState]
|
|
144
155
|
}
|
|
145
156
|
}
|
|
146
157
|
|
|
147
|
-
function
|
|
158
|
+
function inject<
|
|
148
159
|
T,
|
|
149
160
|
Token extends
|
|
150
161
|
| InjectionToken<T>
|
|
151
162
|
| BoundInjectionToken<T, any>
|
|
152
163
|
| FactoryInjectionToken<T, any>,
|
|
153
|
-
S extends ZodObject
|
|
154
|
-
>(token: Token, args?: S extends ZodObject
|
|
164
|
+
S extends ZodObject | unknown = Token['schema'],
|
|
165
|
+
>(token: Token, args?: S extends ZodObject ? z.input<S> : never): T {
|
|
155
166
|
// @ts-expect-error In case we have a class
|
|
156
167
|
const realToken = token[InjectableTokenMeta] ?? token
|
|
157
168
|
|
|
158
|
-
const instance =
|
|
169
|
+
const instance = getFactoryContext().locator.getSyncInstance(
|
|
170
|
+
realToken,
|
|
171
|
+
args,
|
|
172
|
+
)
|
|
159
173
|
if (!instance) {
|
|
160
174
|
if (promiseCollector) {
|
|
161
|
-
const promise =
|
|
175
|
+
const promise = getFactoryContext().inject(realToken, args)
|
|
162
176
|
promiseCollector(promise)
|
|
163
177
|
} else {
|
|
164
178
|
throw new Error(`[Injector] Cannot initiate ${realToken.toString()}`)
|
|
@@ -168,7 +182,7 @@ export function getInjectors({ baseLocator }: CreateInjectorsOptions) {
|
|
|
168
182
|
{
|
|
169
183
|
get() {
|
|
170
184
|
throw new Error(
|
|
171
|
-
`[Injector] Trying to access ${realToken.toString()} before it's initialized, please use
|
|
185
|
+
`[Injector] Trying to access ${realToken.toString()} before it's initialized, please use asyncInject() instead of inject() or do not use the value outside of class methods`,
|
|
172
186
|
)
|
|
173
187
|
},
|
|
174
188
|
},
|
|
@@ -178,12 +192,11 @@ export function getInjectors({ baseLocator }: CreateInjectorsOptions) {
|
|
|
178
192
|
}
|
|
179
193
|
|
|
180
194
|
const injectors: Injectors = {
|
|
195
|
+
asyncInject,
|
|
181
196
|
inject,
|
|
182
|
-
syncInject,
|
|
183
197
|
wrapSyncInit,
|
|
184
|
-
|
|
198
|
+
provideFactoryContext,
|
|
185
199
|
} as Injectors
|
|
186
|
-
InjectorsBase.set(baseLocator, injectors)
|
|
187
200
|
|
|
188
201
|
return injectors
|
|
189
202
|
}
|
package/src/utils/index.mts
CHANGED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
BoundInjectionToken,
|
|
3
|
+
ClassType,
|
|
4
|
+
FactoryInjectionToken,
|
|
5
|
+
InjectionToken,
|
|
6
|
+
} from '../injection-token.mjs'
|
|
7
|
+
|
|
8
|
+
// Utility types for string manipulation and union handling
|
|
9
|
+
export type Join<TElements, TSeparator extends string> =
|
|
10
|
+
TElements extends Readonly<[infer First, ...infer Rest]>
|
|
11
|
+
? Rest extends ReadonlyArray<string>
|
|
12
|
+
? First extends string
|
|
13
|
+
? `${First}${Rest extends [] ? '' : TSeparator}${Join<Rest, TSeparator>}`
|
|
14
|
+
: never
|
|
15
|
+
: never
|
|
16
|
+
: ''
|
|
17
|
+
|
|
18
|
+
// credits goes to https://stackoverflow.com/a/50375286
|
|
19
|
+
export type UnionToIntersection<U> = (
|
|
20
|
+
U extends any ? (k: U) => void : never
|
|
21
|
+
) extends (k: infer I) => void
|
|
22
|
+
? I
|
|
23
|
+
: never
|
|
24
|
+
|
|
25
|
+
// Converts union to overloaded function
|
|
26
|
+
export type UnionToOvlds<U> = UnionToIntersection<
|
|
27
|
+
U extends any ? (f: U) => void : never
|
|
28
|
+
>
|
|
29
|
+
|
|
30
|
+
export type PopUnion<U> =
|
|
31
|
+
UnionToOvlds<U> extends (a: infer A) => void ? A : never
|
|
32
|
+
|
|
33
|
+
export type IsUnion<T> = [T] extends [UnionToIntersection<T>] ? false : true
|
|
34
|
+
|
|
35
|
+
export type UnionToArray<T, A extends unknown[] = []> =
|
|
36
|
+
IsUnion<T> extends true
|
|
37
|
+
? UnionToArray<Exclude<T, PopUnion<T>>, [PopUnion<T>, ...A]>
|
|
38
|
+
: [T, ...A]
|
|
39
|
+
|
|
40
|
+
// InjectState interface for managing injection state
|
|
41
|
+
export interface InjectState {
|
|
42
|
+
currentIndex: number
|
|
43
|
+
isFrozen: boolean
|
|
44
|
+
requests: {
|
|
45
|
+
token:
|
|
46
|
+
| InjectionToken<any>
|
|
47
|
+
| BoundInjectionToken<any, any>
|
|
48
|
+
| FactoryInjectionToken<any, any>
|
|
49
|
+
| ClassType
|
|
50
|
+
promise: Promise<any>
|
|
51
|
+
}[]
|
|
52
|
+
}
|
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
# @Injectable decorator
|
|
2
|
-
|
|
3
|
-
The `@Injectable` decorator is used to define a class or a factory class as an injectable service.
|
|
4
|
-
|
|
5
|
-
## Parameters
|
|
6
|
-
|
|
7
|
-
The `@Injectable` decorator accepts the following fields in its options:
|
|
8
|
-
|
|
9
|
-
- `token`: The injection token to use for the service. If not provided, will create a new token using the class itself.
|
|
10
|
-
- `scope`: The lifetime of the service. Accepts `InjectableScope.Singleton` or `InjectableScope.Instance`. By default, it will be `InjectableScope.Singleton`.
|
|
11
|
-
- `type`: The type of the service. Accepts `InjectableType.Class` or `InjectableType.Factory`. By default, it will be `InjectableType.Class`.
|
|
12
|
-
- `registry`: The registry to use for the service. Uses the default registry if not provided.
|
|
13
|
-
|
|
14
|
-
## Usage
|
|
15
|
-
|
|
16
|
-
Injectable can be used as a class decorator or a factory decorator.
|
|
17
|
-
|
|
18
|
-
Inside a class that you're decorating with `@Injectable` you can use `inject` and `syncInject` functions to inject the services.
|
|
19
|
-
|
|
20
|
-
```ts
|
|
21
|
-
@Injectable()
|
|
22
|
-
class GreeterService {
|
|
23
|
-
sayHello(name: string) {
|
|
24
|
-
return `Hello ${name}`
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
@Injectable()
|
|
29
|
-
class UserService {
|
|
30
|
-
private readonly greeterService = syncInject(GreeterService)
|
|
31
|
-
|
|
32
|
-
makeGreeting(name: string) {
|
|
33
|
-
return this.greeterService.sayHello(name)
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
Please note that `syncInject` can be used only with services that are created with `InjectableScope.Singleton`.
|
|
39
|
-
|
|
40
|
-
If you need to inject a service that is created with `InjectableScope.Instance`, you can use `inject` function. and it will return a Promise.
|
|
41
|
-
|
|
42
|
-
For example:
|
|
43
|
-
|
|
44
|
-
```ts
|
|
45
|
-
@Injectable({ scope: InjectableScope.Instance })
|
|
46
|
-
class GreeterService {
|
|
47
|
-
private readonly createdAt = new Date()
|
|
48
|
-
|
|
49
|
-
sayHello(name: string) {
|
|
50
|
-
return `Hello ${name} ${this.createdAt.toISOString()}`
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
@Injectable()
|
|
55
|
-
class UserService {
|
|
56
|
-
private readonly greeterService = inject(GreeterService)
|
|
57
|
-
|
|
58
|
-
async makeGreeting(name: string) {
|
|
59
|
-
const service = await this.greeterService
|
|
60
|
-
return service.sayHello(name)
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
## Examples
|
|
66
|
-
|
|
67
|
-
### Simple class
|
|
68
|
-
|
|
69
|
-
```ts
|
|
70
|
-
@Injectable()
|
|
71
|
-
class GreeterService {
|
|
72
|
-
sayHello(name: string) {
|
|
73
|
-
return `Hello ${name}`
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const greeterService = await inject(GreeterService)
|
|
78
|
-
console.log(greeterService.sayHello('John'))
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
### Simple Factory
|
|
82
|
-
|
|
83
|
-
```ts
|
|
84
|
-
@Injectable({ type: InjectableType.Factory })
|
|
85
|
-
class GreeterServiceFactory {
|
|
86
|
-
create() {
|
|
87
|
-
return new GreeterService()
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const greeterService = await inject(GreeterServiceFactory)
|
|
92
|
-
console.log(greeterService.sayHello('John'))
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
### Class with token
|
|
96
|
-
|
|
97
|
-
```ts
|
|
98
|
-
export interface GreeterServiceInterface {
|
|
99
|
-
sayHello(name: string): string
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const GreeterServiceParams = z.object({
|
|
103
|
-
context: z.string(),
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
export const GREETER_SERVICE = InjectionToken.create<
|
|
107
|
-
GreeterServiceInterface,
|
|
108
|
-
typeof GreeterServiceParams
|
|
109
|
-
>('GreeterService', GreeterServiceParams)
|
|
110
|
-
|
|
111
|
-
@Injectable({ token: GREETER_SERVICE })
|
|
112
|
-
class GreeterService {
|
|
113
|
-
constructor(private readonly config: z.infer<typeof GreeterServiceParams>) {}
|
|
114
|
-
|
|
115
|
-
sayHello(name: string) {
|
|
116
|
-
return `Hello ${name} ${this.config.context}`
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const greeterService = await inject(GREETER_SERVICE, { context: 'World' })
|
|
121
|
-
console.log(greeterService.sayHello('John'))
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
### Factory with token
|
|
125
|
-
|
|
126
|
-
```ts
|
|
127
|
-
export interface GreeterServiceInterface {
|
|
128
|
-
sayHello(name: string): string
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const GreeterServiceParams = z.object({
|
|
132
|
-
context: z.string(),
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
export const GREETER_SERVICE = InjectionToken.create<
|
|
136
|
-
GreeterServiceInterface,
|
|
137
|
-
typeof GreeterServiceParams
|
|
138
|
-
>('GreeterService', GreeterServiceParams)
|
|
139
|
-
|
|
140
|
-
@Injectable({ type: InjectableType.Factory, token: GREETER_SERVICE })
|
|
141
|
-
class GreeterServiceFactory {
|
|
142
|
-
create(ctx: FactoryContext, args: z.infer<typeof GreeterServiceParams>) {
|
|
143
|
-
return new GreeterService(args)
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const greeterService = await inject(GREETER_SERVICE, { context: 'World' })
|
|
148
|
-
console.log(greeterService.sayHello('John'))
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
## InjectableScope
|
|
152
|
-
|
|
153
|
-
The `InjectableScope` enum defines the scope of the service.
|
|
154
|
-
|
|
155
|
-
- `InjectableScope.Singleton`: The service will be created once and reused.
|
|
156
|
-
- `InjectableScope.Instance`: The service will be created every time it is injected.
|
|
157
|
-
|
|
158
|
-
## InjectableType
|
|
159
|
-
|
|
160
|
-
The `InjectableType` enum defines the type of the service.
|
|
161
|
-
|
|
162
|
-
- `InjectableType.Class`: The service will be a class.
|
|
163
|
-
- `InjectableType.Factory`: The service will be a factory.
|
|
164
|
-
|
|
165
|
-
## Registry
|
|
166
|
-
|
|
167
|
-
The `Registry` is the registry of the service. It is used to store the service and its dependencies.
|
|
168
|
-
|
|
169
|
-
## FactoryContext
|
|
170
|
-
|
|
171
|
-
The `FactoryContext` is the context of the factory. It is used to add additional information to the factory.
|
|
172
|
-
|
|
173
|
-
Context API:
|
|
174
|
-
|
|
175
|
-
- `inject`: Injects the service, same as global inject, but additionally it will track the dependencies of the service.
|
|
176
|
-
- `on`: Adds a listener to the service locator event bus.
|
|
177
|
-
- `getDependencies`: Returns the dependencies of the service.
|
|
178
|
-
- `invalidate`: Invalidates self and all the services that depend on it.
|
|
179
|
-
- `addEffect`: Adds an effect to the service. Effect is a function that will be called when the service is invalidated.
|
|
180
|
-
- `setTtl`: Sets the ttl of the service.
|
|
181
|
-
- `getTtl`: Returns the ttl of the service.
|
|
182
|
-
- `locator`: Returns the service locator you are currently in.
|