@gasket/core
Advanced tools
Comparing version 7.0.1-canary.12 to 7.0.1
@@ -5,9 +5,16 @@ "use strict"; | ||
}); | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function() { | ||
return _default; | ||
function _export(target, all) { | ||
for(var name in all)Object.defineProperty(target, name, { | ||
enumerable: true, | ||
get: all[name] | ||
}); | ||
} | ||
_export(exports, { | ||
GasketEngine: function() { | ||
return GasketEngine; | ||
}, | ||
lifecycleMethods: function() { | ||
return lifecycleMethods; | ||
} | ||
}); | ||
const _debug = /*#__PURE__*/ _interop_require_default(require("debug")); | ||
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { | ||
@@ -42,18 +49,20 @@ try { | ||
} | ||
function _interop_require_default(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
default: obj | ||
}; | ||
} | ||
const debug = (0, _debug.default)('gasket:engine'); | ||
let dynamicNamingId = 0; | ||
const lifecycleMethods = [ | ||
'exec', | ||
'execSync', | ||
'execWaterfall', | ||
'execWaterfallSync', | ||
'execMap', | ||
'execMapSync', | ||
'execApply', | ||
'execApplySync' | ||
]; | ||
class GasketEngine { | ||
/** | ||
* Resolves plugins | ||
* @param plugins | ||
* @private | ||
*/ _registerPlugins(plugins) { | ||
_registerPlugins(plugins) { | ||
// map the plugin name to module contents for easy lookup | ||
this._pluginMap = plugins.reduce((acc, plugin)=>{ | ||
// TODO: throw if plugin is not an object | ||
if (typeof plugin !== 'object' || Array.isArray(plugin)) { | ||
throw new Error(`Plugin ${plugin.name} must be an object`); | ||
} | ||
const { name, hooks } = plugin; | ||
@@ -66,2 +75,13 @@ if (!name) { | ||
} | ||
// Add base metadata hook if not present | ||
if (!plugin.hooks.metadata) { | ||
plugin.hooks.metadata = /*#__PURE__*/ function() { | ||
var _ref = _async_to_generator(function*(_, metadata) { | ||
return metadata; | ||
}); | ||
return function(_, metadata) { | ||
return _ref.apply(this, arguments); | ||
}; | ||
}(); | ||
} | ||
acc[name] = plugin; | ||
@@ -98,2 +118,20 @@ return acc; | ||
} | ||
_registerActions() { | ||
this.actions = {}; | ||
const actionPluginMap = {}; | ||
Object.entries(this._pluginMap).forEach(([pluginName, plugin])=>{ | ||
const { actions } = plugin; | ||
if (actions) { | ||
Object.keys(actions).forEach((actionName)=>{ | ||
if (actionPluginMap[actionName]) { | ||
// eslint-disable-next-line no-console | ||
console.error(`Action '${actionName}' from '${pluginName}' was registered by '${actionPluginMap[actionName]}'`); | ||
return; | ||
} | ||
actionPluginMap[actionName] = plugin.name; | ||
this.actions[actionName] = actions[actionName]; | ||
}); | ||
} | ||
}); | ||
} | ||
/** | ||
@@ -125,3 +163,5 @@ * Injects additional lifecycle hooks at runtime. | ||
}, | ||
callback: handler.bind(null, this) | ||
invoke: (gasket, ...args)=>{ | ||
return handler(gasket, ...args); | ||
} | ||
}; | ||
@@ -134,2 +174,3 @@ delete this._plans[event]; | ||
* plugins to finish. | ||
* @param {import("@gasket/core").Gasket} gasket - The gasket instance | ||
* @param {string} event The event to execute | ||
@@ -139,7 +180,7 @@ * @param {...*} args Args for hooks | ||
* the order executed | ||
*/ exec(event, ...args) { | ||
*/ exec(gasket, event, ...args) { | ||
return this._execWithCachedPlan({ | ||
event, | ||
type: 'exec', | ||
prepare: (hookConfig, trace)=>{ | ||
prepare: (hookConfig)=>{ | ||
const subscribers = hookConfig.subscribers; | ||
@@ -149,6 +190,7 @@ const executionPlan = []; | ||
this._executeInOrder(hookConfig, (plugin)=>{ | ||
pluginThunks[plugin] = (pluginTasks, ...passedArgs)=>{ | ||
pluginThunks[plugin] = (passedGasket, pluginTasks, ...passedArgs)=>{ | ||
pluginTasks[plugin] = Promise.all(subscribers[plugin].ordering.after.map((dep)=>pluginTasks[dep])).then(()=>{ | ||
trace(plugin); | ||
return subscribers[plugin].callback(...passedArgs); | ||
var _passedGasket_traceHookStart; | ||
(_passedGasket_traceHookStart = passedGasket.traceHookStart) === null || _passedGasket_traceHookStart === void 0 ? void 0 : _passedGasket_traceHookStart.call(passedGasket, plugin, event); | ||
return subscribers[plugin].invoke(passedGasket, ...passedArgs); | ||
}); | ||
@@ -163,3 +205,3 @@ return pluginTasks[plugin]; | ||
const pluginTasks = {}; | ||
return Promise.all(executionPlan.map((fn)=>fn(pluginTasks, ...args))); | ||
return Promise.all(executionPlan.map((fn)=>fn(gasket, pluginTasks, ...args))); | ||
} | ||
@@ -173,2 +215,3 @@ }); | ||
* possible. | ||
* @param {import("@gasket/core").Gasket} gasket - The gasket instance | ||
* @param {string} event The event to execute | ||
@@ -178,13 +221,14 @@ * @param {...*} args Args for hooks | ||
* the order executed | ||
*/ execSync(event, ...args) { | ||
*/ execSync(gasket, event, ...args) { | ||
return this._execWithCachedPlan({ | ||
event, | ||
type: 'execSync', | ||
prepare: (hookConfig, trace)=>{ | ||
prepare: (hookConfig)=>{ | ||
const subscribers = hookConfig.subscribers; | ||
const executionPlan = []; | ||
this._executeInOrder(hookConfig, (plugin)=>{ | ||
executionPlan.push((...execArgs)=>{ | ||
trace(plugin); | ||
return subscribers[plugin].callback(...execArgs); | ||
executionPlan.push((passedGasket, ...execArgs)=>{ | ||
var _passedGasket_traceHookStart; | ||
(_passedGasket_traceHookStart = passedGasket.traceHookStart) === null || _passedGasket_traceHookStart === void 0 ? void 0 : _passedGasket_traceHookStart.call(passedGasket, plugin, event); | ||
return subscribers[plugin].invoke(passedGasket, ...execArgs); | ||
}); | ||
@@ -195,3 +239,3 @@ }); | ||
exec: (executionPlan)=>{ | ||
return executionPlan.map((fn)=>fn(...args)); | ||
return executionPlan.map((fn)=>fn(gasket, ...args)); | ||
} | ||
@@ -205,2 +249,3 @@ }); | ||
* present in the map. | ||
* @param {import("@gasket/core").Gasket} gasket - The gasket instance | ||
* @param {string} event The event to execute | ||
@@ -210,14 +255,15 @@ * @param {...*} args Args for hooks | ||
* the plugin and each value the result from the hook | ||
*/ execMap(event, ...args) { | ||
*/ execMap(gasket, event, ...args) { | ||
return this._execWithCachedPlan({ | ||
event, | ||
type: 'execMap', | ||
prepare: (hookConfig, trace)=>{ | ||
prepare: (hookConfig)=>{ | ||
const subscribers = hookConfig.subscribers; | ||
const executionPlan = {}; | ||
this._executeInOrder(hookConfig, (plugin)=>{ | ||
executionPlan[plugin] = (pluginTasks, ...passedArgs)=>{ | ||
executionPlan[plugin] = (passedGasket, pluginTasks, ...passedArgs)=>{ | ||
pluginTasks[plugin] = Promise.all(subscribers[plugin].ordering.after.map((dep)=>pluginTasks[dep])).then(()=>{ | ||
trace(plugin); | ||
return subscribers[plugin].callback(...passedArgs); | ||
var _passedGasket_traceHookStart; | ||
(_passedGasket_traceHookStart = passedGasket.traceHookStart) === null || _passedGasket_traceHookStart === void 0 ? void 0 : _passedGasket_traceHookStart.call(passedGasket, plugin, event); | ||
return subscribers[plugin].invoke(passedGasket, ...passedArgs); | ||
}); | ||
@@ -233,5 +279,5 @@ return pluginTasks[plugin]; | ||
const resultMap = {}; | ||
yield Promise.all(Object.entries(executionPlan).map(function() { | ||
yield Promise.all(Object.entries(executionPlan).map(/*#__PURE__*/ function() { | ||
var _ref = _async_to_generator(function*([plugin, thunk]) { | ||
resultMap[plugin] = yield thunk(pluginTasks, ...args); | ||
resultMap[plugin] = yield thunk(gasket, pluginTasks, ...args); | ||
}); | ||
@@ -249,2 +295,3 @@ return function(_) { | ||
* Like `execMap`, only all hooks must execute synchronously | ||
* @param {import("@gasket/core").Gasket} gasket - The gasket instance | ||
* @param {string} event The event to execute | ||
@@ -254,13 +301,14 @@ * @param {...*} args Args for hooks | ||
* the plugin and each value the result from the hook | ||
*/ execMapSync(event, ...args) { | ||
*/ execMapSync(gasket, event, ...args) { | ||
return this._execWithCachedPlan({ | ||
event, | ||
type: 'execMapSync', | ||
prepare: (hookConfig, trace)=>{ | ||
prepare: (hookConfig)=>{ | ||
const subscribers = hookConfig.subscribers; | ||
const executionPlan = []; | ||
this._executeInOrder(hookConfig, (plugin)=>{ | ||
executionPlan.push((resultMap, ...passedArgs)=>{ | ||
trace(plugin); | ||
resultMap[plugin] = subscribers[plugin].callback(...passedArgs); | ||
executionPlan.push((passedGasket, resultMap, ...passedArgs)=>{ | ||
var _passedGasket_traceHookStart; | ||
(_passedGasket_traceHookStart = passedGasket.traceHookStart) === null || _passedGasket_traceHookStart === void 0 ? void 0 : _passedGasket_traceHookStart.call(passedGasket, plugin, event); | ||
resultMap[plugin] = subscribers[plugin].invoke(passedGasket, ...passedArgs); | ||
}); | ||
@@ -272,3 +320,3 @@ }); | ||
const resultMap = {}; | ||
executionPlan.forEach((thunk)=>thunk(resultMap, ...args)); | ||
executionPlan.forEach((thunk)=>thunk(gasket, resultMap, ...args)); | ||
return resultMap; | ||
@@ -282,2 +330,3 @@ } | ||
* to the next hook. It's like an asynchronous version of `Array.prototype.reduce`. | ||
* @param {import("@gasket/core").Gasket} gasket - The gasket instance | ||
* @param {string} event The event to execute | ||
@@ -287,14 +336,16 @@ * @param {any} value Value to pass to initial hook | ||
* @returns {Promise} The result of the final executed hook. | ||
*/ execWaterfall(event, value, ...otherArgs) { | ||
*/ execWaterfall(gasket, event, value, ...otherArgs) { | ||
const type = 'execWaterfall'; | ||
return this._execWithCachedPlan({ | ||
event, | ||
type: 'execWaterfall', | ||
prepare: (hookConfig, trace)=>{ | ||
type, | ||
prepare: (hookConfig)=>{ | ||
const subscribers = hookConfig.subscribers; | ||
return (passedValue, ...args)=>{ | ||
return (passedGasket, passedValue, ...args)=>{ | ||
let result = Promise.resolve(passedValue); | ||
this._executeInOrder(hookConfig, (plugin)=>{ | ||
result = result.then((nextValue)=>{ | ||
trace(plugin); | ||
return subscribers[plugin].callback(nextValue, ...args); | ||
var _passedGasket_traceHookStart; | ||
(_passedGasket_traceHookStart = passedGasket.traceHookStart) === null || _passedGasket_traceHookStart === void 0 ? void 0 : _passedGasket_traceHookStart.call(passedGasket, plugin, event); | ||
return subscribers[plugin].invoke(passedGasket, nextValue, ...args); | ||
}); | ||
@@ -306,3 +357,3 @@ }); | ||
exec: (executionPlan)=>{ | ||
return executionPlan(value, ...otherArgs); | ||
return executionPlan(gasket, value, ...otherArgs); | ||
} | ||
@@ -316,2 +367,3 @@ }); | ||
* methods whenever possible. | ||
* @param {import("@gasket/core").Gasket} gasket - The gasket instance | ||
* @param {string} event The event to execute | ||
@@ -321,13 +373,14 @@ * @param {any} value Value to pass to initial hook | ||
* @returns {Promise} The result of the final executed hook. | ||
*/ execWaterfallSync(event, value, ...otherArgs) { | ||
*/ execWaterfallSync(gasket, event, value, ...otherArgs) { | ||
return this._execWithCachedPlan({ | ||
event, | ||
type: 'execWaterfallSync', | ||
prepare: (hookConfig, trace)=>{ | ||
prepare: (hookConfig)=>{ | ||
const subscribers = hookConfig.subscribers; | ||
return (passedValue, ...args)=>{ | ||
return (passedGasket, passedValue, ...args)=>{ | ||
let result = passedValue; | ||
this._executeInOrder(hookConfig, (plugin)=>{ | ||
trace(plugin); | ||
result = subscribers[plugin].callback(result, ...args); | ||
var _passedGasket_traceHookStart; | ||
(_passedGasket_traceHookStart = passedGasket.traceHookStart) === null || _passedGasket_traceHookStart === void 0 ? void 0 : _passedGasket_traceHookStart.call(passedGasket, plugin, event); | ||
result = subscribers[plugin].invoke(passedGasket, result, ...args); | ||
}); | ||
@@ -338,3 +391,3 @@ return result; | ||
exec: (executionPlan)=>{ | ||
return executionPlan(value, ...otherArgs); | ||
return executionPlan(gasket, value, ...otherArgs); | ||
} | ||
@@ -347,2 +400,3 @@ }); | ||
* the plugin itself. | ||
* @param {import("@gasket/core").Gasket} gasket - The gasket instance | ||
* @param {string} event The event to execute | ||
@@ -352,7 +406,7 @@ * @param {Function} applyFn Function to apply | ||
* the order executed | ||
*/ execApply(event, applyFn) { | ||
*/ execApply(gasket, event, applyFn) { | ||
return this._execWithCachedPlan({ | ||
event, | ||
type: 'execApply', | ||
prepare: (hookConfig, trace)=>{ | ||
prepare: (hookConfig)=>{ | ||
const subscribers = hookConfig.subscribers; | ||
@@ -362,6 +416,8 @@ const executionPlan = []; | ||
this._executeInOrder(hookConfig, (plugin)=>{ | ||
pluginThunks[plugin] = (fn, pluginTasks)=>{ | ||
pluginThunks[plugin] = (passedGasket, passedApplyFn, pluginTasks)=>{ | ||
const callback = (...args)=>subscribers[plugin].invoke(passedGasket, ...args); | ||
pluginTasks[plugin] = Promise.all(subscribers[plugin].ordering.after.map((dep)=>pluginTasks[dep])).then(()=>{ | ||
trace(plugin); | ||
return fn(this._pluginMap[plugin], subscribers[plugin].callback); | ||
var _passedGasket_traceHookStart; | ||
(_passedGasket_traceHookStart = passedGasket.traceHookStart) === null || _passedGasket_traceHookStart === void 0 ? void 0 : _passedGasket_traceHookStart.call(passedGasket, plugin, event); | ||
return passedApplyFn(this._pluginMap[plugin], callback); | ||
}); | ||
@@ -376,3 +432,3 @@ return pluginTasks[plugin]; | ||
const pluginTasks = {}; | ||
return Promise.all(executionPlan.map((fn)=>fn(applyFn, pluginTasks))); | ||
return Promise.all(executionPlan.map((fn)=>fn(gasket, applyFn, pluginTasks))); | ||
} | ||
@@ -383,2 +439,3 @@ }); | ||
* Like `execApply`, only all hooks must execute synchronously. | ||
* @param {import("@gasket/core").Gasket} gasket - The gasket instance | ||
* @param {string} event The event to execute | ||
@@ -388,13 +445,15 @@ * @param {Function} applyFn Function to apply | ||
* the order executed | ||
*/ execApplySync(event, applyFn) { | ||
*/ execApplySync(gasket, event, applyFn) { | ||
return this._execWithCachedPlan({ | ||
event, | ||
type: 'execApplySync', | ||
prepare: (hookConfig, trace)=>{ | ||
prepare: (hookConfig)=>{ | ||
const subscribers = hookConfig.subscribers; | ||
const executionPlan = []; | ||
this._executeInOrder(hookConfig, (plugin)=>{ | ||
executionPlan.push((fn)=>{ | ||
trace(plugin); | ||
return fn(this._pluginMap[plugin], subscribers[plugin].callback); | ||
executionPlan.push((passedGasket, passApplyFn)=>{ | ||
var _passedGasket_traceHookStart; | ||
const callback = (...args)=>subscribers[plugin].invoke(passedGasket, ...args); | ||
(_passedGasket_traceHookStart = passedGasket.traceHookStart) === null || _passedGasket_traceHookStart === void 0 ? void 0 : _passedGasket_traceHookStart.call(passedGasket, plugin, event); | ||
return passApplyFn(this._pluginMap[plugin], callback); | ||
}); | ||
@@ -405,3 +464,3 @@ }); | ||
exec: (executionPlan)=>{ | ||
return executionPlan.map((fn)=>fn(applyFn)); | ||
return executionPlan.map((fn)=>fn(gasket, applyFn)); | ||
} | ||
@@ -414,20 +473,12 @@ }); | ||
* @param {object} options options | ||
* @param options.event | ||
* @param options.type | ||
* @param options.prepare | ||
* @param options.exec | ||
* @param {string} options.event - The event to execute | ||
* @param {string} options.type - The type of execution | ||
* @param {Function} options.prepare - Prepare function | ||
* @param {Function} options.exec - Execution function | ||
* @returns {*} result | ||
*/ _execWithCachedPlan({ event, type, prepare, exec }) { | ||
debug(`${' '.repeat(this._traceDepth++)}${type} ${event}`); | ||
const traceDepth = this._traceDepth; | ||
const trace = (plugin)=>debug(`${' '.repeat(traceDepth)}${plugin}:${event}`); | ||
const hookConfig = this._getHookConfig(event); | ||
const plansByType = this._plans[event] || (this._plans[event] = {}); | ||
const plan = plansByType[type] || (plansByType[type] = prepare(hookConfig, trace)); | ||
const result = exec(plan); | ||
if (typeof (result === null || result === void 0 ? void 0 : result.finally) === 'function') { | ||
return result.finally(()=>this._traceDepth--); | ||
} | ||
this._traceDepth--; | ||
return result; | ||
const plan = plansByType[type] || (plansByType[type] = prepare(hookConfig)); | ||
return exec(plan); | ||
} | ||
@@ -520,16 +571,7 @@ /** | ||
this._plans = {}; | ||
this._traceDepth = 0; | ||
this._registerPlugins(plugins); | ||
this._registerHooks(); | ||
this._registerActions(); | ||
// Allow methods to be called without context (to support destructuring) | ||
[ | ||
'exec', | ||
'execWaterfall', | ||
'execMap', | ||
'execApply', | ||
'execSync', | ||
'execWaterfallSync', | ||
'execMapSync', | ||
'execApplySync' | ||
].forEach((method)=>{ | ||
lifecycleMethods.forEach((method)=>{ | ||
this[method] = this[method].bind(this); | ||
@@ -539,2 +581,1 @@ }); | ||
} | ||
const _default = GasketEngine; |
128
cjs/index.js
@@ -1,2 +0,2 @@ | ||
/* eslint-disable no-console, no-process-env */ "use strict"; | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { | ||
@@ -12,127 +12,9 @@ value: true | ||
_export(exports, { | ||
GasketEngine: function() { | ||
return _engine.default; | ||
Gasket: function() { | ||
return _gasket.Gasket; | ||
}, | ||
makeGasket: function() { | ||
return makeGasket; | ||
return _gasket.makeGasket; | ||
} | ||
}); | ||
const _engine = /*#__PURE__*/ _interop_require_default(require("./engine.js")); | ||
const _utils = require("@gasket/utils"); | ||
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { | ||
try { | ||
var info = gen[key](arg); | ||
var value = info.value; | ||
} catch (error) { | ||
reject(error); | ||
return; | ||
} | ||
if (info.done) { | ||
resolve(value); | ||
} else { | ||
Promise.resolve(value).then(_next, _throw); | ||
} | ||
} | ||
function _async_to_generator(fn) { | ||
return function() { | ||
var self = this, args = arguments; | ||
return new Promise(function(resolve, reject) { | ||
var gen = fn.apply(self, args); | ||
function _next(value) { | ||
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); | ||
} | ||
function _throw(err) { | ||
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); | ||
} | ||
_next(undefined); | ||
}); | ||
}; | ||
} | ||
function _interop_require_default(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
default: obj | ||
}; | ||
} | ||
/** | ||
* Get the environment to use for the gasket instance. | ||
* Defaults to `development`. | ||
* @returns {string} env | ||
*/ function getEnvironment() { | ||
// TODO: enable if cli commands and flags are to be used with v7 | ||
// if (flags.env) { | ||
// debug('Environment was passed through command line flags', flags.env); | ||
// return flags.env; | ||
// } | ||
const { GASKET_ENV } = process.env; | ||
if (GASKET_ENV) { | ||
return GASKET_ENV; | ||
} | ||
// TODO: enable if cli commands and flags are to be used with v7 | ||
// // special snowflake case to match up `local` env with command unless set | ||
// if (commandId === 'local') { | ||
// debug('Environment defaulting to `local` due to `local` command'); | ||
// return 'local'; | ||
// } | ||
const { NODE_ENV } = process.env; | ||
if (NODE_ENV) { | ||
console.warn(`No env specified, falling back to NODE_ENV: "${NODE_ENV}".`); | ||
return NODE_ENV; | ||
} | ||
console.warn('No env specified, falling back to "development".'); | ||
return 'development'; | ||
} | ||
/* eslint-enable no-console, no-process-env */ // TODO: Add JSDoc types | ||
/** | ||
* | ||
* @param instance | ||
*/ function registerActions(instance) { | ||
const actions = {}; | ||
const actionPluginMap = {}; | ||
instance.execApplySync('actions', function() { | ||
var _ref = _async_to_generator(function*(plugin, handler) { | ||
const results = handler(); // The gasket parameter is automatically applied | ||
if (results) { | ||
Object.keys(results).forEach((actionName)=>{ | ||
if (actionPluginMap[actionName]) { | ||
// eslint-disable-next-line no-console | ||
console.error(`Action '${actionName}' from '${plugin.name}' was registered by '${actionPluginMap[actionName]}'`); | ||
return; | ||
} | ||
actionPluginMap[actionName] = plugin.name; | ||
actions[actionName] = results[actionName]; | ||
}); | ||
} | ||
}); | ||
return function(plugin, handler) { | ||
return _ref.apply(this, arguments); | ||
}; | ||
}()); | ||
return actions; | ||
} | ||
// TODO: Add JSDoc types | ||
class Gasket extends _engine.default { | ||
constructor(gasketConfig){ | ||
var _config; | ||
const env = getEnvironment(); | ||
const config = (0, _utils.applyConfigOverrides)(gasketConfig, { | ||
env | ||
}); | ||
config.env = env; | ||
var _root; | ||
(_root = (_config = config).root) !== null && _root !== void 0 ? _root : _config.root = process.cwd(); | ||
// start the engine | ||
super(config.plugins); | ||
this.config = config; | ||
this.command = null; | ||
this.execSync('init'); | ||
this.actions = registerActions(this); | ||
this.config = this.execWaterfallSync('configure', config); | ||
} | ||
} | ||
// TODO: Add JSDoc types | ||
/** | ||
* | ||
* @param gasketConfigDefinition | ||
*/ function makeGasket(gasketConfigDefinition) { | ||
return new Gasket(gasketConfigDefinition); | ||
} | ||
const _gasket = require("./gasket.js"); |
@@ -1,8 +0,11 @@ | ||
import debugPkg from 'debug'; | ||
let dynamicNamingId = 0; | ||
const debug = debugPkg('gasket:engine'); | ||
export const lifecycleMethods = [ | ||
'exec', 'execSync', | ||
'execWaterfall', 'execWaterfallSync', | ||
'execMap', 'execMapSync', | ||
'execApply', 'execApplySync' | ||
]; | ||
let dynamicNamingId = 0; | ||
class GasketEngine { | ||
export class GasketEngine { | ||
constructor(plugins) { | ||
@@ -15,19 +18,13 @@ if (!plugins || !Array.isArray(plugins) || !plugins.length) { | ||
this._plans = {}; | ||
this._traceDepth = 0; | ||
this._registerPlugins(plugins); | ||
this._registerHooks(); | ||
this._registerActions(); | ||
// Allow methods to be called without context (to support destructuring) | ||
[ | ||
'exec', 'execWaterfall', 'execMap', 'execApply', | ||
'execSync', 'execWaterfallSync', 'execMapSync', 'execApplySync' | ||
].forEach(method => { this[method] = this[method].bind(this); }); | ||
lifecycleMethods.forEach(method => { | ||
this[method] = this[method].bind(this); | ||
}); | ||
} | ||
/** | ||
* Resolves plugins | ||
* @param plugins | ||
* @private | ||
*/ | ||
_registerPlugins(plugins) { | ||
@@ -38,3 +35,6 @@ | ||
.reduce((acc, plugin) => { | ||
// TODO: throw if plugin is not an object | ||
if (typeof plugin !== 'object' || Array.isArray(plugin)) { | ||
throw new Error(`Plugin ${plugin.name} must be an object`); | ||
} | ||
const { name, hooks } = plugin; | ||
@@ -48,2 +48,7 @@ if (!name) { | ||
// Add base metadata hook if not present | ||
if (!plugin.hooks.metadata) { | ||
plugin.hooks.metadata = async (_, metadata) => metadata; | ||
} | ||
acc[name] = plugin; | ||
@@ -88,2 +93,24 @@ return acc; | ||
_registerActions() { | ||
this.actions = {}; | ||
const actionPluginMap = {}; | ||
Object.entries(this._pluginMap).forEach(([pluginName, plugin]) => { | ||
const { actions } = plugin; | ||
if (actions) { | ||
Object.keys(actions).forEach(actionName => { | ||
if (actionPluginMap[actionName]) { | ||
// eslint-disable-next-line no-console | ||
console.error( | ||
`Action '${actionName}' from '${pluginName}' was registered by '${actionPluginMap[actionName]}'` | ||
); | ||
return; | ||
} | ||
actionPluginMap[actionName] = plugin.name; | ||
this.actions[actionName] = actions[actionName]; | ||
}); | ||
} | ||
}); | ||
} | ||
/** | ||
@@ -117,3 +144,5 @@ * Injects additional lifecycle hooks at runtime. | ||
}, | ||
callback: handler.bind(null, this) | ||
invoke: (gasket, ...args) => { | ||
return handler(gasket, ...args); | ||
} | ||
}; | ||
@@ -128,2 +157,3 @@ | ||
* plugins to finish. | ||
* @param {import("@gasket/core").Gasket} gasket - The gasket instance | ||
* @param {string} event The event to execute | ||
@@ -134,7 +164,7 @@ * @param {...*} args Args for hooks | ||
*/ | ||
exec(event, ...args) { | ||
exec(gasket, event, ...args) { | ||
return this._execWithCachedPlan({ | ||
event, | ||
type: 'exec', | ||
prepare: (hookConfig, trace) => { | ||
prepare: (hookConfig) => { | ||
const subscribers = hookConfig.subscribers; | ||
@@ -144,8 +174,8 @@ const executionPlan = []; | ||
this._executeInOrder(hookConfig, plugin => { | ||
pluginThunks[plugin] = (pluginTasks, ...passedArgs) => { | ||
pluginThunks[plugin] = (passedGasket, pluginTasks, ...passedArgs) => { | ||
pluginTasks[plugin] = Promise | ||
.all(subscribers[plugin].ordering.after.map(dep => pluginTasks[dep])) | ||
.then(() => { | ||
trace(plugin); | ||
return subscribers[plugin].callback(...passedArgs); | ||
passedGasket.traceHookStart?.(plugin, event); | ||
return subscribers[plugin].invoke(passedGasket, ...passedArgs); | ||
}); | ||
@@ -161,3 +191,3 @@ return pluginTasks[plugin]; | ||
const pluginTasks = {}; | ||
return Promise.all(executionPlan.map(fn => fn(pluginTasks, ...args))); | ||
return Promise.all(executionPlan.map(fn => fn(gasket, pluginTasks, ...args))); | ||
} | ||
@@ -172,2 +202,3 @@ }); | ||
* possible. | ||
* @param {import("@gasket/core").Gasket} gasket - The gasket instance | ||
* @param {string} event The event to execute | ||
@@ -178,13 +209,13 @@ * @param {...*} args Args for hooks | ||
*/ | ||
execSync(event, ...args) { | ||
execSync(gasket, event, ...args) { | ||
return this._execWithCachedPlan({ | ||
event, | ||
type: 'execSync', | ||
prepare: (hookConfig, trace) => { | ||
prepare: (hookConfig) => { | ||
const subscribers = hookConfig.subscribers; | ||
const executionPlan = []; | ||
this._executeInOrder(hookConfig, plugin => { | ||
executionPlan.push((...execArgs) => { | ||
trace(plugin); | ||
return subscribers[plugin].callback(...execArgs); | ||
executionPlan.push((passedGasket, ...execArgs) => { | ||
passedGasket.traceHookStart?.(plugin, event); | ||
return subscribers[plugin].invoke(passedGasket, ...execArgs); | ||
}); | ||
@@ -196,3 +227,3 @@ }); | ||
exec: executionPlan => { | ||
return executionPlan.map(fn => fn(...args)); | ||
return executionPlan.map(fn => fn(gasket, ...args)); | ||
} | ||
@@ -207,2 +238,3 @@ }); | ||
* present in the map. | ||
* @param {import("@gasket/core").Gasket} gasket - The gasket instance | ||
* @param {string} event The event to execute | ||
@@ -213,16 +245,16 @@ * @param {...*} args Args for hooks | ||
*/ | ||
execMap(event, ...args) { | ||
execMap(gasket, event, ...args) { | ||
return this._execWithCachedPlan({ | ||
event, | ||
type: 'execMap', | ||
prepare: (hookConfig, trace) => { | ||
prepare: (hookConfig) => { | ||
const subscribers = hookConfig.subscribers; | ||
const executionPlan = {}; | ||
this._executeInOrder(hookConfig, plugin => { | ||
executionPlan[plugin] = (pluginTasks, ...passedArgs) => { | ||
executionPlan[plugin] = (passedGasket, pluginTasks, ...passedArgs) => { | ||
pluginTasks[plugin] = Promise | ||
.all(subscribers[plugin].ordering.after.map(dep => pluginTasks[dep])) | ||
.then(() => { | ||
trace(plugin); | ||
return subscribers[plugin].callback(...passedArgs); | ||
passedGasket.traceHookStart?.(plugin, event); | ||
return subscribers[plugin].invoke(passedGasket, ...passedArgs); | ||
}); | ||
@@ -242,3 +274,3 @@ return pluginTasks[plugin]; | ||
.map(async ([plugin, thunk]) => { | ||
resultMap[plugin] = await thunk(pluginTasks, ...args); | ||
resultMap[plugin] = await thunk(gasket, pluginTasks, ...args); | ||
}) | ||
@@ -253,2 +285,3 @@ ); | ||
* Like `execMap`, only all hooks must execute synchronously | ||
* @param {import("@gasket/core").Gasket} gasket - The gasket instance | ||
* @param {string} event The event to execute | ||
@@ -259,13 +292,13 @@ * @param {...*} args Args for hooks | ||
*/ | ||
execMapSync(event, ...args) { | ||
execMapSync(gasket, event, ...args) { | ||
return this._execWithCachedPlan({ | ||
event, | ||
type: 'execMapSync', | ||
prepare: (hookConfig, trace) => { | ||
prepare: (hookConfig) => { | ||
const subscribers = hookConfig.subscribers; | ||
const executionPlan = []; | ||
this._executeInOrder(hookConfig, plugin => { | ||
executionPlan.push((resultMap, ...passedArgs) => { | ||
trace(plugin); | ||
resultMap[plugin] = subscribers[plugin].callback(...passedArgs); | ||
executionPlan.push((passedGasket, resultMap, ...passedArgs) => { | ||
passedGasket.traceHookStart?.(plugin, event); | ||
resultMap[plugin] = subscribers[plugin].invoke(passedGasket, ...passedArgs); | ||
}); | ||
@@ -278,3 +311,3 @@ }); | ||
const resultMap = {}; | ||
executionPlan.forEach(thunk => thunk(resultMap, ...args)); | ||
executionPlan.forEach(thunk => thunk(gasket, resultMap, ...args)); | ||
return resultMap; | ||
@@ -289,2 +322,3 @@ } | ||
* to the next hook. It's like an asynchronous version of `Array.prototype.reduce`. | ||
* @param {import("@gasket/core").Gasket} gasket - The gasket instance | ||
* @param {string} event The event to execute | ||
@@ -295,10 +329,11 @@ * @param {any} value Value to pass to initial hook | ||
*/ | ||
execWaterfall(event, value, ...otherArgs) { | ||
execWaterfall(gasket, event, value, ...otherArgs) { | ||
const type = 'execWaterfall'; | ||
return this._execWithCachedPlan({ | ||
event, | ||
type: 'execWaterfall', | ||
prepare: (hookConfig, trace) => { | ||
type, | ||
prepare: (hookConfig) => { | ||
const subscribers = hookConfig.subscribers; | ||
return (passedValue, ...args) => { | ||
return (passedGasket, passedValue, ...args) => { | ||
let result = Promise.resolve(passedValue); | ||
@@ -308,4 +343,4 @@ | ||
result = result.then((nextValue) => { | ||
trace(plugin); | ||
return subscribers[plugin].callback(nextValue, ...args); | ||
passedGasket.traceHookStart?.(plugin, event); | ||
return subscribers[plugin].invoke(passedGasket, nextValue, ...args); | ||
}); | ||
@@ -317,4 +352,4 @@ }); | ||
}, | ||
exec: executionPlan => { | ||
return executionPlan(value, ...otherArgs); | ||
exec: (executionPlan) => { | ||
return executionPlan(gasket, value, ...otherArgs); | ||
} | ||
@@ -329,2 +364,3 @@ }); | ||
* methods whenever possible. | ||
* @param {import("@gasket/core").Gasket} gasket - The gasket instance | ||
* @param {string} event The event to execute | ||
@@ -335,15 +371,15 @@ * @param {any} value Value to pass to initial hook | ||
*/ | ||
execWaterfallSync(event, value, ...otherArgs) { | ||
execWaterfallSync(gasket, event, value, ...otherArgs) { | ||
return this._execWithCachedPlan({ | ||
event, | ||
type: 'execWaterfallSync', | ||
prepare: (hookConfig, trace) => { | ||
prepare: (hookConfig) => { | ||
const subscribers = hookConfig.subscribers; | ||
return (passedValue, ...args) => { | ||
return (passedGasket, passedValue, ...args) => { | ||
let result = passedValue; | ||
this._executeInOrder(hookConfig, plugin => { | ||
trace(plugin); | ||
result = subscribers[plugin].callback(result, ...args); | ||
passedGasket.traceHookStart?.(plugin, event); | ||
result = subscribers[plugin].invoke(passedGasket, result, ...args); | ||
}); | ||
@@ -355,3 +391,3 @@ | ||
exec: executionPlan => { | ||
return executionPlan(value, ...otherArgs); | ||
return executionPlan(gasket, value, ...otherArgs); | ||
} | ||
@@ -365,2 +401,3 @@ }); | ||
* the plugin itself. | ||
* @param {import("@gasket/core").Gasket} gasket - The gasket instance | ||
* @param {string} event The event to execute | ||
@@ -371,7 +408,7 @@ * @param {Function} applyFn Function to apply | ||
*/ | ||
execApply(event, applyFn) { | ||
execApply(gasket, event, applyFn) { | ||
return this._execWithCachedPlan({ | ||
event, | ||
type: 'execApply', | ||
prepare: (hookConfig, trace) => { | ||
prepare: (hookConfig) => { | ||
const subscribers = hookConfig.subscribers; | ||
@@ -381,8 +418,9 @@ const executionPlan = []; | ||
this._executeInOrder(hookConfig, plugin => { | ||
pluginThunks[plugin] = (fn, pluginTasks) => { | ||
pluginThunks[plugin] = (passedGasket, passedApplyFn, pluginTasks) => { | ||
const callback = (...args) => subscribers[plugin].invoke(passedGasket, ...args); | ||
pluginTasks[plugin] = Promise | ||
.all(subscribers[plugin].ordering.after.map(dep => pluginTasks[dep])) | ||
.then(() => { | ||
trace(plugin); | ||
return fn(this._pluginMap[plugin], subscribers[plugin].callback); | ||
passedGasket.traceHookStart?.(plugin, event); | ||
return passedApplyFn(this._pluginMap[plugin], callback); | ||
}); | ||
@@ -398,3 +436,3 @@ return pluginTasks[plugin]; | ||
const pluginTasks = {}; | ||
return Promise.all(executionPlan.map(fn => fn(applyFn, pluginTasks))); | ||
return Promise.all(executionPlan.map(fn => fn(gasket, applyFn, pluginTasks))); | ||
} | ||
@@ -406,2 +444,3 @@ }); | ||
* Like `execApply`, only all hooks must execute synchronously. | ||
* @param {import("@gasket/core").Gasket} gasket - The gasket instance | ||
* @param {string} event The event to execute | ||
@@ -412,13 +451,14 @@ * @param {Function} applyFn Function to apply | ||
*/ | ||
execApplySync(event, applyFn) { | ||
execApplySync(gasket, event, applyFn) { | ||
return this._execWithCachedPlan({ | ||
event, | ||
type: 'execApplySync', | ||
prepare: (hookConfig, trace) => { | ||
prepare: (hookConfig) => { | ||
const subscribers = hookConfig.subscribers; | ||
const executionPlan = []; | ||
this._executeInOrder(hookConfig, plugin => { | ||
executionPlan.push(fn => { | ||
trace(plugin); | ||
return fn(this._pluginMap[plugin], subscribers[plugin].callback); | ||
this._executeInOrder(hookConfig, (plugin) => { | ||
executionPlan.push((passedGasket, passApplyFn) => { | ||
const callback = (...args) => subscribers[plugin].invoke(passedGasket, ...args); | ||
passedGasket.traceHookStart?.(plugin, event); | ||
return passApplyFn(this._pluginMap[plugin], callback); | ||
}); | ||
@@ -429,3 +469,3 @@ }); | ||
exec: executionPlan => { | ||
return executionPlan.map(fn => fn(applyFn)); | ||
return executionPlan.map(fn => fn(gasket, applyFn)); | ||
} | ||
@@ -439,13 +479,9 @@ }); | ||
* @param {object} options options | ||
* @param options.event | ||
* @param options.type | ||
* @param options.prepare | ||
* @param options.exec | ||
* @param {string} options.event - The event to execute | ||
* @param {string} options.type - The type of execution | ||
* @param {Function} options.prepare - Prepare function | ||
* @param {Function} options.exec - Execution function | ||
* @returns {*} result | ||
*/ | ||
_execWithCachedPlan({ event, type, prepare, exec }) { | ||
debug(`${' '.repeat(this._traceDepth++)}${type} ${event}`); | ||
const traceDepth = this._traceDepth; | ||
const trace = plugin => debug(`${' '.repeat(traceDepth)}${plugin}:${event}`); | ||
const hookConfig = this._getHookConfig(event); | ||
@@ -456,11 +492,6 @@ const plansByType = this._plans[event] || ( | ||
const plan = plansByType[type] || ( | ||
plansByType[type] = prepare(hookConfig, trace) | ||
plansByType[type] = prepare(hookConfig) | ||
); | ||
const result = exec(plan); | ||
if (typeof result?.finally === 'function') { | ||
return result.finally(() => this._traceDepth--); | ||
} | ||
this._traceDepth--; | ||
return result; | ||
return exec(plan); | ||
} | ||
@@ -560,3 +591,1 @@ | ||
} | ||
export default GasketEngine; |
@@ -8,2 +8,10 @@ declare module '@gasket/core' { | ||
export type ActionId = keyof GasketActions; | ||
export type ActionHandler<Id extends ActionId> = ( | ||
gasket: Gasket, | ||
...args: Parameters<GasketActions[Id]> | ||
) => ReturnType<GasketActions[Id]>; | ||
// To be extended by plugins | ||
@@ -13,4 +21,4 @@ export interface HookExecTypes { | ||
init(): void | ||
actions(): Partial<GasketActions> | ||
configure(config: GasketConfig): GasketConfig | ||
ready(): MaybeAsync<void> | ||
} | ||
@@ -45,2 +53,4 @@ | ||
name: string; | ||
version?: string; | ||
description?: string; | ||
dependencies?: Array<string>; | ||
@@ -50,4 +60,10 @@ hooks: { | ||
}; | ||
actions?: { | ||
[K in ActionId]?: ActionHandler<K>; | ||
}; | ||
metadata?: Record<string, any>; | ||
}; | ||
export type Preset = Omit<Plugin, 'actions'>; | ||
// This is the config | ||
@@ -63,2 +79,4 @@ export interface GasketConfig { | ||
actions: GasketActions | ||
exec<Id extends HookId>( | ||
@@ -75,7 +93,7 @@ hook: Id, | ||
...args: Parameters<HookExecTypes[Id]> | ||
): ReturnType<HookExecTypes[Id]>; | ||
): Promise<ResolvedType<ReturnType<HookExecTypes[Id]>>>; | ||
execWaterfallSync<Id extends HookId>( | ||
hook: Id, | ||
...args: Parameters<HookExecTypes[Id]> | ||
): ReturnType<HookExecTypes[Id]>; | ||
): ResolvedType<ReturnType<HookExecTypes[Id]>>; | ||
execApply<Id extends HookId, Return = void>( | ||
@@ -102,2 +120,5 @@ hook: Id, | ||
export interface Gasket extends GasketEngine { | ||
constructor(config: GasketConfigDefinition); | ||
new (config: GasketConfigDefinition): Gasket | ||
command: { | ||
@@ -107,6 +128,9 @@ id: string; | ||
config: GasketConfig; | ||
new (config: GasketConfigDefinition): Gasket | ||
actions: GasketActions | ||
symbol: Symbol; | ||
traceBranch(): GasketTrace | ||
traceRoot(): Gasket | ||
} | ||
export type GasketTrace = Proxy<Gasket>; | ||
type PartialRecursive<T> = T extends Object | ||
@@ -134,2 +158,1 @@ ? { [K in keyof T]?: PartialRecursive<T[K]> } | undefined | ||
} | ||
103
lib/index.js
@@ -1,103 +0,6 @@ | ||
/* eslint-disable no-console, no-process-env */ | ||
import { Gasket, makeGasket } from './gasket.js'; | ||
import GasketEngine from './engine.js'; | ||
import { applyConfigOverrides } from '@gasket/utils'; | ||
/** | ||
* Get the environment to use for the gasket instance. | ||
* Defaults to `development`. | ||
* @returns {string} env | ||
*/ | ||
function getEnvironment( | ||
// flags, commandId, warn | ||
) { | ||
// TODO: enable if cli commands and flags are to be used with v7 | ||
// if (flags.env) { | ||
// debug('Environment was passed through command line flags', flags.env); | ||
// return flags.env; | ||
// } | ||
const { GASKET_ENV } = process.env; | ||
if (GASKET_ENV) { | ||
return GASKET_ENV; | ||
} | ||
// TODO: enable if cli commands and flags are to be used with v7 | ||
// // special snowflake case to match up `local` env with command unless set | ||
// if (commandId === 'local') { | ||
// debug('Environment defaulting to `local` due to `local` command'); | ||
// return 'local'; | ||
// } | ||
const { NODE_ENV } = process.env; | ||
if (NODE_ENV) { | ||
console.warn(`No env specified, falling back to NODE_ENV: "${NODE_ENV}".`); | ||
return NODE_ENV; | ||
} | ||
console.warn('No env specified, falling back to "development".'); | ||
return 'development'; | ||
} | ||
/* eslint-enable no-console, no-process-env */ | ||
// TODO: Add JSDoc types | ||
/** | ||
* | ||
* @param instance | ||
*/ | ||
function registerActions(instance) { | ||
const actions = {}; | ||
const actionPluginMap = {}; | ||
instance.execApplySync('actions', async (plugin, handler) => { | ||
const results = handler(); // The gasket parameter is automatically applied | ||
if (results) { | ||
Object.keys(results).forEach(actionName => { | ||
if (actionPluginMap[actionName]) { | ||
// eslint-disable-next-line no-console | ||
console.error( | ||
`Action '${actionName}' from '${plugin.name}' was registered by '${actionPluginMap[actionName]}'` | ||
); | ||
return; | ||
} | ||
actionPluginMap[actionName] = plugin.name; | ||
actions[actionName] = results[actionName]; | ||
}); | ||
} | ||
}); | ||
return actions; | ||
} | ||
// TODO: Add JSDoc types | ||
class Gasket extends GasketEngine { | ||
constructor(gasketConfig) { | ||
const env = getEnvironment(); | ||
const config = applyConfigOverrides(gasketConfig, { env }); | ||
config.env = env; | ||
config.root ??= process.cwd(); | ||
// start the engine | ||
super(config.plugins); | ||
this.config = config; | ||
this.command = null; | ||
this.execSync('init'); | ||
this.actions = registerActions(this); | ||
this.config = this.execWaterfallSync('configure', config); | ||
} | ||
} | ||
// TODO: Add JSDoc types | ||
/** | ||
* | ||
* @param gasketConfigDefinition | ||
*/ | ||
function makeGasket(gasketConfigDefinition) { | ||
return new Gasket(gasketConfigDefinition); | ||
} | ||
export { | ||
makeGasket, | ||
GasketEngine | ||
Gasket, | ||
makeGasket | ||
}; |
{ | ||
"name": "@gasket/core", | ||
"version": "7.0.1-canary.12", | ||
"version": "7.0.1", | ||
"description": "Entry point to setting up Gasket instances", | ||
"type": "module", | ||
"types": "lib/index.d.ts", | ||
"files": [ | ||
"lib", | ||
"cjs", | ||
"docs" | ||
], | ||
"exports": { | ||
@@ -10,11 +17,6 @@ ".": { | ||
"types": "./lib/index.d.ts" | ||
} | ||
}, | ||
"./package.json": "./package.json" | ||
}, | ||
"types": "lib/index.d.ts", | ||
"files": [ | ||
"lib", | ||
"cjs" | ||
], | ||
"scripts": { | ||
"build": "swc lib -d cjs --delete-dir-on-start --strip-leading-paths", | ||
"lint": "eslint .", | ||
@@ -28,2 +30,4 @@ "lint:fix": "npm run lint -- --fix", | ||
"typecheck:watch": "tsc --watch", | ||
"build": "swc lib -d cjs --delete-dir-on-start --strip-leading-paths", | ||
"postbuild": "node -e \"require('fs').writeFileSync('cjs/package.json', '{}')\"", | ||
"prepublishOnly": "npm run build" | ||
@@ -45,6 +49,2 @@ }, | ||
"author": "GoDaddy Operating Company, LLC", | ||
"maintainers": [ | ||
"Jacob Page <jpage@godaddy.com>", | ||
"Charlie Robbins <charlie.robbins@gmail.com>" | ||
], | ||
"license": "MIT", | ||
@@ -54,3 +54,3 @@ "bugs": { | ||
}, | ||
"homepage": "https://github.com/godaddy/gasket/tree/main/packages/gasket-engine", | ||
"homepage": "https://github.com/godaddy/gasket/tree/main/packages/gasket-core", | ||
"dependencies": { | ||
@@ -67,8 +67,8 @@ "debug": "^4.3.4" | ||
"eslint": "^8.56.0", | ||
"eslint-config-godaddy": "^7.1.0", | ||
"eslint-plugin-jest": "^27.6.3", | ||
"eslint-config-godaddy": "^7.1.1", | ||
"eslint-plugin-jest": "^28.6.0", | ||
"eslint-plugin-json": "^3.1.0", | ||
"eslint-plugin-unicorn": "^44.0.0", | ||
"eslint-plugin-unicorn": "^55.0.0", | ||
"jest": "^29.7.0", | ||
"typescript": "^5.4.4" | ||
"typescript": "^5.4.5" | ||
}, | ||
@@ -92,6 +92,15 @@ "eslintConfig": { | ||
"no-sync": 0 | ||
} | ||
}, | ||
"overrides": [ | ||
{ | ||
"files": [ | ||
"test/**/*.js" | ||
], | ||
"rules": { | ||
"jsdoc/require-jsdoc": "off" | ||
} | ||
} | ||
] | ||
}, | ||
"type": "module", | ||
"gitHead": "30055cdc6bb04a78c07c5ede230242db17327260" | ||
"gitHead": "0426b7f57bc2618606955912c0f3cd55174f5367" | ||
} |
221
README.md
@@ -11,3 +11,3 @@ # @gasket/core | ||
Add a `gasket.mjs` file to the root of your project. | ||
Add a `gasket.js` file to the root of your project. | ||
This can be a `.js` extension if your package.json has the `type` field set to `module`. | ||
@@ -17,3 +17,3 @@ It is also possible to use with a `.ts` extension if you have TypeScript configured. | ||
```js | ||
// gasket.mjs | ||
// gasket.js | ||
import { makeGasket } from '@gasket/core'; | ||
@@ -31,3 +31,3 @@ import LoggerPlugin from '@gasket/plugin-logger'; | ||
You can now import the Gasket instance from your `gasket.mjs` file into your | ||
You can now import the Gasket instance from your `gasket.js` file into your | ||
application code. | ||
@@ -41,3 +41,3 @@ With a Gasket, you can fire **actions** that will trigger **lifecycles** hooked | ||
(See [Plugins Guide]). | ||
In your `gasket.mjs` file, you can import plugins and add them to the `plugins` | ||
In your `gasket.js` file, you can import plugins and add them to the `plugins` | ||
array of the Gasket configuration. | ||
@@ -50,4 +50,4 @@ | ||
1. [init] | ||
2. [actions] | ||
3. [configure] | ||
2. [configure] | ||
2. [ready] | ||
@@ -60,9 +60,9 @@ ### init | ||
```js | ||
// gasket-plugin-example.mjs | ||
// gasket-plugin-example.js | ||
export const name = 'gasket-plugin-example'; | ||
const name = 'gasket-plugin-example'; | ||
let _initializedTime; | ||
export const hooks = { | ||
const hooks = { | ||
init(gasket) { | ||
@@ -72,2 +72,4 @@ _initializedTime = Date.now(); | ||
}; | ||
export default { name, hooks }; | ||
``` | ||
@@ -77,2 +79,76 @@ | ||
recommended. | ||
Instead, a plugin can register [actions] that can be executed to retrieve | ||
values the plugin wishes to make available. | ||
### configure | ||
The `configure` lifecycle is the first lifecycle executed when a Gasket is | ||
instantiated. | ||
This allows any [registered plugins] to adjust the configuration before further | ||
lifecycles are executed. | ||
```js | ||
// gasket-plugin-example.js | ||
const name = 'gasket-plugin-example'; | ||
const hooks = { | ||
configure(gasket, gasketConfig) { | ||
// Modify the configuration | ||
return { | ||
...gasketConfig, | ||
example: true | ||
}; | ||
} | ||
}; | ||
export default { name, hooks }; | ||
``` | ||
In this example, we register an action `getDoodads` that will only execute if the | ||
`example` configuration is set to `true`. | ||
It will then execute the `doodads` lifecycle, allowing any registered plugin to | ||
provide doodads. | ||
### ready | ||
The `ready` lifecycle is the last lifecycle executed and is used to signal that | ||
the Gasket instance is fully initialized and ready to be used. | ||
```js | ||
// gasket-plugin-example.js | ||
const name = 'gasket-plugin-example'; | ||
const hooks = { | ||
async ready(gasket) { | ||
console.log('Gasket is ready!'); | ||
} | ||
}; | ||
export default { name, hooks }; | ||
``` | ||
## Actions | ||
Plugins can register actions that can be fired by the application code | ||
where the Gasket is imported, or in other plugins. | ||
```js | ||
// gasket-plugin-example.js | ||
const name = 'gasket-plugin-example'; | ||
const actions = { | ||
async getDoodads(gasket) { | ||
if (gasket.config.example) { | ||
const dodaads = await gasket.exec('dodaads'); | ||
return dodaads.flat() | ||
} | ||
} | ||
}; | ||
export default { name, actions }; | ||
``` | ||
If a plugin needs to make properties available to other plugins, it should | ||
@@ -82,9 +158,15 @@ register an action that can be executed to retrieve the value. | ||
```diff | ||
// gasket-plugin-example.mjs | ||
// gasket-plugin-example.js | ||
export const name = 'gasket-plugin-example'; | ||
const name = 'gasket-plugin-example'; | ||
let _initializedTime; | ||
+ let _initializedTime; | ||
export const hooks = { | ||
+ const actions = { | ||
+ getInitializedTime() { | ||
+ return _initializedTime; | ||
+ } | ||
+ }; | ||
const hooks = { | ||
init(gasket) { | ||
@@ -94,73 +176,96 @@ - gasket.initializedTime = Date.now(); | ||
}, | ||
+ actions() { | ||
+ return { | ||
+ getInitializedTime() { | ||
+ return _initializedTime; | ||
+ } | ||
+ } | ||
+ }, | ||
configure(gasket) { | ||
- const time = gasket.initializedTime; | ||
+ const time = gasket.actions.getInitializedTime(); | ||
// do something with time... | ||
} | ||
}; | ||
- export default { name, hooks }; | ||
+ export default { name, actions, hooks }; | ||
``` | ||
### actions | ||
## Debugging | ||
The `actions` lifecycle is the second lifecycle executed when a Gasket is created. | ||
This will let plugins register actions that can be fired by the application code | ||
where the Gasket is imported, or in other plugins. | ||
Gasket makes use of the [debug] module to provide various debug outputs. Gasket | ||
packages and plugins use the `gasket` namespace. | ||
```js | ||
// gasket-plugin-example.mjs | ||
```shell | ||
DEBUG=gasket:* npm run start | ||
``` | ||
export const name = 'gasket-plugin-example'; | ||
### Tracing | ||
export const hooks = { | ||
actions(gasket) { | ||
return { | ||
async getDoodads() { | ||
if(gasket.config.example) { | ||
const dodaads = await gasket.exec('dodaads'); | ||
return dodaads.flat() | ||
} | ||
} | ||
} | ||
} | ||
}; | ||
You can narrow down to see the action and lifecycle execution order in the | ||
console output under the `gasket:trace` namespace. | ||
```shell | ||
DEBUG=gasket:trace* npm run start | ||
``` | ||
### configure | ||
![trace-example.png](docs/trace-example.png) | ||
The `configure` lifecycle is the first lifecycle executed when a Gasket is | ||
instantiated. | ||
This allows any [registered plugins] to adjust the configuration before further | ||
lifecycles are executed. | ||
The following symbols indicate the step and type of execution: | ||
- `⋌` New Trace Branch | ||
- `★` Action Start | ||
- `◆` Synchronous Lifecycle Start | ||
- `◇` Asynchronous Lifecycle Start | ||
- `↪` Plugin lifecycle Hook | ||
When ever the app or a plugin executes a lifecycle or an action, it will be | ||
passed a traceable proxy object, which can be used to follow the execution | ||
path of the application. | ||
Any action or lifecycle that is executed from the root `gasket` object will | ||
start a new trace "branch". | ||
New branches can be created by calling `gasket.traceBranch()` to help debug | ||
certain lifecycle flows. | ||
Additionally, it is possible to start fresh traces by calling | ||
`gasket.traceRoot()`. | ||
This method should will exit the current branch's trace history | ||
and start a fresh. | ||
Use this sparingly only for situations such as tracing handling for new requests. | ||
## Recursion Protection | ||
Gasket uses the trace history to catch and prevent infinite recursion. | ||
If a lifecycle is executed more than once in the same trace history, | ||
it will throw an error and halt the execution. | ||
While it is ok to execute the same action at various steps in an event chain, | ||
you must avoid calling the same **lifecycle** from within itself. | ||
Memoization can help avoid this issue, and using `req` as a key can help for | ||
request-specific memoization, which is also a good performance optimization. | ||
```js | ||
// gasket-plugin-example.mjs | ||
// gasket-plugin-example.js | ||
export const name = 'gasket-plugin-example'; | ||
const reqMap = new WeakMap(); | ||
export const hooks = { | ||
configure(gasket, gasketConfig) { | ||
// Modify the configuration | ||
return { | ||
...gasketConfig, | ||
example: true | ||
}; | ||
const name = 'gasket-plugin-example'; | ||
const actions = { | ||
async getDoodads(gasket, req) { | ||
if(!reqMap.has(req)) { | ||
const doodads = await gasket.exec('doodads', req); | ||
reqMap.set(req, doodads); | ||
} | ||
return reqMap.get(req); | ||
} | ||
}; | ||
const hooks = { | ||
// ... | ||
}; | ||
export default { name, actions, hooks }; | ||
``` | ||
In this example, we register an action `getDoodads` that will only execute if the | ||
`example` configuration is set to `true`. | ||
It will then execute the `doodads` lifecycle, allowing any registered plugin to | ||
provide doodads. | ||
[init]: #init | ||
[configure]: #configure | ||
[ready]: #ready | ||
[actions]: #actions | ||
[configure]: #configure | ||
[registered plugins]: #registered-plugins | ||
[Plugins Guide]:/packages/gasket-cli/docs/plugins.md | ||
[debug]:https://github.com/debug-js/debug |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
No website
QualityPackage does not have a website.
Found 1 instance in 1 package
134166
16
1734
0
262
2