dox, hook registration, ensureUniqueReduction, middleware, ...
This commit is contained in:
cha0s 2022-08-10 10:09:02 -05:00
parent 23f2fae001
commit c3910ba5f0
60 changed files with 1921 additions and 1736 deletions

View File

@ -1,7 +1,7 @@
<div align="center"> <div align="center">
<h1>flecks</h1> <h1>flecks</h1>
<p> <p>
Flecks is a dynamic, configuration-driven, fullstack application production system. Its purpose Flecks is an exceptionally extensible fullstack application production system. Its true purpose
is to make application development a more joyful endeavor. Intelligent defaults combined with is to make application development a more joyful endeavor. Intelligent defaults combined with
a highly dynamic structure encourage consistency while allowing you to easily express your own a highly dynamic structure encourage consistency while allowing you to easily express your own
opinions. opinions.

View File

@ -22,7 +22,7 @@
- [x] remove `invokeParallel()` - [x] remove `invokeParallel()`
- [x] Specialize `invokeReduce()` with `invokeMerge()`. - [x] Specialize `invokeReduce()` with `invokeMerge()`.
- [x] Rename all hooks to dot-first notation; rewrite `lookupFlecks()`. - [x] Rename all hooks to dot-first notation; rewrite `lookupFlecks()`.
- [ ] ensureUniqueReduction moved into invokeMerge - [x] ensureUniqueReduction moved into invokeMerge
- [x] `bootstrap({without: ['badplatform']})` should be handled by passing `{platforms: ['!badplatform']}` - [x] `bootstrap({without: ['badplatform']})` should be handled by passing `{platforms: ['!badplatform']}`
- [ ] user redux server hydrate fails if no user in req - [ ] user redux server hydrate fails if no user in req
- [ ] governor fails if not in server up - [ ] governor fails if not in server up
@ -31,3 +31,4 @@
- [ ] rename `@flecks/web` to `@flecks/web` - [ ] rename `@flecks/web` to `@flecks/web`
- [ ] simultaneous babel compilation across all compiled flecks - [ ] simultaneous babel compilation across all compiled flecks
- [ ] add building to publish process ... - [ ] add building to publish process ...
- [ ] @babel/register@7.18.x has a bug

1
packages/core/QUIRKS.md Normal file
View File

@ -0,0 +1 @@
- I use the variable `r` a lot when referencing a reducer's accumulator value

View File

@ -35,8 +35,10 @@ config.use.push(({config}) => {
} }
}); });
// Fleck build configuration.
config.use.unshift(fleck()); config.use.unshift(fleck());
// AirBnb linting.
config.use.unshift( config.use.unshift(
airbnb({ airbnb({
eslint: { eslint: {
@ -45,13 +47,13 @@ config.use.unshift(
}), }),
); );
// Include a shebang and set the executable bit..
config.use.push(banner({ config.use.push(banner({
banner: '#!/usr/bin/env node', banner: '#!/usr/bin/env node',
include: /^cli\.js$/, include: /^cli\.js$/,
pluginId: 'shebang', pluginId: 'shebang',
raw: true, raw: true,
})) }))
config.use.push(({config}) => { config.use.push(({config}) => {
config config
.plugin('executable') .plugin('executable')

View File

@ -2,17 +2,13 @@
Hooks are how everything happens in flecks. There are many hooks and the hooks provided by flecks are documented at the [hooks reference page](https://github.com/cha0s/flecks/blob/gh-pages/hooks.md). Hooks are how everything happens in flecks. There are many hooks and the hooks provided by flecks are documented at the [hooks reference page](https://github.com/cha0s/flecks/blob/gh-pages/hooks.md).
To define hooks (and turn your plain ol' boring JS modules into beautiful interesting flecks), you only have to import the `Hooks` symbol and key your default export: To define hooks (and turn your plain ol' boring JS modules into beautiful interesting flecks), you only have to export a `hooks` object:
```javascript ```javascript
import {Hooks} from '@flecks/core'; export const hooks = {
export default {
[Hooks]: {
'@flecks/core.starting': () => { '@flecks/core.starting': () => {
console.log('hello, gorgeous'); console.log('hello, gorgeous');
}, },
},
}; };
``` ```
@ -133,15 +129,15 @@ assert(foo.type === 'Foo');
```javascript ```javascript
{ {
// The property added when extending the class to return the numeric ID. // The property added when extending the class to return the numeric ID.
idAttribute = 'id', idProperty = 'id',
// The property added when extending the class to return the type. // The property added when extending the class to return the type.
typeAttribute = 'type', typeProperty = 'type',
// A function called with the `Gathered` object to allow checking validity. // A function called with the `Gathered` object to allow checking validity.
check = () => {}, check = () => {},
} }
``` ```
As an example, when `@flecks/db/server` gathers models, `typeAttribute` is set to `name`, because Sequelize requires its model classes to have a unique `name` property. As an example, when `@flecks/db/server` gathers models, `typeProperty` is set to `name`, because Sequelize requires its model classes to have a unique `name` property.
**Note:** the numeric IDs are useful for efficient serialization between the client and server, but **if you are using this property, ensure that `flecks.gather()` is called equivalently on both the client and the server**. As a rule of thumb, if you have serializable `Gathered`s, they should be invoked and defined in `your-fleck`, and not in `your-fleck/[platform]`, so that they are invoked for every platform. **Note:** the numeric IDs are useful for efficient serialization between the client and server, but **if you are using this property, ensure that `flecks.gather()` is called equivalently on both the client and the server**. As a rule of thumb, if you have serializable `Gathered`s, they should be invoked and defined in `your-fleck`, and not in `your-fleck/[platform]`, so that they are invoked for every platform.
@ -152,19 +148,15 @@ Complementary to gather hooks above, `Flecks.provide()` allows you to ergonomica
Here's an example of how you could manually provide `@flecks/db/server.models` in your own fleck: Here's an example of how you could manually provide `@flecks/db/server.models` in your own fleck:
```javascript ```javascript
import {Hooks} foom '@flecks/core';
import SomeModel from './models/some-model'; import SomeModel from './models/some-model';
import AnotherModel from './models/another-model'; import AnotherModel from './models/another-model';
export default { export const hooks = {
[Hooks]: {
'@flecks/db/server.models': () => ({ '@flecks/db/server.models': () => ({
SomeModel, SomeModel,
AnotherModel, AnotherModel,
}), }),
}, }
};
``` ```
If you think about the example above, you might realize that it will become a lot of typing to keep adding new models over time. Provider hooks exist to reduce this maintenance burden for you. If you think about the example above, you might realize that it will become a lot of typing to keep adding new models over time. Provider hooks exist to reduce this maintenance burden for you.
@ -183,12 +175,10 @@ models/
then, this `index.js`: then, this `index.js`:
```javascript ```javascript
import {Flecks, Hooks} from '@flecks/core'; import {Flecks} from '@flecks/core';
export default { export const hooks = {
[Hooks]: {
'@flecks/db/server.models': Flecks.provide(require.context('./models', false, /\.js$/)), '@flecks/db/server.models': Flecks.provide(require.context('./models', false, /\.js$/)),
},
}; };
``` ```
@ -212,10 +202,7 @@ is *exactly equivalent* to the gather example above. By default, `Flecks.provide
When a Model (or any other) is gathered as above, an implicit hook is called: `${hook}.decorate`. This allows other flecks to decorate whatever has been gathered: When a Model (or any other) is gathered as above, an implicit hook is called: `${hook}.decorate`. This allows other flecks to decorate whatever has been gathered:
```javascript ```javascript
import {Hooks} from '@flecks/core'; export const hooks = {
export default {
[Hooks]: {
'@flecks/db/server.models.decorate': (Models) => { '@flecks/db/server.models.decorate': (Models) => {
return { return {
...Models, ...Models,
@ -230,13 +217,12 @@ export default {
}, },
}; };
}, },
},
}; };
``` ```
#### `Flecks.decorate(context, options)` #### `Flecks.decorate(context, options)`
As with above, there exists an API for making the maintenance of decorators more ergonomic. As with above, there exists an API for making the maintenance of decorators even more ergonomic.
Supposing our fleck is structured like so: Supposing our fleck is structured like so:
@ -266,12 +252,12 @@ export default (User) => {
then, this `index.js`: then, this `index.js`:
```javascript ```javascript
import {Flecks, Hooks} from '@flecks/core'; import {Flecks} from '@flecks/core';
export default { export const hooks = {
[Hooks]: { '@flecks/db/server.models.decorate': (
'@flecks/db/server.models.decorate': Flecks.decorate(require.context('./models/decorators', false, /\.js$/)), Flecks.decorate(require.context('./models/decorators', false, /\.js$/))
}, ),
}; };
``` ```
@ -307,7 +293,7 @@ Our `flecks.yml` could be configured like so:
In this application, when `@flecks/http/server.request.route` is invoked, `@flecks/user/session`'s implementation is invoked (which reifies the user's session from cookies), followed by `my-cool-fleck`'s (which, we assume, does some kind of very cool dark mode check). In this application, when `@flecks/http/server.request.route` is invoked, `@flecks/user/session`'s implementation is invoked (which reifies the user's session from cookies), followed by `my-cool-fleck`'s (which, we assume, does some kind of very cool dark mode check).
### Ellipses ### Ellipses/elision
It may not always be ergonomic to configure the order of every single implementation, but enough to specify which implementations must run first (or last). It may not always be ergonomic to configure the order of every single implementation, but enough to specify which implementations must run first (or last).

View File

@ -1,7 +1,5 @@
import {Hooks} from '@flecks/core'; export const hooks = {
export default {
[Hooks]: {
/** /**
* Hook into neutrino configuration. * Hook into neutrino configuration.
* @param {string} target The build target; e.g. `server`. * @param {string} target The build target; e.g. `server`.
@ -54,7 +52,7 @@ export default {
args: [ args: [
'<somearg>', '<somearg>',
], ],
description: 'This sure is some command', description: 'This command does tests and also blows up',
options: [ options: [
'-t, --test', 'Do a test', '-t, --test', 'Do a test',
'-b, --blow-up', 'Blow up instead of running the command', '-b, --blow-up', 'Blow up instead of running the command',
@ -117,6 +115,4 @@ export default {
config.stats = 'verbose'; config.stats = 'verbose';
} }
}, },
},
}; };

View File

@ -59,6 +59,7 @@
"babel-merge": "^3.0.0", "babel-merge": "^3.0.0",
"babel-plugin-prepend": "^1.0.2", "babel-plugin-prepend": "^1.0.2",
"chai": "4.2.0", "chai": "4.2.0",
"chai-as-promised": "7.1.1",
"commander": "^8.3.0", "commander": "^8.3.0",
"debug": "4.3.1", "debug": "4.3.1",
"enhanced-resolve": "^5.9.2", "enhanced-resolve": "^5.9.2",

View File

@ -11,7 +11,7 @@ const {
FLECKS_CORE_ROOT = process.cwd(), FLECKS_CORE_ROOT = process.cwd(),
} = process.env; } = process.env;
const resolver = (source) => (path) => { const resolveValidModulePath = (source) => (path) => {
// Does the file resolve as source? // Does the file resolve as source?
try { try {
R.resolve(`${source}/${path}`); R.resolve(`${source}/${path}`);
@ -39,7 +39,7 @@ module.exports = () => ({config, options}) => {
.set(name, join(FLECKS_CORE_ROOT, 'src')); .set(name, join(FLECKS_CORE_ROOT, 'src'));
// Calculate entry points from `files`. // Calculate entry points from `files`.
files files
.filter(resolver(source)) .filter(resolveValidModulePath(source))
.forEach((file) => { .forEach((file) => {
const trimmed = join(dirname(file), basename(file, extname(file))); const trimmed = join(dirname(file), basename(file, extname(file)));
config config

View File

@ -1,2 +1,4 @@
// Get a runtime require function by hook or by crook. :)
// eslint-disable-next-line no-eval // eslint-disable-next-line no-eval
module.exports = eval('"undefined" !== typeof require ? require : undefined'); module.exports = eval('"undefined" !== typeof require ? require : undefined');

View File

@ -1,16 +0,0 @@
export default async (flecks, hook, ...args) => {
const track = {};
return Object.entries(flecks.invoke(hook, ...args))
.reduce(async (r, [pkg, impl]) => {
const aimpl = await impl;
Object.keys(aimpl).forEach((key) => {
if (track[key]) {
throw new ReferenceError(
`Conflict in ${hook}: '${track[key]}' implemented '${key}', followed by '${pkg}'`,
);
}
track[key] = pkg;
});
return {...(await r), ...aimpl};
}, {});
};

View File

@ -16,24 +16,38 @@ import Middleware from './middleware';
const debug = D('@flecks/core/flecks'); const debug = D('@flecks/core/flecks');
const debugSilly = debug.extend('silly'); const debugSilly = debug.extend('silly');
// Symbols for Gathered classes.
export const ById = Symbol.for('@flecks/core.byId'); export const ById = Symbol.for('@flecks/core.byId');
export const ByType = Symbol.for('@flecks/core.byType'); export const ByType = Symbol.for('@flecks/core.byType');
export const Hooks = Symbol.for('@flecks/core.hooks');
/**
* Capitalize a string.
*
* @param {string} string
* @returns {string}
*/
const capitalize = (string) => string.substring(0, 1).toUpperCase() + string.substring(1); const capitalize = (string) => string.substring(0, 1).toUpperCase() + string.substring(1);
/**
* CamelCase a string.
*
* @param {string} string
* @returns {string}
*/
const camelCase = (string) => string.split(/[_-]/).map(capitalize).join(''); const camelCase = (string) => string.split(/[_-]/).map(capitalize).join('');
// Track gathered for HMR.
const hotGathered = new Map(); const hotGathered = new Map();
const wrapperClass = (Class, id, idAttribute, type, typeAttribute) => { // Wrap classes to expose their flecks ID and type.
const wrapGathered = (Class, id, idProperty, type, typeProperty) => {
class Subclass extends Class { class Subclass extends Class {
static get [idAttribute]() { static get [idProperty]() {
return id; return id;
} }
static get [typeAttribute]() { static get [typeProperty]() {
return type; return type;
} }
@ -43,72 +57,121 @@ const wrapperClass = (Class, id, idAttribute, type, typeAttribute) => {
export default class Flecks { 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.
*/
constructor({ constructor({
config = {}, config = {},
flecks = {}, flecks = {},
platforms = [], platforms = [],
} = {}) { } = {}) {
this.config = { const emptyConfigForAllFlecks = Object.fromEntries(
...Object.fromEntries(Object.keys(flecks).map((path) => [path, {}])), Object.keys(flecks).map((path) => [path, {}]),
...config, );
}; this.config = {...emptyConfigForAllFlecks, ...config};
this.hooks = {};
this.flecks = {};
this.platforms = platforms; this.platforms = platforms;
const entries = Object.entries(flecks); const entries = Object.entries(flecks);
debugSilly('paths: %O', entries.map(([fleck]) => fleck)); debugSilly('paths: %O', entries.map(([fleck]) => fleck));
for (let i = 0; i < entries.length; i++) { for (let i = 0; i < entries.length; i++) {
const [fleck, M] = entries[i]; const [fleck, M] = entries[i];
this.registerFleck(fleck, M); this.registerFleckHooks(fleck, M);
this.invoke('@flecks/core.registered', fleck, M);
} }
this.configureFlecks(); this.configureFlecksDefaults();
debugSilly('config: %O', this.config); debugSilly('config: %O', this.config);
} }
configureFleck(fleck) { /**
* Configure defaults for a fleck.
*
* @param {string} fleck
* @protected
*/
configureFleckDefaults(fleck) {
this.config[fleck] = { this.config[fleck] = {
...this.invokeFleck('@flecks/core.config', fleck), ...this.invokeFleck('@flecks/core.config', fleck),
...this.config[fleck], ...this.config[fleck],
}; };
} }
configureFlecks() { /**
const defaultConfig = this.invoke('@flecks/core.config'); * Configure defaults for all flecks.
const flecks = Object.keys(defaultConfig); *
* @protected
*/
configureFlecksDefaults() {
const flecks = this.flecksImplementing('@flecks/core.config');
for (let i = 0; i < flecks.length; i++) { for (let i = 0; i < flecks.length; i++) {
this.configureFleck(flecks[i]); this.configureFleckDefaults(flecks[i]);
} }
} }
/**
* [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.
*/
static decorate( static decorate(
context, context,
{ {
transformer = camelCase, transformer = camelCase,
} = {}, } = {},
) { ) {
return (Gathered, flecks) => { return (Gathered, flecks) => (
context.keys() context.keys()
.forEach((path) => { .reduce(
(Gathered, path) => {
const key = transformer(this.dasherizePath(path));
if (!Gathered[key]) {
return Gathered;
}
const {default: M} = context(path); const {default: M} = context(path);
if ('function' !== typeof M) { if ('function' !== typeof M) {
throw new ReferenceError( throw new ReferenceError(
`Flecks.decorate(): require(${ `Flecks.decorate(): require(${path}).default is not a function (from: ${context.id})`,
path
}).default is not a function (from: ${
context.id
})`,
); );
} }
const key = transformer(this.symbolizePath(path)); return {...Gathered, [key]: M(Gathered[key], flecks)};
if (Gathered[key]) { },
// eslint-disable-next-line no-param-reassign Gathered,
Gathered[key] = M(Gathered[key], flecks); )
} );
});
return Gathered;
};
} }
/**
* Destroy this instance.
*/
destroy() { destroy() {
this.config = {}; this.config = {};
this.hooks = {}; this.hooks = {};
@ -116,12 +179,20 @@ export default class Flecks {
this.platforms = []; this.platforms = [];
} }
/**
* Lists all flecks implementing a hook, including platform-specific and elided variants.
*
* @param {string} hook
* @returns {string[]} The expanded list of flecks.
*/
expandedFlecks(hook) { expandedFlecks(hook) {
const flecks = this.lookupFlecks(hook); const flecks = this.lookupFlecks(hook);
let expanded = []; let expanded = [];
for (let i = 0; i < flecks.length; ++i) { for (let i = 0; i < flecks.length; ++i) {
const fleck = flecks[i]; const fleck = flecks[i];
// Just the fleck.
expanded.push(fleck); expanded.push(fleck);
// Platform-specific variants.
for (let j = 0; j < this.platforms.length; ++j) { for (let j = 0; j < this.platforms.length; ++j) {
const platform = this.platforms[j]; const platform = this.platforms[j];
const variant = join(fleck, platform); const variant = join(fleck, platform);
@ -130,6 +201,7 @@ export default class Flecks {
} }
} }
} }
// Expand elided flecks.
const index = expanded.findIndex((fleck) => '...' === fleck); const index = expanded.findIndex((fleck) => '...' === fleck);
if (-1 !== index) { if (-1 !== index) {
if (-1 !== expanded.slice(index + 1).findIndex((fleck) => '...' === fleck)) { if (-1 !== expanded.slice(index + 1).findIndex((fleck) => '...' === fleck)) {
@ -158,33 +230,66 @@ export default class Flecks {
return expanded; return expanded;
} }
/**
* Get the module for a fleck.
*
* @param {*} fleck
*
* @returns {*}
*/
fleck(fleck) { fleck(fleck) {
return this.flecks[fleck]; return this.flecks[fleck];
} }
/**
* Test whether a fleck implements a hook.
*
* @param {*} fleck
* @param {string} hook
* @returns {boolean}
*/
fleckImplements(fleck, hook) { fleckImplements(fleck, hook) {
return !!this.hooks[hook].find(({fleck: candidate}) => fleck === candidate); return !!this.hooks[hook].find(({fleck: candidate}) => fleck === candidate);
} }
/**
* Get a list of flecks implementing a hook.
*
* @param {string} hook
* @returns {string[]}
*/
flecksImplementing(hook) { flecksImplementing(hook) {
return this.hooks[hook]?.map(({fleck}) => fleck) || []; 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}.
*/
gather( gather(
hook, hook,
{ {
idAttribute = 'id', idProperty = 'id',
typeAttribute = 'type', typeProperty = 'type',
check = () => {}, check = () => {},
} = {}, } = {},
) { ) {
if (!hook || 'string' !== typeof hook) { if (!hook || 'string' !== typeof hook) {
throw new TypeError('Flecks.gather(): Expects parameter 1 (hook) to be string'); throw new TypeError('Flecks.gather(): Expects parameter 1 (hook) to be string');
} }
// Gather classes and check.
const raw = this.invokeMerge(hook); const raw = this.invokeMerge(hook);
check(raw, hook); check(raw, hook);
// Decorate and check.
const decorated = this.invokeComposed(`${hook}.decorate`, raw); const decorated = this.invokeComposed(`${hook}.decorate`, raw);
check(decorated, `${hook}.decorate`); check(decorated, `${hook}.decorate`);
// Assign unique IDs to each class and sort by type.
let uid = 1; let uid = 1;
const ids = {}; const ids = {};
const types = ( const types = (
@ -193,50 +298,78 @@ export default class Flecks {
.sort(([ltype], [rtype]) => (ltype < rtype ? -1 : 1)) .sort(([ltype], [rtype]) => (ltype < rtype ? -1 : 1))
.map(([type, Class]) => { .map(([type, Class]) => {
const id = uid++; const id = uid++;
ids[id] = wrapperClass(Class, id, idAttribute, type, typeAttribute); ids[id] = wrapGathered(Class, id, idProperty, type, typeProperty);
return [type, ids[id]]; return [type, ids[id]];
}), }),
) )
); );
// Conglomerate all ID and type keys along with Symbols for accessing either/or.
const gathered = { const gathered = {
...ids, ...ids,
...types, ...types,
[ById]: ids, [ById]: ids,
[ByType]: types, [ByType]: types,
}; };
hotGathered.set(hook, {idAttribute, gathered, typeAttribute}); // Register for HMR?
if (module.hot) {
hotGathered.set(hook, {idProperty, gathered, typeProperty});
}
debug("gathered '%s': %O", hook, Object.keys(gathered[ByType])); debug("gathered '%s': %O", hook, Object.keys(gathered[ByType]));
return gathered; 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 {*}
*/
get(path, defaultValue) { get(path, defaultValue) {
return get(this.config, 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 {*}
*/
invoke(hook, ...args) { invoke(hook, ...args) {
if (!this.hooks[hook]) { if (!this.hooks[hook]) {
return {}; return {};
} }
return this.flecksImplementing(hook) return this.flecksImplementing(hook)
.reduce((r, fleck) => ({ .reduce((r, fleck) => ({...r, [fleck]: this.invokeFleck(hook, fleck, ...args)}), {});
...r,
[fleck]: this.invokeFleck(hook, fleck, ...args),
}), {});
} }
invokeComposed(hook, arg, ...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) {
if (!this.hooks[hook]) { if (!this.hooks[hook]) {
return arg; return initial;
} }
const flecks = this.expandedFlecks(hook); const flecks = this.expandedFlecks(hook);
if (0 === flecks.length) { if (0 === flecks.length) {
return arg; return initial;
} }
return flecks return flecks
.filter((fleck) => this.fleckImplements(fleck, hook)) .filter((fleck) => this.fleckImplements(fleck, hook))
.reduce((r, fleck) => this.invokeFleck(hook, fleck, r, ...args), arg); .reduce((r, fleck) => this.invokeFleck(hook, fleck, r, ...args), initial);
} }
/**
* An async version of `invokeComposed`.
*
* @see {@link Flecks#invokeComposed}
*/
async invokeComposedAsync(hook, arg, ...args) { async invokeComposedAsync(hook, arg, ...args) {
if (!this.hooks[hook]) { if (!this.hooks[hook]) {
return arg; return arg;
@ -250,6 +383,13 @@ export default class Flecks {
.reduce(async (r, fleck) => this.invokeFleck(hook, fleck, await r, ...args), arg); .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[]}
*/
invokeFlat(hook, ...args) { invokeFlat(hook, ...args) {
if (!this.hooks[hook]) { if (!this.hooks[hook]) {
return []; return [];
@ -257,6 +397,14 @@ export default class Flecks {
return this.hooks[hook].map(({fleck}) => this.invokeFleck(hook, fleck, ...args)); 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 {*}
*/
invokeFleck(hook, fleck, ...args) { invokeFleck(hook, fleck, ...args) {
debugSilly('invokeFleck(%s, %s, ...)', hook, fleck); debugSilly('invokeFleck(%s, %s, ...)', hook, fleck);
if (!this.hooks[hook]) { if (!this.hooks[hook]) {
@ -270,33 +418,116 @@ export default class Flecks {
return candidate.fn(...(args.concat(this))); 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}
*/
invokeMerge(hook, ...args) { invokeMerge(hook, ...args) {
return this.invokeReduce(hook, (r, o) => ({...r, ...o}), {}, ...args); return this.invokeReduce(hook, this.constructor.$$invokeMerge, {}, ...args);
} }
/**
* An async version of `invokeMerge`.
*
* @see {@link Flecks#invokeMerge}
*/
async invokeMergeAsync(hook, ...args) { async invokeMergeAsync(hook, ...args) {
return this.invokeReduceAsync(hook, (r, o) => ({...r, ...o}), {}, ...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});
};
}
/**
* 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 {*}
*/
invokeReduce(hook, reducer, initial, ...args) { invokeReduce(hook, reducer, initial, ...args) {
if (!this.hooks[hook]) { if (!this.hooks[hook]) {
return initial; return initial;
} }
return this.hooks[hook] return this.hooks[hook]
.reduce((r, {fleck}) => reducer(r, this.invokeFleck(hook, fleck, ...args)), initial); .reduce(
(r, {fleck}) => reducer(r, this.invokeFleck(hook, fleck, ...args), fleck, hook),
initial,
);
} }
/**
* An async version of `invokeReduce`.
*
* @see {@link Flecks#invokeReduce}
*/
async invokeReduceAsync(hook, reducer, initial, ...args) { async invokeReduceAsync(hook, reducer, initial, ...args) {
if (!this.hooks[hook]) { if (!this.hooks[hook]) {
return initial; return initial;
} }
return this.hooks[hook] return this.hooks[hook]
.reduce( .reduce(
async (r, {fleck}) => reducer(await r, await this.invokeFleck(hook, fleck, ...args)), async (r, {fleck}) => (
reducer(await r, await this.invokeFleck(hook, fleck, ...args), fleck, hook)
),
initial, 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[]}
*/
invokeSequential(hook, ...args) { invokeSequential(hook, ...args) {
if (!this.hooks[hook]) { if (!this.hooks[hook]) {
return []; return [];
@ -315,6 +546,11 @@ export default class Flecks {
return results; return results;
} }
/**
* An async version of `invokeSequential`.
*
* @see {@link Flecks#invokeSequential}
*/
async invokeSequentialAsync(hook, ...args) { async invokeSequentialAsync(hook, ...args) {
if (!this.hooks[hook]) { if (!this.hooks[hook]) {
return []; return [];
@ -334,10 +570,18 @@ export default class Flecks {
return results; return results;
} }
isOnPlatform(platform) { /**
return -1 !== this.platforms.indexOf(platform); * 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[]}
*/
lookupFlecks(hook) { lookupFlecks(hook) {
const index = hook.indexOf('.'); const index = hook.indexOf('.');
if (-1 === index) { if (-1 === index) {
@ -346,31 +590,37 @@ export default class Flecks {
return this.get([hook.slice(0, index), hook.slice(index + 1)], ['...']); return this.get([hook.slice(0, index), hook.slice(index + 1)], ['...']);
} }
/**
* Make a middleware function from configured middleware.
* @param {string} hook
* @returns {function}
*/
makeMiddleware(hook) { makeMiddleware(hook) {
debugSilly('makeMiddleware(...): %s', hook); debugSilly('makeMiddleware(...): %s', hook);
if (!this.hooks[hook]) { if (!this.hooks[hook]) {
return Promise.resolve(); return (...args) => args.pop()();
} }
const flecks = this.expandedFlecks(hook); const flecks = this.expandedFlecks(hook);
if (0 === flecks.length) { if (0 === flecks.length) {
return Promise.resolve(); return (...args) => args.pop()();
} }
const middleware = flecks const middleware = flecks
.filter((fleck) => this.fleckImplements(fleck, hook)); .filter((fleck) => this.fleckImplements(fleck, hook));
debugSilly('middleware: %O', middleware); debugSilly('middleware: %O', middleware);
const instance = new Middleware(middleware.map((fleck) => this.invokeFleck(hook, fleck))); const instance = new Middleware(middleware.map((fleck) => this.invokeFleck(hook, fleck)));
return async (...args) => { return instance.dispatch.bind(instance);
const next = args.pop();
try {
await instance.promise(...args);
next();
}
catch (error) {
next(error);
}
};
} }
/**
* 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}
*/
static provide( static provide(
context, context,
{ {
@ -393,7 +643,7 @@ export default class Flecks {
); );
} }
return [ return [
transformer(this.symbolizePath(path)), transformer(this.dasherizePath(path)),
invoke ? M(flecks) : M, invoke ? M(flecks) : M,
]; ];
}), }),
@ -401,9 +651,103 @@ export default class Flecks {
); );
} }
/**
* Refresh a fleck's hooks, configuration, and any gathered classes.
*
* @example
* module.hot.accept('@flecks/example', async () => {
* flecks.refresh('@flecks/example', require('@flecks/example'));
* });
* @param {string} fleck
* @param {object} M The fleck module
* @protected
*/
refresh(fleck, M) { refresh(fleck, M) {
debug('refreshing %s...', fleck); debug('refreshing %s...', fleck);
// Remove old hook implementations. // Remove old hook implementations.
this.unregisterFleckHooks(fleck);
// Replace the fleck.
this.registerFleckHooks(fleck, M);
// Write config.
this.configureFleckDefaults(fleck);
// HMR.
if (module.hot) {
this.refreshGathered(fleck);
}
}
/**
* Refresh gathered classes for a fleck.
*
* @param {string} fleck
*/
refreshGathered(fleck) {
const it = hotGathered.entries();
for (let current = it.next(); current.done !== true; current = it.next()) {
const {
value: [
hook,
{
idProperty,
gathered,
typeProperty,
},
],
} = current;
const updates = this.invokeFleck(hook, fleck);
if (updates) {
debug('updating gathered %s from %s...', hook, fleck);
const entries = Object.entries(updates);
for (let i = 0, [type, Class] = entries[i]; i < entries.length; ++i) {
const {[type]: {[idProperty]: id}} = gathered;
const Subclass = wrapGathered(Class, id, idProperty, type, typeProperty);
// eslint-disable-next-line no-multi-assign
gathered[type] = gathered[id] = gathered[ById][id] = gathered[ByType][type] = Subclass;
this.invoke('@flecks/core.hmr.gathered', Subclass, hook);
}
}
}
}
/**
* 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); const keys = Object.keys(this.hooks);
for (let j = 0; j < keys.length; j++) { for (let j = 0; j < keys.length; j++) {
const key = keys[j]; const key = keys[j];
@ -414,82 +758,6 @@ export default class Flecks {
} }
} }
} }
// Replace the fleck.
this.registerFleck(fleck, M);
// Write config.
this.configureFleck(fleck);
// HMR.
this.updateHotGathered(fleck);
}
registerFleck(fleck, M) {
debugSilly('registering %s...', fleck);
this.flecks[fleck] = M;
if (M.default) {
const {default: {[Hooks]: hooks}} = M;
if (hooks) {
const keys = Object.keys(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: hooks[key]});
}
}
}
else {
debugSilly("'%s' has no default export", fleck);
}
}
set(path, value) {
return set(this.config, path, value);
}
static symbolizePath(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)));
}
async up(hook) {
await Promise.all(this.invokeFlat('@flecks/core.starting'));
await this.invokeSequentialAsync(hook);
}
updateHotGathered(fleck) {
const it = hotGathered.entries();
for (let current = it.next(); current.done !== true; current = it.next()) {
const {
value: [
hook,
{
idAttribute,
gathered,
typeAttribute,
},
],
} = current;
const updates = this.invokeFleck(hook, fleck);
if (updates) {
debug('updating gathered %s from %s...', hook, fleck);
const entries = Object.entries(updates);
for (let i = 0, [type, Class] = entries[i]; i < entries.length; ++i) {
const {[type]: {[idAttribute]: id}} = gathered;
const Subclass = wrapperClass(Class, id, idAttribute, type, typeAttribute);
// eslint-disable-next-line no-multi-assign
gathered[type] = gathered[id] = gathered[ById][id] = gathered[ByType][type] = Subclass;
this.invoke('@flecks/core.hmr.gathered', Subclass, hook);
}
}
}
} }
} }

View File

@ -1,24 +1,18 @@
import {Hooks} from './flecks';
export {default as Class} from './class'; export {default as Class} from './class';
export {default as compose} from './compose'; export {default as compose} from './compose';
export {default as D} from './debug'; export {default as D} from './debug';
export {default as ensureUniqueReduction} from './ensure-unique-reduction';
export {default as EventEmitter} from './event-emitter'; export {default as EventEmitter} from './event-emitter';
export { export {
default as Flecks, default as Flecks,
ById, ById,
ByType, ByType,
Hooks,
} from './flecks'; } from './flecks';
export default { export const hooks = {
[Hooks]: {
'@flecks/core.config': () => ({ '@flecks/core.config': () => ({
/** /**
* The ID of your application. * The ID of your application.
*/ */
id: 'flecks', id: 'flecks',
}), }),
},
}; };

View File

@ -34,6 +34,7 @@ module.exports = {
rules: { rules: {
'babel/object-curly-spacing': 'off', 'babel/object-curly-spacing': 'off',
'brace-style': ['error', 'stroustrup'], 'brace-style': ['error', 'stroustrup'],
'import/prefer-default-export': 'off',
'jsx-a11y/control-has-associated-label': ['error', {assert: 'either'}], 'jsx-a11y/control-has-associated-label': ['error', {assert: 'either'}],
'jsx-a11y/label-has-associated-control': ['error', {assert: 'either'}], 'jsx-a11y/label-has-associated-control': ['error', {assert: 'either'}],
'no-plusplus': 'off', 'no-plusplus': 'off',

View File

@ -22,6 +22,7 @@ const {
FLECKS_CORE_SYNC_FOR_ESLINT = false, FLECKS_CORE_SYNC_FOR_ESLINT = false,
} = process.env; } = process.env;
// This is kinda nuts, but ESLint doesn't support its configuration files returning a promise!
if (FLECKS_CORE_SYNC_FOR_ESLINT) { if (FLECKS_CORE_SYNC_FOR_ESLINT) {
(async () => { (async () => {
debug('bootstrapping flecks...'); debug('bootstrapping flecks...');
@ -50,6 +51,7 @@ else {
module.exports = JSON.parse(readFileSync(join(cacheDirectory, 'eslintrc.json')).toString()); module.exports = JSON.parse(readFileSync(join(cacheDirectory, 'eslintrc.json')).toString());
} }
catch (error) { catch (error) {
// Just silly. By synchronously spawning... ourselves, the spawned copy can use async.
const {stderr, stdout} = spawnSync('node', [__filename], { const {stderr, stdout} = spawnSync('node', [__filename], {
env: { env: {
FLECKS_CORE_SYNC_FOR_ESLINT: true, FLECKS_CORE_SYNC_FOR_ESLINT: true,

View File

@ -4,7 +4,6 @@ import {inspect} from 'util';
import airbnb from '@neutrinojs/airbnb'; import airbnb from '@neutrinojs/airbnb';
import neutrino from 'neutrino'; import neutrino from 'neutrino';
import {Hooks} from '../flecks';
import commands from './commands'; import commands from './commands';
import R from '../bootstrap/require'; import R from '../bootstrap/require';
@ -31,8 +30,7 @@ export {default as fleck} from '../bootstrap/fleck';
export {default as require} from '../bootstrap/require'; export {default as require} from '../bootstrap/require';
export {JsonStream, transform} from './stream'; export {JsonStream, transform} from './stream';
export default { export const hooks = {
[Hooks]: {
'@flecks/core.build': (target, config, flecks) => { '@flecks/core.build': (target, config, flecks) => {
const { const {
'eslint.exclude': exclude, 'eslint.exclude': exclude,
@ -107,5 +105,4 @@ export default {
*/ */
profile: [], profile: [],
}), }),
},
}; };

View File

@ -1,7 +1,12 @@
import {expect} from 'chai'; import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import {Flecks} from '@flecks/core'; import {Flecks} from '@flecks/core';
chai.use(chaiAsPromised);
const {expect} = chai;
const testOne = require('./one'); const testOne = require('./one');
const testTwo = require('./two'); const testTwo = require('./two');
@ -33,3 +38,13 @@ it('can invoke merge async', async () => {
expect(await flecks.invokeMergeAsync('@flecks/core/test/invoke-merge-async')) expect(await flecks.invokeMergeAsync('@flecks/core/test/invoke-merge-async'))
.to.deep.equal({foo: 69, bar: 420}); .to.deep.equal({foo: 69, bar: 420});
}); });
it('can enforce uniqueness', () => {
expect(() => flecks.invokeMergeUnique('@flecks/core/test/invoke-merge-unique'))
.to.throw(ReferenceError);
});
it('can enforce uniqueness async', async () => {
expect(flecks.invokeMergeUniqueAsync('@flecks/core/test/invoke-merge-unique-async'))
.to.be.rejectedWith(ReferenceError);
});

View File

@ -0,0 +1,51 @@
import {expect} from 'chai';
import {Flecks} from '@flecks/core';
const testOne = require('./one');
const testTwo = require('./two');
it('can make middleware', (done) => {
let flecks;
let foo;
let mw;
flecks = new Flecks({
config: {
'@flecks/core/test': {
middleware: [
'@flecks/core/one',
'@flecks/core/two',
],
},
},
flecks: {
'@flecks/core/one': testOne,
'@flecks/core/two': testTwo,
},
});
foo = {bar: 1};
mw = flecks.makeMiddleware('@flecks/core/test.middleware');
mw(foo, () => {
expect(foo.bar).to.equal(4);
flecks = new Flecks({
config: {
'@flecks/core/test': {
middleware: [
'@flecks/core/two',
'@flecks/core/one',
],
},
},
flecks: {
'@flecks/core/one': testOne,
'@flecks/core/two': testTwo,
},
});
foo = {bar: 1};
mw = flecks.makeMiddleware('@flecks/core/test.middleware');
mw(foo, () => {
expect(foo.bar).to.equal(3);
done();
});
});
});

View File

@ -1,5 +1,5 @@
// eslint-disable-next-line import/no-extraneous-dependencies, import/no-unresolved // eslint-disable-next-line import/no-extraneous-dependencies, import/no-unresolved
import {Flecks, Hooks} from '@flecks/core'; import {Flecks} from '@flecks/core';
export const testNodespace = () => [ export const testNodespace = () => [
/* eslint-disable no-eval */ /* eslint-disable no-eval */
@ -8,8 +8,7 @@ export const testNodespace = () => [
/* eslint-enable no-eval */ /* eslint-enable no-eval */
]; ];
export default { export const hooks = {
[Hooks]: {
'@flecks/core.config': () => ({ '@flecks/core.config': () => ({
foo: 'bar', foo: 'bar',
}), }),
@ -26,5 +25,11 @@ export default {
}, },
'@flecks/core/test/invoke-merge': () => ({foo: 69}), '@flecks/core/test/invoke-merge': () => ({foo: 69}),
'@flecks/core/test/invoke-merge-async': () => new Promise((resolve) => resolve({foo: 69})), '@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': () => (foo, next) => {
// eslint-disable-next-line no-param-reassign
foo.bar += 1;
next();
}, },
}; };

View File

@ -1,7 +1,6 @@
import {Flecks, Hooks} from '@flecks/core'; import {Flecks} from '@flecks/core';
export default { export const hooks = {
[Hooks]: {
'@flecks/core/one/test-gather': ( '@flecks/core/one/test-gather': (
Flecks.provide(require.context('./things', false, /\.js$/)) Flecks.provide(require.context('./things', false, /\.js$/))
), ),
@ -15,5 +14,11 @@ export default {
}), }),
'@flecks/core/test/invoke-merge': () => ({bar: 420}), '@flecks/core/test/invoke-merge': () => ({bar: 420}),
'@flecks/core/test/invoke-merge-async': () => new Promise((resolve) => resolve({bar: 420})), '@flecks/core/test/invoke-merge-async': () => new Promise((resolve) => resolve({bar: 420})),
'@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': () => (foo, next) => {
// eslint-disable-next-line no-param-reassign
foo.bar *= 2;
next();
}, },
}; };

View File

@ -1,7 +1,4 @@
import {Hooks} from '@flecks/core'; export const hooks = {
export default {
[Hooks]: {
/** /**
* Gather database models. * Gather database models.
* *
@ -23,6 +20,5 @@ export default {
'@flecks/db/server.models.decorate': ( '@flecks/db/server.models.decorate': (
Flecks.decorate(require.context('./models/decorators', false, /\.js$/)) Flecks.decorate(require.context('./models/decorators', false, /\.js$/))
), ),
},
}; };

View File

@ -1,5 +1,3 @@
import {Hooks} from '@flecks/core';
import {createDatabaseConnection} from './connection'; import {createDatabaseConnection} from './connection';
import containers from './containers'; import containers from './containers';
@ -9,8 +7,7 @@ export {default as Model} from './model';
export {createDatabaseConnection}; export {createDatabaseConnection};
export default { export const hooks = {
[Hooks]: {
'@flecks/core.config': () => ({ '@flecks/core.config': () => ({
/** /**
* The database to connect to. * The database to connect to.
@ -42,7 +39,7 @@ export default {
'@flecks/core.starting': (flecks) => { '@flecks/core.starting': (flecks) => {
flecks.set('$flecks/db.models', flecks.gather( flecks.set('$flecks/db.models', flecks.gather(
'@flecks/db/server.models', '@flecks/db/server.models',
{typeAttribute: 'name'}, {typeProperty: 'name'},
)); ));
}, },
'@flecks/docker.containers': containers, '@flecks/docker.containers': containers,
@ -53,5 +50,4 @@ export default {
Models: flecks.get('$flecks/db.models'), Models: flecks.get('$flecks/db.models'),
sequelize: flecks.get('$flecks/db/sequelize'), sequelize: flecks.get('$flecks/db/sequelize'),
}), }),
},
}; };

View File

@ -1,7 +1,4 @@
import {Hooks} from '@flecks/core'; export const hooks = {
export default {
[Hooks]: {
/** /**
* Define docker containers. * Define docker containers.
* *
@ -22,6 +19,5 @@ export default {
ports: {3000: 3000}, ports: {3000: 3000},
}, },
}), }),
},
}; };

View File

@ -1,10 +1,7 @@
import {Hooks} from '@flecks/core';
import commands from './commands'; import commands from './commands';
import startContainer from './start-container'; import startContainer from './start-container';
export default { export const hooks = {
[Hooks]: {
'@flecks/core.config': () => ({ '@flecks/core.config': () => ({
/** /**
* Whether to run docker containers. * Whether to run docker containers.
@ -22,5 +19,4 @@ export default {
.map(([key, config]) => startContainer(flecks, key, config)), .map(([key, config]) => startContainer(flecks, key, config)),
); );
}, },
},
}; };

View File

@ -17,6 +17,7 @@ import {
isObjectExpression, isObjectExpression,
isStringLiteral, isStringLiteral,
isThisExpression, isThisExpression,
isVariableDeclaration,
} from '@babel/types'; } from '@babel/types';
import {require as R} from '@flecks/core/server'; import {require as R} from '@flecks/core/server';
import {parse as parseComment} from 'comment-parser'; import {parse as parseComment} from 'comment-parser';
@ -75,15 +76,14 @@ class ParserState {
} }
const implementationVisitor = (fn) => ({ const implementationVisitor = (fn) => ({
ExportDefaultDeclaration(path) { ExportNamedDeclaration(path) {
const {declaration} = path.node; const {declaration} = path.node;
if (isObjectExpression(declaration)) { if (isVariableDeclaration(declaration)) {
const {properties} = declaration; const {declarations} = declaration;
properties.forEach((property) => { declarations.forEach((declarator) => {
const {key, value} = property; if ('hooks' === declarator.id.name) {
if (isIdentifier(key) && key.name === 'Hooks') { if (isObjectExpression(declarator.init)) {
if (isObjectExpression(value)) { const {properties} = declarator.init;
const {properties} = value;
properties.forEach((property) => { properties.forEach((property) => {
const {key} = property; const {key} = property;
if (isLiteral(key)) { if (isLiteral(key)) {

View File

@ -1,9 +1,6 @@
import {Hooks} from '@flecks/core';
import commands from './commands'; import commands from './commands';
export default { export const hooks = {
[Hooks]: {
'@flecks/core.commands': commands, '@flecks/core.commands': commands,
'@flecks/core.config': () => ({ '@flecks/core.config': () => ({
/** /**
@ -13,5 +10,4 @@ export default {
*/ */
filenameRewriters: {}, filenameRewriters: {},
}), }),
},
}; };

View File

@ -1,7 +1,4 @@
import {Hooks} from '@flecks/core'; export const hooks = {
export default {
[Hooks]: {
/** /**
* Invoked when electron is initializing. * Invoked when electron is initializing.
* @param {Electron.App} app The electron app. See: https://www.electronjs.org/docs/latest/api/app * @param {Electron.App} app The electron app. See: https://www.electronjs.org/docs/latest/api/app
@ -19,6 +16,5 @@ export default {
'@flecks/electron/server.window': (win) => { '@flecks/electron/server.window': (win) => {
win.maximize(); win.maximize();
}, },
},
}; };

View File

@ -1,7 +1,6 @@
import cluster from 'cluster'; import cluster from 'cluster';
import {join} from 'path'; import {join} from 'path';
import {Hooks} from '@flecks/core';
import {require as R} from '@flecks/core/server'; import {require as R} from '@flecks/core/server';
import { import {
app, app,
@ -21,8 +20,7 @@ async function createWindow(flecks) {
await flecks.invokeSequentialAsync('@flecks/electron/server.window', win); await flecks.invokeSequentialAsync('@flecks/electron/server.window', win);
} }
export default { export const hooks = {
[Hooks]: {
'@flecks/core.config': () => ({ '@flecks/core.config': () => ({
/** /**
* Browser window options. * Browser window options.
@ -135,5 +133,4 @@ export default {
} }
await flecks.invokeSequentialAsync('@flecks/electron/server.initialize', app); await flecks.invokeSequentialAsync('@flecks/electron/server.initialize', app);
}, },
},
}; };

View File

@ -1,9 +1,6 @@
import {Hooks} from '@flecks/core';
import commands from './commands'; import commands from './commands';
export default { export const hooks = {
[Hooks]: {
'@flecks/core.commands': commands, '@flecks/core.commands': commands,
'@flecks/core.config': () => ({ '@flecks/core.config': () => ({
/** /**
@ -17,5 +14,4 @@ export default {
}, },
}), }),
'@flecks/core.targets': () => ['fleck'], '@flecks/core.targets': () => ['fleck'],
},
}; };

View File

@ -1,12 +1,11 @@
import {ByType, Flecks, Hooks} from '@flecks/core'; import {ByType, Flecks} from '@flecks/core';
import LimitedPacket from './limited-packet'; import LimitedPacket from './limited-packet';
import createLimiter from './limiter'; import createLimiter from './limiter';
export {default as createLimiter} from './limiter'; export {default as createLimiter} from './limiter';
export default { export const hooks = {
[Hooks]: {
'@flecks/core.config': () => ({ '@flecks/core.config': () => ({
/** /**
* All keys used to determine fingerprint. * All keys used to determine fingerprint.
@ -131,5 +130,4 @@ export default {
]), ]),
) )
), ),
},
}; };

View File

@ -1,7 +1,4 @@
import {Hooks} from '@flecks/core'; export const hooks = {
export default {
[Hooks]: {
/** /**
* Define React Providers. * Define React Providers.
* *
@ -31,6 +28,5 @@ export default {
// You can also just: // You can also just:
return Component; return Component;
}, },
},
}; };

View File

@ -1,4 +1,4 @@
import {D, Hooks} from '@flecks/core'; import {D} from '@flecks/core';
import {hydrate, render} from '@hot-loader/react-dom'; import {hydrate, render} from '@hot-loader/react-dom';
import React from 'react'; import React from 'react';
@ -10,8 +10,7 @@ const debug = D('@flecks/react/client');
export {FlecksContext}; export {FlecksContext};
export default { export const hooks = {
[Hooks]: {
'@flecks/web/client.up': async (flecks) => { '@flecks/web/client.up': async (flecks) => {
const {ssr} = flecks.get('@flecks/react'); const {ssr} = flecks.get('@flecks/react');
debug('%sing...', ssr ? 'hydrat' : 'render'); debug('%sing...', ssr ? 'hydrat' : 'render');
@ -25,5 +24,4 @@ export default {
); );
debug('rendered'); debug('rendered');
}, },
},
}; };

View File

@ -1,5 +1,3 @@
import {Hooks} from '@flecks/core';
export {default as ReactDom} from '@hot-loader/react-dom'; export {default as ReactDom} from '@hot-loader/react-dom';
export {default as classnames} from 'classnames'; export {default as classnames} from 'classnames';
export {default as PropTypes} from 'prop-types'; export {default as PropTypes} from 'prop-types';
@ -14,13 +12,11 @@ export {default as useEvent} from './hooks/use-event';
export {default as useFlecks} from './hooks/use-flecks'; export {default as useFlecks} from './hooks/use-flecks';
export {default as usePrevious} from './hooks/use-previous'; export {default as usePrevious} from './hooks/use-previous';
export default { export const hooks = {
[Hooks]: {
'@flecks/core.config': () => ({ '@flecks/core.config': () => ({
/** /**
* Whether to enable server-side rendering. * Whether to enable server-side rendering.
*/ */
ssr: true, ssr: true,
}), }),
},
}; };

View File

@ -1,15 +1,12 @@
import {Hooks} from '@flecks/core';
// eslint-disable-next-line import/no-extraneous-dependencies // eslint-disable-next-line import/no-extraneous-dependencies
import {createReduxHistory, history} from '@flecks/react/router/context'; import {createReduxHistory, history} from '@flecks/react/router/context';
import {unstable_HistoryRouter as HistoryRouter} from 'react-router-dom'; import {unstable_HistoryRouter as HistoryRouter} from 'react-router-dom';
import {HistoryRouter as ReduxHistoryRouter} from 'redux-first-history/rr6'; import {HistoryRouter as ReduxHistoryRouter} from 'redux-first-history/rr6';
export default { export const hooks = {
[Hooks]: {
'@flecks/react.providers': (req, flecks) => ( '@flecks/react.providers': (req, flecks) => (
flecks.fleck('@flecks/redux') flecks.fleck('@flecks/redux')
? [ReduxHistoryRouter, {history: createReduxHistory(flecks.get('$flecks/redux.store'))}] ? [ReduxHistoryRouter, {history: createReduxHistory(flecks.get('$flecks/redux.store'))}]
: [HistoryRouter, {history}] : [HistoryRouter, {history}]
), ),
},
}; };

View File

@ -1,17 +1,14 @@
import {Hooks} from '@flecks/core';
// eslint-disable-next-line import/no-extraneous-dependencies // eslint-disable-next-line import/no-extraneous-dependencies
import {routerMiddleware, routerReducer} from '@flecks/react/router/context'; import {routerMiddleware, routerReducer} from '@flecks/react/router/context';
export * from 'react-router-dom'; export * from 'react-router-dom';
export * from 'redux-first-history'; export * from 'redux-first-history';
export default { export const hooks = {
[Hooks]: {
'@flecks/redux.slices': () => ({ '@flecks/redux.slices': () => ({
router: routerReducer, router: routerReducer,
}), }),
'@flecks/redux.store': (options) => { '@flecks/redux.store': (options) => {
options.middleware.push(routerMiddleware); options.middleware.push(routerMiddleware);
}, },
},
}; };

View File

@ -1,10 +1,7 @@
import {Hooks} from '@flecks/core';
import {StaticRouter} from 'react-router-dom/server'; import {StaticRouter} from 'react-router-dom/server';
export default { export const hooks = {
[Hooks]: {
'@flecks/react.providers': (req, flecks) => ( '@flecks/react.providers': (req, flecks) => (
flecks.get('@flecks/react.ssr') ? [StaticRouter, {location: req.url}] : [] flecks.get('@flecks/react.ssr') ? [StaticRouter, {location: req.url}] : []
), ),
},
}; };

View File

@ -1,10 +1,8 @@
import {Hooks} from '@flecks/core';
import {augmentBuild} from '@flecks/web/server'; import {augmentBuild} from '@flecks/web/server';
import ssr from './ssr'; import ssr from './ssr';
export default { export const hooks = {
[Hooks]: {
'@flecks/core.build': (target, config, flecks) => { '@flecks/core.build': (target, config, flecks) => {
// Resolution. // Resolution.
config.use.push(({config}) => { config.use.push(({config}) => {
@ -24,5 +22,4 @@ export default {
'@flecks/web/server.stream.html': (stream, req, flecks) => ( '@flecks/web/server.stream.html': (stream, req, flecks) => (
flecks.get('@flecks/react.ssr') ? ssr(stream, req, flecks) : stream flecks.get('@flecks/react.ssr') ? ssr(stream, req, flecks) : stream
), ),
},
}; };

View File

@ -1,5 +1,3 @@
import {Hooks} from '@flecks/core';
import containers from './containers'; import containers from './containers';
import createClient from './create-client'; import createClient from './create-client';
@ -27,8 +25,7 @@ const safeKeys = async (client, pattern, caret) => {
export const keys = (client, pattern) => safeKeys(client, pattern, 0); export const keys = (client, pattern) => safeKeys(client, pattern, 0);
export default { export const hooks = {
[Hooks]: {
'@flecks/core.config': () => ({ '@flecks/core.config': () => ({
/** /**
* Redis server host. * Redis server host.
@ -43,5 +40,4 @@ export default {
'@flecks/repl.context': (flecks) => ({ '@flecks/repl.context': (flecks) => ({
redisClient: createClient(flecks), redisClient: createClient(flecks),
}), }),
},
}; };

View File

@ -1,4 +1,4 @@
import {D, Hooks} from '@flecks/core'; import {D} from '@flecks/core';
import redisAdapter from '@socket.io/redis-adapter'; import redisAdapter from '@socket.io/redis-adapter';
import ConnectRedis from 'connect-redis'; import ConnectRedis from 'connect-redis';
import session from 'express-session'; import session from 'express-session';
@ -10,8 +10,7 @@ const debugSilly = debug.extend('silly');
const RedisStore = ConnectRedis(session); const RedisStore = ConnectRedis(session);
export default { export const hooks = {
[Hooks]: {
'@flecks/user.session': async (flecks) => { '@flecks/user.session': async (flecks) => {
const client = createClient(flecks, {legacyMode: true}); const client = createClient(flecks, {legacyMode: true});
await client.connect(); await client.connect();
@ -28,5 +27,4 @@ export default {
adapter: redisAdapter(pubClient, subClient), adapter: redisAdapter(pubClient, subClient),
}; };
}, },
},
}; };

View File

@ -1,7 +1,4 @@
import {Hooks} from '@flecks/core'; export const hooks = {
export default {
[Hooks]: {
/** /**
* Define side-effects to run against Redux actions. * Define side-effects to run against Redux actions.
*/ */
@ -40,6 +37,5 @@ export default {
options.enhancers.splice(someIndex, 1); options.enhancers.splice(someIndex, 1);
options.middleware.push(mySpecialMiddleware); options.middleware.push(mySpecialMiddleware);
}, },
},
}; };

View File

@ -1,13 +1,12 @@
import {ensureUniqueReduction, Flecks, Hooks} from '@flecks/core'; import {Flecks} from '@flecks/core';
import {Provider} from 'react-redux'; import {Provider} from 'react-redux';
import configureStore, {createReducer} from '../store'; import configureStore, {createReducer} from '../store';
import localStorageEnhancer from './local-storage'; import localStorageEnhancer from './local-storage';
export default { export const hooks = {
[Hooks]: {
'@flecks/react.providers': async (req, flecks) => { '@flecks/react.providers': async (req, flecks) => {
const slices = await ensureUniqueReduction(flecks, '@flecks/redux.slices'); const slices = await flecks.invokeMergeUnique('@flecks/redux.slices');
const reducer = createReducer(flecks, slices); const reducer = createReducer(flecks, slices);
// Hydrate from server. // Hydrate from server.
const {preloadedState} = flecks.get('@flecks/redux/client'); const {preloadedState} = flecks.get('@flecks/redux/client');
@ -22,5 +21,4 @@ export default {
'@flecks/socket.packets.decorate': ( '@flecks/socket.packets.decorate': (
Flecks.decorate(require.context('./packets/decorators', false, /\.js$/)) Flecks.decorate(require.context('./packets/decorators', false, /\.js$/))
), ),
},
}; };

View File

@ -1,12 +1,10 @@
import {Flecks, Hooks} from '@flecks/core'; import {Flecks} from '@flecks/core';
export * from '@reduxjs/toolkit'; export * from '@reduxjs/toolkit';
export * from 'react-redux'; export * from 'react-redux';
export * from './actions'; export * from './actions';
export default { export const hooks = {
[Hooks]: {
'@flecks/socket.packets': Flecks.provide(require.context('./packets', false, /\.js$/)), '@flecks/socket.packets': Flecks.provide(require.context('./packets', false, /\.js$/)),
},
}; };

View File

@ -1,4 +1,4 @@
import {D, ensureUniqueReduction, Hooks} from '@flecks/core'; import {D} from '@flecks/core';
import {Provider} from 'react-redux'; import {Provider} from 'react-redux';
import {hydrateServer} from './actions'; import {hydrateServer} from './actions';
@ -8,10 +8,9 @@ import configureStore from './store';
const debug = D('@flecks/redux/server'); const debug = D('@flecks/redux/server');
const debugSilly = debug.extend('silly'); const debugSilly = debug.extend('silly');
export default { export const hooks = {
[Hooks]: {
'@flecks/web/server.request.route': (flecks) => async (req, res, next) => { '@flecks/web/server.request.route': (flecks) => async (req, res, next) => {
const slices = await ensureUniqueReduction(flecks, '@flecks/redux.slices'); const slices = await flecks.invokeMergeUnique('@flecks/redux.slices');
const reducer = createReducer(flecks, slices); const reducer = createReducer(flecks, slices);
// Let the slices have a(n async) chance to hydrate with server data. // Let the slices have a(n async) chance to hydrate with server data.
await Promise.all( await Promise.all(
@ -32,5 +31,4 @@ export default {
}, },
}), }),
'@flecks/react.providers': (req) => [Provider, {store: req.redux}], '@flecks/react.providers': (req) => [Provider, {store: req.redux}],
},
}; };

View File

@ -1,7 +1,4 @@
import {Hooks} from '@flecks/core'; export const hooks = {
export default {
[Hooks]: {
/** /**
* Define REPL commands. * Define REPL commands.
* *
@ -25,6 +22,5 @@ export default {
someValue: 'foobar', someValue: 'foobar',
}; };
}, },
},
}; };

View File

@ -1,11 +1,7 @@
import {Hooks} from '@flecks/core';
import commands from './commands'; import commands from './commands';
import {createReplServer} from './repl'; import {createReplServer} from './repl';
export default { export const hooks = {
[Hooks]: {
'@flecks/core.commands': commands, '@flecks/core.commands': commands,
'@flecks/server.up': (flecks) => createReplServer(flecks), '@flecks/server.up': (flecks) => createReplServer(flecks),
},
}; };

View File

@ -1,12 +1,8 @@
import {Hooks} from '@flecks/core'; export const hooks = {
export default {
[Hooks]: {
/** /**
* Define sequential actions to run when the server comes up. * Define sequential actions to run when the server comes up.
*/ */
'@flecks/server.up': async () => { '@flecks/server.up': async () => {
await youCanDoAsyncThingsHere(); await youCanDoAsyncThingsHere();
}, },
},
}; };

View File

@ -33,7 +33,8 @@ const {version} = require('../package.json');
rcs, rcs,
}); });
try { try {
await global.flecks.up('@flecks/server.up'); await Promise.all(global.flecks.invokeFlat('@flecks/core.starting'));
await global.flecks.invokeSequentialAsync('@flecks/server.up');
debug('up!'); debug('up!');
} }
catch (error) { catch (error) {

View File

@ -1,7 +1,4 @@
import {Hooks} from '@flecks/core'; export const hooks = {
export default {
[Hooks]: {
'@flecks/core.config': () => ({ '@flecks/core.config': () => ({
/** /**
* Whether HMR is enabled. * Whether HMR is enabled.
@ -24,5 +21,4 @@ export default {
modules: false, modules: false,
}, },
}), }),
},
}; };

View File

@ -1,7 +1,3 @@
import {Hooks} from '@flecks/core'; export const hooks = {
export default {
[Hooks]: {
'@flecks/core.targets': () => ['server'], '@flecks/core.targets': () => ['server'],
},
}; };

View File

@ -1,7 +1,4 @@
import {Hooks} from '@flecks/core'; export const hooks = {
export default {
[Hooks]: {
/** /**
* Modify Socket.io client configuration. * Modify Socket.io client configuration.
* *
@ -60,6 +57,5 @@ export default {
// Express-style route middleware... // Express-style route middleware...
next(); next();
}, },
},
}; };

View File

@ -1,9 +1,6 @@
import {Hooks} from '@flecks/core';
import SocketClient from './socket'; import SocketClient from './socket';
export default { export const hooks = {
[Hooks]: {
'@flecks/web/client.up': (flecks) => { '@flecks/web/client.up': (flecks) => {
const socket = new SocketClient(flecks); const socket = new SocketClient(flecks);
flecks.set('$flecks/socket.socket', socket); flecks.set('$flecks/socket.socket', socket);
@ -16,5 +13,4 @@ export default {
}, },
path: `/${id}/socket.io`, path: `/${id}/socket.io`,
}), }),
},
}; };

View File

@ -1,5 +1,3 @@
import {Hooks} from '@flecks/core';
import badPacketsCheck from './packet/bad-packets-check'; import badPacketsCheck from './packet/bad-packets-check';
import Bundle from './packet/bundle'; import Bundle from './packet/bundle';
import Redirect from './packet/redirect'; import Redirect from './packet/redirect';
@ -9,8 +7,7 @@ export {default as normalize} from './normalize';
export * from './hooks'; export * from './hooks';
export {default as Packet, Packer, ValidationError} from './packet'; export {default as Packet, Packer, ValidationError} from './packet';
export default { export const hooks = {
[Hooks]: {
'@flecks/core.starting': (flecks) => { '@flecks/core.starting': (flecks) => {
flecks.set('$flecks/socket.packets', flecks.gather( flecks.set('$flecks/socket.packets', flecks.gather(
'@flecks/socket.packets', '@flecks/socket.packets',
@ -32,5 +29,4 @@ export default {
Redirect, Redirect,
Refresh, Refresh,
}), }),
},
}; };

View File

@ -1,10 +1,7 @@
import {Hooks} from '@flecks/core';
import createIntercom from './create-intercom'; import createIntercom from './create-intercom';
import Sockets from './sockets'; import Sockets from './sockets';
export default { export const hooks = {
[Hooks]: {
'@flecks/web/server.request.socket': ({config: {'$flecks/socket.sockets': sockets}}) => (req, res, next) => { '@flecks/web/server.request.socket': ({config: {'$flecks/socket.sockets': sockets}}) => (req, res, next) => {
req.intercom = createIntercom(sockets, 'web'); req.intercom = createIntercom(sockets, 'web');
next(); next();
@ -21,5 +18,4 @@ export default {
'@flecks/socket.server': ({config: {'@flecks/core': {id}}}) => ({ '@flecks/socket.server': ({config: {'@flecks/core': {id}}}) => ({
path: `/${id}/socket.io`, path: `/${id}/socket.io`,
}), }),
},
}; };

View File

@ -1,7 +1,4 @@
import {Hooks} from '@flecks/core'; export const hooks = {
export default {
[Hooks]: {
/** /**
* Modify express-session configuration. * Modify express-session configuration.
* *
@ -10,6 +7,4 @@ export default {
'@flecks/user.session': () => ({ '@flecks/user.session': () => ({
saveUninitialized: true, saveUninitialized: true,
}), }),
},
}; };

View File

@ -1,13 +1,10 @@
import {Hooks} from '@flecks/core';
import {Logout} from './packets'; import {Logout} from './packets';
import {user, users} from './state'; import {user, users} from './state';
export * from './state'; export * from './state';
export default { export const hooks = {
[Hooks]: {
'@flecks/redux.slices': () => ({ '@flecks/redux.slices': () => ({
user, user,
users, users,
@ -15,5 +12,4 @@ export default {
'@flecks/socket.packets': (flecks) => ({ '@flecks/socket.packets': (flecks) => ({
Logout: Logout(flecks), Logout: Logout(flecks),
}), }),
},
}; };

View File

@ -1,11 +1,10 @@
import {randomBytes} from 'crypto'; import {randomBytes} from 'crypto';
import {Flecks, Hooks} from '@flecks/core'; import {Flecks} from '@flecks/core';
import passport from 'passport'; import passport from 'passport';
import LocalStrategy from 'passport-local'; import LocalStrategy from 'passport-local';
export default { export const hooks = {
[Hooks]: {
'@flecks/core.config': () => ({ '@flecks/core.config': () => ({
/** /**
* Path to redirect to after failed login. * Path to redirect to after failed login.
@ -66,5 +65,4 @@ export default {
}, },
)); ));
}, },
},
}; };

View File

@ -1,12 +1,11 @@
import {D, Flecks, Hooks} from '@flecks/core'; import {D, Flecks} from '@flecks/core';
import passport from 'passport'; import passport from 'passport';
import LogOps from 'passport/lib/http/request'; import LogOps from 'passport/lib/http/request';
const debug = D('@flecks/user/passport'); const debug = D('@flecks/user/passport');
const debugSilly = debug.extend('silly'); const debugSilly = debug.extend('silly');
export default { export const hooks = {
[Hooks]: {
'@flecks/db/server.models': Flecks.provide(require.context('./models', false, /\.js$/)), '@flecks/db/server.models': Flecks.provide(require.context('./models', false, /\.js$/)),
'@flecks/web/server.request.route': (flecks) => (req, res, next) => { '@flecks/web/server.request.route': (flecks) => (req, res, next) => {
debugSilly('@flecks/web/server.request.route: passport.initialize()'); debugSilly('@flecks/web/server.request.route: passport.initialize()');
@ -81,5 +80,4 @@ export default {
}); });
}); });
}, },
},
}; };

View File

@ -1,12 +1,11 @@
import {D, Hooks} from '@flecks/core'; import {D} from '@flecks/core';
import express from 'express'; import express from 'express';
import expressSession from 'express-session'; import expressSession from 'express-session';
const debug = D('@flecks/user/session'); const debug = D('@flecks/user/session');
const debugSilly = debug.extend('silly'); const debugSilly = debug.extend('silly');
export default { export const hooks = {
[Hooks]: {
'@flecks/core.config': () => ({ '@flecks/core.config': () => ({
/** /**
* Set the cookie secret for session encryption. * Set the cookie secret for session encryption.
@ -55,5 +54,4 @@ export default {
next(); next();
}); });
}, },
},
}; };

View File

@ -1,7 +1,4 @@
import {Hooks} from '@flecks/core'; export const hooks = {
export default {
[Hooks]: {
/** /**
* Define sequential actions to run when the client comes up. * Define sequential actions to run when the client comes up.
*/ */
@ -58,5 +55,4 @@ export default {
'@flecks/web/server.up': async () => { '@flecks/web/server.up': async () => {
await youCanDoAsyncThingsHere(); await youCanDoAsyncThingsHere();
}, },
},
}; };

View File

@ -1,6 +1,6 @@
import {D, Flecks} from '@flecks/core'; import {D, Flecks} from '@flecks/core';
// eslint-disable-next-line import/no-extraneous-dependencies // eslint-disable-next-line import/no-extraneous-dependencies, import/no-unresolved
const {version} = require('@flecks/web/package.json'); const {version} = require('@flecks/web/package.json');
(async () => { (async () => {
@ -56,7 +56,8 @@ const {version} = require('@flecks/web/package.json');
const flecks = new Flecks(runtime); const flecks = new Flecks(runtime);
window.flecks = flecks; window.flecks = flecks;
try { try {
await flecks.up('@flecks/web/client.up'); await Promise.all(flecks.invokeFlat('@flecks/core.starting'));
await flecks.invokeSequentialAsync('@flecks/web/client.up');
window.document.querySelector('#root').style.display = 'block'; window.document.querySelector('#root').style.display = 'block';
debug('up!'); debug('up!');
} }

View File

@ -1,7 +1,7 @@
import {stat, unlink} from 'fs/promises'; import {stat, unlink} from 'fs/promises';
import {join} from 'path'; import {join} from 'path';
import {D, Hooks} from '@flecks/core'; import {D} from '@flecks/core';
import {Flecks, spawnWith} from '@flecks/core/server'; import {Flecks, spawnWith} from '@flecks/core/server';
import augmentBuild from './augment-build'; import augmentBuild from './augment-build';
@ -16,8 +16,7 @@ const debug = D('@flecks/web/server');
export {augmentBuild}; export {augmentBuild};
export default { export const hooks = {
[Hooks]: {
'@flecks/core.build': augmentBuild, '@flecks/core.build': augmentBuild,
'@flecks/core.build.alter': async (neutrinoConfigs, flecks) => { '@flecks/core.build.alter': async (neutrinoConfigs, flecks) => {
// Don't build if there's a fleck target. // Don't build if there's a fleck target.
@ -210,5 +209,4 @@ export default {
'@flecks/repl.context': (flecks) => ({ '@flecks/repl.context': (flecks) => ({
httpServer: flecks.get('$flecks/web/server.instance'), httpServer: flecks.get('$flecks/web/server.instance'),
}), }),
},
}; };