UNPKG

@adjust/core

Version:

A framework for creating highly customisable open source software

331 lines 14.9 kB
var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const path_1 = __importDefault(require("path")); const fs_1 = __importDefault(require("fs")); const classModuleProvider_1 = require("./moduleProviders/classModuleProvider"); const isMain_1 = require("../utils/isMain"); const ipcMain_1 = require("../communication/ipcMain"); const ipcRenderer_1 = require("../communication/ipcRenderer"); const extendedObject_1 = require("../utils/extendedObject"); const moduleView_1 = require("../module/moduleView"); /** * Keeps track of all modules classes and module providers */ class RegistrySingleton { constructor() { // Stores all the module providers this.moduleProviders = {}; // The module collection folders, which are the locations to load the module from this.collectionFolders = { default: path_1.default.join(process.cwd(), "dist", "modules"), }; } async request(request) { // Normalize the request const normalizedRequest = { type: request.type, use: request.use || "one", data: request.data || {}, parent: request.parent, openView: request.openView || false, }; // Retrieve the providers for this request const providers = await this.getProviders(normalizedRequest); // Retrieve the modules for each of the providers that should be used const modules = providers.map(provider => provider.getModule(normalizedRequest)); // Only return a single module and not an array if use was set to "one" if (normalizedRequest.use == "one") return modules[0]; // Return the retrieved modules return modules; } // Module provider methods /** * Retrieves all the providers for the given request * @param request The request to retrieve the providers for * @returns A list of module providers in sorted order from highest to lowest priority */ async getProviders(request) { // Retrieve the interfaceID const interfaceID = request.type; // Get all providers for this interface let providers = this.moduleProviders[interfaceID.ID]; // Make sure there are any providers if (providers) { // Check if there are any providers if (providers.length == 0) return []; // Get the priorities of all provides let providerPriorities = providers.map(provider => ({ provider: provider, priority: provider.getPriority(request), })); // Filter out any providers with priority 0 providerPriorities = providerPriorities.filter(({ priority }) => priority > 0); // Sort the providers by their priority providerPriorities.sort((a, b) => b.priority - a.priority); // Decide what modules should be used if (request.use instanceof Function) { return request.use(providerPriorities); } else if (request.use == "all") { return providerPriorities.map(({ provider }) => provider); } else { // else request.use == "one" (or is invalid) if (providerPriorities.length == 0) return []; return [providerPriorities[0].provider]; } } // If there are no providers for this type, return an empty array return []; } /** * Adds the provider to the registry * @param provider The provider to add to the registry */ addProvider(provider) { // Retrieve the interfaceID const interfaceID = provider.getType(); // Get all providers for this interface let providers = this.moduleProviders[interfaceID.ID]; // If no providers are present, create the list if (!providers) providers = this.moduleProviders[interfaceID.ID] = []; // Add this provider to the providers providers.push(provider); } /** * Removes the provider from the registry * @param provider The provider to remove * @returns Whether or not the provider was in the registry to start with */ removeProvider(provider) { // Retrieve the interfaceID const interfaceID = provider.getType(); // Get all providers for this interface let providers = this.moduleProviders[interfaceID.ID]; // Make sure there is a list of providers for this type if (providers) { // Remove the provider if present const index = providers.indexOf(provider); if (index != -1) { providers.splice(index, 1); return true; } } // If the if statements above failed, the provider has not been removed return false; } /** * Retrieves a module based on the given request specification to be the root of your application * @param request The request to base the module to retrieve on * @returns The module that was created */ async createRoot(request) { return this.request(Object.assign({ parent: undefined }, request)); } // Interface methods /** * Creates a unique ID for the interface * @param location The location of the interface in string form (use __filename), should be unique * @returns An interface ID for recognizing classes using the interface */ createInterfaceID(location) { return { ID: location, toString: () => location, }; } // /** Keep track of the lowest interface ID that hasn't been used yet */ // protected newInterfaceID: number = 1; // Module loading related methods /** * Retrieves the module object of which Adjust is a depedency * The node module that's not part of adjust (node as in node.js) */ getParentNodeModule() { // Find the last module that's located in the running process const p = process.cwd(); // Define the 'current' module and previous module let m = module; // Go through the parent's until no parent is left, or it's not in the process let reachedProcess = false; while (m && m.parent) { // If the parent's file starts the same as the process, we have reached the process if (m.parent.filename.substring(0, p.length) == p) { reachedProcess = true; // If it doesn't, but we had already reached the process, we surpassed the process } else if (reachedProcess) { return m; } // Go to the parent m = m.parent; } // Return the last found module if we didn't go out of the process return m; } /** * Requires a given path and returns its result * @param collectionName The name of the collection to take the module from * @param path A path that's relative to the modules folder * @returns The exports of the file */ requireModuleFile(collectionName, path) { return require(path_1.default.join(this.collectionFolders[collectionName] || this.collectionFolders.default, path)); } /** * Requires a given path and returns the obtained Module class if present * @param modulePath A collection name followed by relative path, E.G. default/myFolder/myModule * @returns A module class, or undefined */ getModuleClass(modulePath) { // Extract the collection name from the path const dirs = modulePath.split(path_1.default.sep); const collectionName = dirs.shift() || "default"; const path = dirs.join(path_1.default.sep); // Check if the file could be a module if (this.isModulePath(modulePath)) { // Get the contents of the file const exports = this.requireModuleFile(collectionName, path); // Get the default from the exports const def = exports.default; // Check if the default export is a module if (this.isModuleClass(def)) { // Add the path tp the module // @ts-ignore def.path = (collectionName != "default" ? collectionName : "") + path_1.default.sep + path; // Check if the module still needs a view if (def.getConfig().viewClass === undefined) { // Check if a view class was provided in the file, and if so, assign it to the module const viewClass = extendedObject_1.ExtendedObject.find(exports, (exp, k) => k != "default" && exp.prototype instanceof moduleView_1.ModuleView); if (viewClass) def.getConfig().viewClass = viewClass; } // Return the module return def; } } } /** * Maps module classes to module providers * @param moduleClasses The module classes to create module providers for * @returns The created module providers */ createClassModuleProviders(moduleClasses) { return moduleClasses.map(moduleClass => new classModuleProvider_1.ClassModuleProvider(moduleClass.getConfig().type, moduleClass)); } /** * Loads all of the default modules that are available */ loadDefaultClassModuleProviders(filter) { this.loadClassModuleProviders(path_1.default.join(__dirname, "..", "modules"), "Base", filter); } /** * Loads all of the class module providers into the registry * @param folder The folder to load the modules from * @param collectionName The name of the collection you are defining, is "default" by default * @param filter An optional function that decides what module classes to load (return true to be used) */ loadClassModuleProviders(folder = this.collectionFolders.default, collectionName = "default", filter = () => true) { // Store the collection this.collectionFolders[collectionName] = folder; // Obtain all of the module classes let moduleClasses = this.loadModuleClasses(collectionName, filter); // Filter out any module classes without an interface (probably a class to be extended) moduleClasses = moduleClasses.filter(moduleClass => moduleClass.getConfig().type != undefined && !moduleClass.getConfig().abstract); // Create module providers for each of the classes const moduleProviders = this.createClassModuleProviders(moduleClasses); // Add all of the module providers to the registry moduleProviders.forEach(moduleProvider => this.addProvider(moduleProvider)); } /** * Loads all modules from the given collection * @param collectionName The collection to load from * @param filter An optional function that decides what module classes to load (return true to be used) * @returns All the Module classes that could be found */ loadModuleClasses(collectionName = "default", filter = () => true) { // The module classes to return const outModules = []; // The root path to look at const startPath = this.collectionFolders[collectionName]; // A method to go through a single folder at a given path const readDir = path => { // Get and read the files in the directory const files = fs_1.default.readdirSync(path); files.forEach(file => { // Obtain the full path for the file const filePath = path_1.default.join(path, file); // Check if this file is a directory or not, and if it is; recurse if (fs_1.default.lstatSync(filePath).isDirectory()) { readDir(filePath); } else { // Get the file path relative to the modules folder const relativeFilePath = filePath.substring(startPath.length + 1); // Retrieve any possible module class located at this path const moduleClass = this.getModuleClass(path_1.default.join(collectionName, relativeFilePath)); // Check whether or not the class should be used if (!filter(moduleClass)) return; // Add the module to the output if (moduleClass) outModules.push(moduleClass); } }); }; // Start the recursion readDir(startPath); // Return the loaded configs return outModules; } /** * Checks whether a given object (class) is a sub type of the Module class * @param object The object to check * @returns Whether it is a subclass of Module */ isModuleClass(object) { // Go through all calsses in the inherticence chain while (object != null && object instanceof Object) { // If the object is module, it's a module class if (object.name == "Module") return true; // Comparison to Module doesn't work, probably require issues // Check the super class object = object.__proto__; } // If the Module class couldn't be found return false; } /** * Checks the file path to determine whether the file can contain a module * @param path The path to check * @returns Whether or not the file can contain a module */ isModulePath(path) { // TODO: Decide on a file/path convention for modules return !path.match(/\.d\./g) && !!path.match(/\.js$/g); } } exports.RegistrySingleton = RegistrySingleton; exports.Registry = new RegistrySingleton(); // Synchonize collectionFolders between main and sub processes if (isMain_1.isMain) { //@ts-ignore ipcMain_1.IpcMain.on("Registry.getCollections", () => exports.Registry.collectionFolders); } else { // TODO: do a proper fix for test cases (which don't have ipcMain nor ipcRenderer) try { //@ts-ignore exports.Registry.collectionFolders = ipcRenderer_1.IpcRenderer.sendSync("Registry.getCollections")[0]; } catch (e) { } } //# sourceMappingURL=registry.js.map