@navios/core 0.1.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/LICENSE +7 -0
- package/README.md +145 -0
- package/dist/index.d.mts +425 -0
- package/dist/index.d.ts +425 -0
- package/dist/index.js +1744 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1657 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +45 -0
- package/src/__tests__/controller.spec.mts +59 -0
- package/src/attribute.factory.mts +155 -0
- package/src/decorators/controller.decorator.mts +36 -0
- package/src/decorators/endpoint.decorator.mts +81 -0
- package/src/decorators/index.mts +4 -0
- package/src/decorators/module.decorator.mts +47 -0
- package/src/decorators/use-guards.decorator.mts +43 -0
- package/src/exceptions/bad-request.exception.mts +7 -0
- package/src/exceptions/conflict.exception.mts +7 -0
- package/src/exceptions/forbidden.exception.mts +7 -0
- package/src/exceptions/http.exception.mts +7 -0
- package/src/exceptions/index.mts +7 -0
- package/src/exceptions/internal-server-error.exception.mts +7 -0
- package/src/exceptions/not-found.exception.mts +10 -0
- package/src/exceptions/unauthorized.exception.mts +7 -0
- package/src/index.mts +10 -0
- package/src/interfaces/can-activate.mts +5 -0
- package/src/interfaces/index.mts +2 -0
- package/src/interfaces/navios-module.mts +3 -0
- package/src/metadata/controller.metadata.mts +71 -0
- package/src/metadata/endpoint.metadata.mts +69 -0
- package/src/metadata/index.mts +4 -0
- package/src/metadata/injectable.metadata.mts +11 -0
- package/src/metadata/module.metadata.mts +62 -0
- package/src/navios.application.mts +113 -0
- package/src/navios.factory.mts +17 -0
- package/src/service-locator/__tests__/injectable.spec.mts +170 -0
- package/src/service-locator/decorators/get-injectable-token.mts +23 -0
- package/src/service-locator/decorators/index.mts +2 -0
- package/src/service-locator/decorators/injectable.decorator.mts +103 -0
- package/src/service-locator/enums/index.mts +1 -0
- package/src/service-locator/enums/injectable-scope.enum.mts +10 -0
- package/src/service-locator/errors/errors.enum.mts +7 -0
- package/src/service-locator/errors/factory-not-found.mts +8 -0
- package/src/service-locator/errors/index.mts +6 -0
- package/src/service-locator/errors/instance-destroying.mts +8 -0
- package/src/service-locator/errors/instance-expired.mts +8 -0
- package/src/service-locator/errors/instance-not-found.mts +8 -0
- package/src/service-locator/errors/unknown-error.mts +15 -0
- package/src/service-locator/event-emitter.mts +107 -0
- package/src/service-locator/index.mts +14 -0
- package/src/service-locator/inject.mts +37 -0
- package/src/service-locator/injection-token.mts +36 -0
- package/src/service-locator/injector.mts +18 -0
- package/src/service-locator/interfaces/factory.interface.mts +3 -0
- package/src/service-locator/override.mts +22 -0
- package/src/service-locator/proxy-service-locator.mts +80 -0
- package/src/service-locator/service-locator-abstract-factory-context.mts +23 -0
- package/src/service-locator/service-locator-event-bus.mts +96 -0
- package/src/service-locator/service-locator-instance-holder.mts +63 -0
- package/src/service-locator/service-locator-manager.mts +89 -0
- package/src/service-locator/service-locator.mts +445 -0
- package/src/service-locator/sync-injector.mts +55 -0
- package/src/services/controller-adapter.service.mts +124 -0
- package/src/services/execution-context.mts +53 -0
- package/src/services/guard-runner.service.mts +93 -0
- package/src/services/index.mts +4 -0
- package/src/services/module-loader.service.mts +68 -0
- package/src/tokens/application.token.mts +9 -0
- package/src/tokens/execution-context.token.mts +8 -0
- package/src/tokens/index.mts +4 -0
- package/src/tokens/reply.token.mts +7 -0
- package/src/tokens/request.token.mts +9 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
Injectable,
|
|
6
|
+
InjectableType,
|
|
7
|
+
} from '../decorators/index.mjs'
|
|
8
|
+
import { InjectableScope } from '../enums/index.mjs'
|
|
9
|
+
import { getInjectableToken, syncInject } from '../index.mjs'
|
|
10
|
+
import { inject } from '../inject.mjs'
|
|
11
|
+
import { InjectionToken } from '../injection-token.mjs'
|
|
12
|
+
import { getServiceLocator } from '../injector.mjs'
|
|
13
|
+
|
|
14
|
+
describe('Injectable decorator', () => {
|
|
15
|
+
it('should work with class', async () => {
|
|
16
|
+
@Injectable()
|
|
17
|
+
class Test {}
|
|
18
|
+
|
|
19
|
+
const value = await inject(Test)
|
|
20
|
+
expect(value).toBeInstanceOf(Test)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('should work with inner inject', async () => {
|
|
24
|
+
@Injectable()
|
|
25
|
+
class Test {
|
|
26
|
+
makeFoo() {
|
|
27
|
+
return 'foo'
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@Injectable()
|
|
32
|
+
class Test2 {
|
|
33
|
+
fooMaker = inject(Test)
|
|
34
|
+
|
|
35
|
+
async makeFoo() {
|
|
36
|
+
const fooMaker = await this.fooMaker
|
|
37
|
+
return fooMaker.makeFoo()
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const value = await inject(Test2)
|
|
42
|
+
expect(value).toBeInstanceOf(Test2)
|
|
43
|
+
const result = await value.makeFoo()
|
|
44
|
+
expect(result).toBe('foo')
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('should work with factory', async () => {
|
|
48
|
+
@Injectable({ type: InjectableType.Factory })
|
|
49
|
+
class Test {
|
|
50
|
+
create() {
|
|
51
|
+
return 'foo'
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const value = await inject(Test)
|
|
56
|
+
expect(value).toBe('foo')
|
|
57
|
+
})
|
|
58
|
+
it('should work with request scope', async () => {
|
|
59
|
+
@Injectable({
|
|
60
|
+
scope: InjectableScope.Instance,
|
|
61
|
+
type: InjectableType.Factory,
|
|
62
|
+
})
|
|
63
|
+
class Test {
|
|
64
|
+
create() {
|
|
65
|
+
return Date.now()
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const value = await inject(Test)
|
|
70
|
+
await new Promise((resolve) => setTimeout(resolve, 10))
|
|
71
|
+
const value2 = await inject(Test)
|
|
72
|
+
expect(value).not.toBe(value2)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('should work with injection token', async () => {
|
|
76
|
+
const token = InjectionToken.create('Test')
|
|
77
|
+
|
|
78
|
+
@Injectable({ token })
|
|
79
|
+
class Test {}
|
|
80
|
+
|
|
81
|
+
const value = await inject(token)
|
|
82
|
+
expect(value).toBeInstanceOf(Test)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('should work with injection token and schema', async () => {
|
|
86
|
+
class TestFoo {
|
|
87
|
+
constructor(public readonly foo: string) {}
|
|
88
|
+
}
|
|
89
|
+
const token = InjectionToken.create(
|
|
90
|
+
TestFoo,
|
|
91
|
+
z.object({
|
|
92
|
+
foo: z.string(),
|
|
93
|
+
}),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
@Injectable({ token, type: InjectableType.Factory })
|
|
97
|
+
class Test {
|
|
98
|
+
create(ctx: any, args: { foo: string }) {
|
|
99
|
+
return new TestFoo(args.foo)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const value = await inject(token, { foo: 'bar' })
|
|
104
|
+
const differentValue = await inject(token, { foo: 'baz' })
|
|
105
|
+
const sameValue = await inject(token, { foo: 'bar' })
|
|
106
|
+
expect(value).toBeInstanceOf(TestFoo)
|
|
107
|
+
expect(value.foo).toBe('bar')
|
|
108
|
+
expect(differentValue).toBeInstanceOf(TestFoo)
|
|
109
|
+
expect(differentValue.foo).toBe('baz')
|
|
110
|
+
expect(value).not.toBe(differentValue)
|
|
111
|
+
expect(value).toBe(sameValue)
|
|
112
|
+
})
|
|
113
|
+
it('should work with invalidation', async () => {
|
|
114
|
+
@Injectable()
|
|
115
|
+
class Test {
|
|
116
|
+
value = Date.now()
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
@Injectable()
|
|
120
|
+
class Test2 {
|
|
121
|
+
test = inject(Test)
|
|
122
|
+
|
|
123
|
+
async makeFoo() {
|
|
124
|
+
const test = await this.test
|
|
125
|
+
return test.value
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const identifier = getServiceLocator().getInstanceIdentifier(
|
|
129
|
+
getInjectableToken(Test),
|
|
130
|
+
undefined,
|
|
131
|
+
)
|
|
132
|
+
const inst1 = await inject(Test2)
|
|
133
|
+
expect(inst1).toBeInstanceOf(Test2)
|
|
134
|
+
const result1 = await inst1.makeFoo()
|
|
135
|
+
const inst2 = await inject(Test2)
|
|
136
|
+
expect(inst1).toBe(inst2)
|
|
137
|
+
const result2 = await inst2.makeFoo()
|
|
138
|
+
await getServiceLocator().invalidate(identifier)
|
|
139
|
+
await new Promise((resolve) => setTimeout(resolve, 10))
|
|
140
|
+
const inst3 = await inject(Test2)
|
|
141
|
+
expect(inst1).not.toBe(inst3)
|
|
142
|
+
const result3 = await inst3.makeFoo()
|
|
143
|
+
expect(result1).not.toBe(result3)
|
|
144
|
+
expect(result2).not.toBe(result3)
|
|
145
|
+
expect(result1).toBe(result2)
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('should work with syncInject', async () => {
|
|
149
|
+
@Injectable()
|
|
150
|
+
class Test {
|
|
151
|
+
value = Date.now()
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
@Injectable()
|
|
155
|
+
class Test2 {
|
|
156
|
+
test = syncInject(Test)
|
|
157
|
+
|
|
158
|
+
makeFoo() {
|
|
159
|
+
return this.test.value
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const inst1 = await inject(Test2)
|
|
163
|
+
expect(inst1).toBeInstanceOf(Test2)
|
|
164
|
+
const result1 = inst1.makeFoo()
|
|
165
|
+
const inst2 = await inject(Test2)
|
|
166
|
+
expect(inst1).toBe(inst2)
|
|
167
|
+
const result2 = await inst2.makeFoo()
|
|
168
|
+
expect(result1).toBe(result2)
|
|
169
|
+
})
|
|
170
|
+
})
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ClassType,
|
|
3
|
+
ClassTypeWithInstance,
|
|
4
|
+
InjectionToken,
|
|
5
|
+
} from '../injection-token.mjs'
|
|
6
|
+
|
|
7
|
+
import { InjectableTokenMeta } from './injectable.decorator.mjs'
|
|
8
|
+
|
|
9
|
+
export function getInjectableToken<R, T extends ClassTypeWithInstance<R>>(
|
|
10
|
+
target: ClassType,
|
|
11
|
+
): R extends { create(...args: any[]): infer V }
|
|
12
|
+
? InjectionToken<V>
|
|
13
|
+
: InjectionToken<R> {
|
|
14
|
+
// @ts-expect-error We inject the token into the class itself
|
|
15
|
+
const token = target[InjectableTokenMeta] as InjectionToken<any, any>
|
|
16
|
+
if (!token) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
`[ServiceLocator] Class ${target.name} is not decorated with @Injectable.`,
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
// @ts-expect-error We detect factory or class
|
|
22
|
+
return token
|
|
23
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { ClassType } from '../injection-token.mjs'
|
|
2
|
+
|
|
3
|
+
import { InjectableScope } from '../enums/index.mjs'
|
|
4
|
+
import { InjectionToken } from '../injection-token.mjs'
|
|
5
|
+
import { getServiceLocator, provideServiceLocator } from '../injector.mjs'
|
|
6
|
+
import { makeProxyServiceLocator } from '../proxy-service-locator.mjs'
|
|
7
|
+
import { setPromiseCollector } from '../sync-injector.mjs'
|
|
8
|
+
|
|
9
|
+
export enum InjectableType {
|
|
10
|
+
Class = 'Class',
|
|
11
|
+
Factory = 'Factory',
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface InjectableOptions {
|
|
15
|
+
scope?: InjectableScope
|
|
16
|
+
type?: InjectableType
|
|
17
|
+
token?: InjectionToken<any, any>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const InjectableTokenMeta = Symbol('InjectableTokenMeta')
|
|
21
|
+
|
|
22
|
+
export function Injectable({
|
|
23
|
+
scope = InjectableScope.Singleton,
|
|
24
|
+
type = InjectableType.Class,
|
|
25
|
+
token,
|
|
26
|
+
}: InjectableOptions = {}) {
|
|
27
|
+
return (target: ClassType, context: ClassDecoratorContext) => {
|
|
28
|
+
if (context.kind !== 'class') {
|
|
29
|
+
throw new Error(
|
|
30
|
+
'[ServiceLocator] @Injectable decorator can only be used on classes.',
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
let injectableToken: InjectionToken<any, any> =
|
|
34
|
+
token ?? InjectionToken.create(target)
|
|
35
|
+
const locator = getServiceLocator()
|
|
36
|
+
if (!locator) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
'[ServiceLocator] Service locator is not initialized. Please provide the service locator before using the @Injectable decorator.',
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
if (type === InjectableType.Class) {
|
|
42
|
+
locator.registerAbstractFactory(
|
|
43
|
+
injectableToken,
|
|
44
|
+
async (ctx) => {
|
|
45
|
+
if (scope === InjectableScope.Instance) {
|
|
46
|
+
ctx.setTtl(0)
|
|
47
|
+
}
|
|
48
|
+
const proxyServiceLocator = makeProxyServiceLocator(
|
|
49
|
+
getServiceLocator(),
|
|
50
|
+
ctx,
|
|
51
|
+
)
|
|
52
|
+
const promises: Promise<any>[] = []
|
|
53
|
+
const promiseCollector = (promise: Promise<any>) => {
|
|
54
|
+
promises.push(promise)
|
|
55
|
+
}
|
|
56
|
+
const originalPromiseCollector = setPromiseCollector(promiseCollector)
|
|
57
|
+
const tryInit = () => {
|
|
58
|
+
const original = provideServiceLocator(proxyServiceLocator)
|
|
59
|
+
let result = new target()
|
|
60
|
+
provideServiceLocator(original)
|
|
61
|
+
return result
|
|
62
|
+
}
|
|
63
|
+
const result = tryInit()
|
|
64
|
+
setPromiseCollector(originalPromiseCollector)
|
|
65
|
+
if (promises.length > 0) {
|
|
66
|
+
await Promise.all(promises)
|
|
67
|
+
return tryInit()
|
|
68
|
+
}
|
|
69
|
+
return result
|
|
70
|
+
},
|
|
71
|
+
scope,
|
|
72
|
+
)
|
|
73
|
+
} else if (type === InjectableType.Factory) {
|
|
74
|
+
locator.registerAbstractFactory(
|
|
75
|
+
injectableToken,
|
|
76
|
+
async (ctx, args: any) => {
|
|
77
|
+
if (scope === InjectableScope.Instance) {
|
|
78
|
+
ctx.setTtl(0)
|
|
79
|
+
}
|
|
80
|
+
const proxyServiceLocator = makeProxyServiceLocator(
|
|
81
|
+
getServiceLocator(),
|
|
82
|
+
ctx,
|
|
83
|
+
)
|
|
84
|
+
const original = provideServiceLocator(proxyServiceLocator)
|
|
85
|
+
const builder = new target()
|
|
86
|
+
if (typeof builder.create !== 'function') {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`[ServiceLocator] Factory ${target.name} does not implement the create method.`,
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
provideServiceLocator(original)
|
|
92
|
+
return builder.create(ctx, args)
|
|
93
|
+
},
|
|
94
|
+
scope,
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// @ts-expect-error
|
|
99
|
+
target[InjectableTokenMeta] = injectableToken
|
|
100
|
+
|
|
101
|
+
return target
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './injectable-scope.enum.mjs'
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ErrorsEnum } from './errors.enum.mjs'
|
|
2
|
+
|
|
3
|
+
export class UnknownError extends Error {
|
|
4
|
+
code = ErrorsEnum.UnknownError
|
|
5
|
+
parent?: Error
|
|
6
|
+
|
|
7
|
+
constructor(message: string | Error) {
|
|
8
|
+
if (message instanceof Error) {
|
|
9
|
+
super(message.message)
|
|
10
|
+
this.parent = message
|
|
11
|
+
return
|
|
12
|
+
}
|
|
13
|
+
super(message)
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-empty-object-type */
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-unsafe-function-type */
|
|
4
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
5
|
+
export type EventsConfig = {
|
|
6
|
+
[event: string]: any[]
|
|
7
|
+
}
|
|
8
|
+
export type EventsNames<Events extends EventsConfig> = Exclude<keyof Events, symbol | number>
|
|
9
|
+
export type EventsArgs<
|
|
10
|
+
Events extends EventsConfig,
|
|
11
|
+
Name extends EventsNames<Events>,
|
|
12
|
+
> = Events[Name] extends any[] ? Events[Name] : []
|
|
13
|
+
|
|
14
|
+
export type ChannelEmitter<
|
|
15
|
+
Events extends EventsConfig,
|
|
16
|
+
Ns extends string,
|
|
17
|
+
E extends EventsNames<Events>,
|
|
18
|
+
> = {
|
|
19
|
+
emit<Args extends EventsArgs<Events, E>>(ns: Ns, event: E, ...args: Args): Promise<any>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface EventEmitterInterface<Events extends EventsConfig> {
|
|
23
|
+
on<E extends EventsNames<Events>, Args extends EventsArgs<Events, E>>(
|
|
24
|
+
event: E,
|
|
25
|
+
listener: (...args: Args) => void,
|
|
26
|
+
): () => void
|
|
27
|
+
emit<E extends EventsNames<Events>, Args extends EventsArgs<Events, E>>(
|
|
28
|
+
event: E,
|
|
29
|
+
...args: Args
|
|
30
|
+
): void
|
|
31
|
+
addChannel<
|
|
32
|
+
E extends EventsNames<Events>,
|
|
33
|
+
Ns extends string,
|
|
34
|
+
Emmiter extends ChannelEmitter<Events, Ns, E>,
|
|
35
|
+
>(
|
|
36
|
+
ns: Ns,
|
|
37
|
+
event: E,
|
|
38
|
+
target: Emmiter,
|
|
39
|
+
): () => void
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export class EventEmitter<Events extends EventsConfig = {}>
|
|
43
|
+
implements EventEmitterInterface<Events>
|
|
44
|
+
{
|
|
45
|
+
private listeners: Map<EventsNames<Events>, Set<Function>> = new Map()
|
|
46
|
+
|
|
47
|
+
on<E extends EventsNames<Events>, Args extends EventsArgs<Events, E>>(
|
|
48
|
+
event: E,
|
|
49
|
+
listener: (...args: Args) => void,
|
|
50
|
+
) {
|
|
51
|
+
if (!this.listeners.has(event)) {
|
|
52
|
+
this.listeners.set(event, new Set())
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
this.listeners.get(event)!.add(listener)
|
|
56
|
+
|
|
57
|
+
return () => {
|
|
58
|
+
this.off(event, listener)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
off<E extends EventsNames<Events>, Args extends EventsArgs<Events, E>>(
|
|
63
|
+
event: E,
|
|
64
|
+
listener: (...args: Args) => void,
|
|
65
|
+
) {
|
|
66
|
+
if (!this.listeners.has(event)) {
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
this.listeners.get(event)!.delete(listener)
|
|
71
|
+
if (this.listeners.get(event)!.size === 0) {
|
|
72
|
+
this.listeners.delete(event)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
once<E extends EventsNames<Events>, Args extends EventsArgs<Events, E>>(
|
|
77
|
+
event: E,
|
|
78
|
+
listener: (...args: Args) => void,
|
|
79
|
+
) {
|
|
80
|
+
const off = this.on(event, (...args) => {
|
|
81
|
+
off()
|
|
82
|
+
// @ts-expect-error - This is a valid call
|
|
83
|
+
listener(...args)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
return off
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async emit<E extends EventsNames<Events>, Args extends EventsArgs<Events, E>>(
|
|
90
|
+
event: E,
|
|
91
|
+
...args: Args
|
|
92
|
+
): Promise<any> {
|
|
93
|
+
if (!this.listeners.has(event)) {
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return Promise.all(Array.from(this.listeners.get(event)!).map(listener => listener(...args)))
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
addChannel<
|
|
101
|
+
E extends EventsNames<Events>,
|
|
102
|
+
Ns extends string,
|
|
103
|
+
Emitter extends ChannelEmitter<Events, Ns, E>,
|
|
104
|
+
>(ns: Ns, event: E, target: Emitter) {
|
|
105
|
+
return this.on(event, (...args) => target.emit(ns, event, ...args))
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export * from './decorators/index.mjs'
|
|
2
|
+
export * from './enums/index.mjs'
|
|
3
|
+
export * from './errors/index.mjs'
|
|
4
|
+
export * from './event-emitter.mjs'
|
|
5
|
+
export * from './inject.mjs'
|
|
6
|
+
export * from './injection-token.mjs'
|
|
7
|
+
export * from './injector.mjs'
|
|
8
|
+
export * from './override.mjs'
|
|
9
|
+
export * from './service-locator.mjs'
|
|
10
|
+
export * from './service-locator-abstract-factory-context.mjs'
|
|
11
|
+
export * from './service-locator-event-bus.mjs'
|
|
12
|
+
export * from './service-locator-instance-holder.mjs'
|
|
13
|
+
export * from './service-locator-manager.mjs'
|
|
14
|
+
export * from './sync-injector.mjs'
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { AnyZodObject, z } from 'zod'
|
|
2
|
+
|
|
3
|
+
import type { ClassType } from './injection-token.mjs'
|
|
4
|
+
|
|
5
|
+
import { getInjectableToken } from './decorators/index.mjs'
|
|
6
|
+
import { InjectionToken } from './injection-token.mjs'
|
|
7
|
+
import { getServiceLocator } from './injector.mjs'
|
|
8
|
+
|
|
9
|
+
export function inject<T extends ClassType>(token: T): Promise<InstanceType<T>>
|
|
10
|
+
export function inject<T, S extends AnyZodObject>(
|
|
11
|
+
token: InjectionToken<T, S>,
|
|
12
|
+
args: z.input<S>,
|
|
13
|
+
): Promise<T>
|
|
14
|
+
export function inject<T>(token: InjectionToken<T, undefined>): Promise<T>
|
|
15
|
+
export function inject<
|
|
16
|
+
T,
|
|
17
|
+
Token extends InjectionToken<T>,
|
|
18
|
+
S extends AnyZodObject | unknown = Token['schema'],
|
|
19
|
+
>(
|
|
20
|
+
token: Token,
|
|
21
|
+
args?: S extends AnyZodObject ? z.input<S> : never,
|
|
22
|
+
): Promise<T> {
|
|
23
|
+
if (token.schema) {
|
|
24
|
+
const parsed = token.schema.safeParse(args)
|
|
25
|
+
if (!parsed.success) {
|
|
26
|
+
throw new Error(
|
|
27
|
+
`[ServiceLocator] Invalid arguments for ${token.name.toString()}: ${parsed.error}`,
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
let realToken: InjectionToken<T, S> = token
|
|
32
|
+
if (!(token instanceof InjectionToken)) {
|
|
33
|
+
realToken = getInjectableToken(token) as InjectionToken<T, S>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return getServiceLocator().getOrThrowInstance(realToken, args)
|
|
37
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { AnyZodObject } from 'zod'
|
|
2
|
+
|
|
3
|
+
import { randomUUID } from 'crypto'
|
|
4
|
+
|
|
5
|
+
export type ClassType = new (...args: any[]) => any
|
|
6
|
+
|
|
7
|
+
export type ClassTypeWithInstance<T> = new (...args: any[]) => T
|
|
8
|
+
|
|
9
|
+
export class InjectionToken<T, S extends AnyZodObject | unknown = unknown> {
|
|
10
|
+
public id = randomUUID()
|
|
11
|
+
constructor(
|
|
12
|
+
public readonly name: string | symbol | ClassType,
|
|
13
|
+
public readonly schema: AnyZodObject | undefined,
|
|
14
|
+
) {}
|
|
15
|
+
|
|
16
|
+
static create<T extends ClassType>(
|
|
17
|
+
name: T,
|
|
18
|
+
): InjectionToken<InstanceType<T>, undefined>
|
|
19
|
+
static create<T extends ClassType, Schema extends AnyZodObject>(
|
|
20
|
+
name: T,
|
|
21
|
+
schema: Schema,
|
|
22
|
+
): InjectionToken<InstanceType<T>, Schema>
|
|
23
|
+
static create<T>(name: string): InjectionToken<T, undefined>
|
|
24
|
+
static create<T, Schema extends AnyZodObject>(
|
|
25
|
+
name: string,
|
|
26
|
+
schema: Schema,
|
|
27
|
+
): InjectionToken<T, Schema>
|
|
28
|
+
static create(name: string, schema?: unknown) {
|
|
29
|
+
// @ts-expect-error
|
|
30
|
+
return new InjectionToken(name, schema)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
toString() {
|
|
34
|
+
return this.name
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ServiceLocator } from './service-locator.mjs'
|
|
2
|
+
|
|
3
|
+
let serviceLocator: ServiceLocator = new ServiceLocator()
|
|
4
|
+
|
|
5
|
+
export function provideServiceLocator(locator: ServiceLocator): ServiceLocator {
|
|
6
|
+
const original = serviceLocator
|
|
7
|
+
serviceLocator = locator
|
|
8
|
+
return original
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function getServiceLocator(): ServiceLocator {
|
|
12
|
+
if (!serviceLocator) {
|
|
13
|
+
throw new Error(
|
|
14
|
+
'[ServiceLocator] Service locator is not initialized. Please provide the service locator before using the @Injectable decorator.',
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
return serviceLocator
|
|
18
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ClassType, InjectionToken } from './injection-token.mjs'
|
|
2
|
+
|
|
3
|
+
import { getServiceLocator } from './injector.mjs'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Useful for tests or when you want to override a service
|
|
7
|
+
* with a different implementation.
|
|
8
|
+
*/
|
|
9
|
+
export function override<T>(token: InjectionToken<T>, target: ClassType) {
|
|
10
|
+
const serviceLocator = getServiceLocator()
|
|
11
|
+
const originalDefinition = serviceLocator['abstractFactories'].get(token)
|
|
12
|
+
serviceLocator.registerAbstractFactory(token, async (ctx, args: any) => {
|
|
13
|
+
const builder = new target()
|
|
14
|
+
return builder.create(ctx, args)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
return () => {
|
|
18
|
+
if (originalDefinition) {
|
|
19
|
+
serviceLocator.registerAbstractFactory(token, originalDefinition)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|