@gasket/core
Advanced tools
+201
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { | ||
| value: true | ||
| }); | ||
| function _export(target, all) { | ||
| for(var name in all)Object.defineProperty(target, name, { | ||
| enumerable: true, | ||
| get: all[name] | ||
| }); | ||
| } | ||
| _export(exports, { | ||
| GasketTrace: function() { | ||
| return GasketTrace; | ||
| }, | ||
| makeTraceBranch: function() { | ||
| return makeTraceBranch; | ||
| } | ||
| }); | ||
| const _debug = /*#__PURE__*/ _interop_require_default(require("debug")); | ||
| const _engine = require("./engine.js"); | ||
| const _gasket = require("./gasket.js"); | ||
| function _define_property(obj, key, value) { | ||
| if (key in obj) { | ||
| Object.defineProperty(obj, key, { | ||
| value: value, | ||
| enumerable: true, | ||
| configurable: true, | ||
| writable: true | ||
| }); | ||
| } else { | ||
| obj[key] = value; | ||
| } | ||
| return obj; | ||
| } | ||
| function _interop_require_default(obj) { | ||
| return obj && obj.__esModule ? obj : { | ||
| default: obj | ||
| }; | ||
| } | ||
| /** | ||
| * Lookup map for lifecycles method names. | ||
| * Using object[key] is fastest | ||
| * @see https://jsben.ch/pm4cd | ||
| * @type {{[p: string]: boolean}} | ||
| */ const lifecycles = Object.fromEntries(_engine.lifecycleMethods.map((method)=>[ | ||
| method, | ||
| true | ||
| ])); | ||
| const reSync = /sync$/i; | ||
| const icon = (type)=>reSync.test(type) ? '◆' : '◇'; | ||
| class Tracer { | ||
| constructor(parent, branchId){ | ||
| _define_property(this, "traceHookStart", (pluginName, event)=>{ | ||
| this.trace(`↪ ${pluginName}:${event}`); | ||
| }); | ||
| _define_property(this, "traceLifecycleStart", (type, event)=>{ | ||
| const name = `${type}(${event})`; | ||
| const { traceStack } = this; | ||
| if (traceStack.includes(name)) { | ||
| throw new Error(`Recursive lifecycle detected: ${[ | ||
| ...traceStack, | ||
| name | ||
| ].join(' -> ')}`); | ||
| } | ||
| traceStack.push(name); | ||
| const ico = icon(type); | ||
| this.trace(`${ico} ${name}`); | ||
| }); | ||
| _define_property(this, "traceActionStart", (name)=>{ | ||
| const { traceStack } = this; | ||
| traceStack.push(name); | ||
| this.trace(`★ ${name}`); | ||
| }); | ||
| // TODO: not implemented | ||
| // eslint-disable-next-line no-unused-vars | ||
| _define_property(this, "traceLifecycleEnd", (type, event)=>{ | ||
| // const name = `${type}(${event})`; | ||
| // this.trace(`x ${name}`); | ||
| }); | ||
| // TODO: not implemented | ||
| // eslint-disable-next-line no-unused-vars | ||
| _define_property(this, "traceActionEnd", (name)=>{ | ||
| // this.trace(`x ${name}`); | ||
| }); | ||
| var _parent_traceStack; | ||
| // make a stack copy for each isolate to avoid contamination | ||
| this.traceStack = [ | ||
| ...(_parent_traceStack = parent === null || parent === void 0 ? void 0 : parent.traceStack) !== null && _parent_traceStack !== void 0 ? _parent_traceStack : [] | ||
| ]; | ||
| const _debug1 = (0, _debug.default)(`gasket:trace:${branchId}`); | ||
| this.trace = (message)=>{ | ||
| const indent = ' '.repeat(this.traceStack.length); | ||
| _debug1(`${indent}${message}`); | ||
| }; | ||
| } | ||
| } | ||
| class GasketTrace { | ||
| constructor(parent, newBranchId = null){ | ||
| _define_property(this, "traceBranch", ()=>{ | ||
| return makeTraceBranch(this._proxy); | ||
| }); | ||
| this.branchId = newBranchId !== null && newBranchId !== void 0 ? newBranchId : parent.branchId; | ||
| this.engine = parent.engine; | ||
| const tracer = this._tracer = new Tracer(parent._tracer, this.branchId); | ||
| if (newBranchId) { | ||
| var _parent_branchId; | ||
| const parentId = (_parent_branchId = parent.branchId) !== null && _parent_branchId !== void 0 ? _parent_branchId : 'root'; | ||
| tracer.trace(`⋌ ${parentId}`); | ||
| } | ||
| const self = this; | ||
| this._proxy = new Proxy(this, { | ||
| get (target, prop) { | ||
| if (typeof prop === 'string' && lifecycles[prop] === true) { | ||
| return isolateLifecycle(self, prop, parent.engine[prop]); | ||
| } | ||
| if (prop === 'actions') { | ||
| return interceptActions(self, parent.engine.actions); | ||
| } | ||
| if (prop in target) { | ||
| return target[prop]; | ||
| } | ||
| return parent[prop]; | ||
| }, | ||
| set (target, prop, newValue) { | ||
| if (prop in target) { | ||
| return false; | ||
| } | ||
| // TODO: Do we want to restrict attaching to gaskets? | ||
| parent[prop] = newValue; | ||
| return true; | ||
| } | ||
| }); | ||
| this.traceHookStart = tracer.traceHookStart; | ||
| this.trace = tracer.trace; | ||
| this.traceRoot = ()=>{ | ||
| if (parent instanceof _gasket.Gasket) return parent; | ||
| return parent.traceRoot(); | ||
| }; | ||
| } | ||
| } | ||
| _define_property(GasketTrace, "_nextBranchId", 0); | ||
| /** | ||
| * Wrap a lifecycle function to trace start and end. | ||
| * A GasketTrace instance is passed to the lifecycle function | ||
| * to allow for tracing and further branching. | ||
| * @type {import('./internal').isolateLifecycle<any>} | ||
| */ function isolateLifecycle(source, name, fn) { | ||
| const instance = new GasketTrace(source._proxy); | ||
| return (...args)=>{ | ||
| const [event] = args; | ||
| instance._tracer.traceLifecycleStart(name, event); | ||
| const result = fn(// @ts-expect-error - isolate is a Gasket proxy | ||
| instance._proxy, ...args); | ||
| if (typeof (result === null || result === void 0 ? void 0 : result.finally) === 'function') { | ||
| return result.finally(()=>{ | ||
| instance._tracer.traceLifecycleEnd(name, event); | ||
| }); | ||
| } | ||
| instance._tracer.traceLifecycleEnd(name, event); | ||
| return result; | ||
| }; | ||
| } | ||
| /** | ||
| * Wrap an action function to trace start and end. | ||
| * A GasketTrace instance is passed to the action function | ||
| * to allow for tracing and further branching. | ||
| * @type {import('./internal').isolateAction<any>} | ||
| */ function isolateAction(source, name, fn) { | ||
| const instance = new GasketTrace(source._proxy); | ||
| return (...args)=>{ | ||
| instance._tracer.traceActionStart(name); | ||
| const result = fn(// @ts-expect-error - isolate is a Gasket proxy | ||
| instance._proxy, ...args); | ||
| if (typeof (result === null || result === void 0 ? void 0 : result.finally) === 'function') { | ||
| return result.finally(()=>{ | ||
| instance._tracer.traceActionEnd(name); | ||
| }); | ||
| } | ||
| instance._tracer.traceActionEnd(name); | ||
| return result; | ||
| }; | ||
| } | ||
| /** | ||
| * Create a proxy of actions to intercept the functions | ||
| * and return a traceable version. | ||
| * @type {import('./internal').interceptActions} | ||
| */ function interceptActions(source, actions) { | ||
| return new Proxy(actions, { | ||
| get (target, prop) { | ||
| if (prop in target && typeof prop === 'string') { | ||
| return isolateAction(source, prop, target[prop]); | ||
| } | ||
| return actions[prop]; | ||
| } | ||
| }); | ||
| } | ||
| function makeTraceBranch(gasket) { | ||
| const instance = new GasketTrace(gasket, GasketTrace._nextBranchId++); | ||
| // return instance; | ||
| return instance._proxy; | ||
| } |
| import { ActionHandler, GasketTrace, HookHandler } from '@gasket/core'; | ||
| type isolateLifecycle<T> = (source: GasketTrace, name: string, fn: HookHandler<T>) => HookHandler<T> | ||
| type isolateAction<T> = (source: GasketTrace, name: string, fn: ActionHandler<T>) => ActionHandler<T> | ||
| type interceptActions = (source: GasketTrace, actions: GasketActions) => GasketActions | ||
| type makeTraceBranch = (source: Gasket | GasketTrace) => GasketTrace |
+194
| import debugPkg from 'debug'; | ||
| import { lifecycleMethods } from './engine.js'; | ||
| import { Gasket } from './gasket.js'; | ||
| /** | ||
| * Lookup map for lifecycles method names. | ||
| * Using object[key] is fastest | ||
| * @see https://jsben.ch/pm4cd | ||
| * @type {{[p: string]: boolean}} | ||
| */ | ||
| const lifecycles = Object.fromEntries(lifecycleMethods.map(method => [method, true])); | ||
| const reSync = /sync$/i; | ||
| const icon = (type) => reSync.test(type) ? '◆' : '◇'; | ||
| class Tracer { | ||
| constructor(parent, branchId) { | ||
| // make a stack copy for each isolate to avoid contamination | ||
| this.traceStack = [...(parent?.traceStack ?? [])]; | ||
| const _debug = debugPkg(`gasket:trace:${branchId}`); | ||
| this.trace = (message) => { | ||
| const indent = ' '.repeat(this.traceStack.length); | ||
| _debug(`${indent}${message}`); | ||
| }; | ||
| } | ||
| traceHookStart = (pluginName, event) => { | ||
| this.trace(`↪ ${pluginName}:${event}`); | ||
| }; | ||
| traceLifecycleStart = (type, event) => { | ||
| const name = `${type}(${event})`; | ||
| const { traceStack } = this; | ||
| if (traceStack.includes(name)) { | ||
| throw new Error(`Recursive lifecycle detected: ${[...traceStack, name].join(' -> ')}`); | ||
| } | ||
| traceStack.push(name); | ||
| const ico = icon(type); | ||
| this.trace(`${ico} ${name}`); | ||
| }; | ||
| traceActionStart = (name) => { | ||
| const { traceStack } = this; | ||
| traceStack.push(name); | ||
| this.trace(`★ ${name}`); | ||
| }; | ||
| // TODO: not implemented | ||
| // eslint-disable-next-line no-unused-vars | ||
| traceLifecycleEnd = (type, event) => { | ||
| // const name = `${type}(${event})`; | ||
| // this.trace(`x ${name}`); | ||
| }; | ||
| // TODO: not implemented | ||
| // eslint-disable-next-line no-unused-vars | ||
| traceActionEnd = (name) => { | ||
| // this.trace(`x ${name}`); | ||
| }; | ||
| } | ||
| /** @type {import('@gasket/core').GasketTrace} */ | ||
| export class GasketTrace { | ||
| static _nextBranchId = 0; | ||
| constructor(parent, newBranchId = null) { | ||
| this.branchId = newBranchId ?? parent.branchId; | ||
| this.engine = parent.engine; | ||
| const tracer = this._tracer = new Tracer(parent._tracer, this.branchId); | ||
| if (newBranchId) { | ||
| const parentId = parent.branchId ?? 'root'; | ||
| tracer.trace(`⋌ ${parentId}`); | ||
| } | ||
| const self = this; | ||
| this._proxy = new Proxy(this, { | ||
| get(target, prop) { | ||
| if (typeof prop === 'string' && lifecycles[prop] === true) { | ||
| return isolateLifecycle(self, prop, parent.engine[prop]); | ||
| } | ||
| if (prop === 'actions') { | ||
| return interceptActions(self, parent.engine.actions); | ||
| } | ||
| if (prop in target) { | ||
| return target[prop]; | ||
| } | ||
| return parent[prop]; | ||
| }, | ||
| set(target, prop, newValue) { | ||
| if (prop in target) { | ||
| return false; | ||
| } | ||
| // TODO: Do we want to restrict attaching to gaskets? | ||
| parent[prop] = newValue; | ||
| return true; | ||
| } | ||
| }); | ||
| this.traceHookStart = tracer.traceHookStart; | ||
| this.trace = tracer.trace; | ||
| this.traceRoot = () => { | ||
| if (parent instanceof Gasket) return parent; | ||
| return parent.traceRoot(); | ||
| }; | ||
| } | ||
| traceBranch = () => { | ||
| return makeTraceBranch(this._proxy); | ||
| }; | ||
| } | ||
| /** | ||
| * Wrap a lifecycle function to trace start and end. | ||
| * A GasketTrace instance is passed to the lifecycle function | ||
| * to allow for tracing and further branching. | ||
| * @type {import('./internal').isolateLifecycle<any>} | ||
| */ | ||
| function isolateLifecycle(source, name, fn) { | ||
| const instance = new GasketTrace(source._proxy); | ||
| return (...args) => { | ||
| const [event] = args; | ||
| instance._tracer.traceLifecycleStart(name, event); | ||
| const result = fn( | ||
| // @ts-expect-error - isolate is a Gasket proxy | ||
| instance._proxy, | ||
| ...args | ||
| ); | ||
| if (typeof result?.finally === 'function') { | ||
| return result.finally(() => { | ||
| instance._tracer.traceLifecycleEnd(name, event); | ||
| }); | ||
| } | ||
| instance._tracer.traceLifecycleEnd(name, event); | ||
| return result; | ||
| }; | ||
| } | ||
| /** | ||
| * Wrap an action function to trace start and end. | ||
| * A GasketTrace instance is passed to the action function | ||
| * to allow for tracing and further branching. | ||
| * @type {import('./internal').isolateAction<any>} | ||
| */ | ||
| function isolateAction(source, name, fn) { | ||
| const instance = new GasketTrace(source._proxy); | ||
| return (...args) => { | ||
| instance._tracer.traceActionStart(name); | ||
| const result = fn( | ||
| // @ts-expect-error - isolate is a Gasket proxy | ||
| instance._proxy, | ||
| ...args | ||
| ); | ||
| if (typeof result?.finally === 'function') { | ||
| return result.finally(() => { | ||
| instance._tracer.traceActionEnd(name); | ||
| }); | ||
| } | ||
| instance._tracer.traceActionEnd(name); | ||
| return result; | ||
| }; | ||
| } | ||
| /** | ||
| * Create a proxy of actions to intercept the functions | ||
| * and return a traceable version. | ||
| * @type {import('./internal').interceptActions} | ||
| */ | ||
| function interceptActions(source, actions) { | ||
| return new Proxy(actions, { | ||
| get(target, prop) { | ||
| if (prop in target && typeof prop === 'string') { | ||
| return isolateAction(source, prop, target[prop]); | ||
| } | ||
| return actions[prop]; | ||
| } | ||
| }); | ||
| } | ||
| /** | ||
| * Create a new GasketTrace instance from a Gasket. | ||
| * @type {import('./internal').makeTraceBranch} | ||
| */ | ||
| export function makeTraceBranch(gasket) { | ||
| const instance = new GasketTrace(gasket, GasketTrace._nextBranchId++); | ||
| // return instance; | ||
| return instance._proxy; | ||
| } |
+12
-13
@@ -169,3 +169,3 @@ "use strict"; | ||
| * plugins to finish. | ||
| * @param gasket | ||
| * @param {import("@gasket/core").Gasket} gasket - The gasket instance | ||
| * @param {string} event The event to execute | ||
@@ -207,3 +207,3 @@ * @param {...*} args Args for hooks | ||
| * possible. | ||
| * @param gasket | ||
| * @param {import("@gasket/core").Gasket} gasket - The gasket instance | ||
| * @param {string} event The event to execute | ||
@@ -239,3 +239,3 @@ * @param {...*} args Args for hooks | ||
| * present in the map. | ||
| * @param gasket | ||
| * @param {import("@gasket/core").Gasket} gasket - The gasket instance | ||
| * @param {string} event The event to execute | ||
@@ -283,3 +283,3 @@ * @param {...*} args Args for hooks | ||
| * Like `execMap`, only all hooks must execute synchronously | ||
| * @param gasket | ||
| * @param {import("@gasket/core").Gasket} gasket - The gasket instance | ||
| * @param {string} event The event to execute | ||
@@ -316,3 +316,3 @@ * @param {...*} args Args for hooks | ||
| * to the next hook. It's like an asynchronous version of `Array.prototype.reduce`. | ||
| * @param gasket | ||
| * @param {import("@gasket/core").Gasket} gasket - The gasket instance | ||
| * @param {string} event The event to execute | ||
@@ -351,3 +351,3 @@ * @param {any} value Value to pass to initial hook | ||
| * methods whenever possible. | ||
| * @param gasket | ||
| * @param {import("@gasket/core").Gasket} gasket - The gasket instance | ||
| * @param {string} event The event to execute | ||
@@ -382,3 +382,3 @@ * @param {any} value Value to pass to initial hook | ||
| * the plugin itself. | ||
| * @param gasket | ||
| * @param {import("@gasket/core").Gasket} gasket - The gasket instance | ||
| * @param {string} event The event to execute | ||
@@ -418,3 +418,3 @@ * @param {Function} applyFn Function to apply | ||
| * Like `execApply`, only all hooks must execute synchronously. | ||
| * @param gasket | ||
| * @param {import("@gasket/core").Gasket} gasket - The gasket instance | ||
| * @param {string} event The event to execute | ||
@@ -450,7 +450,6 @@ * @param {Function} applyFn Function to apply | ||
| * @param {object} options options | ||
| * @param [options.gasket] | ||
| * @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 | ||
@@ -457,0 +456,0 @@ */ _execWithCachedPlan({ event, type, prepare, exec }) { |
+15
-9
@@ -21,3 +21,3 @@ /* eslint-disable no-console, no-process-env */ "use strict"; | ||
| const _utils = require("@gasket/utils"); | ||
| const _branch = require("./branch.js"); | ||
| const _trace = require("./trace.js"); | ||
| /** | ||
@@ -36,12 +36,18 @@ * Get the environment to use for the gasket instance. | ||
| class Gasket { | ||
| branch() { | ||
| return (0, _branch.makeBranch)(this); | ||
| traceBranch() { | ||
| return (0, _trace.makeTraceBranch)(this); | ||
| } | ||
| traceRoot() { | ||
| return this; | ||
| } | ||
| get actions() { | ||
| return this.branch().actions; | ||
| // @ts-ignore -- actions from proxy | ||
| return this.traceBranch().actions; | ||
| } | ||
| constructor(gasketConfig){ | ||
| /** | ||
| * @param {import('@gasket/core').GasketConfigDefinition} configDef - Gasket configuration | ||
| */ constructor(configDef){ | ||
| var _config; | ||
| const env = getEnvironment(); | ||
| const config = (0, _utils.applyConfigOverrides)(gasketConfig, { | ||
| const config = (0, _utils.applyConfigOverrides)(configDef, { | ||
| env | ||
@@ -59,3 +65,3 @@ }); | ||
| this[method] = (...args)=>{ | ||
| return this.branch()[method](...args); | ||
| return this.traceBranch()[method](...args); | ||
| }; | ||
@@ -76,4 +82,4 @@ }); | ||
| } | ||
| function makeGasket(gasketConfigDefinition) { | ||
| return new Gasket(gasketConfigDefinition); | ||
| function makeGasket(configDef) { | ||
| return new Gasket(configDef); | ||
| } |
+12
-13
@@ -152,3 +152,3 @@ let dynamicNamingId = 0; | ||
| * plugins to finish. | ||
| * @param gasket | ||
| * @param {import("@gasket/core").Gasket} gasket - The gasket instance | ||
| * @param {string} event The event to execute | ||
@@ -194,3 +194,3 @@ * @param {...*} args Args for hooks | ||
| * possible. | ||
| * @param gasket | ||
| * @param {import("@gasket/core").Gasket} gasket - The gasket instance | ||
| * @param {string} event The event to execute | ||
@@ -228,3 +228,3 @@ * @param {...*} args Args for hooks | ||
| * present in the map. | ||
| * @param gasket | ||
| * @param {import("@gasket/core").Gasket} gasket - The gasket instance | ||
| * @param {string} event The event to execute | ||
@@ -273,3 +273,3 @@ * @param {...*} args Args for hooks | ||
| * Like `execMap`, only all hooks must execute synchronously | ||
| * @param gasket | ||
| * @param {import("@gasket/core").Gasket} gasket - The gasket instance | ||
| * @param {string} event The event to execute | ||
@@ -308,3 +308,3 @@ * @param {...*} args Args for hooks | ||
| * to the next hook. It's like an asynchronous version of `Array.prototype.reduce`. | ||
| * @param gasket | ||
| * @param {import("@gasket/core").Gasket} gasket - The gasket instance | ||
| * @param {string} event The event to execute | ||
@@ -347,3 +347,3 @@ * @param {any} value Value to pass to initial hook | ||
| * methods whenever possible. | ||
| * @param gasket | ||
| * @param {import("@gasket/core").Gasket} gasket - The gasket instance | ||
| * @param {string} event The event to execute | ||
@@ -382,3 +382,3 @@ * @param {any} value Value to pass to initial hook | ||
| * the plugin itself. | ||
| * @param gasket | ||
| * @param {import("@gasket/core").Gasket} gasket - The gasket instance | ||
| * @param {string} event The event to execute | ||
@@ -422,3 +422,3 @@ * @param {Function} applyFn Function to apply | ||
| * Like `execApply`, only all hooks must execute synchronously. | ||
| * @param gasket | ||
| * @param {import("@gasket/core").Gasket} gasket - The gasket instance | ||
| * @param {string} event The event to execute | ||
@@ -455,7 +455,6 @@ * @param {Function} applyFn Function to apply | ||
| * @param {object} options options | ||
| * @param [options.gasket] | ||
| * @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 | ||
@@ -462,0 +461,0 @@ */ |
+23
-13
@@ -5,3 +5,3 @@ /* eslint-disable no-console, no-process-env */ | ||
| import { applyConfigOverrides } from '@gasket/utils'; | ||
| import { makeBranch } from './branch.js'; | ||
| import { makeTraceBranch } from './trace.js'; | ||
@@ -25,8 +25,13 @@ /** | ||
| // TODO: Add JSDoc types | ||
| /** | ||
| * The Gasket class is the main entry point for the Gasket API. | ||
| */ | ||
| export class Gasket { | ||
| constructor(gasketConfig) { | ||
| /** | ||
| * @param {import('@gasket/core').GasketConfigDefinition} configDef - Gasket configuration | ||
| */ | ||
| constructor(configDef) { | ||
| const env = getEnvironment(); | ||
| const config = applyConfigOverrides(gasketConfig, { env }); | ||
| const config = applyConfigOverrides(configDef, { env }); | ||
| config.env = env; | ||
@@ -46,3 +51,3 @@ config.root ??= process.cwd(); | ||
| this[method] = (...args) => { | ||
| return this.branch()[method](...args); | ||
| return this.traceBranch()[method](...args); | ||
| }; | ||
@@ -66,18 +71,23 @@ }); | ||
| branch() { | ||
| return makeBranch(this); | ||
| traceBranch() { | ||
| return makeTraceBranch(this); | ||
| } | ||
| traceRoot() { | ||
| return this; | ||
| } | ||
| get actions() { | ||
| return this.branch().actions; | ||
| // @ts-ignore -- actions from proxy | ||
| return this.traceBranch().actions; | ||
| } | ||
| } | ||
| // TODO: Add JSDoc types | ||
| /** | ||
| * | ||
| * @param gasketConfigDefinition | ||
| * Make a new Gasket instance. | ||
| * @param {import('@gasket/core').GasketConfigDefinition} configDef - Gasket configuration | ||
| * @returns {Gasket} gasket instance | ||
| */ | ||
| export function makeGasket(gasketConfigDefinition) { | ||
| return new Gasket(gasketConfigDefinition); | ||
| export function makeGasket(configDef) { | ||
| return new Gasket(configDef); | ||
| } |
+3
-4
@@ -118,7 +118,7 @@ declare module '@gasket/core' { | ||
| new (config: GasketConfigDefinition): Gasket | ||
| branch(): GasketBranch | ||
| root(): Gasket | ||
| traceBranch(): GasketTrace | ||
| traceRoot(): Gasket | ||
| } | ||
| export interface GasketBranch extends Gasket {} | ||
| export type GasketTrace = Proxy<Gasket>; | ||
@@ -147,2 +147,1 @@ type PartialRecursive<T> = T extends Object | ||
| } | ||
+13
-3
| { | ||
| "name": "@gasket/core", | ||
| "version": "7.0.0-next.59", | ||
| "version": "7.0.0-next.60", | ||
| "description": "Entry point to setting up Gasket instances", | ||
@@ -89,5 +89,15 @@ "type": "module", | ||
| "no-sync": 0 | ||
| } | ||
| }, | ||
| "overrides": [ | ||
| { | ||
| "files": [ | ||
| "test/**/*.js" | ||
| ], | ||
| "rules": { | ||
| "jsdoc/require-jsdoc": "off" | ||
| } | ||
| } | ||
| ] | ||
| }, | ||
| "gitHead": "3bace117422475f9e5239728e1870658ac97de52" | ||
| "gitHead": "ea9feae6cd291d4361c06e09659e50dbec19bcea" | ||
| } |
+147
-50
@@ -46,4 +46,4 @@ # @gasket/core | ||
| 1. [init] | ||
| 2. [actions] | ||
| 3. [configure] | ||
| 2. [configure] | ||
| 2. [ready] | ||
@@ -73,2 +73,76 @@ ### init | ||
| 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,4 +156,10 @@ register an action that can be executed to retrieve the value. | ||
| let _initializedTime; | ||
| + let _initializedTime; | ||
| + const actions = { | ||
| + getInitializedTime() { | ||
| + return _initializedTime; | ||
| + } | ||
| + }; | ||
| const hooks = { | ||
@@ -90,79 +170,96 @@ init(gasket) { | ||
| }, | ||
| + 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, 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.js | ||
| ```shell | ||
| DEBUG=gasket:* npm run start | ||
| ``` | ||
| const name = 'gasket-plugin-example'; | ||
| ### Tracing | ||
| 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. | ||
| export default { name, hooks }; | ||
| ```shell | ||
| DEBUG=gasket:trace* npm run start | ||
| ``` | ||
| ### 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. | ||
| 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.js | ||
| const reqMap = new WeakMap(); | ||
| const name = 'gasket-plugin-example'; | ||
| const hooks = { | ||
| configure(gasket, gasketConfig) { | ||
| // Modify the configuration | ||
| return { | ||
| ...gasketConfig, | ||
| example: true | ||
| }; | ||
| 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, 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 |
-150
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { | ||
| value: true | ||
| }); | ||
| function _export(target, all) { | ||
| for(var name in all)Object.defineProperty(target, name, { | ||
| enumerable: true, | ||
| get: all[name] | ||
| }); | ||
| } | ||
| _export(exports, { | ||
| GasketBranch: function() { | ||
| return GasketBranch; | ||
| }, | ||
| makeBranch: function() { | ||
| return makeBranch; | ||
| } | ||
| }); | ||
| const _debug = /*#__PURE__*/ _interop_require_default(require("debug")); | ||
| const _engine = require("./engine.js"); | ||
| function _define_property(obj, key, value) { | ||
| if (key in obj) { | ||
| Object.defineProperty(obj, key, { | ||
| value: value, | ||
| enumerable: true, | ||
| configurable: true, | ||
| writable: true | ||
| }); | ||
| } else { | ||
| obj[key] = value; | ||
| } | ||
| return obj; | ||
| } | ||
| function _interop_require_default(obj) { | ||
| return obj && obj.__esModule ? obj : { | ||
| default: obj | ||
| }; | ||
| } | ||
| const reSync = /sync$/i; | ||
| const icon = (type)=>reSync.test(type) ? '◆' : '◇'; | ||
| class GasketTracer { | ||
| constructor(parent, branchId){ | ||
| _define_property(this, "traceHookStart", (pluginName, event)=>{ | ||
| this.trace(`↪ ${pluginName}:${event}`); | ||
| }); | ||
| _define_property(this, "traceLifecycleStart", (type, event)=>{ | ||
| const name = `${type}(${event})`; | ||
| const { traceStack } = this; | ||
| if (traceStack.includes(name)) { | ||
| throw new Error(`Recursive lifecycle detected: ${[ | ||
| ...traceStack, | ||
| name | ||
| ].join(' -> ')}`); | ||
| } | ||
| traceStack.push(name); | ||
| const ico = icon(type); | ||
| this.trace(`${ico} ${name}`); | ||
| }); | ||
| _define_property(this, "traceActionStart", (name)=>{ | ||
| const { traceStack } = this; | ||
| if (traceStack.includes(name)) { | ||
| throw new Error(`Recursive action detected: ${[ | ||
| ...traceStack, | ||
| name | ||
| ].join(' -> ')}`); | ||
| } | ||
| traceStack.push(name); | ||
| this.trace(`★ ${name}`); | ||
| }); | ||
| // TODO: not implemented | ||
| // eslint-disable-next-line no-unused-vars | ||
| _define_property(this, "traceLifecycleEnd", (type, event)=>{ | ||
| // const name = `${type}(${event})`; | ||
| // this.trace(`x ${name}`); | ||
| }); | ||
| // TODO: not implemented | ||
| // eslint-disable-next-line no-unused-vars | ||
| _define_property(this, "traceActionEnd", (name)=>{ | ||
| // this.trace(`x ${name}`); | ||
| }); | ||
| var _parent_traceStack; | ||
| this.traceStack = [ | ||
| ...(_parent_traceStack = parent === null || parent === void 0 ? void 0 : parent.traceStack) !== null && _parent_traceStack !== void 0 ? _parent_traceStack : [] | ||
| ]; | ||
| const _debug1 = (0, _debug.default)(`gasket:branch:${branchId}`); | ||
| this.trace = (message)=>{ | ||
| const indent = ' '.repeat(this.traceStack.length); | ||
| _debug1(`${indent}${message}`); | ||
| }; | ||
| } | ||
| } | ||
| class GasketBranch { | ||
| constructor(parent){ | ||
| _define_property(this, "branch", ()=>{ | ||
| const b = new GasketBranch(this.proxy); | ||
| return b.proxy; | ||
| }); | ||
| var _parent_branchId; | ||
| const parentId = (_parent_branchId = parent.branchId) !== null && _parent_branchId !== void 0 ? _parent_branchId : 'root'; | ||
| this.branchId = GasketBranch._nextBranchId++; | ||
| const tracer = this._tracer = new GasketTracer(parent._tracer, this.branchId); | ||
| tracer.trace(`⋌ ${parentId}`); | ||
| this.proxy = new Proxy(this, { | ||
| get (target, prop) { | ||
| if (prop in target) { | ||
| return target[prop]; | ||
| } | ||
| return parent[prop]; | ||
| }, | ||
| set (target, prop, newValue) { | ||
| if (prop in target) { | ||
| return false; | ||
| } | ||
| // TODO: Do we want to restrict attaching to gaskets? | ||
| parent[prop] = newValue; | ||
| return true; | ||
| } | ||
| }); | ||
| const withTrace = (fn, traceStart, traceEnd)=>{ | ||
| return (...args)=>{ | ||
| traceStart(...args); | ||
| const result = fn(this.proxy, ...args); | ||
| if (typeof (result === null || result === void 0 ? void 0 : result.finally) === 'function') { | ||
| return result.finally(()=>{ | ||
| traceEnd(...args); | ||
| }); | ||
| } | ||
| traceEnd(...args); | ||
| return result; | ||
| }; | ||
| }; | ||
| this.traceHookStart = tracer.traceHookStart; | ||
| this.trace = tracer.trace; | ||
| this.hook = parent.engine.hook.bind(parent.engine); | ||
| _engine.lifecycleMethods.forEach((name)=>{ | ||
| const lifecycleFn = parent.engine[name]; | ||
| this[name] = withTrace(lifecycleFn, (event)=>tracer.traceLifecycleStart(name, event), (event)=>tracer.traceLifecycleEnd(name, event)); | ||
| }); | ||
| this.actions = Object.entries(parent.engine.actions).reduce((acc, [name, actionFn])=>{ | ||
| acc[name] = withTrace(actionFn, ()=>tracer.traceActionStart(name), ()=>tracer.traceActionEnd(name)); | ||
| return acc; | ||
| }, {}); | ||
| } | ||
| } | ||
| _define_property(GasketBranch, "_nextBranchId", 0); | ||
| function makeBranch(gasket) { | ||
| const instance = new GasketBranch(gasket); | ||
| // return instance; | ||
| return instance.proxy; | ||
| } |
-138
| import debugPkg from 'debug'; | ||
| import { lifecycleMethods } from './engine.js'; | ||
| const reSync = /sync$/i; | ||
| const icon = (type) => reSync.test(type) ? '◆' : '◇'; | ||
| class GasketTracer { | ||
| constructor(parent, branchId) { | ||
| this.traceStack = [...(parent?.traceStack ?? [])]; | ||
| const _debug = debugPkg(`gasket:branch:${branchId}`); | ||
| this.trace = (message) => { | ||
| const indent = ' '.repeat(this.traceStack.length); | ||
| _debug(`${indent}${message}`); | ||
| }; | ||
| } | ||
| traceHookStart = (pluginName, event) => { | ||
| this.trace(`↪ ${pluginName}:${event}`); | ||
| }; | ||
| traceLifecycleStart = (type, event) => { | ||
| const name = `${type}(${event})`; | ||
| const { traceStack } = this; | ||
| if (traceStack.includes(name)) { | ||
| throw new Error(`Recursive lifecycle detected: ${[...traceStack, name].join(' -> ')}`); | ||
| } | ||
| traceStack.push(name); | ||
| const ico = icon(type); | ||
| this.trace(`${ico} ${name}`); | ||
| }; | ||
| traceActionStart = (name) => { | ||
| const { traceStack } = this; | ||
| if (traceStack.includes(name)) { | ||
| throw new Error(`Recursive action detected: ${[...traceStack, name].join(' -> ')}`); | ||
| } | ||
| traceStack.push(name); | ||
| this.trace(`★ ${name}`); | ||
| }; | ||
| // TODO: not implemented | ||
| // eslint-disable-next-line no-unused-vars | ||
| traceLifecycleEnd = (type, event) => { | ||
| // const name = `${type}(${event})`; | ||
| // this.trace(`x ${name}`); | ||
| }; | ||
| // TODO: not implemented | ||
| // eslint-disable-next-line no-unused-vars | ||
| traceActionEnd = (name) => { | ||
| // this.trace(`x ${name}`); | ||
| }; | ||
| } | ||
| export class GasketBranch { | ||
| static _nextBranchId = 0; | ||
| constructor(parent) { | ||
| const parentId = parent.branchId ?? 'root'; | ||
| this.branchId = GasketBranch._nextBranchId++; | ||
| const tracer = this._tracer = new GasketTracer(parent._tracer, this.branchId); | ||
| tracer.trace(`⋌ ${parentId}`); | ||
| this.proxy = new Proxy(this, { | ||
| get(target, prop) { | ||
| if (prop in target) { | ||
| return target[prop]; | ||
| } | ||
| return parent[prop]; | ||
| }, | ||
| set(target, prop, newValue) { | ||
| if (prop in target) { | ||
| return false; | ||
| } | ||
| // TODO: Do we want to restrict attaching to gaskets? | ||
| parent[prop] = newValue; | ||
| return true; | ||
| } | ||
| }); | ||
| const withTrace = (fn, traceStart, traceEnd) => { | ||
| return (...args) => { | ||
| traceStart(...args); | ||
| const result = fn(this.proxy, ...args); | ||
| if (typeof result?.finally === 'function') { | ||
| return result.finally(() => { | ||
| traceEnd(...args); | ||
| }); | ||
| } | ||
| traceEnd(...args); | ||
| return result; | ||
| }; | ||
| }; | ||
| this.traceHookStart = tracer.traceHookStart; | ||
| this.trace = tracer.trace; | ||
| this.hook = parent.engine.hook.bind(parent.engine); | ||
| lifecycleMethods.forEach(name => { | ||
| const lifecycleFn = parent.engine[name]; | ||
| this[name] = withTrace( | ||
| lifecycleFn, | ||
| (event) => tracer.traceLifecycleStart(name, event), | ||
| (event) => tracer.traceLifecycleEnd(name, event) | ||
| ); | ||
| }); | ||
| this.actions = Object.entries(parent.engine.actions) | ||
| .reduce((acc, [name, actionFn]) => { | ||
| acc[name] = withTrace( | ||
| actionFn, | ||
| () => tracer.traceActionStart(name), | ||
| () => tracer.traceActionEnd(name) | ||
| ); | ||
| return acc; | ||
| }, {}); | ||
| } | ||
| branch = () => { | ||
| const b = new GasketBranch(this.proxy); | ||
| return b.proxy; | ||
| }; | ||
| } | ||
| /** | ||
| * | ||
| * @param gasket | ||
| */ | ||
| export function makeBranch(gasket) { | ||
| const instance = new GasketBranch(gasket); | ||
| // return instance; | ||
| return instance.proxy; | ||
| } |
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
74001
12.69%13
8.33%1729
7.59%262
58.79%