Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 | 268x 540x 540x 513x 27x 513x 513x 651x 651x 651x 651x 513x 513x 138x 140x 138x 138x 138x 501x 501x 21x 497x 32x 32x 497x 16x 27x 27x 27x 27x 27x 27x 27x 27x 27x 27x 27x 23x 4x | import type { FactoryRecord } from '../../token/registry.mjs'
import type { Injectors } from '../../utils/index.mjs'
import type { ServiceInitializationContext } from '../context/service-initialization-context.mjs'
import { InjectableType } from '../../enums/index.mjs'
import { DIError } from '../../errors/index.mjs'
/**
* Creates service instances from registry records.
*
* Handles both class-based (@Injectable) and factory-based (@Factory) services,
* managing the instantiation lifecycle including lifecycle hook invocation.
*/
export class ServiceInitializer {
constructor(private readonly injectors: Injectors) {}
/**
* Instantiates a service based on its registry record.
* @param ctx The factory context for dependency injection
* @param record The factory record from the registry
* @param args Optional arguments for the service
* @returns Promise resolving to [undefined, instance] or [error]
*/
async instantiateService<T>(
ctx: ServiceInitializationContext,
record: FactoryRecord<T, any>,
args: any = undefined,
): Promise<[undefined, T] | [DIError]> {
try {
switch (record.type) {
case InjectableType.Class:
return this.instantiateClass(ctx, record, args)
case InjectableType.Factory:
return this.instantiateFactory(ctx, record, args)
default:
throw DIError.unknown(
`[ServiceInitializer] Unknown service type: ${record.type}`,
)
}
} catch (error) {
return [
error instanceof DIError
? error
: DIError.initializationError(record.target.name, error as Error),
]
}
}
/**
* Instantiates a class-based service (Injectable decorator).
* @param ctx The factory context for dependency injection
* @param record The factory record from the registry
* @param args Optional arguments for the service constructor
* @returns Promise resolving to [undefined, instance] or [error]
*/
private async instantiateClass<T>(
ctx: ServiceInitializationContext,
record: FactoryRecord<T, any>,
args: any,
): Promise<[undefined, T] | [DIError]> {
try {
const tryLoad = this.injectors.wrapSyncInit(() => {
const original = this.injectors.provideFactoryContext(
ctx as ServiceInitializationContext,
)
let result = new record.target(...(args ? [args] : []))
this.injectors.provideFactoryContext(original)
return result
})
let [instance, promises, injectState] = tryLoad()
if (promises.length > 0) {
const results = await Promise.allSettled(promises)
Iif (results.some((result) => result.status === 'rejected')) {
throw DIError.initializationError(
record.target.name,
new Error('Service cannot be instantiated'),
)
}
const newRes = tryLoad(injectState)
instance = newRes[0]
promises = newRes[1]
}
Iif (promises.length > 0) {
console.error(
`[ServiceInitializer] ${record.target.name} has problem with it's definition.
One or more of the dependencies are registered as a InjectableScope.Transient and are used with inject.
Please use asyncInject instead of inject to load those dependencies.`,
)
throw DIError.initializationError(
record.target.name,
new Error('Service cannot be instantiated'),
)
}
// Handle lifecycle hooks
if ('onServiceInit' in instance) {
await (instance as any).onServiceInit()
}
if ('onServiceDestroy' in instance) {
ctx.addDestroyListener(async () => {
await (instance as any).onServiceDestroy()
})
}
return [undefined, instance]
} catch (error) {
return [
error instanceof DIError
? error
: DIError.initializationError(record.target.name, error as Error),
]
}
}
/**
* Instantiates a factory-based service (Factory decorator).
* @param ctx The factory context for dependency injection
* @param record The factory record from the registry
* @param args Optional arguments for the factory
* @returns Promise resolving to [undefined, instance] or [error]
*/
private async instantiateFactory<T>(
ctx: ServiceInitializationContext,
record: FactoryRecord<T, any>,
args: any,
): Promise<[undefined, T] | [DIError]> {
try {
const tryLoad = this.injectors.wrapSyncInit(() => {
const original = this.injectors.provideFactoryContext(ctx)
let result = new record.target()
this.injectors.provideFactoryContext(original)
return result
})
let [builder, promises, injectState] = tryLoad()
Iif (promises.length > 0) {
const results = await Promise.allSettled(promises)
if (results.some((result) => result.status === 'rejected')) {
throw DIError.initializationError(
record.target.name,
new Error('Service cannot be instantiated'),
)
}
const newRes = tryLoad(injectState)
builder = newRes[0]
promises = newRes[1]
}
Iif (promises.length > 0) {
console.error(
`[ServiceInitializer] ${record.target.name} has problem with it's definition.
One or more of the dependencies are registered as a InjectableScope.Transient and are used with inject.
Please use asyncInject instead of inject to load those dependencies.`,
)
throw DIError.initializationError(
record.target.name,
new Error('Service cannot be instantiated'),
)
}
Iif (typeof builder.create !== 'function') {
throw DIError.initializationError(
record.target.name,
new Error('Factory does not implement the create method'),
)
}
const instance = await builder.create(ctx, args)
return [undefined, instance]
} catch (error) {
return [
error instanceof DIError
? error
: DIError.initializationError(record.target.name, error as Error),
]
}
}
}
|