2022-02-25 04:58:08 -06:00
|
|
|
// eslint-disable-next-line max-classes-per-file
|
2024-01-16 00:28:20 -06:00
|
|
|
const {
|
2022-02-25 04:58:08 -06:00
|
|
|
basename,
|
|
|
|
dirname,
|
|
|
|
extname,
|
|
|
|
join,
|
2024-01-16 00:28:20 -06:00
|
|
|
} = require('path');
|
2022-02-25 04:58:08 -06:00
|
|
|
|
2024-01-16 00:28:20 -06:00
|
|
|
const get = require('lodash.get');
|
2024-01-31 09:30:06 -06:00
|
|
|
const set = require('set-value');
|
2022-02-25 04:58:08 -06:00
|
|
|
|
2024-01-16 00:28:20 -06:00
|
|
|
const compose = require('./compose');
|
|
|
|
const D = require('./debug');
|
|
|
|
const Digraph = require('./digraph');
|
|
|
|
const Middleware = require('./middleware');
|
2022-02-25 04:58:08 -06:00
|
|
|
|
|
|
|
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
|
|
|
|
2024-01-08 22:58:03 -06:00
|
|
|
// Symbol for hook ordering.
|
2024-01-09 14:23:12 -06:00
|
|
|
const HookPriority = Symbol.for('@flecks/core.hookPriority');
|
2024-01-08 22:58:03 -06:00
|
|
|
|
2022-08-10 10:09:02 -05:00
|
|
|
// Symbols for Gathered classes.
|
2024-01-16 00:28:20 -06:00
|
|
|
exports.ById = Symbol.for('@flecks/core.byId');
|
|
|
|
exports.ByType = Symbol.for('@flecks/core.byType');
|
2022-02-25 04:58:08 -06:00
|
|
|
|
2022-08-10 10:09:02 -05: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);
|
|
|
|
|
2022-08-10 10:09:02 -05:00
|
|
|
/**
|
|
|
|
* CamelCase a string.
|
|
|
|
*
|
|
|
|
* @param {string} string
|
|
|
|
* @returns {string}
|
|
|
|
*/
|
2022-02-25 04:58:08 -06:00
|
|
|
const camelCase = (string) => string.split(/[_-]/).map(capitalize).join('');
|
|
|
|
|
2022-08-10 10:09:02 -05:00
|
|
|
// 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 {
|
|
|
|
|
2022-08-10 10:09:02 -05:00
|
|
|
static get [idProperty]() {
|
2022-02-25 04:58:08 -06:00
|
|
|
return id;
|
|
|
|
}
|
|
|
|
|
2022-08-10 10:09:02 -05:00
|
|
|
static get [typeProperty]() {
|
2022-02-25 04:58:08 -06:00
|
|
|
return type;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
return Subclass;
|
|
|
|
};
|
|
|
|
|
2024-02-04 09:16:09 -06:00
|
|
|
class Flecks {
|
2022-02-25 04:58:08 -06:00
|
|
|
|
2022-08-10 10:09:02 -05:00
|
|
|
config = {};
|
|
|
|
|
2024-01-09 14:23:12 -06:00
|
|
|
$$expandedFlecksCache = {};
|
|
|
|
|
2022-08-10 10:09:02 -05:00
|
|
|
flecks = {};
|
|
|
|
|
2024-01-28 11:09:34 -06:00
|
|
|
$$gathered = {};
|
|
|
|
|
2022-08-10 10:09:02 -05:00
|
|
|
hooks = {};
|
|
|
|
|
|
|
|
/**
|
2024-01-16 00:28:20 -06:00
|
|
|
* @param {object} runtime
|
|
|
|
* @param {object} runtime.config configuration (e.g. loaded from `flecks.yml`).
|
|
|
|
* @param {object} runtime.flecks fleck modules.
|
2022-08-10 10:09:02 -05:00
|
|
|
*/
|
2022-02-25 04:58:08 -06:00
|
|
|
constructor({
|
|
|
|
config = {},
|
|
|
|
flecks = {},
|
|
|
|
} = {}) {
|
2022-08-10 10:09:02 -05:00
|
|
|
const emptyConfigForAllFlecks = Object.fromEntries(
|
|
|
|
Object.keys(flecks).map((path) => [path, {}]),
|
|
|
|
);
|
|
|
|
this.config = {...emptyConfigForAllFlecks, ...config};
|
2022-02-25 04:58:08 -06:00
|
|
|
const entries = Object.entries(flecks);
|
2024-02-04 09:16:09 -06:00
|
|
|
this.constructor.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];
|
2022-08-10 10:09:02 -05:00
|
|
|
this.registerFleckHooks(fleck, M);
|
|
|
|
this.invoke('@flecks/core.registered', fleck, M);
|
2022-02-25 04:58:08 -06:00
|
|
|
}
|
2022-08-10 10:09:02 -05:00
|
|
|
this.configureFlecksDefaults();
|
2024-02-04 09:16:09 -06:00
|
|
|
this.constructor.debugSilly('config: %O', this.config);
|
2022-02-28 20:42:40 -06:00
|
|
|
}
|
|
|
|
|
2022-08-10 10:09:02 -05:00
|
|
|
/**
|
|
|
|
* 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],
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-08-10 10:09:02 -05:00
|
|
|
/**
|
|
|
|
* 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++) {
|
2022-08-10 10:09:02 -05:00
|
|
|
this.configureFleckDefaults(flecks[i]);
|
2022-02-28 20:42:40 -06:00
|
|
|
}
|
2022-02-25 04:58:08 -06:00
|
|
|
}
|
|
|
|
|
2022-08-10 10:09:02 -05: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,
|
|
|
|
} = {},
|
|
|
|
) {
|
2022-08-10 10:09:02 -05:00
|
|
|
return (Gathered, flecks) => (
|
2022-02-25 04:58:08 -06:00
|
|
|
context.keys()
|
2022-08-10 10:09:02 -05:00
|
|
|
.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
|
|
|
}
|
|
|
|
|
2024-01-29 08:15:22 -06:00
|
|
|
async checkAndDecorateRawGathered(hook, raw, check) {
|
2023-12-08 05:32:28 -06:00
|
|
|
// Gather classes and check.
|
|
|
|
check(raw, hook);
|
|
|
|
// Decorate and check.
|
2024-01-29 08:15:22 -06:00
|
|
|
const decorated = await this.invokeComposedAsync(`${hook}.decorate`, raw);
|
2023-12-08 05:32:28 -06:00
|
|
|
check(decorated, `${hook}.decorate`);
|
|
|
|
return decorated;
|
|
|
|
}
|
|
|
|
|
2022-08-10 10:09:02 -05:00
|
|
|
/**
|
|
|
|
* Destroy this instance.
|
|
|
|
*/
|
2022-03-22 00:57:40 -05:00
|
|
|
destroy() {
|
2024-01-28 11:09:34 -06:00
|
|
|
this.$$expandedFlecksCache = {};
|
2022-03-22 00:57:40 -05:00
|
|
|
this.config = {};
|
2024-01-28 11:09:34 -06:00
|
|
|
this.$$gathered = {};
|
2022-03-22 00:57:40 -05:00
|
|
|
this.hooks = {};
|
|
|
|
this.flecks = {};
|
|
|
|
}
|
|
|
|
|
2022-08-10 10:09:02 -05:00
|
|
|
/**
|
|
|
|
* 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) {
|
2024-01-09 14:23:12 -06:00
|
|
|
if (this.$$expandedFlecksCache[hook]) {
|
2024-01-09 20:59:38 -06:00
|
|
|
return [...this.$$expandedFlecksCache[hook]];
|
2024-01-09 14:23:12 -06:00
|
|
|
}
|
2022-02-25 04:58:08 -06:00
|
|
|
const flecks = this.lookupFlecks(hook);
|
|
|
|
let expanded = [];
|
|
|
|
for (let i = 0; i < flecks.length; ++i) {
|
|
|
|
const fleck = flecks[i];
|
|
|
|
expanded.push(fleck);
|
|
|
|
}
|
2024-01-09 14:23:12 -06:00
|
|
|
// Handle elision.
|
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.`,
|
|
|
|
);
|
|
|
|
}
|
2024-01-09 14:23:12 -06:00
|
|
|
// Split at the elision point and remove the ellipses.
|
2022-02-25 04:58:08 -06:00
|
|
|
const before = expanded.slice(0, index);
|
|
|
|
const after = expanded.slice(index + 1);
|
2024-01-09 14:23:12 -06:00
|
|
|
expanded.splice(index, 1);
|
|
|
|
// Expand elided flecks.
|
|
|
|
const elided = [];
|
2022-02-25 04:58:08 -06:00
|
|
|
const implementing = this.flecksImplementing(hook);
|
|
|
|
for (let i = 0; i < implementing.length; ++i) {
|
|
|
|
const fleck = implementing[i];
|
2024-01-09 14:23:12 -06:00
|
|
|
if (!expanded.includes(fleck)) {
|
|
|
|
elided.push(fleck);
|
|
|
|
}
|
2022-02-25 04:58:08 -06:00
|
|
|
}
|
2024-01-09 14:23:12 -06:00
|
|
|
// Map the fleck implementations to vertices in a dependency graph.
|
|
|
|
const graph = this.flecksHookGraph([...before, ...elided, ...after], hook);
|
2024-01-08 22:58:03 -06:00
|
|
|
// 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(', ')
|
|
|
|
}`,
|
|
|
|
);
|
|
|
|
}
|
2024-01-09 14:23:12 -06:00
|
|
|
// Sort the graph.
|
|
|
|
expanded = [
|
|
|
|
...before,
|
|
|
|
...graph.sort().filter((fleck) => !expanded.includes(fleck)),
|
|
|
|
...after,
|
|
|
|
];
|
2022-02-25 04:58:08 -06:00
|
|
|
}
|
2024-01-09 20:59:38 -06:00
|
|
|
// Build another graph, but add dependencies connecting the final ordering. If cycles exist,
|
|
|
|
// the ordering violated the expectation of one or more implementations.
|
2024-01-09 01:10:23 -06:00
|
|
|
const graph = this.flecksHookGraph(expanded, hook);
|
|
|
|
expanded.forEach((fleck, i) => {
|
|
|
|
if (i < expanded.length - 1) {
|
2024-01-09 20:59:38 -06:00
|
|
|
graph.addDependency(expanded[i + 1], fleck);
|
2024-01-09 01:10:23 -06:00
|
|
|
}
|
|
|
|
});
|
|
|
|
const cycles = graph.detectCycles();
|
|
|
|
if (cycles.length > 0) {
|
|
|
|
cycles.forEach(([l, r]) => {
|
|
|
|
const lImplementation = this.fleckImplementation(l, hook);
|
2024-01-09 14:23:12 -06:00
|
|
|
const {before: lBefore = [], after: lAfter = []} = lImplementation?.[HookPriority] || {};
|
2024-01-09 01:10:23 -06:00
|
|
|
const explanation = [hook];
|
|
|
|
if (lBefore.includes(r)) {
|
|
|
|
explanation.push(l, 'before', r);
|
|
|
|
}
|
|
|
|
if (lAfter.includes(r)) {
|
|
|
|
explanation.push(l, 'after', r);
|
|
|
|
}
|
|
|
|
const rImplementation = this.fleckImplementation(r, hook);
|
2024-01-09 14:23:12 -06:00
|
|
|
const {before: rBefore = [], after: rAfter = []} = rImplementation?.[HookPriority] || {};
|
2024-01-09 01:10:23 -06:00
|
|
|
if (rBefore.includes(l)) {
|
|
|
|
explanation.push(r, 'before', l);
|
|
|
|
}
|
|
|
|
if (rAfter.includes(l)) {
|
|
|
|
explanation.push(r, 'after', l);
|
|
|
|
}
|
2024-02-04 09:16:09 -06:00
|
|
|
this.constructor.debug(
|
2024-01-09 14:23:12 -06:00
|
|
|
"Suspicious ordering specification for '%s': '%s' expected to run %s '%s'!",
|
|
|
|
...explanation,
|
|
|
|
);
|
2024-01-09 01:10:23 -06:00
|
|
|
});
|
|
|
|
}
|
2024-01-08 22:58:03 -06:00
|
|
|
// Filter unimplemented.
|
2024-01-09 14:23:12 -06:00
|
|
|
this.$$expandedFlecksCache[hook] = expanded // eslint-disable-line no-return-assign
|
2024-01-08 22:58:03 -06:00
|
|
|
.filter((fleck) => this.fleckImplementation(fleck, hook));
|
2024-02-04 09:16:09 -06:00
|
|
|
this.constructor.debugSilly("cached hook expansion for '%s': %O", hook, expanded);
|
2024-01-09 20:59:38 -06:00
|
|
|
return [...this.$$expandedFlecksCache[hook]];
|
2022-02-25 04:58:08 -06:00
|
|
|
}
|
|
|
|
|
2022-08-10 10:09:02 -05:00
|
|
|
/**
|
|
|
|
* Get the module for a fleck.
|
|
|
|
*
|
|
|
|
* @param {*} fleck
|
|
|
|
*
|
|
|
|
* @returns {*}
|
|
|
|
*/
|
2022-02-25 04:58:08 -06:00
|
|
|
fleck(fleck) {
|
|
|
|
return this.flecks[fleck];
|
|
|
|
}
|
|
|
|
|
2022-08-10 10:09:02 -05:00
|
|
|
/**
|
2024-01-08 22:58:03 -06:00
|
|
|
* Get a fleck's implementation of a hook.
|
2022-08-10 10:09:02 -05:00
|
|
|
*
|
|
|
|
* @param {*} fleck
|
|
|
|
* @param {string} hook
|
|
|
|
* @returns {boolean}
|
|
|
|
*/
|
2024-01-08 22:58:03 -06:00
|
|
|
fleckImplementation(fleck, hook) {
|
2024-01-09 01:10:23 -06:00
|
|
|
if (!this.hooks[hook]) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
const found = this.hooks[hook]?.find(({fleck: candidate}) => fleck === candidate);
|
|
|
|
if (!found) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
return found.fn;
|
2022-02-25 04:58:08 -06:00
|
|
|
}
|
|
|
|
|
2022-08-10 10:09:02 -05: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) || [];
|
|
|
|
}
|
|
|
|
|
2024-01-09 01:10:23 -06:00
|
|
|
/**
|
|
|
|
* Create a dependency graph from a list of flecks.
|
|
|
|
* @param {string[]} flecks
|
|
|
|
* @param {string} hook
|
|
|
|
* @returns {Digraph} graph
|
|
|
|
*/
|
|
|
|
flecksHookGraph(flecks, hook) {
|
|
|
|
const graph = new Digraph();
|
|
|
|
flecks
|
|
|
|
.forEach((fleck) => {
|
|
|
|
graph.ensureTail(fleck);
|
|
|
|
const implementation = this.fleckImplementation(fleck, hook);
|
2024-01-09 14:23:12 -06:00
|
|
|
if (implementation?.[HookPriority]) {
|
|
|
|
if (implementation[HookPriority].before) {
|
|
|
|
implementation[HookPriority].before.forEach((before) => {
|
2024-01-09 20:59:38 -06:00
|
|
|
graph.addDependency(before, fleck);
|
2024-01-09 01:10:23 -06:00
|
|
|
});
|
|
|
|
}
|
2024-01-09 14:23:12 -06:00
|
|
|
if (implementation[HookPriority].after) {
|
|
|
|
implementation[HookPriority].after.forEach((after) => {
|
2024-01-09 20:59:38 -06:00
|
|
|
graph.addDependency(fleck, after);
|
2024-01-09 01:10:23 -06:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2024-01-09 14:23:12 -06:00
|
|
|
this.invoke('@flecks/core.priority', graph, hook);
|
2024-01-09 01:10:23 -06:00
|
|
|
return graph;
|
|
|
|
}
|
|
|
|
|
2024-01-13 02:17:18 -06:00
|
|
|
/**
|
|
|
|
* Create a mixed instance of flecks.
|
|
|
|
* @param {Object} config Configuration.
|
|
|
|
* @returns {Flecks} A flecks instance.
|
|
|
|
*/
|
2024-01-28 08:05:25 -06:00
|
|
|
static async from(runtime) {
|
2024-01-16 00:28:20 -06:00
|
|
|
const {flecks} = runtime;
|
2024-01-19 03:59:20 -06:00
|
|
|
const mixinDescription = Object.entries(flecks)
|
|
|
|
.map(([path, {mixin}]) => [path, mixin]).filter(([, mixin]) => mixin);
|
2024-02-04 09:16:09 -06:00
|
|
|
this.debugSilly('mixins: %O', mixinDescription.map(([path]) => path));
|
2024-01-19 03:59:20 -06:00
|
|
|
const Flecks = compose(...mixinDescription.map(([, mixin]) => mixin))(this);
|
2024-01-28 11:09:34 -06:00
|
|
|
const instance = new Flecks(runtime);
|
|
|
|
await instance.gatherHooks();
|
2024-01-28 11:55:08 -06:00
|
|
|
await instance.invokeSequentialAsync('@flecks/core.starting');
|
2024-01-28 11:09:34 -06:00
|
|
|
return instance;
|
2024-01-13 02:17:18 -06:00
|
|
|
}
|
|
|
|
|
2022-08-10 10:09:02 -05:00
|
|
|
/**
|
|
|
|
* 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}.
|
|
|
|
*/
|
2024-01-29 08:15:22 -06:00
|
|
|
async gather(
|
2022-02-25 04:58:08 -06:00
|
|
|
hook,
|
|
|
|
{
|
2022-08-10 10:09:02 -05:00
|
|
|
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');
|
|
|
|
}
|
2022-08-10 10:09:02 -05:00
|
|
|
// Gather classes and check.
|
2024-01-29 08:15:22 -06:00
|
|
|
const raw = await this.invokeMergeAsync(hook);
|
|
|
|
const decorated = await this.checkAndDecorateRawGathered(hook, raw, check);
|
2022-08-10 10:09:02 -05:00
|
|
|
// 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++;
|
2022-08-10 10:09:02 -05:00
|
|
|
ids[id] = wrapGathered(Class, id, idProperty, type, typeProperty);
|
2022-02-25 04:58:08 -06:00
|
|
|
return [type, ids[id]];
|
|
|
|
}),
|
|
|
|
)
|
|
|
|
);
|
2022-08-10 10:09:02 -05:00
|
|
|
// 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,
|
2024-01-16 00:28:20 -06:00
|
|
|
[exports.ById]: ids,
|
|
|
|
[exports.ByType]: types,
|
2022-02-25 04:58:08 -06:00
|
|
|
};
|
2024-01-28 11:09:34 -06:00
|
|
|
this.$$gathered[hook] = {
|
|
|
|
check,
|
|
|
|
idProperty,
|
|
|
|
typeProperty,
|
|
|
|
gathered,
|
|
|
|
};
|
2024-02-04 09:16:09 -06:00
|
|
|
this.constructor.debug("gathered '%s': %O", hook, Object.keys(gathered[exports.ByType]));
|
2022-02-25 04:58:08 -06:00
|
|
|
return gathered;
|
|
|
|
}
|
|
|
|
|
2024-01-28 11:09:34 -06:00
|
|
|
gathered(type) {
|
|
|
|
return this.$$gathered[type]?.gathered;
|
|
|
|
}
|
|
|
|
|
|
|
|
async gatherHooks() {
|
|
|
|
const gathering = await this.invokeAsync('@flecks/core.gathered');
|
|
|
|
await Promise.all(
|
|
|
|
Object.entries(gathering)
|
|
|
|
.map(([fleck, gathering]) => (
|
|
|
|
Promise.all(
|
|
|
|
Object.entries(gathering)
|
|
|
|
.map(([type, options]) => (
|
|
|
|
this.gather(`${fleck}.${type}`, options)
|
|
|
|
)),
|
|
|
|
)
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-08-10 10:09:02 -05:00
|
|
|
/**
|
|
|
|
* 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);
|
|
|
|
}
|
|
|
|
|
2024-01-12 05:00:32 -06:00
|
|
|
/**
|
2024-02-04 16:01:30 -06:00
|
|
|
* Interpolate a string with flecks configuration values.
|
2024-01-12 05:00:32 -06:00
|
|
|
* @param {string} string
|
|
|
|
* @returns The interpolated string.
|
|
|
|
*/
|
|
|
|
interpolate(string) {
|
|
|
|
return string.replace(/\[(.*?)\]/g, (match) => this.get(match));
|
|
|
|
}
|
|
|
|
|
2022-08-10 10:09:02 -05:00
|
|
|
/**
|
|
|
|
* 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)
|
2022-08-10 10:09:02 -05:00
|
|
|
.reduce((r, fleck) => ({...r, [fleck]: this.invokeFleck(hook, fleck, ...args)}), {});
|
|
|
|
}
|
|
|
|
|
2024-01-22 09:16:07 -06:00
|
|
|
/**
|
2024-01-22 12:11:23 -06:00
|
|
|
* Return an object whose keys are fleck paths and values are the `await`ed result of invoking
|
|
|
|
* the hook.
|
2024-01-22 09:16:07 -06:00
|
|
|
* @param {string} hook
|
|
|
|
* @param {...any} args Arguments passed to each implementation.
|
|
|
|
* @returns {*}
|
|
|
|
*/
|
|
|
|
async invokeAsync(hook, ...args) {
|
|
|
|
if (!this.hooks[hook]) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
return this.flecksImplementing(hook)
|
|
|
|
.reduce(
|
|
|
|
async (r, fleck) => ({
|
|
|
|
...(await r),
|
|
|
|
[fleck]: await this.invokeFleck(hook, fleck, ...args),
|
|
|
|
}),
|
|
|
|
{},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-08-10 10:09:02 -05:00
|
|
|
/**
|
|
|
|
* 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]) {
|
2022-08-10 10:09:02 -05:00
|
|
|
return initial;
|
2022-02-25 04:58:08 -06:00
|
|
|
}
|
|
|
|
const flecks = this.expandedFlecks(hook);
|
|
|
|
return flecks
|
2022-08-10 10:09:02 -05:00
|
|
|
.reduce((r, fleck) => this.invokeFleck(hook, fleck, r, ...args), initial);
|
2022-02-25 04:58:08 -06:00
|
|
|
}
|
|
|
|
|
2022-08-10 10:09:02 -05: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);
|
|
|
|
return flecks
|
|
|
|
.reduce(async (r, fleck) => this.invokeFleck(hook, fleck, await r, ...args), arg);
|
|
|
|
}
|
|
|
|
|
2022-08-10 10:09:02 -05:00
|
|
|
/**
|
|
|
|
* 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));
|
|
|
|
}
|
|
|
|
|
2022-08-10 10:09:02 -05:00
|
|
|
/**
|
|
|
|
* 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) {
|
2024-02-04 09:16:09 -06:00
|
|
|
this.constructor.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)));
|
|
|
|
}
|
|
|
|
|
2022-08-10 10:09:02 -05:00
|
|
|
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) {
|
2022-08-10 10:09:02 -05:00
|
|
|
return this.invokeReduce(hook, this.constructor.$$invokeMerge, {}, ...args);
|
2022-03-08 14:50:16 -06:00
|
|
|
}
|
|
|
|
|
2022-08-10 10:09:02 -05:00
|
|
|
/**
|
|
|
|
* An async version of `invokeMerge`.
|
|
|
|
*
|
|
|
|
* @see {@link Flecks#invokeMerge}
|
|
|
|
*/
|
2022-03-08 14:50:16 -06:00
|
|
|
async invokeMergeAsync(hook, ...args) {
|
2022-08-10 10:09:02 -05:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2022-08-10 10:09:02 -05: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]
|
2022-08-10 10:09:02 -05:00
|
|
|
.reduce(
|
|
|
|
(r, {fleck}) => reducer(r, this.invokeFleck(hook, fleck, ...args), fleck, hook),
|
|
|
|
initial,
|
|
|
|
);
|
2022-02-25 04:58:08 -06:00
|
|
|
}
|
|
|
|
|
2022-08-10 10:09:02 -05: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(
|
2022-08-10 10:09:02 -05:00
|
|
|
async (r, {fleck}) => (
|
|
|
|
reducer(await r, await this.invokeFleck(hook, fleck, ...args), fleck, hook)
|
|
|
|
),
|
2022-02-25 04:58:08 -06:00
|
|
|
initial,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-08-10 10:09:02 -05:00
|
|
|
/**
|
|
|
|
* 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 results = [];
|
2024-01-09 20:59:38 -06:00
|
|
|
const flecks = this.expandedFlecks(hook);
|
2022-02-25 04:58:08 -06:00
|
|
|
while (flecks.length > 0) {
|
|
|
|
const fleck = flecks.shift();
|
2024-01-08 22:58:03 -06:00
|
|
|
results.push(this.invokeFleck(hook, fleck, ...args));
|
2022-02-25 04:58:08 -06:00
|
|
|
}
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
|
2022-08-10 10:09:02 -05:00
|
|
|
/**
|
|
|
|
* 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 results = [];
|
2024-01-09 20:59:38 -06:00
|
|
|
const flecks = this.expandedFlecks(hook);
|
2022-02-25 04:58:08 -06:00
|
|
|
while (flecks.length > 0) {
|
|
|
|
const fleck = flecks.shift();
|
2024-01-08 22:58:03 -06:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2022-08-10 10:09:02 -05:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
}
|
|
|
|
|
2022-08-10 10:09:02 -05:00
|
|
|
/**
|
|
|
|
* Make a middleware function from configured middleware.
|
|
|
|
* @param {string} hook
|
|
|
|
* @returns {function}
|
|
|
|
*/
|
2022-02-25 04:58:08 -06:00
|
|
|
makeMiddleware(hook) {
|
2024-02-04 09:16:09 -06:00
|
|
|
this.constructor.debugSilly('makeMiddleware(...): %s', hook);
|
2022-02-25 04:58:08 -06:00
|
|
|
if (!this.hooks[hook]) {
|
2022-08-10 10:09:02 -05:00
|
|
|
return (...args) => args.pop()();
|
2022-02-25 04:58:08 -06:00
|
|
|
}
|
|
|
|
const flecks = this.expandedFlecks(hook);
|
|
|
|
if (0 === flecks.length) {
|
2024-01-09 20:59:38 -06:00
|
|
|
// No flecks, immediate dispatch.
|
2022-08-10 10:09:02 -05:00
|
|
|
return (...args) => args.pop()();
|
2022-02-25 04:58:08 -06:00
|
|
|
}
|
2024-02-04 09:16:09 -06:00
|
|
|
this.constructor.debugSilly('middleware: %O', flecks);
|
2024-01-08 22:58:03 -06:00
|
|
|
const instance = new Middleware(flecks.map((fleck) => this.invokeFleck(hook, fleck)));
|
2022-08-10 10:09:02 -05:00
|
|
|
return instance.dispatch.bind(instance);
|
|
|
|
}
|
|
|
|
|
2024-01-09 14:23:12 -06:00
|
|
|
/**
|
|
|
|
* Scedule the priority of a hook implementation.
|
|
|
|
*
|
|
|
|
* @param {function} implementation
|
|
|
|
* @param {object} schedule
|
|
|
|
*/
|
|
|
|
static priority(implementation, schedule = {}) {
|
|
|
|
const normalized = {};
|
|
|
|
if (schedule.after) {
|
|
|
|
normalized.after = Array.isArray(schedule.after) ? schedule.after : [schedule.after];
|
|
|
|
}
|
|
|
|
if (schedule.before) {
|
|
|
|
normalized.before = Array.isArray(schedule.before) ? schedule.before : [schedule.before];
|
|
|
|
}
|
|
|
|
implementation[HookPriority] = normalized;
|
|
|
|
return implementation;
|
|
|
|
}
|
|
|
|
|
2022-08-10 10:09:02 -05:00
|
|
|
/**
|
|
|
|
* 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,
|
|
|
|
{
|
2022-03-12 20:02:19 -06:00
|
|
|
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);
|
2022-03-12 20:02:19 -06:00
|
|
|
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 [
|
2022-08-10 10:09:02 -05:00
|
|
|
transformer(this.dasherizePath(path)),
|
2022-03-12 20:02:19 -06:00
|
|
|
invoke ? M(flecks) : M,
|
2022-02-25 04:58:08 -06:00
|
|
|
];
|
|
|
|
}),
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-08-10 10:09:02 -05: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) {
|
2024-02-04 09:16:09 -06:00
|
|
|
this.constructor.debug('refreshing %s...', fleck);
|
2022-02-25 04:58:08 -06:00
|
|
|
// Remove old hook implementations.
|
2022-08-10 10:09:02 -05:00
|
|
|
this.unregisterFleckHooks(fleck);
|
2022-02-25 04:58:08 -06:00
|
|
|
// Replace the fleck.
|
2022-08-10 10:09:02 -05:00
|
|
|
this.registerFleckHooks(fleck, M);
|
2022-02-25 04:58:08 -06:00
|
|
|
// Write config.
|
2022-08-10 10:09:02 -05:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2022-08-10 10:09:02 -05:00
|
|
|
/**
|
|
|
|
* Refresh gathered classes for a fleck.
|
|
|
|
*
|
|
|
|
* @param {string} fleck
|
|
|
|
*/
|
2024-01-28 11:09:34 -06:00
|
|
|
async refreshGathered(fleck) {
|
2024-01-29 08:15:22 -06:00
|
|
|
await Promise.all(
|
|
|
|
Object.entries(this.$$gathered)
|
|
|
|
.map(async ([
|
|
|
|
hook,
|
|
|
|
{
|
|
|
|
check,
|
|
|
|
idProperty,
|
|
|
|
gathered,
|
|
|
|
typeProperty,
|
|
|
|
},
|
|
|
|
]) => {
|
|
|
|
let raw;
|
|
|
|
// If decorating, gather all again
|
|
|
|
if (this.fleckImplementation(fleck, `${hook}.decorate`)) {
|
|
|
|
raw = await this.invokeMergeAsync(hook);
|
2024-02-04 09:16:09 -06:00
|
|
|
this.constructor.debugSilly('%s implements %s.decorate', fleck, hook);
|
2024-01-29 08:15:22 -06:00
|
|
|
}
|
|
|
|
// If only implementing, gather and decorate.
|
|
|
|
else if (this.fleckImplementation(fleck, hook)) {
|
|
|
|
raw = await this.invokeFleck(hook, fleck);
|
2024-02-04 09:16:09 -06:00
|
|
|
this.constructor.debugSilly('%s implements %s', fleck, hook);
|
2024-01-29 08:15:22 -06:00
|
|
|
}
|
|
|
|
if (raw) {
|
|
|
|
const decorated = await this.checkAndDecorateRawGathered(hook, raw, check);
|
2024-02-04 09:16:09 -06:00
|
|
|
this.constructor.debug('updating gathered %s from %s...', hook, fleck);
|
|
|
|
this.constructor.debugSilly('%O', decorated);
|
2024-01-29 08:15:22 -06:00
|
|
|
const entries = Object.entries(decorated);
|
|
|
|
entries.forEach(([type, Class]) => {
|
|
|
|
const {[type]: {[idProperty]: id}} = gathered;
|
|
|
|
const Subclass = wrapGathered(Class, id, idProperty, type, typeProperty);
|
|
|
|
// eslint-disable-next-line no-multi-assign
|
|
|
|
gathered[type] = Subclass;
|
|
|
|
gathered[id] = Subclass;
|
|
|
|
gathered[exports.ById][id] = Subclass;
|
|
|
|
gathered[exports.ByType][type] = Subclass;
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2022-08-10 10:09:02 -05:00
|
|
|
/**
|
|
|
|
* Register hooks for a fleck.
|
|
|
|
*
|
|
|
|
* @param {string} fleck
|
|
|
|
* @param {object} M The fleck module
|
|
|
|
* @protected
|
|
|
|
*/
|
|
|
|
registerFleckHooks(fleck, M) {
|
2024-02-04 09:16:09 -06:00
|
|
|
this.constructor.debugSilly('registering %s...', fleck);
|
2022-08-10 10:09:02 -05:00
|
|
|
this.flecks[fleck] = M;
|
|
|
|
if (M.hooks) {
|
2024-01-27 15:41:07 -06:00
|
|
|
const hooks = Object.keys(M.hooks);
|
2024-02-04 09:16:09 -06:00
|
|
|
this.constructor.debugSilly("hooks for '%s': %O", fleck, hooks);
|
2024-01-27 15:41:07 -06:00
|
|
|
for (let j = 0; j < hooks.length; j++) {
|
|
|
|
const hook = hooks[j];
|
|
|
|
if (!this.hooks[hook]) {
|
|
|
|
this.hooks[hook] = [];
|
2022-08-10 10:09:02 -05:00
|
|
|
}
|
2024-01-27 15:41:07 -06:00
|
|
|
if ('function' !== typeof M.hooks[hook]) {
|
|
|
|
throw new TypeError(
|
|
|
|
`Hook implementation must be a function! ('${fleck}' implementing '${hook}')`,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
this.hooks[hook].push({fleck, fn: M.hooks[hook]});
|
2022-08-10 10:09:02 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Unregister hooks for a fleck.
|
|
|
|
* @param {*} fleck
|
|
|
|
*/
|
|
|
|
unregisterFleckHooks(fleck) {
|
2024-01-27 15:41:07 -06:00
|
|
|
const hooks = Object.keys(this.hooks);
|
|
|
|
for (let j = 0; j < hooks.length; j++) {
|
|
|
|
const hook = hooks[j];
|
|
|
|
if (this.hooks[hook]) {
|
|
|
|
const index = this.hooks[hook].findIndex(({fleck: hookPlugin}) => hookPlugin === fleck);
|
2022-08-10 10:09:02 -05:00
|
|
|
if (-1 !== index) {
|
2024-01-27 15:41:07 -06:00
|
|
|
this.hooks[hook].splice(index, 1);
|
2022-08-10 10:09:02 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-04 09:16:09 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
Flecks.debug = debug;
|
|
|
|
Flecks.debugSilly = debugSilly;
|
|
|
|
Flecks.get = get;
|
|
|
|
Flecks.set = set;
|
2024-01-05 22:49:02 -06:00
|
|
|
|
2024-02-04 09:16:09 -06:00
|
|
|
exports.Flecks = Flecks;
|