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">
<h1>flecks</h1>
<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
a highly dynamic structure encourage consistency while allowing you to easily express your own
opinions.

View File

@ -22,7 +22,7 @@
- [x] remove `invokeParallel()`
- [x] Specialize `invokeReduce()` with `invokeMerge()`.
- [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']}`
- [ ] user redux server hydrate fails if no user in req
- [ ] governor fails if not in server up
@ -31,3 +31,4 @@
- [ ] rename `@flecks/web` to `@flecks/web`
- [ ] simultaneous babel compilation across all compiled flecks
- [ ] 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());
// AirBnb linting.
config.use.unshift(
airbnb({
eslint: {
@ -45,13 +47,13 @@ config.use.unshift(
}),
);
// Include a shebang and set the executable bit..
config.use.push(banner({
banner: '#!/usr/bin/env node',
include: /^cli\.js$/,
pluginId: 'shebang',
raw: true,
}))
config.use.push(({config}) => {
config
.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).
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
import {Hooks} from '@flecks/core';
export default {
[Hooks]: {
export const hooks = {
'@flecks/core.starting': () => {
console.log('hello, gorgeous');
},
},
};
```
@ -133,15 +129,15 @@ assert(foo.type === 'Foo');
```javascript
{
// 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.
typeAttribute = 'type',
typeProperty = 'type',
// A function called with the `Gathered` object to allow checking validity.
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.
@ -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:
```javascript
import {Hooks} foom '@flecks/core';
import SomeModel from './models/some-model';
import AnotherModel from './models/another-model';
export default {
[Hooks]: {
export const hooks = {
'@flecks/db/server.models': () => ({
SomeModel,
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.
@ -183,12 +175,10 @@ models/
then, this `index.js`:
```javascript
import {Flecks, Hooks} from '@flecks/core';
import {Flecks} from '@flecks/core';
export default {
[Hooks]: {
export const hooks = {
'@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:
```javascript
import {Hooks} from '@flecks/core';
export default {
[Hooks]: {
export const hooks = {
'@flecks/db/server.models.decorate': (Models) => {
return {
...Models,
@ -230,13 +217,12 @@ export default {
},
};
},
},
};
```
#### `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:
@ -266,12 +252,12 @@ export default (User) => {
then, this `index.js`:
```javascript
import {Flecks, Hooks} from '@flecks/core';
import {Flecks} from '@flecks/core';
export default {
[Hooks]: {
'@flecks/db/server.models.decorate': Flecks.decorate(require.context('./models/decorators', false, /\.js$/)),
},
export const hooks = {
'@flecks/db/server.models.decorate': (
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).
### 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).

View File

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

View File

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

View File

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

View File

@ -1,2 +1,4 @@
// Get a runtime require function by hook or by crook. :)
// eslint-disable-next-line no-eval
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 debugSilly = debug.extend('silly');
// Symbols for Gathered classes.
export const ById = Symbol.for('@flecks/core.byId');
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);
/**
* CamelCase a string.
*
* @param {string} string
* @returns {string}
*/
const camelCase = (string) => string.split(/[_-]/).map(capitalize).join('');
// Track gathered for HMR.
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 {
static get [idAttribute]() {
static get [idProperty]() {
return id;
}
static get [typeAttribute]() {
static get [typeProperty]() {
return type;
}
@ -43,72 +57,121 @@ const wrapperClass = (Class, id, idAttribute, type, typeAttribute) => {
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({
config = {},
flecks = {},
platforms = [],
} = {}) {
this.config = {
...Object.fromEntries(Object.keys(flecks).map((path) => [path, {}])),
...config,
};
this.hooks = {};
this.flecks = {};
const emptyConfigForAllFlecks = Object.fromEntries(
Object.keys(flecks).map((path) => [path, {}]),
);
this.config = {...emptyConfigForAllFlecks, ...config};
this.platforms = platforms;
const entries = Object.entries(flecks);
debugSilly('paths: %O', entries.map(([fleck]) => fleck));
for (let i = 0; i < entries.length; 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);
}
configureFleck(fleck) {
/**
* Configure defaults for a fleck.
*
* @param {string} fleck
* @protected
*/
configureFleckDefaults(fleck) {
this.config[fleck] = {
...this.invokeFleck('@flecks/core.config', fleck),
...this.config[fleck],
};
}
configureFlecks() {
const defaultConfig = this.invoke('@flecks/core.config');
const flecks = Object.keys(defaultConfig);
/**
* Configure defaults for all flecks.
*
* @protected
*/
configureFlecksDefaults() {
const flecks = this.flecksImplementing('@flecks/core.config');
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(
context,
{
transformer = camelCase,
} = {},
) {
return (Gathered, flecks) => {
return (Gathered, flecks) => (
context.keys()
.forEach((path) => {
.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
})`,
`Flecks.decorate(): require(${path}).default is not a function (from: ${context.id})`,
);
}
const key = transformer(this.symbolizePath(path));
if (Gathered[key]) {
// eslint-disable-next-line no-param-reassign
Gathered[key] = M(Gathered[key], flecks);
}
});
return Gathered;
};
return {...Gathered, [key]: M(Gathered[key], flecks)};
},
Gathered,
)
);
}
/**
* Destroy this instance.
*/
destroy() {
this.config = {};
this.hooks = {};
@ -116,12 +179,20 @@ export default class Flecks {
this.platforms = [];
}
/**
* Lists all flecks implementing a hook, including platform-specific and elided variants.
*
* @param {string} hook
* @returns {string[]} The expanded list of flecks.
*/
expandedFlecks(hook) {
const flecks = this.lookupFlecks(hook);
let expanded = [];
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);
@ -130,6 +201,7 @@ export default class Flecks {
}
}
}
// Expand elided flecks.
const index = expanded.findIndex((fleck) => '...' === fleck);
if (-1 !== index) {
if (-1 !== expanded.slice(index + 1).findIndex((fleck) => '...' === fleck)) {
@ -158,33 +230,66 @@ export default class Flecks {
return expanded;
}
/**
* Get the module for a fleck.
*
* @param {*} fleck
*
* @returns {*}
*/
fleck(fleck) {
return this.flecks[fleck];
}
/**
* Test whether a fleck implements a hook.
*
* @param {*} fleck
* @param {string} hook
* @returns {boolean}
*/
fleckImplements(fleck, hook) {
return !!this.hooks[hook].find(({fleck: candidate}) => fleck === candidate);
}
/**
* Get a list of flecks implementing a hook.
*
* @param {string} hook
* @returns {string[]}
*/
flecksImplementing(hook) {
return this.hooks[hook]?.map(({fleck}) => fleck) || [];
}
/**
* Gather and register class types.
*
* @param {string} hook
* @param {object} config
* @param {string} [config.idProperty='id'] The property used to get/set the class ID.
* @param {string} [config.typeProperty='type'] The property used to get/set the class type.
* @param {function} [config.check=() => {}] Check the validity of the gathered classes.
* @returns {object} An object with keys for ID, type, {@link ById}, and {@link ByType}.
*/
gather(
hook,
{
idAttribute = 'id',
typeAttribute = 'type',
idProperty = 'id',
typeProperty = 'type',
check = () => {},
} = {},
) {
if (!hook || 'string' !== typeof hook) {
throw new TypeError('Flecks.gather(): Expects parameter 1 (hook) to be string');
}
// Gather classes and check.
const raw = this.invokeMerge(hook);
check(raw, hook);
// Decorate and check.
const decorated = this.invokeComposed(`${hook}.decorate`, raw);
check(decorated, `${hook}.decorate`);
// Assign unique IDs to each class and sort by type.
let uid = 1;
const ids = {};
const types = (
@ -193,50 +298,78 @@ export default class Flecks {
.sort(([ltype], [rtype]) => (ltype < rtype ? -1 : 1))
.map(([type, Class]) => {
const id = uid++;
ids[id] = wrapperClass(Class, id, idAttribute, type, typeAttribute);
ids[id] = wrapGathered(Class, id, idProperty, type, typeProperty);
return [type, ids[id]];
}),
)
);
// Conglomerate all ID and type keys along with Symbols for accessing either/or.
const gathered = {
...ids,
...types,
[ById]: ids,
[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]));
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) {
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) {
if (!this.hooks[hook]) {
return {};
}
return this.flecksImplementing(hook)
.reduce((r, fleck) => ({
...r,
[fleck]: this.invokeFleck(hook, fleck, ...args),
}), {});
.reduce((r, fleck) => ({...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]) {
return arg;
return initial;
}
const flecks = this.expandedFlecks(hook);
if (0 === flecks.length) {
return arg;
return initial;
}
return flecks
.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) {
if (!this.hooks[hook]) {
return arg;
@ -250,6 +383,13 @@ export default class Flecks {
.reduce(async (r, fleck) => this.invokeFleck(hook, fleck, await r, ...args), arg);
}
/**
* Invokes a hook and returns a flat array of results.
*
* @param {string} hook
* @param {...any} args The arguments passed to each implementation.
* @returns {any[]}
*/
invokeFlat(hook, ...args) {
if (!this.hooks[hook]) {
return [];
@ -257,6 +397,14 @@ export default class Flecks {
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) {
debugSilly('invokeFleck(%s, %s, ...)', hook, fleck);
if (!this.hooks[hook]) {
@ -270,33 +418,116 @@ export default class Flecks {
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) {
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) {
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) {
if (!this.hooks[hook]) {
return initial;
}
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) {
if (!this.hooks[hook]) {
return initial;
}
return this.hooks[hook]
.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,
);
}
/**
* 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) {
if (!this.hooks[hook]) {
return [];
@ -315,6 +546,11 @@ export default class Flecks {
return results;
}
/**
* An async version of `invokeSequential`.
*
* @see {@link Flecks#invokeSequential}
*/
async invokeSequentialAsync(hook, ...args) {
if (!this.hooks[hook]) {
return [];
@ -334,10 +570,18 @@ export default class Flecks {
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) {
const index = hook.indexOf('.');
if (-1 === index) {
@ -346,31 +590,37 @@ export default class Flecks {
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) {
debugSilly('makeMiddleware(...): %s', hook);
if (!this.hooks[hook]) {
return Promise.resolve();
return (...args) => args.pop()();
}
const flecks = this.expandedFlecks(hook);
if (0 === flecks.length) {
return Promise.resolve();
return (...args) => args.pop()();
}
const middleware = flecks
.filter((fleck) => this.fleckImplements(fleck, hook));
debugSilly('middleware: %O', middleware);
const instance = new Middleware(middleware.map((fleck) => this.invokeFleck(hook, fleck)));
return async (...args) => {
const next = args.pop();
try {
await instance.promise(...args);
next();
}
catch (error) {
next(error);
}
};
return instance.dispatch.bind(instance);
}
/**
* Provide classes for e.g. {@link Flecks#gather}
*
* @param {*} context @see {@link https://webpack.js.org/guides/dependency-management/#requirecontext}
* @param {object} config
* @param {function} [config.invoke = true] Invoke the default exports as a function?
* @param {function} [config.transformer = {@link camelCase}]
* Function to run on each context path.
* @returns {object}
*/
static provide(
context,
{
@ -393,7 +643,7 @@ export default class Flecks {
);
}
return [
transformer(this.symbolizePath(path)),
transformer(this.dasherizePath(path)),
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) {
debug('refreshing %s...', fleck);
// 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);
for (let j = 0; j < keys.length; 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 compose} from './compose';
export {default as D} from './debug';
export {default as ensureUniqueReduction} from './ensure-unique-reduction';
export {default as EventEmitter} from './event-emitter';
export {
default as Flecks,
ById,
ByType,
Hooks,
} from './flecks';
export default {
[Hooks]: {
export const hooks = {
'@flecks/core.config': () => ({
/**
* The ID of your application.
*/
id: 'flecks',
}),
},
};

View File

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

View File

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

View File

@ -4,7 +4,6 @@ import {inspect} from 'util';
import airbnb from '@neutrinojs/airbnb';
import neutrino from 'neutrino';
import {Hooks} from '../flecks';
import commands from './commands';
import R from '../bootstrap/require';
@ -31,8 +30,7 @@ export {default as fleck} from '../bootstrap/fleck';
export {default as require} from '../bootstrap/require';
export {JsonStream, transform} from './stream';
export default {
[Hooks]: {
export const hooks = {
'@flecks/core.build': (target, config, flecks) => {
const {
'eslint.exclude': exclude,
@ -107,5 +105,4 @@ export default {
*/
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';
chai.use(chaiAsPromised);
const {expect} = chai;
const testOne = require('./one');
const testTwo = require('./two');
@ -33,3 +38,13 @@ it('can invoke merge async', async () => {
expect(await flecks.invokeMergeAsync('@flecks/core/test/invoke-merge-async'))
.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
import {Flecks, Hooks} from '@flecks/core';
import {Flecks} from '@flecks/core';
export const testNodespace = () => [
/* eslint-disable no-eval */
@ -8,8 +8,7 @@ export const testNodespace = () => [
/* eslint-enable no-eval */
];
export default {
[Hooks]: {
export const hooks = {
'@flecks/core.config': () => ({
foo: 'bar',
}),
@ -26,5 +25,11 @@ export default {
},
'@flecks/core/test/invoke-merge': () => ({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 {
[Hooks]: {
export const hooks = {
'@flecks/core/one/test-gather': (
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-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 default {
[Hooks]: {
export const hooks = {
/**
* Gather database models.
*
@ -23,6 +20,5 @@ export default {
'@flecks/db/server.models.decorate': (
Flecks.decorate(require.context('./models/decorators', false, /\.js$/))
),
},
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,4 @@
import {Hooks} from '@flecks/core';
export default {
[Hooks]: {
export const hooks = {
/**
* Invoked when electron is initializing.
* @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) => {
win.maximize();
},
},
};

View File

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

View File

@ -1,9 +1,6 @@
import {Hooks} from '@flecks/core';
import commands from './commands';
export default {
[Hooks]: {
export const hooks = {
'@flecks/core.commands': commands,
'@flecks/core.config': () => ({
/**
@ -17,5 +14,4 @@ export default {
},
}),
'@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 createLimiter from './limiter';
export {default as createLimiter} from './limiter';
export default {
[Hooks]: {
export const hooks = {
'@flecks/core.config': () => ({
/**
* All keys used to determine fingerprint.
@ -131,5 +130,4 @@ export default {
]),
)
),
},
};

View File

@ -1,7 +1,4 @@
import {Hooks} from '@flecks/core';
export default {
[Hooks]: {
export const hooks = {
/**
* Define React Providers.
*
@ -31,6 +28,5 @@ export default {
// You can also just:
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 React from 'react';
@ -10,8 +10,7 @@ const debug = D('@flecks/react/client');
export {FlecksContext};
export default {
[Hooks]: {
export const hooks = {
'@flecks/web/client.up': async (flecks) => {
const {ssr} = flecks.get('@flecks/react');
debug('%sing...', ssr ? 'hydrat' : 'render');
@ -25,5 +24,4 @@ export default {
);
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 classnames} from 'classnames';
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 usePrevious} from './hooks/use-previous';
export default {
[Hooks]: {
export const hooks = {
'@flecks/core.config': () => ({
/**
* Whether to enable server-side rendering.
*/
ssr: true,
}),
},
};

View File

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

View File

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

View File

@ -1,10 +1,7 @@
import {Hooks} from '@flecks/core';
import {StaticRouter} from 'react-router-dom/server';
export default {
[Hooks]: {
export const hooks = {
'@flecks/react.providers': (req, flecks) => (
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 ssr from './ssr';
export default {
[Hooks]: {
export const hooks = {
'@flecks/core.build': (target, config, flecks) => {
// Resolution.
config.use.push(({config}) => {
@ -24,5 +22,4 @@ export default {
'@flecks/web/server.stream.html': (stream, req, flecks) => (
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 createClient from './create-client';
@ -27,8 +25,7 @@ const safeKeys = async (client, pattern, caret) => {
export const keys = (client, pattern) => safeKeys(client, pattern, 0);
export default {
[Hooks]: {
export const hooks = {
'@flecks/core.config': () => ({
/**
* Redis server host.
@ -43,5 +40,4 @@ export default {
'@flecks/repl.context': (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 ConnectRedis from 'connect-redis';
import session from 'express-session';
@ -10,8 +10,7 @@ const debugSilly = debug.extend('silly');
const RedisStore = ConnectRedis(session);
export default {
[Hooks]: {
export const hooks = {
'@flecks/user.session': async (flecks) => {
const client = createClient(flecks, {legacyMode: true});
await client.connect();
@ -28,5 +27,4 @@ export default {
adapter: redisAdapter(pubClient, subClient),
};
},
},
};

View File

@ -1,7 +1,4 @@
import {Hooks} from '@flecks/core';
export default {
[Hooks]: {
export const hooks = {
/**
* Define side-effects to run against Redux actions.
*/
@ -40,6 +37,5 @@ export default {
options.enhancers.splice(someIndex, 1);
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 configureStore, {createReducer} from '../store';
import localStorageEnhancer from './local-storage';
export default {
[Hooks]: {
export const hooks = {
'@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);
// Hydrate from server.
const {preloadedState} = flecks.get('@flecks/redux/client');
@ -22,5 +21,4 @@ export default {
'@flecks/socket.packets.decorate': (
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 'react-redux';
export * from './actions';
export default {
[Hooks]: {
export const hooks = {
'@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 {hydrateServer} from './actions';
@ -8,10 +8,9 @@ import configureStore from './store';
const debug = D('@flecks/redux/server');
const debugSilly = debug.extend('silly');
export default {
[Hooks]: {
export const hooks = {
'@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);
// Let the slices have a(n async) chance to hydrate with server data.
await Promise.all(
@ -32,5 +31,4 @@ export default {
},
}),
'@flecks/react.providers': (req) => [Provider, {store: req.redux}],
},
};

View File

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

View File

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

View File

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

View File

@ -33,7 +33,8 @@ const {version} = require('../package.json');
rcs,
});
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!');
}
catch (error) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,11 +1,10 @@
import {randomBytes} from 'crypto';
import {Flecks, Hooks} from '@flecks/core';
import {Flecks} from '@flecks/core';
import passport from 'passport';
import LocalStrategy from 'passport-local';
export default {
[Hooks]: {
export const hooks = {
'@flecks/core.config': () => ({
/**
* 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 LogOps from 'passport/lib/http/request';
const debug = D('@flecks/user/passport');
const debugSilly = debug.extend('silly');
export default {
[Hooks]: {
export const hooks = {
'@flecks/db/server.models': Flecks.provide(require.context('./models', false, /\.js$/)),
'@flecks/web/server.request.route': (flecks) => (req, res, next) => {
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 expressSession from 'express-session';
const debug = D('@flecks/user/session');
const debugSilly = debug.extend('silly');
export default {
[Hooks]: {
export const hooks = {
'@flecks/core.config': () => ({
/**
* Set the cookie secret for session encryption.
@ -55,5 +54,4 @@ export default {
next();
});
},
},
};

View File

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

View File

@ -1,6 +1,6 @@
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');
(async () => {
@ -56,7 +56,8 @@ const {version} = require('@flecks/web/package.json');
const flecks = new Flecks(runtime);
window.flecks = flecks;
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';
debug('up!');
}

View File

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