@navios/core 0.1.2 → 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.2",
3
+ "version": "0.1.4",
4
4
  "author": {
5
5
  "name": "Oleksandr Hanzha",
6
6
  "email": "alex@granted.name"
@@ -4,8 +4,6 @@ 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,
@@ -13,28 +11,27 @@ import {
13
11
  } from '../service-locator/index.mjs'
14
12
  import { ConfigServiceInstance } from './config.service.mjs'
15
13
 
16
- export const ConfigProviderInjectionToken = 'ConfigProvider'
17
-
18
14
  export const ConfigProviderOptions = z.object({
19
15
  load: z.function(),
20
16
  })
17
+
21
18
  export const ConfigProvider = InjectionToken.create<
22
19
  ConfigService,
23
20
  typeof ConfigProviderOptions
24
- >(ConfigProviderInjectionToken, ConfigProviderOptions)
21
+ >(ConfigServiceInstance, ConfigProviderOptions)
25
22
 
26
23
  @Injectable({
27
24
  token: ConfigProvider,
28
25
  type: InjectableType.Factory,
29
26
  })
30
27
  export class ConfigProviderFactory {
31
- logger = inject(Logger, {
28
+ logger = syncInject(Logger, {
32
29
  context: 'ConfigService',
33
30
  })
34
31
 
35
32
  async create(ctx: any, args: z.infer<typeof ConfigProviderOptions>) {
36
33
  const { load } = args
37
- const logger = await this.logger
34
+ const logger = this.logger
38
35
  try {
39
36
  const config = await load()
40
37
 
@@ -46,18 +43,11 @@ export class ConfigProviderFactory {
46
43
  }
47
44
  }
48
45
 
49
- export function makeConfigToken<Config extends Record<string, unknown>>(
46
+ export function provideConfig<ConfigMap extends Record<string, unknown>>(
50
47
  options: z.input<typeof ConfigProviderOptions>,
51
- ): InjectionToken<ConfigService<Config>> {
52
- @Injectable({
53
- type: InjectableType.Factory,
54
- })
55
- class ConfigServiceImpl {
56
- configService = inject(ConfigProvider, options)
57
-
58
- create() {
59
- return this.configService
60
- }
61
- }
62
- return getInjectableToken(ConfigServiceImpl)
48
+ ) {
49
+ return InjectionToken.bound(ConfigProvider, options) as InjectionToken<
50
+ ConfigServiceInstance<ConfigMap>,
51
+ undefined
52
+ >
63
53
  }
@@ -1,7 +1,11 @@
1
- import type { BaseEndpointConfig, EndpointFunctionArgs, HttpMethod } from '@navios/common'
1
+ import type {
2
+ BaseEndpointConfig,
3
+ EndpointFunctionArgs,
4
+ HttpMethod,
5
+ } from '@navios/common'
2
6
  import type { AnyZodObject, z, ZodType } from 'zod'
3
7
 
4
- import { getEndpointMetadata } from '../metadata/index.mjs'
8
+ import { EndpointType, getEndpointMetadata } from '../metadata/index.mjs'
5
9
 
6
10
  export type EndpointParams<
7
11
  EndpointDeclaration extends {
@@ -72,6 +76,7 @@ export function Endpoint<
72
76
  }
73
77
  // @ts-expect-error We don't need to set correctly in the metadata
74
78
  endpointMetadata.config = config
79
+ endpointMetadata.type = EndpointType.Config
75
80
  endpointMetadata.classMethod = target.name
76
81
  endpointMetadata.httpMethod = config.method
77
82
  endpointMetadata.url = config.url
@@ -0,0 +1,18 @@
1
+ import type { HttpHeader } from 'fastify/types/utils.js'
2
+
3
+ import { getEndpointMetadata } from '../metadata/index.mjs'
4
+
5
+ export function Header(name: HttpHeader, value: string | number | string[]) {
6
+ return <T extends Function>(
7
+ target: T,
8
+ context: ClassMethodDecoratorContext,
9
+ ) => {
10
+ if (context.kind !== 'method') {
11
+ throw new Error('[Navios] Header decorator can only be used on methods.')
12
+ }
13
+ const metadata = getEndpointMetadata(target, context)
14
+ metadata.headers[name] = value
15
+
16
+ return target
17
+ }
18
+ }
@@ -0,0 +1,18 @@
1
+ import { getEndpointMetadata } from '../metadata/index.mjs'
2
+
3
+ export function HttpCode(code: number) {
4
+ return <T extends Function>(
5
+ target: T,
6
+ context: ClassMethodDecoratorContext,
7
+ ) => {
8
+ if (context.kind !== 'method') {
9
+ throw new Error(
10
+ '[Navios] HttpCode decorator can only be used on methods.',
11
+ )
12
+ }
13
+ const metadata = getEndpointMetadata(target, context)
14
+ metadata.successStatusCode = code
15
+
16
+ return target
17
+ }
18
+ }
@@ -1,4 +1,6 @@
1
1
  export * from './controller.decorator.mjs'
2
2
  export * from './endpoint.decorator.mjs'
3
+ export * from './header.decorator.mjs'
4
+ export * from './http-code.decorator.mjs'
3
5
  export * from './module.decorator.mjs'
4
6
  export * from './use-guards.decorator.mjs'
@@ -184,7 +184,6 @@ export class LoggerInstance implements LoggerService {
184
184
  }
185
185
 
186
186
  static overrideLogger(logger: LoggerService | LogLevel[] | boolean) {
187
- console.log(logger)
188
187
  if (Array.isArray(logger)) {
189
188
  LoggerInstance.logLevels = logger
190
189
  return this.staticInstanceRef?.setLogLevels?.(logger)
@@ -20,8 +20,9 @@ export class PinoWrapper {
20
20
  this.logger.warn(message, ...optionalParams)
21
21
  }
22
22
 
23
- info(message: any, ...optionalParams: any[]) {
24
- this.logger.log(message, ...optionalParams)
23
+ info() {
24
+ // We don't want to populate the logs with the original fastify logs
25
+ // this.logger.debug?.('INFO', message, ...optionalParams)
25
26
  }
26
27
 
27
28
  debug(message: any, ...optionalParams: any[]) {
@@ -32,7 +33,7 @@ export class PinoWrapper {
32
33
  this.logger.verbose?.(message, ...optionalParams)
33
34
  }
34
35
 
35
- silent(message: any, ...optionalParams: any[]) {
36
+ silent() {
36
37
  // noop
37
38
  }
38
39
 
@@ -56,8 +57,8 @@ export class PinoWrapper {
56
57
  }
57
58
  const levels = LoggerInstance['logLevels']
58
59
  if (levels) {
59
- return levels[0]
60
+ return levels.find((level) => level !== 'verbose')
60
61
  }
61
- return 'info'
62
+ return 'warn'
62
63
  }
63
64
  }
@@ -1,4 +1,5 @@
1
1
  import type { BaseEndpointConfig, HttpMethod } from '@navios/common'
2
+ import type { HttpHeader } from 'fastify/types/utils.js'
2
3
 
3
4
  import type { CanActivate } from '../interfaces/index.mjs'
4
5
  import type {
@@ -8,9 +9,18 @@ import type {
8
9
 
9
10
  export const EndpointMetadataKey = Symbol('EndpointMetadataKey')
10
11
 
12
+ export enum EndpointType {
13
+ Unknown = 'unknown',
14
+ Config = 'config',
15
+ Handler = 'handler',
16
+ }
17
+
11
18
  export interface EndpointMetadata {
12
19
  classMethod: string
13
20
  url: string
21
+ successStatusCode: number
22
+ type: EndpointType
23
+ headers: Partial<Record<HttpHeader, number | string | string[] | undefined>>
14
24
  httpMethod: HttpMethod
15
25
  config: BaseEndpointConfig | null
16
26
  guards: Set<
@@ -52,6 +62,9 @@ export function getEndpointMetadata(
52
62
  const newMetadata: EndpointMetadata = {
53
63
  classMethod: target.name,
54
64
  url: '',
65
+ successStatusCode: 200,
66
+ headers: {},
67
+ type: EndpointType.Unknown,
55
68
  httpMethod: 'GET',
56
69
  config: null,
57
70
  guards: new Set<
@@ -16,6 +16,7 @@ import type { NaviosModule } from './interfaces/index.mjs'
16
16
  import type { LoggerService, LogLevel } from './logger/index.mjs'
17
17
  import type { ClassTypeWithInstance } from './service-locator/index.mjs'
18
18
 
19
+ import { HttpException } from './exceptions/index.mjs'
19
20
  import { Logger, PinoWrapper } from './logger/index.mjs'
20
21
  import {
21
22
  getServiceLocator,
@@ -68,6 +69,7 @@ export class NaviosApplication {
68
69
  }
69
70
  await this.moduleLoader.loadModules(this.appModule)
70
71
  this.server = await this.getFastifyInstance(this.options)
72
+ this.configureFastifyInstance(this.server)
71
73
  getServiceLocator().registerInstance(Application, this.server)
72
74
  // Add schema validator and serializer
73
75
  this.server.setValidatorCompiler(validatorCompiler)
@@ -78,6 +80,7 @@ export class NaviosApplication {
78
80
  }
79
81
 
80
82
  await this.initModules()
83
+ await this.server.ready()
81
84
 
82
85
  this.logger.debug('Navios application initialized')
83
86
  }
@@ -110,6 +113,37 @@ export class NaviosApplication {
110
113
  }
111
114
  }
112
115
 
116
+ private configureFastifyInstance(fastifyInstance: FastifyInstance) {
117
+ fastifyInstance.setErrorHandler((error, request, reply) => {
118
+ if (error instanceof HttpException) {
119
+ return reply.status(error.statusCode).send(error.response)
120
+ } else {
121
+ const statusCode = error.statusCode || 500
122
+ const message = error.message || 'Internal Server Error'
123
+ const response = {
124
+ statusCode,
125
+ message,
126
+ error: error.name || 'InternalServerError',
127
+ }
128
+ this.logger.error(
129
+ `Error occurred: ${error.message} on ${request.url}`,
130
+ error,
131
+ )
132
+ return reply.status(statusCode).send(response)
133
+ }
134
+ })
135
+
136
+ fastifyInstance.setNotFoundHandler((req, reply) => {
137
+ const response = {
138
+ statusCode: 404,
139
+ message: 'Not Found',
140
+ error: 'NotFound',
141
+ }
142
+ this.logger.error(`Route not found: ${req.url}`)
143
+ return reply.status(404).send(response)
144
+ })
145
+ }
146
+
113
147
  private async initModules() {
114
148
  const modules = this.moduleLoader.getAllModules()
115
149
  const promises: PromiseLike<any>[] = []
@@ -161,6 +195,7 @@ export class NaviosApplication {
161
195
  if (!this.server) {
162
196
  throw new Error('Server is not initialized. Call init() first.')
163
197
  }
164
- await this.server.listen(options)
198
+ const res = await this.server.listen(options)
199
+ this.logger.debug(`Navios is listening on ${res}`)
165
200
  }
166
201
  }
@@ -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
  }