refactor: priority
This commit is contained in:
parent
26a3ff0cfa
commit
f53244a39e
|
@ -114,6 +114,21 @@ export const hooks = {
|
|||
// Do something with Class...
|
||||
},
|
||||
|
||||
/**
|
||||
* Invoked when flecks is building a fleck dependency graph.
|
||||
* @param {Digraph} graph The dependency graph.
|
||||
* @param {string} hook The hook; e.g. `@flecks/db/server`.
|
||||
*/
|
||||
'@flecks/core.priority': (graph, hook) => {
|
||||
// Make `@flecks/user/server`'s `@flecks/server.up` implementation depend on
|
||||
// `@flecks/db/server`'s:
|
||||
if ('@flecks/server.up' === hook) {
|
||||
graph.addDependency('@flecks/user/server', '@flecks/db/server');
|
||||
// Remove a dependency.
|
||||
graph.removeDependency('@flecks/user/server', '@flecks/db/server');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Invoked when the application is starting. Use for order-independent initialization tasks.
|
||||
*/
|
||||
|
|
|
@ -81,7 +81,6 @@
|
|||
"lodash.get": "^4.4.2",
|
||||
"lodash.intersectionby": "4.7.0",
|
||||
"lodash.set": "^4.3.2",
|
||||
"lodash.without": "^4.4.0",
|
||||
"pirates": "^4.0.5",
|
||||
"rimraf": "^3.0.2",
|
||||
"source-map-loader": "4.0.1",
|
||||
|
|
|
@ -6,6 +6,11 @@ class Digraph {
|
|||
this.arcs.get(tail).add(head);
|
||||
}
|
||||
|
||||
addDependency(head, tail) {
|
||||
this.ensureTail(head);
|
||||
this.ensureTail(tail).add(head);
|
||||
}
|
||||
|
||||
detectCycles() {
|
||||
const cycles = [];
|
||||
const visited = new Set();
|
||||
|
@ -38,6 +43,7 @@ class Digraph {
|
|||
if (!this.arcs.has(tail)) {
|
||||
this.arcs.set(tail, new Set());
|
||||
}
|
||||
return this.arcs.get(tail);
|
||||
}
|
||||
|
||||
neighbors(vertex) {
|
||||
|
@ -73,6 +79,12 @@ class Digraph {
|
|||
.map(([vertex]) => vertex);
|
||||
}
|
||||
|
||||
removeDependency(head, tail) {
|
||||
if (this.arcs.has(tail)) {
|
||||
this.arcs.get(tail).delete(head);
|
||||
}
|
||||
}
|
||||
|
||||
get tails() {
|
||||
return this.arcs.keys();
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ import {
|
|||
|
||||
import get from 'lodash.get';
|
||||
import set from 'lodash.set';
|
||||
import without from 'lodash.without';
|
||||
|
||||
import D from './debug';
|
||||
import Digraph from './digraph';
|
||||
|
@ -18,7 +17,7 @@ const debug = D('@flecks/core/flecks');
|
|||
const debugSilly = debug.extend('silly');
|
||||
|
||||
// Symbol for hook ordering.
|
||||
const HookOrder = Symbol.for('@flecks/core.hookOrder');
|
||||
const HookPriority = Symbol.for('@flecks/core.hookPriority');
|
||||
|
||||
// Symbols for Gathered classes.
|
||||
export const ById = Symbol.for('@flecks/core.byId');
|
||||
|
@ -63,6 +62,8 @@ export default class Flecks {
|
|||
|
||||
config = {};
|
||||
|
||||
$$expandedFlecksCache = {};
|
||||
|
||||
flecks = {};
|
||||
|
||||
hooks = {};
|
||||
|
@ -95,40 +96,6 @@ export default class Flecks {
|
|||
debugSilly('config: %O', this.config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
|
@ -233,13 +200,15 @@ export default class Flecks {
|
|||
* @returns {string[]} The expanded list of flecks.
|
||||
*/
|
||||
expandedFlecks(hook) {
|
||||
if (this.$$expandedFlecksCache[hook]) {
|
||||
return this.$$expandedFlecksCache[hook];
|
||||
}
|
||||
const flecks = this.lookupFlecks(hook);
|
||||
let expanded = [];
|
||||
// Expand configured flecks.
|
||||
for (let i = 0; i < flecks.length; ++i) {
|
||||
const fleck = flecks[i];
|
||||
// Just the fleck.
|
||||
expanded.push(fleck);
|
||||
// Platform-specific variants.
|
||||
for (let j = 0; j < this.platforms.length; ++j) {
|
||||
const platform = this.platforms[j];
|
||||
const variant = join(fleck, platform);
|
||||
|
@ -248,7 +217,7 @@ export default class Flecks {
|
|||
}
|
||||
}
|
||||
}
|
||||
// Expand elided flecks.
|
||||
// Handle elision.
|
||||
const index = expanded.findIndex((fleck) => '...' === fleck);
|
||||
if (-1 !== index) {
|
||||
if (-1 !== expanded.slice(index + 1).findIndex((fleck) => '...' === fleck)) {
|
||||
|
@ -256,23 +225,28 @@ export default class Flecks {
|
|||
`Illegal ordering specification: hook '${hook}' has multiple ellipses.`,
|
||||
);
|
||||
}
|
||||
// Split at the elision point and remove the ellipses.
|
||||
const before = expanded.slice(0, index);
|
||||
const after = expanded.slice(index + 1);
|
||||
expanded.splice(index, 1);
|
||||
// Expand elided flecks.
|
||||
const elided = [];
|
||||
const implementing = this.flecksImplementing(hook);
|
||||
const all = [];
|
||||
for (let i = 0; i < implementing.length; ++i) {
|
||||
const fleck = implementing[i];
|
||||
all.push(fleck);
|
||||
if (!expanded.includes(fleck)) {
|
||||
elided.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)) {
|
||||
all.push(variant);
|
||||
if (this.fleck(variant) && !expanded.includes(variant)) {
|
||||
elided.push(variant);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Map the elided fleck implementations to vertices in a dependency graph.
|
||||
const graph = this.flecksHookGraph(without(all, ...before.concat(after)), hook);
|
||||
// Map the fleck implementations to vertices in a dependency graph.
|
||||
const graph = this.flecksHookGraph([...before, ...elided, ...after], hook);
|
||||
// Check for cycles.
|
||||
const cycles = graph.detectCycles();
|
||||
if (cycles.length > 0) {
|
||||
|
@ -284,8 +258,12 @@ export default class Flecks {
|
|||
}`,
|
||||
);
|
||||
}
|
||||
// Sort the graph and place it.
|
||||
expanded = [...before, ...graph.sort(), ...after];
|
||||
// Sort the graph.
|
||||
expanded = [
|
||||
...before,
|
||||
...graph.sort().filter((fleck) => !expanded.includes(fleck)),
|
||||
...after,
|
||||
];
|
||||
}
|
||||
// Build another graph, but add arcs connecting the final ordering. If cycles exist, the
|
||||
// ordering violated the expectation of one or more implementations.
|
||||
|
@ -299,7 +277,7 @@ export default class Flecks {
|
|||
if (cycles.length > 0) {
|
||||
cycles.forEach(([l, r]) => {
|
||||
const lImplementation = this.fleckImplementation(l, hook);
|
||||
const {before: lBefore = [], after: lAfter = []} = lImplementation[HookOrder] || {};
|
||||
const {before: lBefore = [], after: lAfter = []} = lImplementation?.[HookPriority] || {};
|
||||
const explanation = [hook];
|
||||
if (lBefore.includes(r)) {
|
||||
explanation.push(l, 'before', r);
|
||||
|
@ -308,19 +286,24 @@ export default class Flecks {
|
|||
explanation.push(l, 'after', r);
|
||||
}
|
||||
const rImplementation = this.fleckImplementation(r, hook);
|
||||
const {before: rBefore = [], after: rAfter = []} = rImplementation[HookOrder] || {};
|
||||
const {before: rBefore = [], after: rAfter = []} = rImplementation?.[HookPriority] || {};
|
||||
if (rBefore.includes(l)) {
|
||||
explanation.push(r, 'before', l);
|
||||
}
|
||||
if (rAfter.includes(l)) {
|
||||
explanation.push(r, 'after', l);
|
||||
}
|
||||
debug("Suspicious ordering specification for '%s': '%s' expected to run %s '%s'!", ...explanation);
|
||||
debug(
|
||||
"Suspicious ordering specification for '%s': '%s' expected to run %s '%s'!",
|
||||
...explanation,
|
||||
);
|
||||
});
|
||||
}
|
||||
// Filter unimplemented.
|
||||
return expanded
|
||||
this.$$expandedFlecksCache[hook] = expanded // eslint-disable-line no-return-assign
|
||||
.filter((fleck) => this.fleckImplementation(fleck, hook));
|
||||
debugSilly("cached hook expansion for '%s': %O", hook, expanded);
|
||||
return this.$$expandedFlecksCache[hook];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -374,20 +357,21 @@ export default class Flecks {
|
|||
.forEach((fleck) => {
|
||||
graph.ensureTail(fleck);
|
||||
const implementation = this.fleckImplementation(fleck, hook);
|
||||
if (implementation[HookOrder]) {
|
||||
if (implementation[HookOrder].before) {
|
||||
implementation[HookOrder].before.forEach((before) => {
|
||||
if (implementation?.[HookPriority]) {
|
||||
if (implementation[HookPriority].before) {
|
||||
implementation[HookPriority].before.forEach((before) => {
|
||||
graph.addArc(fleck, before);
|
||||
});
|
||||
}
|
||||
if (implementation[HookOrder].after) {
|
||||
implementation[HookOrder].after.forEach((after) => {
|
||||
if (implementation[HookPriority].after) {
|
||||
implementation[HookPriority].after.forEach((after) => {
|
||||
graph.ensureTail(after);
|
||||
graph.addArc(after, fleck);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
this.invoke('@flecks/core.priority', graph, hook);
|
||||
return graph;
|
||||
}
|
||||
|
||||
|
@ -735,6 +719,24 @@ export default class Flecks {
|
|||
return instance.dispatch.bind(instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide classes for e.g. {@link Flecks#gather}
|
||||
*
|
||||
|
|
|
@ -26,8 +26,11 @@ export const hooks = {
|
|||
'@flecks/core/test/invoke-merge-async': () => new Promise((resolve) => { resolve({foo: 69}); }),
|
||||
'@flecks/core/test/invoke-merge-unique': () => ({foo: 69}),
|
||||
'@flecks/core/test/invoke-merge-unique-async': () => new Promise((resolve) => { resolve({foo: 69}); }),
|
||||
'@flecks/core/test.middleware': Flecks.after(['@flecks/core/two'], () => (foo, next) => {
|
||||
'@flecks/core/test.middleware': Flecks.priority(
|
||||
() => (foo, next) => {
|
||||
foo.bar += 1;
|
||||
next();
|
||||
}),
|
||||
},
|
||||
{after: '@flecks/core/two'},
|
||||
),
|
||||
};
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import {Flecks} from '@flecks/core';
|
||||
|
||||
export const hooks = {
|
||||
'@flecks/core/test.middleware': Flecks.before(['@flecks/core/two'], Flecks.after(
|
||||
['@flecks/core/one'],
|
||||
'@flecks/core/test.middleware': Flecks.priority(
|
||||
() => () => {},
|
||||
)),
|
||||
{after: '@flecks/core/one', before: '@flecks/core/two'},
|
||||
),
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue
Block a user