import { services } from './service-repository'; import type { ServiceUid } from './types'; const LOG_PREFIX = '[lightweight-sdk:service-registry]'; /** * Property decorator that injects the instance registered under `uid` * (via {@link RegisterService}) into the decorated class member. No generic * type parameter is needed here: a legacy property decorator cannot change * the type of the field it decorates, so the field's own type annotation * (`private readonly logger!: LoggerService;`) is what types the member — * annotate it as usual. * * Resolution is lazy and memoized: the service is looked up in * {@link services} the first time the property is read, not at decoration * time — this avoids depending on module evaluation order between a service * and its consumers. After the first read, the property is replaced by the * resolved value on the instance, so later accesses skip the repository * lookup entirely. * @param uid the unique identifier passed to {@link RegisterService} for the target service * @returns a property decorator * @example * ```ts * class ReportGenerator { * ⁣@Service(LOGGER_UID) * private readonly logger!: LoggerService; * * public run(): void { * this.logger.log('generating report'); * } * } * ``` */ export function Service( uid: ServiceUid, ): (prototype: object, propertyKey: string | symbol) => void { return (prototype: object, propertyKey: string | symbol): void => { Object.defineProperty(prototype, propertyKey, { configurable: true, enumerable: true, get(this: object): unknown { let resolved: unknown; try { resolved = services.get(uid); } catch (error) { console.error( `${LOG_PREFIX} Failed to inject service "${uid}" into member "${String(propertyKey)}".`, error, ); throw error; } Object.defineProperty(this, propertyKey, { value: resolved, enumerable: true, configurable: true, }); return resolved; }, }); }; }