import { isClass } from '@cabloy/utils'; import { parseLastWord, skipLastWord, skipPrefix, splitWords } from '@cabloy/word-utils'; import type { IBeanRecord } from '../../bean/type.ts'; import type { Constructable, Functionable, IBeanSceneRecord, IDecoratorBeanInfoOptions, IDecoratorBeanOptionsBase, IDecoratorUseOptionsBase, } from '../../decorator/index.js'; import type { MetadataKey } from './metadata.ts'; import { registerMappedClassMetadataKey } from '../../mappedClass/utils.ts'; import { cast } from '../../types/utils/cast.ts'; import { uuid } from '../../utils/uuid.ts'; import { appMetadata } from './metadata.ts'; import { getSys } from './sysFake.ts'; import { deepExtend } from './util.ts'; export const DecoratorBeanFullName = Symbol('Decorator#BeanFullName'); export const SymbolDecoratorBeanInfo = Symbol('SymbolDecoratorBeanInfo'); export const SymbolDecoratorProxyDisable = Symbol('SymbolDecoratorProxyDisable'); export const SymbolDecoratorPreload = Symbol('SymbolDecoratorPreload'); export const SymbolDecoratorVirtual = Symbol('SymbolDecoratorVirtual'); export const SymbolDecoratorUse = Symbol('SymbolDecoratorUse'); export const DecoratorBeanFullNameOfComposable = Symbol('Decorator#BeanFullNameOfComposable'); export type IAppResourceRecord = Record; export class AppResource { beans: Record = {}; scenes: Record> = {}; /** @internal */ public dispose() { // just need override state rather than clearing state // this.beans = {}; // this.scenes = {}; } addUse(target: object, options: IDecoratorUseOptionsBase) { registerMappedClassMetadataKey(target, SymbolDecoratorUse); const uses = appMetadata.getOwnMetadataMap(true, SymbolDecoratorUse, target); uses[options.prop] = options; if (process.env.DEV) { if (typeof options.prop === 'string' && !options.prop.startsWith('$$')) { console.error(`inject prop name should start with $$: ${options.prop}`); } } } getUses(target: object): Record | undefined { return appMetadata.getMetadata(SymbolDecoratorUse, target); } addBean(beanOptions: Partial>) { const { beanClass, options, optionsPrimitive } = beanOptions; // virtual const virtual = appMetadata.getOwnMetadata(SymbolDecoratorVirtual, beanClass!); // name const { scene, name } = this._parseSceneAndBeanName(beanClass!, beanOptions.scene, beanOptions.name); // beanInfo const beanInfo = appMetadata.getOwnMetadata(SymbolDecoratorBeanInfo, beanClass!); // module const module = beanInfo?.module; if (!module) throw new Error(`module name not parsed for bean: ${scene}.${name}`); // beanFullName const beanFullName = `${module}.${scene}.${name}`; // moduleBelong const moduleBelong = this._parseModuleBelong(module, beanClass, virtual); // options const options2 = this._prepareOnionOptions(options, optionsPrimitive, scene, `${module}:${name}`); // beanOptions2 const beanOptions2 = { ...beanOptions, module, scene: scene as keyof IBeanSceneRecord, name, beanFullName, moduleBelong, options: options2, } as IDecoratorBeanOptionsBase; // record this.beans[beanFullName] = beanOptions2; if (!this.scenes[scene!]) this.scenes[scene!] = {}; if (!this.scenes[scene!][module]) this.scenes[scene!][module] = {}; this.scenes[scene!][module][beanFullName] = beanOptions2; // set metadata appMetadata.defineMetadata(DecoratorBeanFullName, beanFullName, beanOptions2.beanClass); // ok return beanOptions2; } getBeanFullName(A: Constructable | undefined): string | undefined; getBeanFullName(beanFullName: K | undefined): K | undefined; getBeanFullName(beanFullName: string | undefined): string | undefined; getBeanFullName(beanFullName) { if (!beanFullName) return beanFullName; if (typeof beanFullName === 'function' && isClass(beanFullName)) { return appMetadata.getOwnMetadata(DecoratorBeanFullName, beanFullName); } return beanFullName; } getBeanFullNameOfComposable(beanComposable: Functionable | undefined): string | undefined { if (!beanComposable) return; if (!beanComposable[DecoratorBeanFullNameOfComposable]) { beanComposable[DecoratorBeanFullNameOfComposable] = `__composable__:${uuid()}`; } return beanComposable[DecoratorBeanFullNameOfComposable]; } getBean(A: Constructable): IDecoratorBeanOptionsBase | undefined; getBean(beanFullName: K): IDecoratorBeanOptionsBase | undefined; getBean(beanFullName: string): IDecoratorBeanOptionsBase | undefined; getBean(beanFullName): IDecoratorBeanOptionsBase | undefined { const fullName = this.getBeanFullName(beanFullName); if (!fullName) return null!; return this.beans[fullName] as IDecoratorBeanOptionsBase; } _fixClassName(className: string) { while (className.endsWith('2')) { className = className.substring(0, className.length - 1); } return className; } _parseSceneAndBeanName(beanClass: Constructable, scene?: string, name?: string): { scene: string; name: string } { if (scene && name) { return { scene, name }; } // bean class name let beanClassName = this._fixClassName(beanClass.name); // skip prefix: Bean if (beanClassName.toLowerCase().startsWith('bean')) { beanClassName = beanClassName.substring('bean'.length); } // name if (!name) { if (scene) { name = skipPrefix(beanClassName, scene, true)!; } else { name = parseLastWord(beanClassName, true)!; } } // scene if (!scene) { scene = skipLastWord(beanClassName, name, true)!; scene = splitWords(scene, true, '.')!; } // ok return { scene, name }; } _parseModuleBelong(module, beanClass, virtual) { if (!virtual) return module; // check parent let moduleBelong; let parent = Object.getPrototypeOf(beanClass); while (parent) { const beanOptions = this.getBean(parent); if (beanOptions && beanOptions.moduleBelong) { moduleBelong = beanOptions.moduleBelong; break; } parent = Object.getPrototypeOf(parent); } return moduleBelong; } _getModuleBelong(A: Constructable): string | undefined; _getModuleBelong(beanFullName: K): string | undefined; _getModuleBelong(beanFullName: string): string | undefined; _getModuleBelong(beanFullName: Constructable | string): string | undefined { const beanOptions = this.getBean(beanFullName as any); return beanOptions?.moduleBelong; } _getModuleName(A: Constructable): string | undefined; _getModuleName(beanFullName: K): string | undefined; _getModuleName(beanFullName: string): string | undefined; _getModuleName(beanFullName: Constructable | string): string | undefined { const beanOptions = this.getBean(beanFullName as any); return beanOptions?.module; } _prepareOnionOptions(options: unknown, optionsPrimitive: boolean | undefined, scene: any, name: string) { const sys = getSys(); const optionsConfig = cast(sys.config).onions[scene]?.[name]; if (optionsPrimitive) { return optionsConfig === undefined ? options : optionsConfig; } else { return deepExtend({}, options, optionsConfig); } } } export const appResource = new AppResource();