diff --git a/core/cli/src/compiler.ts b/core/cli/src/compiler.ts index 59258ac..3b12289 100644 --- a/core/cli/src/compiler.ts +++ b/core/cli/src/compiler.ts @@ -459,9 +459,9 @@ export class Compiler { } case 'string': { const [method, arg] = /^%[a-z0-9_.]+$/i.test(info) - ? ['resolveParameter', info.slice(1, -1)] + ? ['resolve', info.slice(1, -1)] : ['expand', info]; - return [name, `di.${method}('${arg}')`]; + return [name, `di.parameters.${method}('${arg}')`]; } default: return [name, join('.', source, path, 'args', name)]; @@ -506,11 +506,11 @@ export class Compiler { } if ('nestedTypes' in parameters) { - return 'di.getParameters()'; + return 'di.parameters.getAll()'; } const optional = arg.flags & TypeFlag.Optional ? `, false` : ''; - return `di.resolveParameter('${parameters.path}'${optional})`; + return `di.parameters.resolve('${parameters.path}'${optional})`; } private compileServiceInjection(type: Type, flags: TypeFlag, id: string): string { diff --git a/core/cli/src/containerBuilder.ts b/core/cli/src/containerBuilder.ts index a800b52..0ee7f12 100644 --- a/core/cli/src/containerBuilder.ts +++ b/core/cli/src/containerBuilder.ts @@ -2,7 +2,8 @@ import { SourceFile, Type } from 'ts-morph'; import { DefinitionError } from './errors'; import { ContainerOptions, - ContainerParametersInfo, NestedParameterInfo, + ContainerParametersInfo, + NestedParameterInfo, ServiceDecoratorInfo, ServiceDefinitionInfo, ServiceRegistrationInfo, diff --git a/core/cli/src/definitionScanner.ts b/core/cli/src/definitionScanner.ts index 98af5e0..7dbc550 100644 --- a/core/cli/src/definitionScanner.ts +++ b/core/cli/src/definitionScanner.ts @@ -296,10 +296,7 @@ export class DefinitionScanner { if (Node.isStringLiteral(initializer)) { const value = initializer.getLiteralValue(); - - if (/%[a-z0-9_.]+%/i.test(value)) { - args[name] = value; - } + args[name] = /%[a-z0-9_.]+%/i.test(value) ? value : undefined; } else { args[name] = this.resolveCallbackInfo(arg); } diff --git a/core/cli/src/typeHelper.ts b/core/cli/src/typeHelper.ts index d0097c4..9fee549 100644 --- a/core/cli/src/typeHelper.ts +++ b/core/cli/src/typeHelper.ts @@ -74,7 +74,7 @@ export class TypeHelper { if (args.length === 0) { flags |= TypeFlag.Accessor; type = returnType; - } else if (args.length === 1 && returnType.getText() === 'void') { + } else if (args.length === 1 && returnType.isVoid()) { flags |= TypeFlag.Injector; type = args[0].getValueDeclarationOrThrow().getType(); } @@ -86,10 +86,10 @@ export class TypeHelper { [type, flags] = this.resolveNullable(type.getTypeArguments()[0], flags | TypeFlag.Async); } else if (target === this.refs.get('GlobalIterable', SyntaxKind.TypeAliasDeclaration)) { flags |= TypeFlag.Iterable; - type = type.getTypeArguments()[0]; + [type] = type.getTypeArguments(); } else if (target === this.refs.get('GlobalAsyncIterable', SyntaxKind.TypeAliasDeclaration)) { flags |= TypeFlag.Async | TypeFlag.Iterable; - type = type.getTypeArguments()[0]; + [type] = type.getTypeArguments(); } else if (this.isContainer(target)) { flags |= TypeFlag.Container; type = target; diff --git a/core/dicc/src/container.ts b/core/dicc/src/container.ts index 5288077..1a9c4d2 100644 --- a/core/dicc/src/container.ts +++ b/core/dicc/src/container.ts @@ -1,5 +1,6 @@ import { AsyncLocalStorage } from 'async_hooks'; -import { Store } from './store'; +import { ParameterStore } from './parameterStore'; +import { ServiceStore } from './serviceStore'; import { CompiledAsyncServiceDefinition, CompiledServiceDefinition, @@ -9,12 +10,9 @@ import { ContainerParameters, FindResult, GetResult, - InvalidParameterPathError, IterateResult, - Parameter, ServiceMap, ServiceScope, - UnknownParameterError, } from './types'; import { createAsyncIterator, @@ -24,17 +22,17 @@ import { export class Container = {}, Parameters extends ContainerParameters = {}> { readonly [ServiceMap]?: Services; + readonly parameters: ParameterStore; - private readonly parameters: Parameters; private readonly definitions: Map> = new Map(); private readonly aliases: Map = new Map(); - private readonly globalServices: Store = new Store(); - private readonly localServices: AsyncLocalStorage = new AsyncLocalStorage(); + private readonly globalServices: ServiceStore = new ServiceStore(); + private readonly localServices: AsyncLocalStorage = new AsyncLocalStorage(); private readonly forkHooks: [string, CompiledServiceForkHook][] = []; private readonly creating: Set = new Set(); constructor(parameters: Parameters, definitions: CompiledServiceDefinitionMap) { - this.parameters = parameters; + this.parameters = new ParameterStore(parameters); this.importDefinitions(definitions); } @@ -44,15 +42,6 @@ export class Container = {}, Parameters ext return this.getOrCreate(this.resolve(id), need); } - iterate(alias: Id): IterateResult; - iterate(alias: string): Iterable | AsyncIterable { - const ids = this.resolve(alias, false); - const async = ids.some((id) => this.definitions.get(id)?.async); - return async - ? createAsyncIterator(ids, async (id) => this.getOrCreate(id, false)) - : createIterator(ids, (id) => this.getOrCreate(id, false)); - } - find(alias: Id): FindResult; find(alias: string): Promise | any[] { const ids = this.resolve(alias, false); @@ -64,6 +53,15 @@ export class Container = {}, Parameters ext : ids.map((id) => this.getOrCreate(id, false)).filter((service) => service !== undefined); } + iterate(alias: Id): IterateResult; + iterate(alias: string): Iterable | AsyncIterable { + const ids = this.resolve(alias, false); + const async = ids.some((id) => this.definitions.get(id)?.async); + return async + ? createAsyncIterator(ids, async (id) => this.getOrCreate(id, false)) + : createIterator(ids, (id) => this.getOrCreate(id, false)); + } + register(alias: Id, service: Services[Id], force?: boolean): PromiseLike | void; register(alias: string, service: any): PromiseLike | void { const id = this.resolve(alias); @@ -94,81 +92,9 @@ export class Container = {}, Parameters ext } } - getParameters(): Parameters { - return this.parameters; - } - - resolveParameter

(path: P, need?: true): Parameter; - resolveParameter

(path: P, need: false): Parameter | undefined; - resolveParameter

(path: P, need?: boolean): Parameter | undefined { - try { - return this.doResolveParameter(path); - } catch (e: unknown) { - if (e instanceof UnknownParameterError && need === false) { - return undefined; - } - - throw e; - } - } - - expand(value: string, need?: true): string; - expand(value: string, need: false): string | undefined; - expand(value: string, need?: boolean): string | undefined { - try { - return value.replace(/%([a-z0-9_.]+)%/gi, (_, path) => this.doResolveParameter(path)); - } catch (e: unknown) { - if (e instanceof UnknownParameterError && need === false) { - return undefined; - } - - throw e; - } - } - - private doResolveParameter(path: string): any { - const tokens = path.split(/\./g); - let cursor: any = this.parameters; - - for (let i = 0; i < tokens.length; ++i) { - const token = tokens[i]; - - if (typeof cursor !== 'object' || cursor === null) { - throw new InvalidParameterPathError(tokens.slice(0, i).join('.')); - } - - if (!(token in cursor)) { - throw new UnknownParameterError(tokens.slice(0, i).join('.')); - } - - cursor = cursor[token]; - } - - return this.expandTree(cursor); - } - - private expandTree(value: any): any { - if (typeof value === 'string') { - return this.expand(value); - } - - if (Array.isArray(value)) { - return value.map((v) => this.expandTree(v)); - } - - if (typeof value !== 'object' || value === null) { - return value; - } - - return Object.fromEntries(Object.entries(value).map(([k, v]) => [ - this.expand(k), - this.expandTree(v), - ])); - } - async fork(cb: () => Promise): Promise { const parent = this.currentStore; - const store = new Store(parent); + const store = new ServiceStore(parent); const chain = this.forkHooks.reduceRight((next, [id, hook]) => { return async () => { const callback = async (fork?: any) => { @@ -299,11 +225,11 @@ export class Container = {}, Parameters ext return servicePromise; } - private get currentStore(): Store { + private get currentStore(): ServiceStore { return this.localServices.getStore() ?? this.globalServices; } - private getStore(scope: ServiceScope = 'global'): Store | undefined { + private getStore(scope: ServiceScope = 'global'): ServiceStore | undefined { if (scope === 'global') { return this.globalServices; } else if (scope === 'local') { diff --git a/core/dicc/src/parameterStore.ts b/core/dicc/src/parameterStore.ts new file mode 100644 index 0000000..ba4ac6a --- /dev/null +++ b/core/dicc/src/parameterStore.ts @@ -0,0 +1,98 @@ +import { + Parameter, + InvalidParameterPathError, + ParameterExpansionError, + UnknownParameterError, +} from './types'; + +export class ParameterStore = {}> { + private readonly resolving: Set = new Set(); + + constructor( + private readonly parameters: Parameters, + ) {} + + getAll(): Parameters { + return this.expandValue(this.parameters); + } + + resolve

(path: P, need?: true): Parameter; + resolve

(path: P, need: false): Parameter | undefined; + resolve

(path: P, need?: boolean): Parameter | undefined { + try { + return this.resolvePath(path); + } catch (e: unknown) { + if (e instanceof UnknownParameterError && need === false) { + return undefined; + } + + throw e; + } + } + + expand(value: string, need?: true): string; + expand(value: string, need: false): string | undefined; + expand(value: string, need?: boolean): string | undefined { + try { + return this.expandString(value); + } catch (e: unknown) { + if (e instanceof UnknownParameterError && need === false) { + return undefined; + } + + throw e; + } + } + + private resolvePath(path: string): any { + if (this.resolving.has(path)) { + throw new ParameterExpansionError(`Cannot resolve parameter '${path}': cyclic parameter dependency detected`); + } + + const tokens = path.split(/\./g); + let cursor: any = this.parameters; + + for (let i = 0; i < tokens.length; ++i) { + const token = tokens[i]; + + if (typeof cursor !== 'object' || cursor === null) { + throw new InvalidParameterPathError(tokens.slice(0, i).join('.')); + } + + if (!(token in cursor)) { + throw new UnknownParameterError(tokens.slice(0, i).join('.')); + } + + cursor = cursor[token]; + } + + try { + this.resolving.add(path); + return this.expandValue(cursor); + } finally { + this.resolving.delete(path); + } + } + + private expandString(value: string): string { + return value.replace(/%([a-z0-9_.]+)%/gi, (_, path) => this.resolvePath(path)); + } + + private expandValue(value: any): any { + const type = typeof value; + + if (type === 'string') { + return this.expandString(value); + } + + if (Array.isArray(value)) { + return value.map((v) => this.expandValue(v)); + } + + if (type !== 'object' || value === null) { + return value; + } + + return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, this.expandValue(v)])); + } +} diff --git a/core/dicc/src/store.ts b/core/dicc/src/serviceStore.ts similarity index 85% rename from core/dicc/src/store.ts rename to core/dicc/src/serviceStore.ts index abbc2d8..ce3f986 100644 --- a/core/dicc/src/store.ts +++ b/core/dicc/src/serviceStore.ts @@ -1,8 +1,8 @@ -export class Store { +export class ServiceStore { private readonly services: Map = new Map(); - private readonly parent?: Store; + private readonly parent?: ServiceStore; - constructor(parent?: Store) { + constructor(parent?: ServiceStore) { this.parent = parent; } diff --git a/core/dicc/src/types.ts b/core/dicc/src/types.ts index 19c5d59..ebfc563 100644 --- a/core/dicc/src/types.ts +++ b/core/dicc/src/types.ts @@ -65,7 +65,7 @@ export type ServiceType = export const ServiceMap = Symbol('ServiceMap'); export type ForeignServiceType, Id extends string> = - C extends {[ServiceMap]?: infer Map} + C extends { [ServiceMap]?: infer Map } ? Id extends keyof Map ? Map[Id] : never : never;