@navios/core 0.1.3 → 0.1.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@navios/core",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "author": {
5
5
  "name": "Oleksandr Hanzha",
6
6
  "email": "alex@granted.name"
@@ -4,36 +4,34 @@ import type { ConfigService } from './config-service.interface.mjs'
4
4
 
5
5
  import { Logger } from '../logger/index.mjs'
6
6
  import {
7
- getInjectableToken,
8
- inject,
9
7
  Injectable,
10
8
  InjectableType,
11
9
  InjectionToken,
10
+ syncInject,
12
11
  } from '../service-locator/index.mjs'
13
12
  import { ConfigServiceInstance } from './config.service.mjs'
14
13
 
15
- export const ConfigProviderInjectionToken = 'ConfigProvider'
16
-
17
14
  export const ConfigProviderOptions = z.object({
18
15
  load: z.function(),
19
16
  })
17
+
20
18
  export const ConfigProvider = InjectionToken.create<
21
19
  ConfigService,
22
20
  typeof ConfigProviderOptions
23
- >(ConfigProviderInjectionToken, ConfigProviderOptions)
21
+ >(ConfigServiceInstance, ConfigProviderOptions)
24
22
 
25
23
  @Injectable({
26
24
  token: ConfigProvider,
27
25
  type: InjectableType.Factory,
28
26
  })
29
27
  export class ConfigProviderFactory {
30
- logger = inject(Logger, {
28
+ logger = syncInject(Logger, {
31
29
  context: 'ConfigService',
32
30
  })
33
31
 
34
32
  async create(ctx: any, args: z.infer<typeof ConfigProviderOptions>) {
35
33
  const { load } = args
36
- const logger = await this.logger
34
+ const logger = this.logger
37
35
  try {
38
36
  const config = await load()
39
37
 
@@ -45,18 +43,11 @@ export class ConfigProviderFactory {
45
43
  }
46
44
  }
47
45
 
48
- export function makeConfigToken<Config extends Record<string, unknown>>(
46
+ export function provideConfig<ConfigMap extends Record<string, unknown>>(
49
47
  options: z.input<typeof ConfigProviderOptions>,
50
- ): InjectionToken<ConfigService<Config>> {
51
- @Injectable({
52
- type: InjectableType.Factory,
53
- })
54
- class ConfigServiceImpl {
55
- configService = inject(ConfigProvider, options)
56
-
57
- create() {
58
- return this.configService
59
- }
60
- }
61
- return getInjectableToken(ConfigServiceImpl)
48
+ ) {
49
+ return InjectionToken.bound(ConfigProvider, options) as InjectionToken<
50
+ ConfigServiceInstance<ConfigMap>,
51
+ undefined
52
+ >
62
53
  }
@@ -2,11 +2,12 @@ import { describe, expect, it } from 'vitest'
2
2
  import { z } from 'zod'
3
3
 
4
4
  import {
5
+ getInjectableToken,
5
6
  Injectable,
6
7
  InjectableType,
7
8
  } from '../decorators/index.mjs'
8
9
  import { InjectableScope } from '../enums/index.mjs'
9
- import { getInjectableToken, syncInject } from '../index.mjs'
10
+ import { syncInject } from '../index.mjs'
10
11
  import { inject } from '../inject.mjs'
11
12
  import { InjectionToken } from '../injection-token.mjs'
12
13
  import { getServiceLocator } from '../injector.mjs'
@@ -0,0 +1,124 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { z } from 'zod'
3
+
4
+ import { Injectable, InjectableType } from '../decorators/index.mjs'
5
+ import { inject } from '../inject.mjs'
6
+ import { InjectionToken } from '../injection-token.mjs'
7
+
8
+ describe('InjectToken', () => {
9
+ it('should work with class', async () => {
10
+ const token = InjectionToken.create('Test')
11
+ @Injectable({
12
+ token,
13
+ })
14
+ class Test {}
15
+
16
+ const value = await inject(Test)
17
+ expect(value).toBeInstanceOf(Test)
18
+ })
19
+
20
+ it('should work with class and schema', async () => {
21
+ const schema = z.object({
22
+ test: z.string(),
23
+ })
24
+ const token = InjectionToken.create('Test', schema)
25
+
26
+ @Injectable({
27
+ token,
28
+ })
29
+ class Test {
30
+ makeFoo() {
31
+ return 'foo'
32
+ }
33
+ }
34
+ const value = await inject(token, {
35
+ test: 'test',
36
+ })
37
+
38
+ expect(value).toBeInstanceOf(Test)
39
+ })
40
+
41
+ it('should work with factory', async () => {
42
+ const token = InjectionToken.create('Test')
43
+ @Injectable({
44
+ token,
45
+ type: InjectableType.Factory,
46
+ })
47
+ class Test {
48
+ create() {
49
+ return 'foo'
50
+ }
51
+ }
52
+
53
+ const value = await inject(Test)
54
+ expect(value).toBe('foo')
55
+ })
56
+
57
+ it('should work with factory and schema', async () => {
58
+ const schema = z.object({
59
+ test: z.string(),
60
+ })
61
+ const token = InjectionToken.create<string, typeof schema>('Test', schema)
62
+
63
+ @Injectable({
64
+ token,
65
+ type: InjectableType.Factory,
66
+ })
67
+ class Test {
68
+ create(ctx: any, args: { test: string }) {
69
+ return args.test
70
+ }
71
+ }
72
+ const value = await inject(token, {
73
+ test: 'test',
74
+ })
75
+
76
+ expect(value).toBe('test')
77
+ })
78
+
79
+ it('should work with bound token', async () => {
80
+ const schema = z.object({
81
+ test: z.string(),
82
+ })
83
+ const token = InjectionToken.create('Test', schema)
84
+ const boundToken = InjectionToken.bound(token, {
85
+ test: 'test',
86
+ })
87
+
88
+ @Injectable({
89
+ token,
90
+ })
91
+ class Test {
92
+ makeFoo() {
93
+ return 'foo'
94
+ }
95
+ }
96
+ const value = await inject(boundToken)
97
+
98
+ expect(value).toBeInstanceOf(Test)
99
+ })
100
+
101
+ it('should work with factory token', async () => {
102
+ const schema = z.object({
103
+ test: z.string(),
104
+ })
105
+ const token = InjectionToken.create('Test', schema)
106
+ const factoryInjectionToken = InjectionToken.factory(token, () =>
107
+ Promise.resolve({
108
+ test: 'test',
109
+ }),
110
+ )
111
+
112
+ @Injectable({
113
+ token,
114
+ })
115
+ class Test {
116
+ makeFoo() {
117
+ return 'foo'
118
+ }
119
+ }
120
+ const value = await inject(factoryInjectionToken)
121
+
122
+ expect(value).toBeInstanceOf(Test)
123
+ })
124
+ })
@@ -1,12 +1,8 @@
1
- import type {
2
- ClassType,
3
- ClassTypeWithInstance,
4
- InjectionToken,
5
- } from '../injection-token.mjs'
1
+ import type { ClassType, InjectionToken } from '../injection-token.mjs'
6
2
 
7
3
  import { InjectableTokenMeta } from './injectable.decorator.mjs'
8
4
 
9
- export function getInjectableToken<R, T extends ClassTypeWithInstance<R>>(
5
+ export function getInjectableToken<R>(
10
6
  target: ClassType,
11
7
  ): R extends { create(...args: any[]): infer V }
12
8
  ? InjectionToken<V>
@@ -1,10 +1,11 @@
1
+ import { NaviosException } from '@navios/common'
2
+
1
3
  import type { ClassType } from '../injection-token.mjs'
2
4
 
3
5
  import { InjectableScope } from '../enums/index.mjs'
4
6
  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'
7
+ import { getServiceLocator } from '../injector.mjs'
8
+ import { resolveService } from '../resolve-service.mjs'
8
9
 
9
10
  export enum InjectableType {
10
11
  Class = 'Class',
@@ -33,62 +34,22 @@ export function Injectable({
33
34
  let injectableToken: InjectionToken<any, any> =
34
35
  token ?? InjectionToken.create(target)
35
36
  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
37
  if (type === InjectableType.Class) {
42
38
  locator.registerAbstractFactory(
43
39
  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
- },
40
+ async (ctx) => resolveService(ctx, target),
71
41
  scope,
72
42
  )
73
43
  } else if (type === InjectableType.Factory) {
74
44
  locator.registerAbstractFactory(
75
45
  injectableToken,
76
46
  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()
47
+ const builder = await resolveService(ctx, target)
86
48
  if (typeof builder.create !== 'function') {
87
- throw new Error(
49
+ throw new NaviosException(
88
50
  `[ServiceLocator] Factory ${target.name} does not implement the create method.`,
89
51
  )
90
52
  }
91
- provideServiceLocator(original)
92
53
  return builder.create(ctx, args)
93
54
  },
94
55
  scope,
@@ -4,4 +4,5 @@ export enum ErrorsEnum {
4
4
  InstanceDestroying = 'InstanceDestroying',
5
5
  UnknownError = 'UnknownError',
6
6
  FactoryNotFound = 'FactoryNotFound',
7
+ FactoryTokenNotResolved = 'FactoryTokenNotResolved',
7
8
  }
@@ -0,0 +1,10 @@
1
+ import type { ClassType } from '../injection-token.mjs'
2
+
3
+ import { ErrorsEnum } from './errors.enum.mjs'
4
+
5
+ export class FactoryTokenNotResolved extends Error {
6
+ code = ErrorsEnum.FactoryTokenNotResolved
7
+ constructor(name: string | symbol | ClassType) {
8
+ super(`Factory token not resolved: ${name.toString()}`)
9
+ }
10
+ }
@@ -1,5 +1,6 @@
1
1
  export * from './errors.enum.mjs'
2
2
  export * from './factory-not-found.mjs'
3
+ export * from './factory-token-not-resolved.mjs'
3
4
  export * from './instance-destroying.mjs'
4
5
  export * from './instance-expired.mjs'
5
6
  export * from './instance-not-found.mjs'
@@ -12,3 +12,4 @@ export * from './service-locator-event-bus.mjs'
12
12
  export * from './service-locator-instance-holder.mjs'
13
13
  export * from './service-locator-manager.mjs'
14
14
  export * from './sync-injector.mjs'
15
+ export * from './resolve-service.mjs'
@@ -17,26 +17,12 @@ export function inject<T, S extends ZodOptional<AnyZodObject>>(
17
17
  ): Promise<T>
18
18
 
19
19
  export function inject<T>(token: InjectionToken<T, undefined>): Promise<T>
20
- export function inject<
21
- T,
22
- Token extends InjectionToken<T>,
23
- S extends AnyZodObject | unknown = Token['schema'],
24
- >(
25
- token: Token,
26
- args?: S extends AnyZodObject ? z.input<S> : never,
27
- ): Promise<T> {
28
- if (token.schema) {
29
- const parsed = token.schema.safeParse(args)
30
- if (!parsed.success) {
31
- throw new Error(
32
- `[ServiceLocator] Invalid arguments for ${token.name.toString()}: ${parsed.error}`,
33
- )
34
- }
35
- }
36
- let realToken: InjectionToken<T, S> = token
20
+ export function inject(token: InjectionToken<any>, args?: unknown) {
21
+ let realToken = token
37
22
  if (!(token instanceof InjectionToken)) {
38
- realToken = getInjectableToken(token) as InjectionToken<T, S>
23
+ realToken = getInjectableToken(token)
39
24
  }
40
25
 
26
+ // @ts-expect-error We chek the type in overload
41
27
  return getServiceLocator().getOrThrowInstance(realToken, args)
42
28
  }
@@ -2,7 +2,7 @@ import type { AnyZodObject } from 'zod'
2
2
 
3
3
  import { randomUUID } from 'crypto'
4
4
 
5
- import { ZodOptional } from 'zod'
5
+ import { z, ZodOptional } from 'zod'
6
6
 
7
7
  export type ClassType = new (...args: any[]) => any
8
8
 
@@ -25,17 +25,68 @@ export class InjectionToken<
25
25
  T extends ClassType,
26
26
  Schema extends AnyZodObject | ZodOptional<AnyZodObject>,
27
27
  >(name: T, schema: Schema): InjectionToken<InstanceType<T>, Schema>
28
- static create<T>(name: string): InjectionToken<T, undefined>
28
+ static create<T>(name: string | symbol): InjectionToken<T, undefined>
29
29
  static create<T, Schema extends AnyZodObject | ZodOptional<AnyZodObject>>(
30
- name: string,
30
+ name: string | any,
31
31
  schema: Schema,
32
32
  ): InjectionToken<T, Schema>
33
- static create(name: string, schema?: unknown) {
33
+ static create(name: string | symbol, schema?: unknown) {
34
34
  // @ts-expect-error
35
35
  return new InjectionToken(name, schema)
36
36
  }
37
37
 
38
- toString() {
39
- return this.name
38
+ static bound<T, S extends AnyZodObject | ZodOptional<AnyZodObject>>(
39
+ token: InjectionToken<T, S>,
40
+ value: z.input<S>,
41
+ ): BoundInjectionToken<T, S> {
42
+ return new BoundInjectionToken(token, value)
43
+ }
44
+
45
+ static factory<T, S extends AnyZodObject | ZodOptional<AnyZodObject>>(
46
+ token: InjectionToken<T, S>,
47
+ factory: () => Promise<z.input<S>>,
48
+ ): FactoryInjectionToken<T, S> {
49
+ return new FactoryInjectionToken(token, factory)
50
+ }
51
+
52
+ static refineType<T>(
53
+ token: BoundInjectionToken<any, any>,
54
+ ): BoundInjectionToken<T, any> {
55
+ return token as BoundInjectionToken<T, any>
56
+ }
57
+ }
58
+
59
+ export class BoundInjectionToken<
60
+ T,
61
+ S extends AnyZodObject | ZodOptional<AnyZodObject>,
62
+ > extends InjectionToken<T, undefined> {
63
+ constructor(
64
+ public readonly token: InjectionToken<T, S>,
65
+ public readonly value: z.input<S>,
66
+ ) {
67
+ super(token.name, token.schema)
68
+ this.id = token.id
69
+ }
70
+ }
71
+
72
+ export class FactoryInjectionToken<
73
+ T,
74
+ S extends AnyZodObject | ZodOptional<AnyZodObject>,
75
+ > extends InjectionToken<T, S> {
76
+ public value?: z.input<S>
77
+ public resolved = false
78
+ constructor(
79
+ public readonly token: InjectionToken<T, S>,
80
+ public readonly factory: () => Promise<z.input<S>>,
81
+ ) {
82
+ super(token.name, token.schema)
83
+ }
84
+
85
+ async resolve(): Promise<z.input<S>> {
86
+ if (!this.value) {
87
+ this.value = await this.factory()
88
+ this.resolved = true
89
+ }
90
+ return this.value
40
91
  }
41
92
  }
@@ -0,0 +1,46 @@
1
+ import { NaviosException } from '@navios/common'
2
+
3
+ import type { ClassType } from './injection-token.mjs'
4
+ import type { ServiceLocatorAbstractFactoryContext } from './service-locator-abstract-factory-context.mjs'
5
+
6
+ import { getServiceLocator, provideServiceLocator } from './injector.mjs'
7
+ import { makeProxyServiceLocator } from './proxy-service-locator.mjs'
8
+ import { setPromiseCollector } from './sync-injector.mjs'
9
+
10
+ export async function resolveService<T extends ClassType>(
11
+ ctx: ServiceLocatorAbstractFactoryContext,
12
+ target: T,
13
+ args: any[] = [],
14
+ ): Promise<InstanceType<T>> {
15
+ const proxyServiceLocator = makeProxyServiceLocator(getServiceLocator(), ctx)
16
+ let promises: Promise<any>[] = []
17
+ const promiseCollector = (promise: Promise<any>) => {
18
+ promises.push(promise)
19
+ }
20
+ const originalPromiseCollector = setPromiseCollector(promiseCollector)
21
+ const tryLoad = () => {
22
+ const original = provideServiceLocator(proxyServiceLocator)
23
+ let result = new target(...args)
24
+ provideServiceLocator(original)
25
+ return result
26
+ }
27
+ let instance = tryLoad()
28
+ setPromiseCollector(originalPromiseCollector)
29
+ if (promises.length > 0) {
30
+ await Promise.all(promises)
31
+ promises = []
32
+ instance = tryLoad()
33
+ }
34
+ if (promises.length > 0) {
35
+ console.error(`[ServiceLocator] ${target.name} has problem with it's definition.
36
+
37
+ One or more of the dependencies are registered as a InjectableScope.Instance and are used with syncInject.
38
+
39
+ Please use inject instead of syncInject to load those dependencies.`)
40
+ throw new NaviosException(
41
+ `[ServiceLocator] Service ${target.name} cannot be instantiated.`,
42
+ )
43
+ }
44
+
45
+ return instance
46
+ }