import Plugin from './plugin.js'
/**
* To be recognized as a valid Bajo plugin, your package must be an `ES6 module` and have a `package.json` file
* with the additional `bajo` property and at least the `type` property set to `plugin`.
*
* Other than `type`, you might need to specify these additional properties:
* - `type`: `string`, must be set to `plugin`
* - `alias`: `string`, the alias name of your plugin. Must be unique among all plugins. If it is missing, it will be generated by Bajo as camel-cased version of your plugin namespace.
* - `dependencies`: `array`, if your plugin requires one or more other plugins to work, you need to put their **package names** in this array
* - `appletSupport`: `boolean`, if your plugin doesn't support applet mode, set this to `false` to skip `init()` and `start()` of your plugin
*
* Example:
* ```json
* {
* "name": "my-plugin",
* "version": "1.0.0",
* "description": "My Bajo plugin",
* "type": "module",
* "main": "index.js",
* "bajo": {
* "type": "plugin",
* "alias": "myplugin",
* "dependencies": ["bajo-config", "bajo-cli"]
* }
* }
* ...
* ```
*
* > **Note**: The dot symbol in `package.json` has been replaced with `·` symbol because of JSDoc theme limitation
*
* > **Warning**: Do not confuse with your app's {@link package·json|package.json} specification, since this one is for plugin while the other is for app.
* @memberof Base
* @typedef package·json
*/
/**
* Base class for all plugins.
*
* This is the class you must extend when creating a new plugin. It provides basic methods and properties to manage your plugin.
*
* You need to wrap your plugin in a factory function with a single parameter `pkgName`, which will be called by the Bajo framework
* during boot process. The factory function must return your plugin class.
*
* Don't forget to write `package.json` for your plugin that follows {@link Base.package·json|this specification}. Please do not
* confuse this with your app's {@link package·json|package.json} specification, since this one is for your plugin only.
*
* Example:
* ```js
* // index.js
* async function factory (pkgName) {
* const { Base } = this.app.baseClass // get Base from app's baseClass repository
* const me = this // 'this' is Bajo instance. See Bajo boot process for more details
* class BajoCache extends Base {
* constructor () {
* super(pkgName, me.app)
* this.config = {} // your plugin's configuration object. If omitted, it will be set to an empty object
* }
*
* init = async () => {
* // your plugin's initialization code here
* }
*
* start = async () => {
* // your plugin's start code here
* }
* }
* return BajoCache
* }
* ```
*
* @class
*/
class Base extends Plugin {
/**
* Constructor.
*
* @param {string} pkgName - Package name (the one in package.json)
* @param {Object} app - App instance reference. Useful to call app method inside a plugin
*/
constructor (pkgName, app) {
super(pkgName, app)
/**
* Array of plugin dependencies. If your plugin depends on other plugins, you can specify their package names here e.g. `['bajo-config', 'bajo-cli']`, NOT the plugin name/namespace
* @type {string[]}
*/
this.dependencies = []
this.state = {}
/**
* Package information from `package.json`. It will be automatically loaded by the framework during plugin initialization
* @type {object}
*/
this.pkg = {}
}
/**
* Load plugin configuration. This method will be called by the framework during boot process.
*
* @method
* @async
* @returns {Promise<void>}
*/
loadConfig = async () => {
const { defaultsDeep } = this.app.lib.aneka
const { get, keys, pick, isEmpty, upperFirst, camelCase } = this.app.lib._
const { log, getModuleDir, readAllConfigs } = this.app.bajo
const { parseObject } = this.app.lib
const defKeys = keys(this.config).concat(this.app.getAllNs())
defKeys.push('title')
log.trace('- %s', this.ns)
const dir = this.ns === this.app.mainNs ? (`${this.app.bajo.dir.base}/${this.app.mainNs}`) : getModuleDir(this.pkgName)
let cfg = {}
this.dir = {
pkg: dir,
data: `${this.app.bajo.dir.data}/plugins/${this.ns}`
}
// merge with config from datadir
try {
let altCfg = get(this, `app.options.config.${this.ns}`, {})
if (isEmpty(altCfg)) altCfg = await readAllConfigs(`${this.app.bajo.dir.data}/config/${camelCase(this.ns)}`)
cfg = defaultsDeep({}, altCfg, cfg)
} catch (err) {}
const cfgEnv = get(this, `app.env.${this.ns}`, {})
const cfgArgv = get(this, `app.argv.${this.ns}`, {})
const envValue = this[`config${upperFirst(this.app.bajo.config.env)}`]
cfg = pick(defaultsDeep({}, envValue ?? {}, cfgEnv ?? {}, cfgArgv ?? {}, cfg ?? {}, envValue ?? {}, this.config ?? {}), defKeys)
this.config = parseObject(cfg, { parseValue: true })
}
/**
* Plugin initialization. This method will be called by the framework during boot process, after configuration is loaded.
* @method
* @async
* @returns {Promise<void>}
*/
init = async () => {
}
/**
* Plugin start. This method will be called by the framework during boot process, after initialization. You still can
* modifiy your plugin's configuration here before it is deep frozen.
* @method
* @async
* @returns {Promise<void>}
*/
start = async () => {
}
/**
* Reserved for future use. This method will be called before plugin is stopped.
* @method
* @async
* @returns {Promise<void>}
*/
stop = async () => {
}
/**
* Upon app termination, this method will be called first. Mostly useful for system cleanup,
* delete temporary files, freeing resources etc.
* @method
* @async
* @returns {Promise<void>}
*/
exit = async () => {
await this.dispose()
}
/**
* Dispose internal references.
* @async
* @method
* @returns {Promise<void>}
*/
dispose = async () => {
await super.dispose()
this.state = null
}
}
export default Base