UNPKG

base-domain

Version:

simple module to help build Domain-Driven Design

384 lines (262 loc) 10.7 kB
fs = require 'fs' Path = require 'path' Path.isAbsolute ?= (str) -> str.charAt(0) is '/' { camelize } = require './util' Facade = require './main' { requireFile } = Facade { Base, BaseModel } = Facade MasterDataResource = require './master-data-resource' class ClassInfo constructor: (@name, @relPath, @className, @moduleName) -> Object.defineProperties @::, modFullName: get: -> if @moduleName @moduleName + '/' + @name else @name fullClassName: get: -> camelize(@moduleName) + @className class EntryGeneratorInput constructor: (facadePath, dirname, outfile) -> @validate(facadePath, dirname, outfile) @absDirname = @absolutePath(dirname) # public data @absOutfilePath = @absolutePath(outfile) @facadePath = @relativePath(facadePath) @coreClasses = @getClassInfoList(@absDirname) @modules = @getModulesClasses() @facadeClassName = requireFile(@absolutePath(facadePath)).name @facade = @createFacade() @masterJSONStr = JSON.stringify @getMasterJSON() @factories = @getPreferredFactoryNames() createFacade: -> allModules = {} for moduleName in @getModuleNames() allModules[moduleName] = Path.join(@absDirname, moduleName) return Facade.createInstance dirname: @absDirname modules: allModules master: true ###* @return {Array(ClassInfo)} ### getClassInfoList: (dirPath, moduleName = '') -> relDirname = @relativePath(dirPath) for filename in @getClassFiles(dirPath) name = filename.split('.')[0] relPath = relDirname + '/' + name className = requireFile(Path.resolve dirPath, name).name new ClassInfo(name, relPath, className, moduleName) ###* @return {{[string]: Array(ClassInfo)}} ### getModulesClasses: -> modules = {} for moduleName in @getModuleNames() modulePath = Path.join(@absDirname, moduleName) modules[moduleName] = @getClassInfoList(modulePath, moduleName) return modules ###* @method getMasterJSON @private @return {Object} master data ### getMasterJSON: -> try { masterJSONPath } = @facade.master return null if not fs.existsSync(masterJSONPath) return require(masterJSONPath) catch e return null ###* @return {Array(string)} array of module names ### getModuleNames: -> fs.readdirSync(@absDirname) .filter (subDirName) -> subDirName isnt 'master-data' .filter (subDirName) -> subDirName isnt 'custom-roles' .map (subDirname) => Path.join @absDirname, subDirname .filter (subDirPath) -> fs.statSync(subDirPath).isDirectory() .filter (subDirPath) -> fs.readdirSync(subDirPath).some (filename) -> klass = requireFile Path.join(subDirPath, filename) klass.isBaseDomainClass .map (subDirPath) -> Path.basename(subDirPath) ###* get domain files to load @method getClassFiles @private @return {Array(string)} filenames ### getClassFiles: (path) -> fileInfoDict = {} for filename in fs.readdirSync(path) [ name, ext ] = filename.split('.') continue if ext not in ['js', 'coffee'] klass = requireFile path + '/' + filename fileInfoDict[name] = filename: filename, klass: klass files = [] for name, fileInfo of fileInfoDict { klass, filename } = fileInfo continue if filename in files ParentClass = Object.getPrototypeOf(klass::).constructor if ParentClass.className and pntFileName = fileInfoDict[ParentClass.getName()]?.filename files.push pntFileName unless pntFileName in files files.push filename return files ###* get entities with no factory ### getPreferredFactoryNames: -> factories = {} for classInfo in @coreClasses factories[classInfo.modFullName] = @getPreferredFactoryName(classInfo) for modName, classes of @modules for classInfo in classes factories[classInfo.modFullName] = @getPreferredFactoryName(classInfo) delete factories[k] for k, v of factories when not v? return factories getPreferredFactoryName: (classInfo) -> ModelClass = @facade.require(classInfo.modFullName) return if (ModelClass::) not instanceof BaseModel try factory = @facade.createPreferredFactory(classInfo.modFullName) return "'#{factory.constructor.className}'" catch e return 'null' ###* validate input data ### validate: (facadePath, dirname, outfile) -> absFacadePath = @absolutePath facadePath absDirname = @absolutePath dirname outDir = Path.dirname(@absolutePath outfile) throw new Error("'#{absFacadePath}' is not found.") if not fs.existsSync(absFacadePath) throw new Error("dirname: '#{absDirname}' is not found.") if not fs.existsSync(absDirname) throw new Error("output directory: '#{outDir}' is not found.") if not fs.existsSync(outDir) absolutePath: (path) -> return Path.resolve(path) if Path.isAbsolute path return Path.resolve process.cwd(), path relativePath: (path) -> relPath = Path.relative(Path.dirname(@absOutfilePath), path) if relPath.charAt(0) isnt '.' relPath = './' + relPath return relPath class EntryGenerator @generate: (facadePath, dirname, outfile, esCode = false) -> input = new EntryGeneratorInput(facadePath, dirname, outfile) if esCode generator = new ESCodeGenerator(input, 'const') else generator = new JSCodeGenerator(input, 'var') generator.generate() constructor: (@input, @declaration) -> generate: -> code = [ @getPragmas() @getImportStatements() @getPackedData() @getExportStatements() ].join('\n') + '\n' fs.writeFileSync(@input.absOutfilePath, code) getPragmas: -> '' getPackedData: -> { factories, coreClasses, modules, masterJSONStr, facadeClassName } = @input """ #{@declaration} packedData = { // eslint-disable-next-line quotes, key-spacing, object-curly-spacing, comma-spacing masterData : #{masterJSONStr}, core: { #{@getPackedCode(coreClasses, 2)}, }, modules: { #{@getModulesPackedData(modules)} }, factories: { #{@getFactoriesPackedData(factories, 2)} } } #{facadeClassName}.prototype.init = function init() { return this.initWithPacked(packedData) } """ getPackedCode: (classes, indent) -> spaces = [0...indent * 4].map((x) -> ' ').join('') spaces + classes.map (classInfo) -> "'#{classInfo.name}': #{classInfo.fullClassName}" .join(',\n' + spaces) getModulesPackedData: (modules) -> _ = ' ' Object.keys(modules).map (modName) => modClasses = modules[modName] """ #{_}'#{modName}': { #{@getPackedCode(modClasses, 3)} #{_}} """ .join(',\n') getFactoriesPackedData: (factories, indent) -> spaces = [0...indent * 4].map((x) -> ' ').join('') spaces + Object.keys(factories).map (modelName) => factoryName = factories[modelName] return "'#{modelName}': #{factoryName}" .join(',\n' + spaces) class JSCodeGenerator extends EntryGenerator getPragmas: -> """ /* eslint quote-props: 0, object-shorthand: 0, no-underscore-dangle: 0 */ #{@declaration} __ = function __(m) { return m.default ? m.default : m } """ getImportStatements: -> { coreClasses, modules, facadePath, facadeClassName } = @input # importing modules code = @getRequireStatement(facadeClassName, facadePath) code += @getRequireStatement(classInfo.className, classInfo.relPath) for classInfo in coreClasses for modName, modClasses of modules code += @getRequireStatement(classInfo.fullClassName, classInfo.relPath) for classInfo in modClasses return code getExportStatements: -> { coreClasses, modules, facadeClassName } = @input classNames = coreClasses.map((coreClass) -> coreClass.className) for modName, modClasses of modules classNames = classNames.concat modClasses.map (modClass) -> modClass.fullClassName keyValues = classNames.map (className) -> "#{className}: #{className}" return """ module.exports = { "default": #{facadeClassName}, #{facadeClassName}: #{facadeClassName}, #{keyValues.join(',\n ')} } """ getRequireStatement: (className, path) -> return "#{@declaration} #{className} = __(require('#{path}'))\n" class ESCodeGenerator extends EntryGenerator getPragmas: -> """ // @flow /* eslint quote-props: 0, max-len: 0 */ """ getImportStatements: -> { coreClasses, modules, facadePath, facadeClassName } = @input code = @getImportStatement(facadeClassName, facadePath) code += @getImportStatement(classInfo.className, classInfo.relPath) for classInfo in coreClasses for modName, modClasses of modules code += @getImportStatement(classInfo.fullClassName, classInfo.relPath) for classInfo in modClasses return code getExportStatements: -> { coreClasses, modules, facadeClassName } = @input classNames = coreClasses.map((coreClass) -> coreClass.className) for modName, modClasses of modules classNames = classNames.concat modClasses.map (modClass) -> modClass.fullClassName return """ export default #{facadeClassName} export { #{facadeClassName}, #{classNames.join(',\n ')} } """ ###* get import statement from className and path ### getImportStatement: (className, path) -> return "import #{className} from '#{path}'\n" module.exports = EntryGenerator