flecks/packages/core/src/flecks.js

850 lines
23 KiB
JavaScript
Raw Normal View History

2022-02-25 04:58:08 -06:00
// eslint-disable-next-line max-classes-per-file
import {
basename,
dirname,
extname,
join,
} from 'path';
import get from 'lodash.get';
import set from 'lodash.set';
import without from 'lodash.without';
2022-02-28 10:29:56 -06:00
import D from './debug';
import Digraph from './digraph';
2022-02-25 04:58:08 -06:00
import Middleware from './middleware';
const debug = D('@flecks/core/flecks');
2022-04-04 04:42:01 -05:00
const debugSilly = debug.extend('silly');
2022-02-25 04:58:08 -06:00
// Symbol for hook ordering.
const HookOrder = Symbol.for('@flecks/core.hookOrder');
// Symbols for Gathered classes.
2022-03-08 16:03:06 -06:00
export const ById = Symbol.for('@flecks/core.byId');
export const ByType = Symbol.for('@flecks/core.byType');
2022-02-25 04:58:08 -06:00
/**
* Capitalize a string.
*
* @param {string} string
* @returns {string}
*/
2022-02-25 04:58:08 -06:00
const capitalize = (string) => string.substring(0, 1).toUpperCase() + string.substring(1);
/**
* CamelCase a string.
*
* @param {string} string
* @returns {string}
*/
2022-02-25 04:58:08 -06:00
const camelCase = (string) => string.split(/[_-]/).map(capitalize).join('');
// Track gathered for HMR.
2022-02-25 04:58:08 -06:00
const hotGathered = new Map();
// Wrap classes to expose their flecks ID and type.
const wrapGathered = (Class, id, idProperty, type, typeProperty) => {
2022-02-25 04:58:08 -06:00
class Subclass extends Class {
static get [idProperty]() {
2022-02-25 04:58:08 -06:00
return id;
}
static get [typeProperty]() {
2022-02-25 04:58:08 -06:00
return type;
}
}
return Subclass;
};
export default class Flecks {
config = {};
flecks = {};
hooks = {};
platforms = {};
/**
* @param {object} init
* @param {object} init.config The Flecks configuration (e.g. loaded from `flecks.yml`).
* @param {string[]} init.platforms Platforms this instance is running on.
*/
2022-02-25 04:58:08 -06:00
constructor({
config = {},
flecks = {},
platforms = [],
} = {}) {
const emptyConfigForAllFlecks = Object.fromEntries(
Object.keys(flecks).map((path) => [path, {}]),
);
this.config = {...emptyConfigForAllFlecks, ...config};
2022-02-25 04:58:08 -06:00
this.platforms = platforms;
const entries = Object.entries(flecks);
2022-04-04 04:42:01 -05:00
debugSilly('paths: %O', entries.map(([fleck]) => fleck));
2022-02-25 04:58:08 -06:00
for (let i = 0; i < entries.length; i++) {
const [fleck, M] = entries[i];
this.registerFleckHooks(fleck, M);
this.invoke('@flecks/core.registered', fleck, M);
2022-02-25 04:58:08 -06:00
}
this.configureFlecksDefaults();
2022-04-04 04:42:01 -05:00
debugSilly('config: %O', this.config);
2022-02-28 20:42:40 -06:00
}
/**
* Configure a hook implementation to run after another implementation.
*
* @param {string[]} after
* @param {function} implementation
*/
static after(after, implementation) {
if (!implementation[HookOrder]) {
implementation[HookOrder] = {};
}
if (!implementation[HookOrder].after) {
implementation[HookOrder].after = [];
}
implementation[HookOrder].after.push(...after);
return implementation;
}
/**
* Configure a hook implementation to run before another implementation.
*
* @param {string[]} before
* @param {function} implementation
*/
static before(before, implementation) {
if (!implementation[HookOrder]) {
implementation[HookOrder] = {};
}
if (!implementation[HookOrder].before) {
implementation[HookOrder].before = [];
}
implementation[HookOrder].before.push(...before);
return implementation;
}
/**
* Configure defaults for a fleck.
*
* @param {string} fleck
* @protected
*/
configureFleckDefaults(fleck) {
2022-02-28 20:42:40 -06:00
this.config[fleck] = {
2022-03-08 16:03:06 -06:00
...this.invokeFleck('@flecks/core.config', fleck),
2022-02-28 20:42:40 -06:00
...this.config[fleck],
};
}
/**
* Configure defaults for all flecks.
*
* @protected
*/
configureFlecksDefaults() {
const flecks = this.flecksImplementing('@flecks/core.config');
2022-02-28 20:42:40 -06:00
for (let i = 0; i < flecks.length; i++) {
this.configureFleckDefaults(flecks[i]);
2022-02-28 20:42:40 -06:00
}
2022-02-25 04:58:08 -06:00
}
/**
* [Dasherize]{@link https://en.wiktionary.org/wiki/dasherize} a fleck path.
*
* @param {string} path The path to dasherize.
* @returns {string}
*/
static dasherizePath(path) {
const parts = dirname(path).split('/');
if ('.' === parts[0]) {
parts.shift();
}
if ('index' === parts[parts.length - 1]) {
parts.pop();
}
return join(parts.join('-'), basename(path, extname(path)));
}
/**
* Generate a decorator from a require context.
*
* @param {*} context @see {@link https://webpack.js.org/guides/dependency-management/#requirecontext}
* @param {object} config
* @param {function} [config.transformer = {@link camelCase}]
* Function to run on each context path.
* @returns {function} The decorator.
*/
2022-02-25 04:58:08 -06:00
static decorate(
context,
{
transformer = camelCase,
} = {},
) {
return (Gathered, flecks) => (
2022-02-25 04:58:08 -06:00
context.keys()
.reduce(
(Gathered, path) => {
const key = transformer(this.dasherizePath(path));
if (!Gathered[key]) {
return Gathered;
}
const {default: M} = context(path);
if ('function' !== typeof M) {
throw new ReferenceError(
`Flecks.decorate(): require(${path}).default is not a function (from: ${context.id})`,
);
}
return {...Gathered, [key]: M(Gathered[key], flecks)};
},
Gathered,
)
);
2022-02-25 04:58:08 -06:00
}
2023-12-08 05:32:28 -06:00
checkAndDecorateRawGathered(hook, raw, check) {
// Gather classes and check.
check(raw, hook);
// Decorate and check.
const decorated = this.invokeComposed(`${hook}.decorate`, raw);
check(decorated, `${hook}.decorate`);
return decorated;
}
/**
* Destroy this instance.
*/
2022-03-22 00:57:40 -05:00
destroy() {
this.config = {};
this.hooks = {};
this.flecks = {};
this.platforms = [];
}
/**
* Lists all flecks implementing a hook, including platform-specific and elided variants.
*
* @param {string} hook
* @returns {string[]} The expanded list of flecks.
*/
2022-02-25 04:58:08 -06:00
expandedFlecks(hook) {
const flecks = this.lookupFlecks(hook);
let expanded = [];
for (let i = 0; i < flecks.length; ++i) {
const fleck = flecks[i];
// Just the fleck.
2022-02-25 04:58:08 -06:00
expanded.push(fleck);
// Platform-specific variants.
2022-02-25 04:58:08 -06:00
for (let j = 0; j < this.platforms.length; ++j) {
const platform = this.platforms[j];
const variant = join(fleck, platform);
if (this.fleck(variant)) {
expanded.push(variant);
}
}
}
// Expand elided flecks.
2022-02-25 04:58:08 -06:00
const index = expanded.findIndex((fleck) => '...' === fleck);
if (-1 !== index) {
if (-1 !== expanded.slice(index + 1).findIndex((fleck) => '...' === fleck)) {
throw new Error(
`Illegal ordering specification: hook '${hook}' has multiple ellipses.`,
);
}
const before = expanded.slice(0, index);
const after = expanded.slice(index + 1);
const implementing = this.flecksImplementing(hook);
const all = [];
for (let i = 0; i < implementing.length; ++i) {
const fleck = implementing[i];
all.push(fleck);
for (let j = 0; j < this.platforms.length; ++j) {
const platform = this.platforms[j];
const variant = join(fleck, platform);
if (this.fleck(variant) && this.fleckImplementation(variant, hook)) {
2022-02-25 04:58:08 -06:00
all.push(variant);
}
}
}
// Map the elided fleck implementations to vertices in a dependency graph.
const graph = new Digraph();
without(all, ...before.concat(after))
.forEach((fleck) => {
graph.ensureTail(fleck);
const implementation = this.fleckImplementation(fleck, hook);
if (implementation[HookOrder]) {
if (implementation[HookOrder].before) {
implementation[HookOrder].before.forEach((before) => {
graph.addArc(fleck, before);
});
}
if (implementation[HookOrder].after) {
implementation[HookOrder].after.forEach((after) => {
graph.ensureTail(after);
graph.addArc(after, fleck);
});
}
}
});
// Check for cycles.
const cycles = graph.detectCycles();
if (cycles.length > 0) {
throw new Error(
`Illegal ordering specification: hook '${
hook
}' has positioning cycles: ${
cycles.map(([l, r]) => `${l} <-> ${r}`).join(', ')
}`,
);
}
// Sort the graph and place it.
expanded = [...before, ...graph.sort(), ...after];
2022-02-25 04:58:08 -06:00
}
// Filter unimplemented.
return expanded
.filter((fleck) => this.fleckImplementation(fleck, hook));
2022-02-25 04:58:08 -06:00
}
/**
* Get the module for a fleck.
*
* @param {*} fleck
*
* @returns {*}
*/
2022-02-25 04:58:08 -06:00
fleck(fleck) {
return this.flecks[fleck];
}
/**
* Get a fleck's implementation of a hook.
*
* @param {*} fleck
* @param {string} hook
* @returns {boolean}
*/
fleckImplementation(fleck, hook) {
return this.hooks[hook]?.find(({fleck: candidate}) => fleck === candidate);
2022-02-25 04:58:08 -06:00
}
/**
* Get a list of flecks implementing a hook.
*
* @param {string} hook
* @returns {string[]}
*/
2022-02-25 04:58:08 -06:00
flecksImplementing(hook) {
return this.hooks[hook]?.map(({fleck}) => fleck) || [];
}
/**
* Gather and register class types.
*
* @param {string} hook
* @param {object} config
* @param {string} [config.idProperty='id'] The property used to get/set the class ID.
* @param {string} [config.typeProperty='type'] The property used to get/set the class type.
* @param {function} [config.check=() => {}] Check the validity of the gathered classes.
* @returns {object} An object with keys for ID, type, {@link ById}, and {@link ByType}.
*/
2022-02-25 04:58:08 -06:00
gather(
hook,
{
idProperty = 'id',
typeProperty = 'type',
2022-02-25 04:58:08 -06:00
check = () => {},
} = {},
) {
if (!hook || 'string' !== typeof hook) {
throw new TypeError('Flecks.gather(): Expects parameter 1 (hook) to be string');
}
// Gather classes and check.
2022-03-08 14:50:16 -06:00
const raw = this.invokeMerge(hook);
2023-12-08 05:32:28 -06:00
const decorated = this.checkAndDecorateRawGathered(hook, raw, check);
// Assign unique IDs to each class and sort by type.
2022-02-25 04:58:08 -06:00
let uid = 1;
const ids = {};
const types = (
Object.fromEntries(
Object.entries(decorated)
.sort(([ltype], [rtype]) => (ltype < rtype ? -1 : 1))
.map(([type, Class]) => {
const id = uid++;
ids[id] = wrapGathered(Class, id, idProperty, type, typeProperty);
2022-02-25 04:58:08 -06:00
return [type, ids[id]];
}),
)
);
// Conglomerate all ID and type keys along with Symbols for accessing either/or.
2022-02-25 04:58:08 -06:00
const gathered = {
...ids,
...types,
[ById]: ids,
[ByType]: types,
};
// Register for HMR?
2023-12-08 05:32:28 -06:00
hotGathered.set(
hook,
{
check,
idProperty,
typeProperty,
gathered,
},
);
2022-04-04 04:42:01 -05:00
debug("gathered '%s': %O", hook, Object.keys(gathered[ByType]));
2022-02-25 04:58:08 -06:00
return gathered;
}
/**
* Get a configuration value.
*
* @param {string} path The configuration path e.g. `@flecks/example.config`.
* @param {*} defaultValue The default value if no configuration value is found.
* @returns {*}
*/
2022-02-25 04:58:08 -06:00
get(path, defaultValue) {
return get(this.config, path, defaultValue);
}
/**
* Return an object whose keys are fleck paths and values are the result of invoking the hook.
* @param {string} hook
* @param {...any} args Arguments passed to each implementation.
* @returns {*}
*/
2022-02-25 04:58:08 -06:00
invoke(hook, ...args) {
if (!this.hooks[hook]) {
2022-03-13 10:15:29 -05:00
return {};
2022-02-25 04:58:08 -06:00
}
return this.flecksImplementing(hook)
.reduce((r, fleck) => ({...r, [fleck]: this.invokeFleck(hook, fleck, ...args)}), {});
}
/**
* See: [function composition](https://www.educative.io/edpresso/function-composition-in-javascript).
*
* @configurable
* @param {string} hook
* @param {*} initial The initial value passed to the composition chain.
* @param {...any} args The arguments passed after the accumulator to each implementation.
* @returns {*} The final composed value.
*/
invokeComposed(hook, initial, ...args) {
2022-02-25 04:58:08 -06:00
if (!this.hooks[hook]) {
return initial;
2022-02-25 04:58:08 -06:00
}
const flecks = this.expandedFlecks(hook);
if (0 === flecks.length) {
return initial;
2022-02-25 04:58:08 -06:00
}
return flecks
.reduce((r, fleck) => this.invokeFleck(hook, fleck, r, ...args), initial);
2022-02-25 04:58:08 -06:00
}
/**
* An async version of `invokeComposed`.
*
* @see {@link Flecks#invokeComposed}
*/
2022-03-01 09:21:03 -06:00
async invokeComposedAsync(hook, arg, ...args) {
if (!this.hooks[hook]) {
return arg;
}
const flecks = this.expandedFlecks(hook);
if (0 === flecks.length) {
return arg;
}
return flecks
.reduce(async (r, fleck) => this.invokeFleck(hook, fleck, await r, ...args), arg);
}
/**
* Invokes a hook and returns a flat array of results.
*
* @param {string} hook
* @param {...any} args The arguments passed to each implementation.
* @returns {any[]}
*/
2022-02-25 04:58:08 -06:00
invokeFlat(hook, ...args) {
if (!this.hooks[hook]) {
return [];
}
return this.hooks[hook].map(({fleck}) => this.invokeFleck(hook, fleck, ...args));
}
/**
* Invokes a hook on a single fleck.
*
* @param {string} hook
* @param {*} fleck
* @param {...any} args
* @returns {*}
*/
2022-02-25 04:58:08 -06:00
invokeFleck(hook, fleck, ...args) {
2022-04-04 04:42:01 -05:00
debugSilly('invokeFleck(%s, %s, ...)', hook, fleck);
2022-02-25 04:58:08 -06:00
if (!this.hooks[hook]) {
return undefined;
}
const candidate = this.hooks[hook]
.find(({fleck: candidate}) => candidate === fleck);
if (!candidate) {
return undefined;
}
return candidate.fn(...(args.concat(this)));
}
static $$invokeMerge(r, o) {
return {...r, ...o};
}
/**
* Specialization of `invokeReduce`. Invokes a hook and reduces an object from all the resulting
* objects.
*
* @param {string} hook
* @param {...any} args
* @returns {object}
*/
2022-03-08 14:50:16 -06:00
invokeMerge(hook, ...args) {
return this.invokeReduce(hook, this.constructor.$$invokeMerge, {}, ...args);
2022-03-08 14:50:16 -06:00
}
/**
* An async version of `invokeMerge`.
*
* @see {@link Flecks#invokeMerge}
*/
2022-03-08 14:50:16 -06:00
async invokeMergeAsync(hook, ...args) {
return this.invokeReduceAsync(hook, this.constructor.$$invokeMerge, {}, ...args);
}
static $$invokeMergeUnique() {
const track = {};
return (r, o, fleck, hook) => {
const keys = Object.keys(o);
for (let i = 0; i < keys.length; ++i) {
const key = keys[i];
if (track[key]) {
throw new ReferenceError(
`Conflict in ${hook}: '${track[key]}' implemented '${key}', followed by '${fleck}'`,
);
}
track[key] = fleck;
}
return ({...r, ...o});
};
2022-03-08 14:50:16 -06:00
}
/**
* Specialization of `invokeMerge`. Invokes a hook and reduces an object from all the resulting
* objects.
*
* @param {string} hook
* @param {...any} args
* @returns {object}
*/
invokeMergeUnique(hook, ...args) {
return this.invokeReduce(hook, this.constructor.$$invokeMergeUnique(), {}, ...args);
}
/**
* An async version of `invokeMergeUnique`.
*
* @see {@link Flecks#invokeMergeUnique}
*/
async invokeMergeUniqueAsync(hook, ...args) {
return this.invokeReduceAsync(hook, this.constructor.$$invokeMergeUnique(), {}, ...args);
}
/**
* See: [Array.prototype.reduce](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce)
*
* @param {string} hook
* @param {*} reducer
* @param {*} initial
* @param {...any} args The arguments passed after the accumulator to each implementation.
* @returns {*}
*/
2022-03-08 14:50:16 -06:00
invokeReduce(hook, reducer, initial, ...args) {
2022-02-25 04:58:08 -06:00
if (!this.hooks[hook]) {
return initial;
}
return this.hooks[hook]
.reduce(
(r, {fleck}) => reducer(r, this.invokeFleck(hook, fleck, ...args), fleck, hook),
initial,
);
2022-02-25 04:58:08 -06:00
}
/**
* An async version of `invokeReduce`.
*
* @see {@link Flecks#invokeReduce}
*/
2022-03-08 14:50:16 -06:00
async invokeReduceAsync(hook, reducer, initial, ...args) {
2022-02-25 04:58:08 -06:00
if (!this.hooks[hook]) {
return initial;
}
return this.hooks[hook]
.reduce(
async (r, {fleck}) => (
reducer(await r, await this.invokeFleck(hook, fleck, ...args), fleck, hook)
),
2022-02-25 04:58:08 -06:00
initial,
);
}
/**
* Invokes hooks on a fleck one after another. This is effectively a configurable version of
* {@link Flecks#invokeFlat}.
*
* @configurable
* @param {string} hook
* @param {...any} args The arguments passed to each implementation.
* @returns {any[]}
*/
2022-02-25 04:58:08 -06:00
invokeSequential(hook, ...args) {
if (!this.hooks[hook]) {
return [];
}
const flecks = this.expandedFlecks(hook);
if (0 === flecks.length) {
return [];
}
const results = [];
while (flecks.length > 0) {
const fleck = flecks.shift();
results.push(this.invokeFleck(hook, fleck, ...args));
2022-02-25 04:58:08 -06:00
}
return results;
}
/**
* An async version of `invokeSequential`.
*
* @see {@link Flecks#invokeSequential}
*/
2022-02-25 04:58:08 -06:00
async invokeSequentialAsync(hook, ...args) {
if (!this.hooks[hook]) {
return [];
}
const flecks = this.expandedFlecks(hook);
if (0 === flecks.length) {
return [];
}
const results = [];
while (flecks.length > 0) {
const fleck = flecks.shift();
// eslint-disable-next-line no-await-in-loop
results.push(await this.invokeFleck(hook, fleck, ...args));
2022-02-25 04:58:08 -06:00
}
return results;
}
/**
* Lookup flecks configured for a hook.
*
* If no configuration is found, defaults to ellipses.
*
* @param {string} hook
* @example
* # Given hook @flecks/example.hook, `flecks.yml` could be configured as such:
* '@flecks/example':
* hook: ['...']
* @returns {string[]}
*/
2022-02-25 04:58:08 -06:00
lookupFlecks(hook) {
2022-03-08 16:03:06 -06:00
const index = hook.indexOf('.');
if (-1 === index) {
return ['...'];
}
return this.get([hook.slice(0, index), hook.slice(index + 1)], ['...']);
2022-02-25 04:58:08 -06:00
}
/**
* Make a middleware function from configured middleware.
* @param {string} hook
* @returns {function}
*/
2022-02-25 04:58:08 -06:00
makeMiddleware(hook) {
2022-04-04 04:42:01 -05:00
debugSilly('makeMiddleware(...): %s', hook);
2022-02-25 04:58:08 -06:00
if (!this.hooks[hook]) {
return (...args) => args.pop()();
2022-02-25 04:58:08 -06:00
}
const flecks = this.expandedFlecks(hook);
if (0 === flecks.length) {
return (...args) => args.pop()();
2022-02-25 04:58:08 -06:00
}
debugSilly('middleware: %O', flecks);
const instance = new Middleware(flecks.map((fleck) => this.invokeFleck(hook, fleck)));
return instance.dispatch.bind(instance);
}
/**
* Provide classes for e.g. {@link Flecks#gather}
*
* @param {*} context @see {@link https://webpack.js.org/guides/dependency-management/#requirecontext}
* @param {object} config
* @param {function} [config.invoke = true] Invoke the default exports as a function?
* @param {function} [config.transformer = {@link camelCase}]
* Function to run on each context path.
* @returns {object}
*/
2022-02-25 04:58:08 -06:00
static provide(
context,
{
invoke = true,
2022-02-25 04:58:08 -06:00
transformer = camelCase,
} = {},
) {
return (flecks) => (
Object.fromEntries(
context.keys()
.map((path) => {
const {default: M} = context(path);
if (invoke && 'function' !== typeof M) {
2022-02-25 04:58:08 -06:00
throw new ReferenceError(
`Flecks.provide(): require(${
path
}).default is not a function (from: ${
context.id
})`,
);
}
return [
transformer(this.dasherizePath(path)),
invoke ? M(flecks) : M,
2022-02-25 04:58:08 -06:00
];
}),
)
);
}
/**
* Refresh a fleck's hooks, configuration, and any gathered classes.
*
* @param {string} fleck
* @param {object} M The fleck module
* @protected
*/
2022-02-25 04:58:08 -06:00
refresh(fleck, M) {
debug('refreshing %s...', fleck);
// Remove old hook implementations.
this.unregisterFleckHooks(fleck);
2022-02-25 04:58:08 -06:00
// Replace the fleck.
this.registerFleckHooks(fleck, M);
2022-02-25 04:58:08 -06:00
// Write config.
this.configureFleckDefaults(fleck);
2022-02-25 04:58:08 -06:00
// HMR.
2023-12-08 05:32:28 -06:00
this.refreshGathered(fleck);
2022-02-25 04:58:08 -06:00
}
/**
* Refresh gathered classes for a fleck.
*
* @param {string} fleck
*/
refreshGathered(fleck) {
2022-02-25 04:58:08 -06:00
const it = hotGathered.entries();
for (let current = it.next(); current.done !== true; current = it.next()) {
const {
value: [
hook,
{
2023-12-08 05:32:28 -06:00
check,
idProperty,
2022-02-25 04:58:08 -06:00
gathered,
typeProperty,
2022-02-25 04:58:08 -06:00
},
],
} = current;
2023-12-08 05:32:28 -06:00
let raw;
// If decorating, gather all again
if (this.fleckImplementation(fleck, `${hook}.decorate`)) {
2023-12-08 05:32:28 -06:00
raw = this.invokeMerge(hook);
debugSilly('%s implements %s.decorate', fleck, hook);
}
// If only implementing, gather and decorate.
else if (this.fleckImplementation(fleck, hook)) {
2023-12-08 05:32:28 -06:00
raw = this.invokeFleck(hook, fleck);
debugSilly('%s implements %s', fleck, hook);
}
if (raw) {
const decorated = this.checkAndDecorateRawGathered(hook, raw, check);
debug('updating gathered %s from %s... %O', hook, fleck, decorated);
const entries = Object.entries(decorated);
entries.forEach(([type, Class]) => {
const {[type]: {[idProperty]: id}} = gathered;
const Subclass = wrapGathered(Class, id, idProperty, type, typeProperty);
2022-02-25 04:58:08 -06:00
// eslint-disable-next-line no-multi-assign
gathered[type] = gathered[id] = gathered[ById][id] = gathered[ByType][type] = Subclass;
2023-12-08 05:32:28 -06:00
this.invoke('@flecks/core.hmr.gathered.class', Subclass, hook);
});
this.invoke('@flecks/core.hmr.gathered', gathered, hook);
2022-02-25 04:58:08 -06:00
}
}
}
/**
* Register hooks for a fleck.
*
* @param {string} fleck
* @param {object} M The fleck module
* @protected
*/
registerFleckHooks(fleck, M) {
debugSilly('registering %s...', fleck);
this.flecks[fleck] = M;
if (M.hooks) {
const keys = Object.keys(M.hooks);
debugSilly("hooks for '%s': %O", fleck, keys);
for (let j = 0; j < keys.length; j++) {
const key = keys[j];
if (!this.hooks[key]) {
this.hooks[key] = [];
}
this.hooks[key].push({fleck, fn: M.hooks[key]});
}
}
}
/**
* Set a configuration value.
*
* @param {string} path The configuration path e.g. `@flecks/example.config`.
* @param {*} value The value to set.
* @returns {*} The value that was set.
*/
set(path, value) {
return set(this.config, path, value);
}
/**
* Unregister hooks for a fleck.
* @param {*} fleck
*/
unregisterFleckHooks(fleck) {
const keys = Object.keys(this.hooks);
for (let j = 0; j < keys.length; j++) {
const key = keys[j];
if (this.hooks[key]) {
const index = this.hooks[key].findIndex(({fleck: hookPlugin}) => hookPlugin === fleck);
if (-1 !== index) {
this.hooks[key].splice(index, 1);
}
}
}
}
2022-02-25 04:58:08 -06:00
}
2024-01-05 22:49:02 -06:00
Flecks.get = get;
Flecks.set = set;