diff --git a/README.md b/README.md
index c641563..24efb57 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
flecks
- 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.
diff --git a/TODO.md b/TODO.md
index 491effd..3f87ef8 100644
--- a/TODO.md
+++ b/TODO.md
@@ -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
diff --git a/packages/core/QUIRKS.md b/packages/core/QUIRKS.md
new file mode 100644
index 0000000..12a2a8f
--- /dev/null
+++ b/packages/core/QUIRKS.md
@@ -0,0 +1 @@
+- I use the variable `r` a lot when referencing a reducer's accumulator value
diff --git a/packages/core/build/.neutrinorc.js b/packages/core/build/.neutrinorc.js
index 40c4cbc..eb7167d 100644
--- a/packages/core/build/.neutrinorc.js
+++ b/packages/core/build/.neutrinorc.js
@@ -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')
diff --git a/packages/core/build/dox/concepts/hooks.md b/packages/core/build/dox/concepts/hooks.md
index 7042a98..4bbe26f 100755
--- a/packages/core/build/dox/concepts/hooks.md
+++ b/packages/core/build/dox/concepts/hooks.md
@@ -2,16 +2,12 @@
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]: {
- '@flecks/core.starting': () => {
- console.log('hello, gorgeous');
- },
+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]: {
- '@flecks/db/server.models': () => ({
- SomeModel,
- AnotherModel,
- }),
- },
-};
+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]: {
- '@flecks/db/server.models': Flecks.provide(require.context('./models', false, /\.js$/)),
- },
+export const hooks = {
+ '@flecks/db/server.models': Flecks.provide(require.context('./models', false, /\.js$/)),
};
```
@@ -212,31 +202,27 @@ 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]: {
- '@flecks/db/server.models.decorate': (Models) => {
- return {
- ...Models,
- User: class extends Models.User {
-
- // Let's mix in some logging...
- constructor(...args) {
- super(...args);
- console.log ('Another user decorated!');
- }
-
- },
- };
- },
+export const hooks = {
+ '@flecks/db/server.models.decorate': (Models) => {
+ return {
+ ...Models,
+ User: class extends Models.User {
+
+ // Let's mix in some logging...
+ constructor(...args) {
+ super(...args);
+ console.log ('Another user decorated!');
+ }
+
+ },
+ };
},
};
```
#### `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).
diff --git a/packages/core/build/dox/hooks.js b/packages/core/build/dox/hooks.js
index 3f34046..edd1ad6 100644
--- a/packages/core/build/dox/hooks.js
+++ b/packages/core/build/dox/hooks.js
@@ -1,122 +1,118 @@
-import {Hooks} from '@flecks/core';
+export const hooks = {
-export default {
- [Hooks]: {
- /**
- * Hook into neutrino configuration.
- * @param {string} target The build target; e.g. `server`.
- * @param {Object} config The neutrino configuration.
- */
+ /**
+ * Hook into neutrino configuration.
+ * @param {string} target The build target; e.g. `server`.
+ * @param {Object} config The neutrino configuration.
+ */
'@flecks/core.build': (target, config) => {
- if ('something' === target) {
- config[target].use.push(someNeutrinoMiddleware);
- }
- },
+ if ('something' === target) {
+ config[target].use.push(someNeutrinoMiddleware);
+ }
+ },
- /**
- * Alter build configurations after they have been hooked.
- * @param {Object} configs The neutrino configurations.
- */
- '@flecks/core.build.alter': (configs) => {
- // Maybe we want to do something if a config exists..?
- if (configs.something) {
- // Do something...
- // And then maybe we want to remove it from the build configuration..?
- delete configs.something;
- }
- },
-
- /**
- * Register build configuration.
- */
- '@flecks/core.build.config': () => [
- /**
- * If you document your config files like this, documentation will be automatically
- * generated.
- */
- '.myrc.js',
- /**
- * Make sure you return them as an array expression, like this.
- */
- ['mygeneralrc.js', {specifier: (specific) => `${specific}.mygeneralrc.js`}],
- ],
-
- /**
- * Define CLI commands.
- */
- '@flecks/core.commands': (program) => ({
- // So this could be invoked like:
- // npx flecks something -t --blow-up blah
- something: {
- action: (...args) => {
- // Run the command...
- },
- args: [
- '',
- ],
- description: 'This sure is some command',
- options: [
- '-t, --test', 'Do a test',
- '-b, --blow-up', 'Blow up instead of running the command',
- ],
- },
- }),
-
- /**
- * Define configuration.
- */
- '@flecks/core.config': () => ({
- whatever: 'configuration',
- your: 1337,
- fleck: 'needs',
- /**
- * Also, comments like this will be used to automatically generate documentation.
- */
- though: 'you should keep the values serializable',
- }),
-
- /**
- * Invoked when a fleck is HMR'd
- * @param {string} path The path of the fleck
- * @param {Module} updatedFleck The updated fleck module.
+ /**
+ * Alter build configurations after they have been hooked.
+ * @param {Object} configs The neutrino configurations.
*/
- '@flecks/core.hmr': (path, updatedFleck) => {
- if ('my-fleck' === path) {
- updatedFleck.doSomething();
- }
- },
+ '@flecks/core.build.alter': (configs) => {
+ // Maybe we want to do something if a config exists..?
+ if (configs.something) {
+ // Do something...
+ // And then maybe we want to remove it from the build configuration..?
+ delete configs.something;
+ }
+ },
+ /**
+ * Register build configuration.
+ */
+ '@flecks/core.build.config': () => [
/**
- * Invoked when a gathered class is HMR'd.
- * @param {constructor} Class The class.
- * @param {string} hook The gather hook; e.g. `@flecks/db/server.models`.
- */
- '@flecks/core.hmr.gathered': (Class, hook) => {
- // Do something with Class...
- },
+ * If you document your config files like this, documentation will be automatically
+ * generated.
+ */
+ '.myrc.js',
+ /**
+ * Make sure you return them as an array expression, like this.
+ */
+ ['mygeneralrc.js', {specifier: (specific) => `${specific}.mygeneralrc.js`}],
+ ],
- /**
- * Invoked when the application is starting. Use for order-independent initialization tasks.
- */
- '@flecks/core.starting': (flecks) => {
- flecks.set('$my-fleck/value', initializeMyValue());
+ /**
+ * Define CLI commands.
+ */
+ '@flecks/core.commands': (program) => ({
+ // So this could be invoked like:
+ // npx flecks something -t --blow-up blah
+ something: {
+ action: (...args) => {
+ // Run the command...
+ },
+ args: [
+ '',
+ ],
+ 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',
+ ],
},
+ }),
+ /**
+ * Define configuration.
+ */
+ '@flecks/core.config': () => ({
+ whatever: 'configuration',
+ your: 1337,
+ fleck: 'needs',
/**
- * Define neutrino build targets.
- */
- '@flecks/core.targets': () => ['sometarget'],
+ * Also, comments like this will be used to automatically generate documentation.
+ */
+ though: 'you should keep the values serializable',
+ }),
- /**
- * Hook into webpack configuration.
- * @param {string} target The build target; e.g. `server`.
- * @param {Object} config The neutrino configuration.
- */
- '@flecks/core.webpack': (target, config) => {
- if ('something' === target) {
- config.stats = 'verbose';
- }
- },
+ /**
+ * Invoked when a fleck is HMR'd
+ * @param {string} path The path of the fleck
+ * @param {Module} updatedFleck The updated fleck module.
+ */
+ '@flecks/core.hmr': (path, updatedFleck) => {
+ if ('my-fleck' === path) {
+ updatedFleck.doSomething();
+ }
+ },
+
+ /**
+ * Invoked when a gathered class is HMR'd.
+ * @param {constructor} Class The class.
+ * @param {string} hook The gather hook; e.g. `@flecks/db/server.models`.
+ */
+ '@flecks/core.hmr.gathered': (Class, hook) => {
+ // Do something with Class...
+ },
+
+ /**
+ * Invoked when the application is starting. Use for order-independent initialization tasks.
+ */
+ '@flecks/core.starting': (flecks) => {
+ flecks.set('$my-fleck/value', initializeMyValue());
+ },
+
+ /**
+ * Define neutrino build targets.
+ */
+ '@flecks/core.targets': () => ['sometarget'],
+
+ /**
+ * Hook into webpack configuration.
+ * @param {string} target The build target; e.g. `server`.
+ * @param {Object} config The neutrino configuration.
+ */
+ '@flecks/core.webpack': (target, config) => {
+ if ('something' === target) {
+ config.stats = 'verbose';
+ }
},
};
-
diff --git a/packages/core/package.json b/packages/core/package.json
index 692b460..26aee49 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -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",
diff --git a/packages/core/src/bootstrap/autoentry.js b/packages/core/src/bootstrap/autoentry.js
index c1609f9..8e6a533 100644
--- a/packages/core/src/bootstrap/autoentry.js
+++ b/packages/core/src/bootstrap/autoentry.js
@@ -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
diff --git a/packages/core/src/bootstrap/require.js b/packages/core/src/bootstrap/require.js
index 168357d..76e08c0 100644
--- a/packages/core/src/bootstrap/require.js
+++ b/packages/core/src/bootstrap/require.js
@@ -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');
diff --git a/packages/core/src/ensure-unique-reduction.js b/packages/core/src/ensure-unique-reduction.js
deleted file mode 100644
index 32e5855..0000000
--- a/packages/core/src/ensure-unique-reduction.js
+++ /dev/null
@@ -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};
- }, {});
-};
diff --git a/packages/core/src/flecks.js b/packages/core/src/flecks.js
index 195625f..54f0c43 100644
--- a/packages/core/src/flecks.js
+++ b/packages/core/src/flecks.js
@@ -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) => {
- const {default: M} = context(path);
- if ('function' !== typeof M) {
- throw new ReferenceError(
- `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;
- };
+ .reduce(
+ (Gathered, path) => {
+ const key = transformer(this.dasherizePath(path));
+ if (!Gathered[key]) {
+ return Gathered;
+ }
+ const {default: M} = context(path);
+ if ('function' !== typeof M) {
+ throw new ReferenceError(
+ `Flecks.decorate(): require(${path}).default is not a function (from: ${context.id})`,
+ );
+ }
+ return {...Gathered, [key]: M(Gathered[key], flecks)};
+ },
+ Gathered,
+ )
+ );
}
+ /**
+ * 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);
- }
- }
- }
}
}
diff --git a/packages/core/src/index.js b/packages/core/src/index.js
index 73aeee9..e1d8f51 100644
--- a/packages/core/src/index.js
+++ b/packages/core/src/index.js
@@ -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]: {
- '@flecks/core.config': () => ({
- /**
- * The ID of your application.
- */
- id: 'flecks',
- }),
- },
+export const hooks = {
+ '@flecks/core.config': () => ({
+ /**
+ * The ID of your application.
+ */
+ id: 'flecks',
+ }),
};
diff --git a/packages/core/src/server/build/.eslint.defaults.js b/packages/core/src/server/build/.eslint.defaults.js
index 8cb1d1a..d6b5166 100644
--- a/packages/core/src/server/build/.eslint.defaults.js
+++ b/packages/core/src/server/build/.eslint.defaults.js
@@ -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',
diff --git a/packages/core/src/server/build/eslintrc.js b/packages/core/src/server/build/eslintrc.js
index d1d2a15..3a73bdc 100644
--- a/packages/core/src/server/build/eslintrc.js
+++ b/packages/core/src/server/build/eslintrc.js
@@ -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,
diff --git a/packages/core/src/server/index.js b/packages/core/src/server/index.js
index c7fed24..c355366 100644
--- a/packages/core/src/server/index.js
+++ b/packages/core/src/server/index.js
@@ -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,81 +30,79 @@ export {default as fleck} from '../bootstrap/fleck';
export {default as require} from '../bootstrap/require';
export {JsonStream, transform} from './stream';
-export default {
- [Hooks]: {
- '@flecks/core.build': (target, config, flecks) => {
- const {
- 'eslint.exclude': exclude,
- profile,
- } = flecks.get('@flecks/core/server');
- if (-1 !== profile.indexOf(target)) {
- config.use.push(({config}) => {
- config
- .plugin('profiler')
- .use(
- R.resolve('webpack/lib/debug/ProfilingPlugin'),
- [{outputPath: join(FLECKS_CORE_ROOT, `profile.build-${target}.json`)}],
- );
- });
- }
- if (-1 === exclude.indexOf(target)) {
- const baseConfig = R(flecks.buildConfig('.eslint.defaults.js', target));
- const webpackConfig = neutrino(config).webpack();
- config.use.unshift(
- airbnb({
- eslint: {
- baseConfig: {
- ...baseConfig,
- settings: {
- ...(baseConfig.settings || {}),
- 'import/resolver': {
- ...(baseConfig.settings['import/resolver'] || {}),
- webpack: {
- config: {
- resolve: webpackConfig.resolve,
- },
+export const hooks = {
+ '@flecks/core.build': (target, config, flecks) => {
+ const {
+ 'eslint.exclude': exclude,
+ profile,
+ } = flecks.get('@flecks/core/server');
+ if (-1 !== profile.indexOf(target)) {
+ config.use.push(({config}) => {
+ config
+ .plugin('profiler')
+ .use(
+ R.resolve('webpack/lib/debug/ProfilingPlugin'),
+ [{outputPath: join(FLECKS_CORE_ROOT, `profile.build-${target}.json`)}],
+ );
+ });
+ }
+ if (-1 === exclude.indexOf(target)) {
+ const baseConfig = R(flecks.buildConfig('.eslint.defaults.js', target));
+ const webpackConfig = neutrino(config).webpack();
+ config.use.unshift(
+ airbnb({
+ eslint: {
+ baseConfig: {
+ ...baseConfig,
+ settings: {
+ ...(baseConfig.settings || {}),
+ 'import/resolver': {
+ ...(baseConfig.settings['import/resolver'] || {}),
+ webpack: {
+ config: {
+ resolve: webpackConfig.resolve,
},
},
},
},
},
- }),
- );
- }
- },
- '@flecks/core.build.config': () => [
- /**
- * Babel configuration. See: https://babeljs.io/docs/en/config-files
- */
- 'babel.config.js',
- /**
- * ESLint defaults. The default .eslintrc.js just reads from this file so that the build
- * process can dynamically configure parts of ESLint.
- */
- ['.eslint.defaults.js', {specifier: (specific) => `${specific}.eslint.defaults.js`}],
- /**
- * ESLint configuration. See: https://eslint.org/docs/user-guide/configuring/
- */
- ['.eslintrc.js', {specifier: (specific) => `${specific}.eslintrc.js`}],
- /**
- * Neutrino build configuration. See: https://neutrinojs.org/usage/
- */
- ['.neutrinorc.js', {specifier: (specific) => `${specific}.neutrinorc.js`}],
- /**
- * Webpack (v4) configuration. See: https://v4.webpack.js.org/configuration/
- */
- 'webpack.config.js',
- ],
- '@flecks/core.commands': commands,
- '@flecks/core.config': () => ({
- /**
- * Build targets to exclude from ESLint.
- */
- 'eslint.exclude': [],
- /**
- * Build targets to profile with `webpack.debug.ProfilingPlugin`.
- */
- profile: [],
- }),
+ },
+ }),
+ );
+ }
},
+ '@flecks/core.build.config': () => [
+ /**
+ * Babel configuration. See: https://babeljs.io/docs/en/config-files
+ */
+ 'babel.config.js',
+ /**
+ * ESLint defaults. The default .eslintrc.js just reads from this file so that the build
+ * process can dynamically configure parts of ESLint.
+ */
+ ['.eslint.defaults.js', {specifier: (specific) => `${specific}.eslint.defaults.js`}],
+ /**
+ * ESLint configuration. See: https://eslint.org/docs/user-guide/configuring/
+ */
+ ['.eslintrc.js', {specifier: (specific) => `${specific}.eslintrc.js`}],
+ /**
+ * Neutrino build configuration. See: https://neutrinojs.org/usage/
+ */
+ ['.neutrinorc.js', {specifier: (specific) => `${specific}.neutrinorc.js`}],
+ /**
+ * Webpack (v4) configuration. See: https://v4.webpack.js.org/configuration/
+ */
+ 'webpack.config.js',
+ ],
+ '@flecks/core.commands': commands,
+ '@flecks/core.config': () => ({
+ /**
+ * Build targets to exclude from ESLint.
+ */
+ 'eslint.exclude': [],
+ /**
+ * Build targets to profile with `webpack.debug.ProfilingPlugin`.
+ */
+ profile: [],
+ }),
};
diff --git a/packages/core/test/invoke.js b/packages/core/test/invoke.js
index 708a62f..a830487 100644
--- a/packages/core/test/invoke.js
+++ b/packages/core/test/invoke.js
@@ -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);
+});
diff --git a/packages/core/test/middleware.js b/packages/core/test/middleware.js
new file mode 100644
index 0000000..bbeb25e
--- /dev/null
+++ b/packages/core/test/middleware.js
@@ -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();
+ });
+ });
+});
diff --git a/packages/core/test/one/index.js b/packages/core/test/one/index.js
index c8c3840..329ea1b 100644
--- a/packages/core/test/one/index.js
+++ b/packages/core/test/one/index.js
@@ -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,23 +8,28 @@ export const testNodespace = () => [
/* eslint-enable no-eval */
];
-export default {
- [Hooks]: {
- '@flecks/core.config': () => ({
- foo: 'bar',
- }),
- '@flecks/core/one/test-gather': (
- Flecks.provide(require.context('./things', false, /\.js$/))
- ),
- '@flecks/core/one/test-gather.decorate': (
- Flecks.decorate(require.context('./things/decorators', false, /\.js$/))
- ),
- '@flecks/core/test/invoke': () => 69,
- '@flecks/core/test/invoke-parallel': (O) => {
- // eslint-disable-next-line no-param-reassign
- O.foo *= 2;
- },
- '@flecks/core/test/invoke-merge': () => ({foo: 69}),
- '@flecks/core/test/invoke-merge-async': () => new Promise((resolve) => resolve({foo: 69})),
+export const hooks = {
+ '@flecks/core.config': () => ({
+ foo: 'bar',
+ }),
+ '@flecks/core/one/test-gather': (
+ Flecks.provide(require.context('./things', false, /\.js$/))
+ ),
+ '@flecks/core/one/test-gather.decorate': (
+ Flecks.decorate(require.context('./things/decorators', false, /\.js$/))
+ ),
+ '@flecks/core/test/invoke': () => 69,
+ '@flecks/core/test/invoke-parallel': (O) => {
+ // eslint-disable-next-line no-param-reassign
+ O.foo *= 2;
+ },
+ '@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();
},
};
diff --git a/packages/core/test/two/index.js b/packages/core/test/two/index.js
index 86e323b..847dbe8 100644
--- a/packages/core/test/two/index.js
+++ b/packages/core/test/two/index.js
@@ -1,19 +1,24 @@
-import {Flecks, Hooks} from '@flecks/core';
+import {Flecks} from '@flecks/core';
-export default {
- [Hooks]: {
- '@flecks/core/one/test-gather': (
- Flecks.provide(require.context('./things', false, /\.js$/))
- ),
- '@flecks/core/test/invoke': () => 420,
- '@flecks/core/test/invoke-parallel': (O) => new Promise((resolve) => {
- setTimeout(() => {
- // eslint-disable-next-line no-param-reassign
- O.foo += 2;
- resolve();
- }, 0);
- }),
- '@flecks/core/test/invoke-merge': () => ({bar: 420}),
- '@flecks/core/test/invoke-merge-async': () => new Promise((resolve) => resolve({bar: 420})),
+export const hooks = {
+ '@flecks/core/one/test-gather': (
+ Flecks.provide(require.context('./things', false, /\.js$/))
+ ),
+ '@flecks/core/test/invoke': () => 420,
+ '@flecks/core/test/invoke-parallel': (O) => new Promise((resolve) => {
+ setTimeout(() => {
+ // eslint-disable-next-line no-param-reassign
+ O.foo += 2;
+ resolve();
+ }, 0);
+ }),
+ '@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();
},
};
diff --git a/packages/db/build/dox/hooks.js b/packages/db/build/dox/hooks.js
index b395991..82b8696 100644
--- a/packages/db/build/dox/hooks.js
+++ b/packages/db/build/dox/hooks.js
@@ -1,28 +1,24 @@
-import {Hooks} from '@flecks/core';
+export const hooks = {
+ /**
+ * Gather database models.
+ *
+ * In the example below, your fleck would have a `models` subdirectory, and each model would be
+ * defined in its own file.
+ * See: https://github.com/cha0s/flecks/tree/master/packages/user/src/server/models
+ */
+ '@flecks/db/server.models': Flecks.provide(require.context('./models', false, /\.js$/)),
-export default {
- [Hooks]: {
- /**
- * Gather database models.
- *
- * In the example below, your fleck would have a `models` subdirectory, and each model would be
- * defined in its own file.
- * See: https://github.com/cha0s/flecks/tree/master/packages/user/src/server/models
- */
- '@flecks/db/server.models': Flecks.provide(require.context('./models', false, /\.js$/)),
-
- /**
- * Decorate database models.
- *
- * In the example below, your fleck would have a `models/decorators` subdirectory, and each
- * decorator would be defined in its own file.
- * See: https://github.com/cha0s/flecks/tree/master/packages/user/src/local/server/models/decorators
- *
- * @param {constructor} Model The model to decorate.
- */
- '@flecks/db/server.models.decorate': (
- Flecks.decorate(require.context('./models/decorators', false, /\.js$/))
- ),
- },
+ /**
+ * Decorate database models.
+ *
+ * In the example below, your fleck would have a `models/decorators` subdirectory, and each
+ * decorator would be defined in its own file.
+ * See: https://github.com/cha0s/flecks/tree/master/packages/user/src/local/server/models/decorators
+ *
+ * @param {constructor} Model The model to decorate.
+ */
+ '@flecks/db/server.models.decorate': (
+ Flecks.decorate(require.context('./models/decorators', false, /\.js$/))
+ ),
};
diff --git a/packages/db/src/server.js b/packages/db/src/server.js
index 45eed1d..b26c2a3 100644
--- a/packages/db/src/server.js
+++ b/packages/db/src/server.js
@@ -1,5 +1,3 @@
-import {Hooks} from '@flecks/core';
-
import {createDatabaseConnection} from './connection';
import containers from './containers';
@@ -9,49 +7,47 @@ export {default as Model} from './model';
export {createDatabaseConnection};
-export default {
- [Hooks]: {
- '@flecks/core.config': () => ({
- /**
- * The database to connect to.
- */
- database: ':memory:',
- /**
- * SQL dialect.
- *
- * See: https://sequelize.org/v5/manual/dialects.html
- */
- dialect: 'sqlite',
- /**
- * Database server host.
- */
- host: undefined,
- /**
- * Database server password.
- */
- password: undefined,
- /**
- * Database server port.
- */
- port: undefined,
- /**
- * Database server username.
- */
- username: undefined,
- }),
- '@flecks/core.starting': (flecks) => {
- flecks.set('$flecks/db.models', flecks.gather(
- '@flecks/db/server.models',
- {typeAttribute: 'name'},
- ));
- },
- '@flecks/docker.containers': containers,
- '@flecks/server.up': async (flecks) => {
- flecks.set('$flecks/db/sequelize', await createDatabaseConnection(flecks));
- },
- '@flecks/repl.context': (flecks) => ({
- Models: flecks.get('$flecks/db.models'),
- sequelize: flecks.get('$flecks/db/sequelize'),
- }),
+export const hooks = {
+ '@flecks/core.config': () => ({
+ /**
+ * The database to connect to.
+ */
+ database: ':memory:',
+ /**
+ * SQL dialect.
+ *
+ * See: https://sequelize.org/v5/manual/dialects.html
+ */
+ dialect: 'sqlite',
+ /**
+ * Database server host.
+ */
+ host: undefined,
+ /**
+ * Database server password.
+ */
+ password: undefined,
+ /**
+ * Database server port.
+ */
+ port: undefined,
+ /**
+ * Database server username.
+ */
+ username: undefined,
+ }),
+ '@flecks/core.starting': (flecks) => {
+ flecks.set('$flecks/db.models', flecks.gather(
+ '@flecks/db/server.models',
+ {typeProperty: 'name'},
+ ));
},
+ '@flecks/docker.containers': containers,
+ '@flecks/server.up': async (flecks) => {
+ flecks.set('$flecks/db/sequelize', await createDatabaseConnection(flecks));
+ },
+ '@flecks/repl.context': (flecks) => ({
+ Models: flecks.get('$flecks/db.models'),
+ sequelize: flecks.get('$flecks/db/sequelize'),
+ }),
};
diff --git a/packages/docker/build/dox/hooks.js b/packages/docker/build/dox/hooks.js
index 1acbb32..ddb653b 100644
--- a/packages/docker/build/dox/hooks.js
+++ b/packages/docker/build/dox/hooks.js
@@ -1,27 +1,23 @@
-import {Hooks} from '@flecks/core';
-
-export default {
- [Hooks]: {
- /**
- * Define docker containers.
- *
- * Beware: the user running the server must have Docker privileges.
- * See: https://docs.docker.com/engine/install/linux-postinstall/#manage-docker-as-a-non-root-user
- */
- '@flecks/docker.containers': () => ({
- someContainer: {
- // Environment variables.
- environment: {
- SOME_CONTAINER_VAR: 'hello',
- },
- // The docker image.
- image: 'some-image:latest',
- // Some container path you'd like to persist. Flecks handles the host path.
- mount: '/some/container/path',
- // Expose ports.
- ports: {3000: 3000},
+export const hooks = {
+ /**
+ * Define docker containers.
+ *
+ * Beware: the user running the server must have Docker privileges.
+ * See: https://docs.docker.com/engine/install/linux-postinstall/#manage-docker-as-a-non-root-user
+ */
+ '@flecks/docker.containers': () => ({
+ someContainer: {
+ // Environment variables.
+ environment: {
+ SOME_CONTAINER_VAR: 'hello',
},
- }),
- },
+ // The docker image.
+ image: 'some-image:latest',
+ // Some container path you'd like to persist. Flecks handles the host path.
+ mount: '/some/container/path',
+ // Expose ports.
+ ports: {3000: 3000},
+ },
+ }),
};
diff --git a/packages/docker/src/server.js b/packages/docker/src/server.js
index 413df06..b77ac02 100644
--- a/packages/docker/src/server.js
+++ b/packages/docker/src/server.js
@@ -1,26 +1,22 @@
-import {Hooks} from '@flecks/core';
-
import commands from './commands';
import startContainer from './start-container';
-export default {
- [Hooks]: {
- '@flecks/core.config': () => ({
- /**
- * Whether to run docker containers.
- */
- enabled: true,
- }),
- '@flecks/core.commands': commands,
- '@flecks/server.up': async (flecks) => {
- if (!flecks.get('@flecks/docker/server.enabled')) {
- return;
- }
- const containers = await flecks.invokeMergeAsync('@flecks/docker.containers');
- await Promise.all(
- Object.entries(containers)
- .map(([key, config]) => startContainer(flecks, key, config)),
- );
- },
+export const hooks = {
+ '@flecks/core.config': () => ({
+ /**
+ * Whether to run docker containers.
+ */
+ enabled: true,
+ }),
+ '@flecks/core.commands': commands,
+ '@flecks/server.up': async (flecks) => {
+ if (!flecks.get('@flecks/docker/server.enabled')) {
+ return;
+ }
+ const containers = await flecks.invokeMergeAsync('@flecks/docker.containers');
+ await Promise.all(
+ Object.entries(containers)
+ .map(([key, config]) => startContainer(flecks, key, config)),
+ );
},
};
diff --git a/packages/dox/src/parser.js b/packages/dox/src/parser.js
index ff70fc1..21d5f26 100644
--- a/packages/dox/src/parser.js
+++ b/packages/dox/src/parser.js
@@ -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)) {
diff --git a/packages/dox/src/server.js b/packages/dox/src/server.js
index be9d656..ada37ce 100644
--- a/packages/dox/src/server.js
+++ b/packages/dox/src/server.js
@@ -1,17 +1,13 @@
-import {Hooks} from '@flecks/core';
-
import commands from './commands';
-export default {
- [Hooks]: {
- '@flecks/core.commands': commands,
- '@flecks/core.config': () => ({
- /**
- * Rewrite the output filenames of source files.
- *
- * `filename.replace(new RegExp([key]), [value]);`
- */
- filenameRewriters: {},
- }),
- },
+export const hooks = {
+ '@flecks/core.commands': commands,
+ '@flecks/core.config': () => ({
+ /**
+ * Rewrite the output filenames of source files.
+ *
+ * `filename.replace(new RegExp([key]), [value]);`
+ */
+ filenameRewriters: {},
+ }),
};
diff --git a/packages/electron/build/dox/hooks.js b/packages/electron/build/dox/hooks.js
index 2e716eb..5263dd2 100644
--- a/packages/electron/build/dox/hooks.js
+++ b/packages/electron/build/dox/hooks.js
@@ -1,24 +1,20 @@
-import {Hooks} from '@flecks/core';
+export const hooks = {
+ /**
+ * Invoked when electron is initializing.
+ * @param {Electron.App} app The electron app. See: https://www.electronjs.org/docs/latest/api/app
+ */
+ '@flecks/electron/server.initialize': (app) => {
+ app.on('will-quit', () => {
+ // ...
+ });
+ },
-export default {
- [Hooks]: {
- /**
- * Invoked when electron is initializing.
- * @param {Electron.App} app The electron app. See: https://www.electronjs.org/docs/latest/api/app
- */
- '@flecks/electron/server.initialize': (app) => {
- app.on('will-quit', () => {
- // ...
- });
- },
-
- /**
- * Invoked when a window is created
- * @param {Electron.BrowserWindow} win The electron browser window. See: https://www.electronjs.org/docs/latest/api/browser-window
- */
- '@flecks/electron/server.window': (win) => {
- win.maximize();
- },
+ /**
+ * Invoked when a window is created
+ * @param {Electron.BrowserWindow} win The electron browser window. See: https://www.electronjs.org/docs/latest/api/browser-window
+ */
+ '@flecks/electron/server.window': (win) => {
+ win.maximize();
},
};
diff --git a/packages/electron/src/server/index.js b/packages/electron/src/server/index.js
index 99714cc..a34305f 100644
--- a/packages/electron/src/server/index.js
+++ b/packages/electron/src/server/index.js
@@ -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,119 +20,117 @@ async function createWindow(flecks) {
await flecks.invokeSequentialAsync('@flecks/electron/server.window', win);
}
-export default {
- [Hooks]: {
- '@flecks/core.config': () => ({
- /**
- * Browser window options.
- *
- * See: https://www.electronjs.org/docs/latest/api/browser-window
- */
- browserWindowOptions: {},
- /**
- * Install devtools extensions (by default).
- *
- * If `true`, will install some devtools extensions based on which flecks are enabled.
- *
- * You can pass an array of Chrome store IDs to install a list of custom extensions.
- *
- * Extensions will not be installed if `'production' === process.env.NODE_ENV`
- */
- installExtensions: true,
- /**
- * Quit the app when all windows are closed.
- */
- quitOnClosed: true,
- /**
- * The URL to load in electron by default.
- *
- * Defaults to `http://${flecks.get('@flecks/web/server.public')}`.
- */
- url: undefined,
- }),
- '@flecks/core.webpack': (target, config) => {
- const StartServerWebpackPlugin = R('start-server-webpack-plugin');
- const plugin = config.plugins.find((plugin) => plugin instanceof StartServerWebpackPlugin);
- // Extremely hackish, c'est la vie.
- if (plugin) {
- /* eslint-disable no-underscore-dangle */
- plugin._startServer = function _startServerHacked(callback) {
- const execArgv = this._getArgs();
- const inspectPort = this._getInspectPort(execArgv);
- const clusterOptions = {
- args: [this._entryPoint],
- exec: join(FLECKS_CORE_ROOT, 'node_modules', '.bin', 'electron'),
- execArgv,
- };
- if (inspectPort) {
- clusterOptions.inspectPort = inspectPort;
- }
- cluster.setupMaster(clusterOptions);
- cluster.on('online', (worker) => {
- callback(worker);
- });
- cluster.fork();
+export const hooks = {
+ '@flecks/core.config': () => ({
+ /**
+ * Browser window options.
+ *
+ * See: https://www.electronjs.org/docs/latest/api/browser-window
+ */
+ browserWindowOptions: {},
+ /**
+ * Install devtools extensions (by default).
+ *
+ * If `true`, will install some devtools extensions based on which flecks are enabled.
+ *
+ * You can pass an array of Chrome store IDs to install a list of custom extensions.
+ *
+ * Extensions will not be installed if `'production' === process.env.NODE_ENV`
+ */
+ installExtensions: true,
+ /**
+ * Quit the app when all windows are closed.
+ */
+ quitOnClosed: true,
+ /**
+ * The URL to load in electron by default.
+ *
+ * Defaults to `http://${flecks.get('@flecks/web/server.public')}`.
+ */
+ url: undefined,
+ }),
+ '@flecks/core.webpack': (target, config) => {
+ const StartServerWebpackPlugin = R('start-server-webpack-plugin');
+ const plugin = config.plugins.find((plugin) => plugin instanceof StartServerWebpackPlugin);
+ // Extremely hackish, c'est la vie.
+ if (plugin) {
+ /* eslint-disable no-underscore-dangle */
+ plugin._startServer = function _startServerHacked(callback) {
+ const execArgv = this._getArgs();
+ const inspectPort = this._getInspectPort(execArgv);
+ const clusterOptions = {
+ args: [this._entryPoint],
+ exec: join(FLECKS_CORE_ROOT, 'node_modules', '.bin', 'electron'),
+ execArgv,
};
- /* eslint-enable no-underscore-dangle */
- }
- },
- '@flecks/electron/server.initialize': async (app, flecks) => {
- app.on('window-all-closed', () => {
- const {quitOnClosed} = flecks.get('@flecks/electron/server');
- if (!quitOnClosed) {
- return;
+ if (inspectPort) {
+ clusterOptions.inspectPort = inspectPort;
}
- // Apple has to be *special*.
- if (process.platform === 'darwin') {
- return;
- }
- app.quit();
- });
- app.on('activate', async () => {
- if (BrowserWindow.getAllWindows().length === 0) {
- createWindow();
- }
- });
- await app.whenReady();
- await createWindow(flecks);
- },
- '@flecks/electron/server.window': async (win, flecks) => {
- const {public: $$public} = flecks.get('@flecks/web/server');
- const {
- installExtensions,
- url = `http://${$$public}`,
- } = flecks.get('@flecks/electron/server');
- if (installExtensions && 'production' !== NODE_ENV) {
- const {
- default: installExtension,
- REDUX_DEVTOOLS,
- REACT_DEVELOPER_TOOLS,
- } = __non_webpack_require__('electron-devtools-installer');
- let extensions = installExtensions;
- if (!Array.isArray(extensions)) {
- extensions = [];
- if (flecks.fleck('@flecks/react')) {
- extensions.push(REACT_DEVELOPER_TOOLS);
- }
- if (flecks.fleck('@flecks/redux')) {
- extensions.push(REDUX_DEVTOOLS);
- }
- }
- await installExtension(extensions);
- }
- await win.loadURL(url);
- },
- '@flecks/repl.context': (flecks) => ({
- electron: {
- createWindow: () => createWindow(flecks),
- },
- }),
- '@flecks/server.up': async (flecks) => {
- // `app` will be undefined if we aren't running in an electron environment. Just bail.
- if (!app) {
+ cluster.setupMaster(clusterOptions);
+ cluster.on('online', (worker) => {
+ callback(worker);
+ });
+ cluster.fork();
+ };
+ /* eslint-enable no-underscore-dangle */
+ }
+ },
+ '@flecks/electron/server.initialize': async (app, flecks) => {
+ app.on('window-all-closed', () => {
+ const {quitOnClosed} = flecks.get('@flecks/electron/server');
+ if (!quitOnClosed) {
return;
}
- await flecks.invokeSequentialAsync('@flecks/electron/server.initialize', app);
+ // Apple has to be *special*.
+ if (process.platform === 'darwin') {
+ return;
+ }
+ app.quit();
+ });
+ app.on('activate', async () => {
+ if (BrowserWindow.getAllWindows().length === 0) {
+ createWindow();
+ }
+ });
+ await app.whenReady();
+ await createWindow(flecks);
+ },
+ '@flecks/electron/server.window': async (win, flecks) => {
+ const {public: $$public} = flecks.get('@flecks/web/server');
+ const {
+ installExtensions,
+ url = `http://${$$public}`,
+ } = flecks.get('@flecks/electron/server');
+ if (installExtensions && 'production' !== NODE_ENV) {
+ const {
+ default: installExtension,
+ REDUX_DEVTOOLS,
+ REACT_DEVELOPER_TOOLS,
+ } = __non_webpack_require__('electron-devtools-installer');
+ let extensions = installExtensions;
+ if (!Array.isArray(extensions)) {
+ extensions = [];
+ if (flecks.fleck('@flecks/react')) {
+ extensions.push(REACT_DEVELOPER_TOOLS);
+ }
+ if (flecks.fleck('@flecks/redux')) {
+ extensions.push(REDUX_DEVTOOLS);
+ }
+ }
+ await installExtension(extensions);
+ }
+ await win.loadURL(url);
+ },
+ '@flecks/repl.context': (flecks) => ({
+ electron: {
+ createWindow: () => createWindow(flecks),
},
+ }),
+ '@flecks/server.up': async (flecks) => {
+ // `app` will be undefined if we aren't running in an electron environment. Just bail.
+ if (!app) {
+ return;
+ }
+ await flecks.invokeSequentialAsync('@flecks/electron/server.initialize', app);
},
};
diff --git a/packages/fleck/src/server/index.js b/packages/fleck/src/server/index.js
index b14ad94..c88ff94 100644
--- a/packages/fleck/src/server/index.js
+++ b/packages/fleck/src/server/index.js
@@ -1,21 +1,17 @@
-import {Hooks} from '@flecks/core';
-
import commands from './commands';
-export default {
- [Hooks]: {
- '@flecks/core.commands': commands,
- '@flecks/core.config': () => ({
- /**
- * Webpack stats configuration when building fleck target.
- */
- stats: {
- children: false,
- chunks: false,
- colors: true,
- modules: false,
- },
- }),
- '@flecks/core.targets': () => ['fleck'],
- },
+export const hooks = {
+ '@flecks/core.commands': commands,
+ '@flecks/core.config': () => ({
+ /**
+ * Webpack stats configuration when building fleck target.
+ */
+ stats: {
+ children: false,
+ chunks: false,
+ colors: true,
+ modules: false,
+ },
+ }),
+ '@flecks/core.targets': () => ['fleck'],
};
diff --git a/packages/governor/src/server.js b/packages/governor/src/server.js
index fad2f82..042d7a7 100644
--- a/packages/governor/src/server.js
+++ b/packages/governor/src/server.js
@@ -1,135 +1,133 @@
-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]: {
- '@flecks/core.config': () => ({
- /**
- * All keys used to determine fingerprint.
- */
+export const hooks = {
+ '@flecks/core.config': () => ({
+ /**
+ * All keys used to determine fingerprint.
+ */
+ keys: ['ip'],
+ web: {
keys: ['ip'],
- web: {
- keys: ['ip'],
- points: 60,
- duration: 30,
- ttl: 30,
- },
- socket: {
- keys: ['ip'],
- points: 60,
- duration: 30,
- ttl: 30,
- },
- }),
- '@flecks/db/server.models': Flecks.provide(require.context('./models', false, /\.js$/)),
- '@flecks/web/server.request.route': (flecks) => {
- const {web} = flecks.get('@flecks/governor/server');
- const limiter = flecks.get('$flecks/governor.web.limiter');
- return async (req, res, next) => {
- const {Ban} = flecks.get('$flecks/db.models');
- try {
- await Ban.check(req);
- }
- catch (error) {
- res.status(403).send(`${error.message}
`);
- return;
- }
- req.ban = async (keys, ttl = 0) => {
- const ban = Ban.fromRequest(req, keys, ttl);
- await Ban.create({...ban});
- res.status(403).send(`${Ban.format([ban])}
`);
- };
- try {
- await limiter.consume(req.ip);
- next();
- }
- catch (error) {
- const {ttl, keys} = web;
- const ban = Ban.fromRequest(req, keys, ttl);
- await Ban.create({...ban});
- res.status(429).send(`${Ban.format([ban])}
`);
- }
- };
+ points: 60,
+ duration: 30,
+ ttl: 30,
},
- '@flecks/server.up': async (flecks) => {
- if (flecks.fleck('@flecks/web/server')) {
- const {web} = flecks.get('@flecks/governor/server');
- const limiter = await createLimiter(
- flecks,
- {
- keyPrefix: '@flecks/governor.web.request.route',
- ...web,
- },
- );
- flecks.set('$flecks/governor.web.limiter', limiter);
+ socket: {
+ keys: ['ip'],
+ points: 60,
+ duration: 30,
+ ttl: 30,
+ },
+ }),
+ '@flecks/db/server.models': Flecks.provide(require.context('./models', false, /\.js$/)),
+ '@flecks/web/server.request.route': (flecks) => {
+ const {web} = flecks.get('@flecks/governor/server');
+ const limiter = flecks.get('$flecks/governor.web.limiter');
+ return async (req, res, next) => {
+ const {Ban} = flecks.get('$flecks/db.models');
+ try {
+ await Ban.check(req);
}
- if (flecks.fleck('@flecks/socket/server')) {
- const {[ByType]: Packets} = flecks.get('$flecks/socket.packets');
- const limiters = Object.fromEntries(
- await Promise.all(
- Object.entries(Packets)
- .filter(([, Packet]) => Packet.limit)
- .map(async ([name, Packet]) => (
- [
- name,
- await createLimiter(
- flecks,
- {keyPrefix: `@flecks/governor.packet.${name}`, ...Packet.limit},
- ),
- ]
- )),
- ),
- );
- flecks.set('$flecks/governor.packet.limiters', limiters);
- const {socket} = flecks.get('@flecks/governor/server');
- const limiter = await createLimiter(
- flecks,
- {
- keyPrefix: '@flecks/governor.socket.request.socket',
- ...socket,
- },
- );
- flecks.set('$flecks/governor.socket.limiter', limiter);
+ catch (error) {
+ res.status(403).send(`${error.message}
`);
+ return;
}
- },
- '@flecks/socket/server.request.socket': (flecks) => {
- const limiter = flecks.get('$flecks/governor.socket.limiter');
- return async (socket, next) => {
- const {handshake: req} = socket;
- const {Ban} = flecks.get('$flecks/db.models');
- try {
- await Ban.check(req);
- }
- catch (error) {
- next(error);
- return;
- }
- req.ban = async (keys, ttl) => {
- await Ban.create(Ban.fromRequest(req, keys, ttl));
- socket.disconnect();
- };
- try {
- await limiter.consume(req.ip);
- next();
- }
- catch (error) {
- const {ttl, keys} = socket;
- await Ban.create(Ban.fromRequest(req, keys, ttl));
- next(error);
- }
+ req.ban = async (keys, ttl = 0) => {
+ const ban = Ban.fromRequest(req, keys, ttl);
+ await Ban.create({...ban});
+ res.status(403).send(`${Ban.format([ban])}
`);
};
- },
- '@flecks/socket.packets.decorate': (Packets, flecks) => (
- Object.fromEntries(
- Object.entries(Packets).map(([keyPrefix, Packet]) => [
- keyPrefix,
- !Packet.limit ? Packet : LimitedPacket(flecks, [keyPrefix, Packet]),
- ]),
- )
- ),
+ try {
+ await limiter.consume(req.ip);
+ next();
+ }
+ catch (error) {
+ const {ttl, keys} = web;
+ const ban = Ban.fromRequest(req, keys, ttl);
+ await Ban.create({...ban});
+ res.status(429).send(`${Ban.format([ban])}
`);
+ }
+ };
},
+ '@flecks/server.up': async (flecks) => {
+ if (flecks.fleck('@flecks/web/server')) {
+ const {web} = flecks.get('@flecks/governor/server');
+ const limiter = await createLimiter(
+ flecks,
+ {
+ keyPrefix: '@flecks/governor.web.request.route',
+ ...web,
+ },
+ );
+ flecks.set('$flecks/governor.web.limiter', limiter);
+ }
+ if (flecks.fleck('@flecks/socket/server')) {
+ const {[ByType]: Packets} = flecks.get('$flecks/socket.packets');
+ const limiters = Object.fromEntries(
+ await Promise.all(
+ Object.entries(Packets)
+ .filter(([, Packet]) => Packet.limit)
+ .map(async ([name, Packet]) => (
+ [
+ name,
+ await createLimiter(
+ flecks,
+ {keyPrefix: `@flecks/governor.packet.${name}`, ...Packet.limit},
+ ),
+ ]
+ )),
+ ),
+ );
+ flecks.set('$flecks/governor.packet.limiters', limiters);
+ const {socket} = flecks.get('@flecks/governor/server');
+ const limiter = await createLimiter(
+ flecks,
+ {
+ keyPrefix: '@flecks/governor.socket.request.socket',
+ ...socket,
+ },
+ );
+ flecks.set('$flecks/governor.socket.limiter', limiter);
+ }
+ },
+ '@flecks/socket/server.request.socket': (flecks) => {
+ const limiter = flecks.get('$flecks/governor.socket.limiter');
+ return async (socket, next) => {
+ const {handshake: req} = socket;
+ const {Ban} = flecks.get('$flecks/db.models');
+ try {
+ await Ban.check(req);
+ }
+ catch (error) {
+ next(error);
+ return;
+ }
+ req.ban = async (keys, ttl) => {
+ await Ban.create(Ban.fromRequest(req, keys, ttl));
+ socket.disconnect();
+ };
+ try {
+ await limiter.consume(req.ip);
+ next();
+ }
+ catch (error) {
+ const {ttl, keys} = socket;
+ await Ban.create(Ban.fromRequest(req, keys, ttl));
+ next(error);
+ }
+ };
+ },
+ '@flecks/socket.packets.decorate': (Packets, flecks) => (
+ Object.fromEntries(
+ Object.entries(Packets).map(([keyPrefix, Packet]) => [
+ keyPrefix,
+ !Packet.limit ? Packet : LimitedPacket(flecks, [keyPrefix, Packet]),
+ ]),
+ )
+ ),
};
diff --git a/packages/react/build/dox/hooks.js b/packages/react/build/dox/hooks.js
index d3f8eb8..145bf8d 100644
--- a/packages/react/build/dox/hooks.js
+++ b/packages/react/build/dox/hooks.js
@@ -1,36 +1,32 @@
-import {Hooks} from '@flecks/core';
-
-export default {
- [Hooks]: {
- /**
- * Define React Providers.
- *
- * Note: `req` will be only be defined when server-side rendering.
- * @param {http.ClientRequest} req The HTTP request object.
- */
- '@flecks/react.providers': (req) => {
- // Generally it makes more sense to separate client and server concerns using platform
- // naming conventions, but this is just a small contrived example.
- return req ? serverSideProvider(req) : clientSideProvider();
- },
- /**
- * Define root-level React components that are mounted as siblings on `#main`.
- * Note: `req` will be only be defined when server-side rendering.
- *
- * Return either a React component or an array whose elements must either be a React component
- * or an array of two elements where the first element is the component and the second element
- * is the props passed to the component.
- * @param {http.ClientRequest} req The HTTP request object.
- */
- '@flecks/react.roots': (req) => {
- // Note that we're not returning ``, but `Component`.
- return [
- Component,
- [SomeOtherComponent, {prop: 'value'}]
- ];
- // You can also just:
- return Component;
- },
+export const hooks = {
+ /**
+ * Define React Providers.
+ *
+ * Note: `req` will be only be defined when server-side rendering.
+ * @param {http.ClientRequest} req The HTTP request object.
+ */
+ '@flecks/react.providers': (req) => {
+ // Generally it makes more sense to separate client and server concerns using platform
+ // naming conventions, but this is just a small contrived example.
+ return req ? serverSideProvider(req) : clientSideProvider();
+ },
+ /**
+ * Define root-level React components that are mounted as siblings on `#main`.
+ * Note: `req` will be only be defined when server-side rendering.
+ *
+ * Return either a React component or an array whose elements must either be a React component
+ * or an array of two elements where the first element is the component and the second element
+ * is the props passed to the component.
+ * @param {http.ClientRequest} req The HTTP request object.
+ */
+ '@flecks/react.roots': (req) => {
+ // Note that we're not returning ``, but `Component`.
+ return [
+ Component,
+ [SomeOtherComponent, {prop: 'value'}]
+ ];
+ // You can also just:
+ return Component;
},
};
diff --git a/packages/react/src/client.js b/packages/react/src/client.js
index ac08dfd..8bb8850 100644
--- a/packages/react/src/client.js
+++ b/packages/react/src/client.js
@@ -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,20 +10,18 @@ const debug = D('@flecks/react/client');
export {FlecksContext};
-export default {
- [Hooks]: {
- '@flecks/web/client.up': async (flecks) => {
- const {ssr} = flecks.get('@flecks/react');
- debug('%sing...', ssr ? 'hydrat' : 'render');
- (ssr ? hydrate : render)(
- React.createElement(
- React.StrictMode,
- {},
- [React.createElement(await root(flecks), {key: 'root'})],
- ),
- window.document.getElementById('root'),
- );
- debug('rendered');
- },
+export const hooks = {
+ '@flecks/web/client.up': async (flecks) => {
+ const {ssr} = flecks.get('@flecks/react');
+ debug('%sing...', ssr ? 'hydrat' : 'render');
+ (ssr ? hydrate : render)(
+ React.createElement(
+ React.StrictMode,
+ {},
+ [React.createElement(await root(flecks), {key: 'root'})],
+ ),
+ window.document.getElementById('root'),
+ );
+ debug('rendered');
},
};
diff --git a/packages/react/src/index.js b/packages/react/src/index.js
index 05352a8..97599c1 100644
--- a/packages/react/src/index.js
+++ b/packages/react/src/index.js
@@ -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]: {
- '@flecks/core.config': () => ({
- /**
- * Whether to enable server-side rendering.
- */
- ssr: true,
- }),
- },
+export const hooks = {
+ '@flecks/core.config': () => ({
+ /**
+ * Whether to enable server-side rendering.
+ */
+ ssr: true,
+ }),
};
diff --git a/packages/react/src/router/client.js b/packages/react/src/router/client.js
index 011a5a2..eca628e 100644
--- a/packages/react/src/router/client.js
+++ b/packages/react/src/router/client.js
@@ -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]: {
- '@flecks/react.providers': (req, flecks) => (
- flecks.fleck('@flecks/redux')
- ? [ReduxHistoryRouter, {history: createReduxHistory(flecks.get('$flecks/redux.store'))}]
- : [HistoryRouter, {history}]
- ),
- },
+export const hooks = {
+ '@flecks/react.providers': (req, flecks) => (
+ flecks.fleck('@flecks/redux')
+ ? [ReduxHistoryRouter, {history: createReduxHistory(flecks.get('$flecks/redux.store'))}]
+ : [HistoryRouter, {history}]
+ ),
};
diff --git a/packages/react/src/router/index.js b/packages/react/src/router/index.js
index 591c801..a3981f2 100644
--- a/packages/react/src/router/index.js
+++ b/packages/react/src/router/index.js
@@ -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]: {
- '@flecks/redux.slices': () => ({
- router: routerReducer,
- }),
- '@flecks/redux.store': (options) => {
- options.middleware.push(routerMiddleware);
- },
+export const hooks = {
+ '@flecks/redux.slices': () => ({
+ router: routerReducer,
+ }),
+ '@flecks/redux.store': (options) => {
+ options.middleware.push(routerMiddleware);
},
};
diff --git a/packages/react/src/router/server.js b/packages/react/src/router/server.js
index b393727..0486759 100644
--- a/packages/react/src/router/server.js
+++ b/packages/react/src/router/server.js
@@ -1,10 +1,7 @@
-import {Hooks} from '@flecks/core';
import {StaticRouter} from 'react-router-dom/server';
-export default {
- [Hooks]: {
- '@flecks/react.providers': (req, flecks) => (
- flecks.get('@flecks/react.ssr') ? [StaticRouter, {location: req.url}] : []
- ),
- },
+export const hooks = {
+ '@flecks/react.providers': (req, flecks) => (
+ flecks.get('@flecks/react.ssr') ? [StaticRouter, {location: req.url}] : []
+ ),
};
diff --git a/packages/react/src/server.js b/packages/react/src/server.js
index 00537c2..2bd2d98 100644
--- a/packages/react/src/server.js
+++ b/packages/react/src/server.js
@@ -1,28 +1,25 @@
-import {Hooks} from '@flecks/core';
import {augmentBuild} from '@flecks/web/server';
import ssr from './ssr';
-export default {
- [Hooks]: {
- '@flecks/core.build': (target, config, flecks) => {
- // Resolution.
- config.use.push(({config}) => {
- config.resolve.alias
- .set('react-native', 'react-native-web');
- config.resolve.extensions
- .prepend('.web.js')
- .prepend('.web.jsx');
- });
- // Augment the build on behalf of a missing `@flecks/web`.
- if (!flecks.fleck('@flecks/web/server')) {
- flecks.registerBuildConfig('postcss.config.js', {fleck: '@flecks/web/server'});
- flecks.registerResolver('@flecks/web');
- augmentBuild(target, config, flecks);
- }
- },
- '@flecks/web/server.stream.html': (stream, req, flecks) => (
- flecks.get('@flecks/react.ssr') ? ssr(stream, req, flecks) : stream
- ),
+export const hooks = {
+ '@flecks/core.build': (target, config, flecks) => {
+ // Resolution.
+ config.use.push(({config}) => {
+ config.resolve.alias
+ .set('react-native', 'react-native-web');
+ config.resolve.extensions
+ .prepend('.web.js')
+ .prepend('.web.jsx');
+ });
+ // Augment the build on behalf of a missing `@flecks/web`.
+ if (!flecks.fleck('@flecks/web/server')) {
+ flecks.registerBuildConfig('postcss.config.js', {fleck: '@flecks/web/server'});
+ flecks.registerResolver('@flecks/web');
+ augmentBuild(target, config, flecks);
+ }
},
+ '@flecks/web/server.stream.html': (stream, req, flecks) => (
+ flecks.get('@flecks/react.ssr') ? ssr(stream, req, flecks) : stream
+ ),
};
diff --git a/packages/redis/src/server.js b/packages/redis/src/server.js
index 29ade25..316a961 100644
--- a/packages/redis/src/server.js
+++ b/packages/redis/src/server.js
@@ -1,5 +1,3 @@
-import {Hooks} from '@flecks/core';
-
import containers from './containers';
import createClient from './create-client';
@@ -27,21 +25,19 @@ const safeKeys = async (client, pattern, caret) => {
export const keys = (client, pattern) => safeKeys(client, pattern, 0);
-export default {
- [Hooks]: {
- '@flecks/core.config': () => ({
- /**
- * Redis server host.
- */
- host: 'localhost',
- /**
- * Redis server port.
- */
- port: 6379,
- }),
- '@flecks/docker.containers': containers,
- '@flecks/repl.context': (flecks) => ({
- redisClient: createClient(flecks),
- }),
- },
+export const hooks = {
+ '@flecks/core.config': () => ({
+ /**
+ * Redis server host.
+ */
+ host: 'localhost',
+ /**
+ * Redis server port.
+ */
+ port: 6379,
+ }),
+ '@flecks/docker.containers': containers,
+ '@flecks/repl.context': (flecks) => ({
+ redisClient: createClient(flecks),
+ }),
};
diff --git a/packages/redis/src/session/server.js b/packages/redis/src/session/server.js
index 282d2e4..d126ac7 100644
--- a/packages/redis/src/session/server.js
+++ b/packages/redis/src/session/server.js
@@ -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,23 +10,21 @@ const debugSilly = debug.extend('silly');
const RedisStore = ConnectRedis(session);
-export default {
- [Hooks]: {
- '@flecks/user.session': async (flecks) => {
- const client = createClient(flecks, {legacyMode: true});
- await client.connect();
- return {
- store: new RedisStore({client}),
- };
- },
- '@flecks/socket.server': async (flecks) => {
- const pubClient = createClient(flecks);
- const subClient = createClient(flecks);
- await Promise.all([pubClient.connect(), subClient.connect()]);
- debugSilly('creating adapter');
- return {
- adapter: redisAdapter(pubClient, subClient),
- };
- },
+export const hooks = {
+ '@flecks/user.session': async (flecks) => {
+ const client = createClient(flecks, {legacyMode: true});
+ await client.connect();
+ return {
+ store: new RedisStore({client}),
+ };
+ },
+ '@flecks/socket.server': async (flecks) => {
+ const pubClient = createClient(flecks);
+ const subClient = createClient(flecks);
+ await Promise.all([pubClient.connect(), subClient.connect()]);
+ debugSilly('creating adapter');
+ return {
+ adapter: redisAdapter(pubClient, subClient),
+ };
},
};
diff --git a/packages/redux/build/dox/hooks.js b/packages/redux/build/dox/hooks.js
index 0939102..ad427b2 100644
--- a/packages/redux/build/dox/hooks.js
+++ b/packages/redux/build/dox/hooks.js
@@ -1,45 +1,41 @@
-import {Hooks} from '@flecks/core';
-
-export default {
- [Hooks]: {
- /**
- * Define side-effects to run against Redux actions.
- */
- '@flecks/redux.effects': () => ({
- someActionName: (store, action) => {
- // Runs when `someActionName` actions are dispatched.
- },
- }),
- /**
- * Define root-level reducers for the Redux store.
- */
- '@flecks/redux.reducers': () => {
- return (state, action) => {
- // Whatever you'd like.
- return state;
- };
- },
- /**
- * Define Redux slices.
- *
- * See: https://redux-toolkit.js.org/api/createSlice
- */
- '@flecks/redux.slices': () => {
- const something = createSlice(
- // ...
- );
- return {
- something: something.reducer,
- };
- },
- /**
- * Modify Redux store configuration.
- * @param {Object} options A mutable object with keys for enhancers and middleware.
- */
- '@flecks/redux.store': (options) => {
- options.enhancers.splice(someIndex, 1);
- options.middleware.push(mySpecialMiddleware);
+export const hooks = {
+ /**
+ * Define side-effects to run against Redux actions.
+ */
+ '@flecks/redux.effects': () => ({
+ someActionName: (store, action) => {
+ // Runs when `someActionName` actions are dispatched.
},
+ }),
+ /**
+ * Define root-level reducers for the Redux store.
+ */
+ '@flecks/redux.reducers': () => {
+ return (state, action) => {
+ // Whatever you'd like.
+ return state;
+ };
+ },
+ /**
+ * Define Redux slices.
+ *
+ * See: https://redux-toolkit.js.org/api/createSlice
+ */
+ '@flecks/redux.slices': () => {
+ const something = createSlice(
+ // ...
+ );
+ return {
+ something: something.reducer,
+ };
+ },
+ /**
+ * Modify Redux store configuration.
+ * @param {Object} options A mutable object with keys for enhancers and middleware.
+ */
+ '@flecks/redux.store': (options) => {
+ options.enhancers.splice(someIndex, 1);
+ options.middleware.push(mySpecialMiddleware);
},
};
diff --git a/packages/redux/src/client/index.js b/packages/redux/src/client/index.js
index 2356f77..074447c 100644
--- a/packages/redux/src/client/index.js
+++ b/packages/redux/src/client/index.js
@@ -1,26 +1,24 @@
-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]: {
- '@flecks/react.providers': async (req, flecks) => {
- const slices = await ensureUniqueReduction(flecks, '@flecks/redux.slices');
- const reducer = createReducer(flecks, slices);
- // Hydrate from server.
- const {preloadedState} = flecks.get('@flecks/redux/client');
- const store = await configureStore(flecks, reducer, {preloadedState});
- flecks.set('$flecks/redux.store', store);
- return [Provider, {store}];
- },
- '@flecks/redux.store': ({enhancers}) => {
- // Hydrate from and subscribe to localStorage.
- enhancers.push(localStorageEnhancer);
- },
- '@flecks/socket.packets.decorate': (
- Flecks.decorate(require.context('./packets/decorators', false, /\.js$/))
- ),
+export const hooks = {
+ '@flecks/react.providers': async (req, flecks) => {
+ const slices = await flecks.invokeMergeUnique('@flecks/redux.slices');
+ const reducer = createReducer(flecks, slices);
+ // Hydrate from server.
+ const {preloadedState} = flecks.get('@flecks/redux/client');
+ const store = await configureStore(flecks, reducer, {preloadedState});
+ flecks.set('$flecks/redux.store', store);
+ return [Provider, {store}];
},
+ '@flecks/redux.store': ({enhancers}) => {
+ // Hydrate from and subscribe to localStorage.
+ enhancers.push(localStorageEnhancer);
+ },
+ '@flecks/socket.packets.decorate': (
+ Flecks.decorate(require.context('./packets/decorators', false, /\.js$/))
+ ),
};
diff --git a/packages/redux/src/index.js b/packages/redux/src/index.js
index 1e5ff24..fed21b7 100644
--- a/packages/redux/src/index.js
+++ b/packages/redux/src/index.js
@@ -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]: {
- '@flecks/socket.packets': Flecks.provide(require.context('./packets', false, /\.js$/)),
- },
+export const hooks = {
+ '@flecks/socket.packets': Flecks.provide(require.context('./packets', false, /\.js$/)),
};
diff --git a/packages/redux/src/server.js b/packages/redux/src/server.js
index 2381f57..6ecb4ee 100644
--- a/packages/redux/src/server.js
+++ b/packages/redux/src/server.js
@@ -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,29 +8,27 @@ import configureStore from './store';
const debug = D('@flecks/redux/server');
const debugSilly = debug.extend('silly');
-export default {
- [Hooks]: {
- '@flecks/web/server.request.route': (flecks) => async (req, res, next) => {
- const slices = await ensureUniqueReduction(flecks, '@flecks/redux.slices');
- const reducer = createReducer(flecks, slices);
- // Let the slices have a(n async) chance to hydrate with server data.
- await Promise.all(
- Object.values(slices).map(({hydrateServer}) => hydrateServer?.(req, flecks)),
- );
- const preloadedState = reducer(undefined, hydrateServer({flecks, req}));
- debugSilly(
- 'creating redux store with slices(%O) and state(%O)',
- Object.keys(slices),
- preloadedState,
- );
- req.redux = await configureStore(flecks, reducer, {preloadedState});
- next();
- },
- '@flecks/web.config': async (req) => ({
- '@flecks/redux/client': {
- preloadedState: req.redux.getState(),
- },
- }),
- '@flecks/react.providers': (req) => [Provider, {store: req.redux}],
+export const hooks = {
+ '@flecks/web/server.request.route': (flecks) => async (req, res, next) => {
+ 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(
+ Object.values(slices).map(({hydrateServer}) => hydrateServer?.(req, flecks)),
+ );
+ const preloadedState = reducer(undefined, hydrateServer({flecks, req}));
+ debugSilly(
+ 'creating redux store with slices(%O) and state(%O)',
+ Object.keys(slices),
+ preloadedState,
+ );
+ req.redux = await configureStore(flecks, reducer, {preloadedState});
+ next();
},
+ '@flecks/web.config': async (req) => ({
+ '@flecks/redux/client': {
+ preloadedState: req.redux.getState(),
+ },
+ }),
+ '@flecks/react.providers': (req) => [Provider, {store: req.redux}],
};
diff --git a/packages/repl/build/dox/hooks.js b/packages/repl/build/dox/hooks.js
index fdd351f..a2167ed 100644
--- a/packages/repl/build/dox/hooks.js
+++ b/packages/repl/build/dox/hooks.js
@@ -1,30 +1,26 @@
-import {Hooks} from '@flecks/core';
-
-export default {
- [Hooks]: {
- /**
- * Define REPL commands.
- *
- * Note: commands will be prefixed with a period in the Node REPL.
- */
- '@flecks/repl.commands': () => ({
- someCommand: (...args) => {
- // args are passed from the Node REPL. So, you could invoke it like:
- // .someCommand foo bar
- // and `args` would be `['foo', 'bar']`.
- },
- }),
- /**
- * Provide global context to the REPL.
- */
- '@flecks/repl.context': () => {
- // Now you'd be able to do like:
- // `node> someValue;`
- // and the REPL would evaluate it to `'foobar'`.
- return {
- someValue: 'foobar',
- };
+export const hooks = {
+ /**
+ * Define REPL commands.
+ *
+ * Note: commands will be prefixed with a period in the Node REPL.
+ */
+ '@flecks/repl.commands': () => ({
+ someCommand: (...args) => {
+ // args are passed from the Node REPL. So, you could invoke it like:
+ // .someCommand foo bar
+ // and `args` would be `['foo', 'bar']`.
},
+ }),
+ /**
+ * Provide global context to the REPL.
+ */
+ '@flecks/repl.context': () => {
+ // Now you'd be able to do like:
+ // `node> someValue;`
+ // and the REPL would evaluate it to `'foobar'`.
+ return {
+ someValue: 'foobar',
+ };
},
};
diff --git a/packages/repl/src/server.js b/packages/repl/src/server.js
index 74a2f5b..ecf266a 100644
--- a/packages/repl/src/server.js
+++ b/packages/repl/src/server.js
@@ -1,11 +1,7 @@
-import {Hooks} from '@flecks/core';
-
import commands from './commands';
import {createReplServer} from './repl';
-export default {
- [Hooks]: {
- '@flecks/core.commands': commands,
- '@flecks/server.up': (flecks) => createReplServer(flecks),
- },
+export const hooks = {
+ '@flecks/core.commands': commands,
+ '@flecks/server.up': (flecks) => createReplServer(flecks),
};
diff --git a/packages/server/build/dox/hooks.js b/packages/server/build/dox/hooks.js
index b6f1ac0..a780043 100644
--- a/packages/server/build/dox/hooks.js
+++ b/packages/server/build/dox/hooks.js
@@ -1,12 +1,8 @@
-import {Hooks} from '@flecks/core';
-
-export default {
- [Hooks]: {
- /**
- * Define sequential actions to run when the server comes up.
- */
- '@flecks/server.up': async () => {
- await youCanDoAsyncThingsHere();
- },
+export const hooks = {
+ /**
+ * Define sequential actions to run when the server comes up.
+ */
+ '@flecks/server.up': async () => {
+ await youCanDoAsyncThingsHere();
},
};
diff --git a/packages/server/src/entry.js b/packages/server/src/entry.js
index 869992e..75fe283 100644
--- a/packages/server/src/entry.js
+++ b/packages/server/src/entry.js
@@ -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) {
diff --git a/packages/server/src/index.js b/packages/server/src/index.js
index 522d68c..4a688d5 100644
--- a/packages/server/src/index.js
+++ b/packages/server/src/index.js
@@ -1,28 +1,24 @@
-import {Hooks} from '@flecks/core';
-
-export default {
- [Hooks]: {
- '@flecks/core.config': () => ({
- /**
- * Whether HMR is enabled.
- */
- hot: false,
- /**
- * Arguments to pass along to node. See: https://nodejs.org/api/cli.html
- */
- nodeArgs: [],
- /**
- * Whether to start the server after building.
- */
- start: true,
- /**
- * Webpack stats configuration when building server target.
- */
- stats: {
- chunks: false,
- colors: true,
- modules: false,
- },
- }),
- },
+export const hooks = {
+ '@flecks/core.config': () => ({
+ /**
+ * Whether HMR is enabled.
+ */
+ hot: false,
+ /**
+ * Arguments to pass along to node. See: https://nodejs.org/api/cli.html
+ */
+ nodeArgs: [],
+ /**
+ * Whether to start the server after building.
+ */
+ start: true,
+ /**
+ * Webpack stats configuration when building server target.
+ */
+ stats: {
+ chunks: false,
+ colors: true,
+ modules: false,
+ },
+ }),
};
diff --git a/packages/server/src/server/index.js b/packages/server/src/server/index.js
index 0ad1557..ee501fb 100644
--- a/packages/server/src/server/index.js
+++ b/packages/server/src/server/index.js
@@ -1,7 +1,3 @@
-import {Hooks} from '@flecks/core';
-
-export default {
- [Hooks]: {
- '@flecks/core.targets': () => ['server'],
- },
+export const hooks = {
+ '@flecks/core.targets': () => ['server'],
};
diff --git a/packages/socket/build/dox/hooks.js b/packages/socket/build/dox/hooks.js
index 482336d..878cd8a 100644
--- a/packages/socket/build/dox/hooks.js
+++ b/packages/socket/build/dox/hooks.js
@@ -1,65 +1,61 @@
-import {Hooks} from '@flecks/core';
-
-export default {
- [Hooks]: {
- /**
- * Modify Socket.io client configuration.
- *
- * See: https://socket.io/docs/v4/client-options/
- */
- '@flecks/socket.client': () => ({
- timeout: Infinity,
- }),
- /**
- * Define server-side intercom channels.
- */
- '@flecks/socket.intercom': (req) => ({
- // This would have been called like:
- // `const result = await req.intercom('someChannel', payload)`.
- // `result` will be an `n`-length array, where `n` is the number of server instances. Each
- // element in the array will be the result of `someServiceSpecificInformation()` running
- // against that server instance.
- someChannel: async (payload, server) => {
- return someServiceSpecificInformation();
- },
- }),
- /**
- * Define socket packets.
- *
- * In the example below, your fleck would have a `packets` subdirectory, and each
- * decorator would be defined in its own file.
- * See: https://github.com/cha0s/flecks/tree/master/packages/redux/src/packets
- *
- * See: https://github.com/cha0s/flecks/tree/master/packages/socket/src/packet/packet.js
- * See: https://github.com/cha0s/flecks/tree/master/packages/socket/src/packet/redirect.js
- */
- '@flecks/socket.packets': Flecks.provide(require.context('./packets', false, /\.js$/)),
- /**
- * Decorate database models.
- *
- * In the example below, your fleck would have a `packets/decorators` subdirectory, and each
- * decorator would be defined in its own file.
- * @param {constructor} Packet The packet to decorate.
- */
- '@flecks/socket.packets.decorate': (
- Flecks.decorate(require.context('./packets/decorators', false, /\.js$/))
- ),
-
- /**
- * Modify Socket.io server configuration.
- *
- * See: https://socket.io/docs/v4/server-options/
- */
- '@flecks/socket.server': () => ({
- pingTimeout: Infinity,
- }),
- /**
- * Define middleware to run when a socket connection is established.
- */
- '@flecks/socket/server.request.socket': () => (socket, next) => {
- // Express-style route middleware...
- next();
+export const hooks = {
+ /**
+ * Modify Socket.io client configuration.
+ *
+ * See: https://socket.io/docs/v4/client-options/
+ */
+ '@flecks/socket.client': () => ({
+ timeout: Infinity,
+ }),
+ /**
+ * Define server-side intercom channels.
+ */
+ '@flecks/socket.intercom': (req) => ({
+ // This would have been called like:
+ // `const result = await req.intercom('someChannel', payload)`.
+ // `result` will be an `n`-length array, where `n` is the number of server instances. Each
+ // element in the array will be the result of `someServiceSpecificInformation()` running
+ // against that server instance.
+ someChannel: async (payload, server) => {
+ return someServiceSpecificInformation();
},
+ }),
+ /**
+ * Define socket packets.
+ *
+ * In the example below, your fleck would have a `packets` subdirectory, and each
+ * decorator would be defined in its own file.
+ * See: https://github.com/cha0s/flecks/tree/master/packages/redux/src/packets
+ *
+ * See: https://github.com/cha0s/flecks/tree/master/packages/socket/src/packet/packet.js
+ * See: https://github.com/cha0s/flecks/tree/master/packages/socket/src/packet/redirect.js
+ */
+ '@flecks/socket.packets': Flecks.provide(require.context('./packets', false, /\.js$/)),
+ /**
+ * Decorate database models.
+ *
+ * In the example below, your fleck would have a `packets/decorators` subdirectory, and each
+ * decorator would be defined in its own file.
+ * @param {constructor} Packet The packet to decorate.
+ */
+ '@flecks/socket.packets.decorate': (
+ Flecks.decorate(require.context('./packets/decorators', false, /\.js$/))
+ ),
+
+ /**
+ * Modify Socket.io server configuration.
+ *
+ * See: https://socket.io/docs/v4/server-options/
+ */
+ '@flecks/socket.server': () => ({
+ pingTimeout: Infinity,
+ }),
+ /**
+ * Define middleware to run when a socket connection is established.
+ */
+ '@flecks/socket/server.request.socket': () => (socket, next) => {
+ // Express-style route middleware...
+ next();
},
};
diff --git a/packages/socket/src/client/index.js b/packages/socket/src/client/index.js
index 6b92626..db2f3b1 100644
--- a/packages/socket/src/client/index.js
+++ b/packages/socket/src/client/index.js
@@ -1,20 +1,16 @@
-import {Hooks} from '@flecks/core';
-
import SocketClient from './socket';
-export default {
- [Hooks]: {
- '@flecks/web/client.up': (flecks) => {
- const socket = new SocketClient(flecks);
- flecks.set('$flecks/socket.socket', socket);
- socket.connect();
- socket.listen();
- },
- '@flecks/socket.client': ({config: {'@flecks/core': {id}}}) => ({
- cors: {
- origin: false,
- },
- path: `/${id}/socket.io`,
- }),
+export const hooks = {
+ '@flecks/web/client.up': (flecks) => {
+ const socket = new SocketClient(flecks);
+ flecks.set('$flecks/socket.socket', socket);
+ socket.connect();
+ socket.listen();
},
+ '@flecks/socket.client': ({config: {'@flecks/core': {id}}}) => ({
+ cors: {
+ origin: false,
+ },
+ path: `/${id}/socket.io`,
+ }),
};
diff --git a/packages/socket/src/index.js b/packages/socket/src/index.js
index 0ead1f5..ca8f6a5 100644
--- a/packages/socket/src/index.js
+++ b/packages/socket/src/index.js
@@ -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,28 +7,26 @@ export {default as normalize} from './normalize';
export * from './hooks';
export {default as Packet, Packer, ValidationError} from './packet';
-export default {
- [Hooks]: {
- '@flecks/core.starting': (flecks) => {
- flecks.set('$flecks/socket.packets', flecks.gather(
- '@flecks/socket.packets',
- {check: badPacketsCheck},
- ));
- },
- '@flecks/web.config': async (
- req,
- {config: {'@flecks/socket': {'packets.decorate': decorators = ['...']}}},
- ) => ({
- '@flecks/socket': {
- 'packets.decorate': decorators.filter(
- (decorator) => 'server' !== decorator.split('/').pop(),
- ),
- },
- }),
- '@flecks/socket.packets': (flecks) => ({
- Bundle: Bundle(flecks),
- Redirect,
- Refresh,
- }),
+export const hooks = {
+ '@flecks/core.starting': (flecks) => {
+ flecks.set('$flecks/socket.packets', flecks.gather(
+ '@flecks/socket.packets',
+ {check: badPacketsCheck},
+ ));
},
+ '@flecks/web.config': async (
+ req,
+ {config: {'@flecks/socket': {'packets.decorate': decorators = ['...']}}},
+ ) => ({
+ '@flecks/socket': {
+ 'packets.decorate': decorators.filter(
+ (decorator) => 'server' !== decorator.split('/').pop(),
+ ),
+ },
+ }),
+ '@flecks/socket.packets': (flecks) => ({
+ Bundle: Bundle(flecks),
+ Redirect,
+ Refresh,
+ }),
};
diff --git a/packages/socket/src/server/index.js b/packages/socket/src/server/index.js
index 31065c0..13bfe7d 100644
--- a/packages/socket/src/server/index.js
+++ b/packages/socket/src/server/index.js
@@ -1,25 +1,21 @@
-import {Hooks} from '@flecks/core';
-
import createIntercom from './create-intercom';
import Sockets from './sockets';
-export default {
- [Hooks]: {
- '@flecks/web/server.request.socket': ({config: {'$flecks/socket.sockets': sockets}}) => (req, res, next) => {
- req.intercom = createIntercom(sockets, 'web');
- next();
- },
- '@flecks/web/server.up': async (httpServer, flecks) => {
- const sockets = new Sockets(httpServer, flecks);
- await sockets.connect();
- flecks.set('$flecks/socket.sockets', sockets);
- },
- '@flecks/repl.context': (flecks) => ({
- Packets: flecks.get('$flecks/socket.packets'),
- sockets: flecks.get('$flecks/socket.sockets'),
- }),
- '@flecks/socket.server': ({config: {'@flecks/core': {id}}}) => ({
- path: `/${id}/socket.io`,
- }),
+export const hooks = {
+ '@flecks/web/server.request.socket': ({config: {'$flecks/socket.sockets': sockets}}) => (req, res, next) => {
+ req.intercom = createIntercom(sockets, 'web');
+ next();
},
+ '@flecks/web/server.up': async (httpServer, flecks) => {
+ const sockets = new Sockets(httpServer, flecks);
+ await sockets.connect();
+ flecks.set('$flecks/socket.sockets', sockets);
+ },
+ '@flecks/repl.context': (flecks) => ({
+ Packets: flecks.get('$flecks/socket.packets'),
+ sockets: flecks.get('$flecks/socket.sockets'),
+ }),
+ '@flecks/socket.server': ({config: {'@flecks/core': {id}}}) => ({
+ path: `/${id}/socket.io`,
+ }),
};
diff --git a/packages/user/build/dox/hooks.js b/packages/user/build/dox/hooks.js
index 6ccbe07..ef438c0 100644
--- a/packages/user/build/dox/hooks.js
+++ b/packages/user/build/dox/hooks.js
@@ -1,15 +1,10 @@
-import {Hooks} from '@flecks/core';
-
-export default {
- [Hooks]: {
- /**
- * Modify express-session configuration.
- *
- * See: https://www.npmjs.com/package/express-session
- */
- '@flecks/user.session': () => ({
- saveUninitialized: true,
- }),
- },
+export const hooks = {
+ /**
+ * Modify express-session configuration.
+ *
+ * See: https://www.npmjs.com/package/express-session
+ */
+ '@flecks/user.session': () => ({
+ saveUninitialized: true,
+ }),
};
-
diff --git a/packages/user/src/index.js b/packages/user/src/index.js
index f15676c..38ba658 100644
--- a/packages/user/src/index.js
+++ b/packages/user/src/index.js
@@ -1,19 +1,15 @@
-import {Hooks} from '@flecks/core';
-
import {Logout} from './packets';
import {user, users} from './state';
export * from './state';
-export default {
- [Hooks]: {
- '@flecks/redux.slices': () => ({
- user,
- users,
- }),
- '@flecks/socket.packets': (flecks) => ({
- Logout: Logout(flecks),
- }),
- },
+export const hooks = {
+ '@flecks/redux.slices': () => ({
+ user,
+ users,
+ }),
+ '@flecks/socket.packets': (flecks) => ({
+ Logout: Logout(flecks),
+ }),
};
diff --git a/packages/user/src/local/server/index.js b/packages/user/src/local/server/index.js
index 744bb32..a9bcf26 100644
--- a/packages/user/src/local/server/index.js
+++ b/packages/user/src/local/server/index.js
@@ -1,70 +1,68 @@
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]: {
- '@flecks/core.config': () => ({
- /**
- * Path to redirect to after failed login.
- */
- failureRedirect: '/',
- /**
- * Path to redirect to after successful login.
- */
- successRedirect: '/',
- }),
- '@flecks/db/server.models.decorate': (
- Flecks.decorate(require.context('./models/decorators', false, /\.js$/))
- ),
- '@flecks/web.routes': (flecks) => {
- const {failureRedirect, successRedirect} = flecks.get('@flecks/user/local/server');
- return [
- {
- method: 'post',
- path: '/auth/local',
- middleware: passport.authenticate('local', {failureRedirect, successRedirect}),
- },
- ];
- },
- '@flecks/repl.commands': (flecks) => {
- const {User} = flecks.get('$flecks/db.models');
- return {
- createUser: async (spec) => {
- const [email, maybePassword] = spec.split(' ', 2);
- const password = maybePassword || randomBytes(8).toString('hex');
- const user = User.build({email});
+export const hooks = {
+ '@flecks/core.config': () => ({
+ /**
+ * Path to redirect to after failed login.
+ */
+ failureRedirect: '/',
+ /**
+ * Path to redirect to after successful login.
+ */
+ successRedirect: '/',
+ }),
+ '@flecks/db/server.models.decorate': (
+ Flecks.decorate(require.context('./models/decorators', false, /\.js$/))
+ ),
+ '@flecks/web.routes': (flecks) => {
+ const {failureRedirect, successRedirect} = flecks.get('@flecks/user/local/server');
+ return [
+ {
+ method: 'post',
+ path: '/auth/local',
+ middleware: passport.authenticate('local', {failureRedirect, successRedirect}),
+ },
+ ];
+ },
+ '@flecks/repl.commands': (flecks) => {
+ const {User} = flecks.get('$flecks/db.models');
+ return {
+ createUser: async (spec) => {
+ const [email, maybePassword] = spec.split(' ', 2);
+ const password = maybePassword || randomBytes(8).toString('hex');
+ const user = User.build({email});
+ await user.addHashedPassword(password);
+ await user.save();
+ },
+ resetPassword: async (email) => {
+ const password = randomBytes(8).toString('hex');
+ const user = await User.findOne({where: {email}});
+ if (user) {
await user.addHashedPassword(password);
await user.save();
- },
- resetPassword: async (email) => {
- const password = randomBytes(8).toString('hex');
+ return `\nNew password: ${password}\n\n`;
+ }
+ return 'User not found.\n';
+ },
+ };
+ },
+ '@flecks/server.up': (flecks) => {
+ passport.use(new LocalStrategy(
+ {usernameField: 'email'},
+ async (email, password, fn) => {
+ const {User} = flecks.get('$flecks/db.models');
+ try {
const user = await User.findOne({where: {email}});
- if (user) {
- await user.addHashedPassword(password);
- await user.save();
- return `\nNew password: ${password}\n\n`;
- }
- return 'User not found.\n';
- },
- };
- },
- '@flecks/server.up': (flecks) => {
- passport.use(new LocalStrategy(
- {usernameField: 'email'},
- async (email, password, fn) => {
- const {User} = flecks.get('$flecks/db.models');
- try {
- const user = await User.findOne({where: {email}});
- fn(undefined, user && await user.validatePassword(password) && user);
- }
- catch (error) {
- fn(error);
- }
- },
- ));
- },
+ fn(undefined, user && await user.validatePassword(password) && user);
+ }
+ catch (error) {
+ fn(error);
+ }
+ },
+ ));
},
};
diff --git a/packages/user/src/server/index.js b/packages/user/src/server/index.js
index 00b3b34..d5d45e9 100644
--- a/packages/user/src/server/index.js
+++ b/packages/user/src/server/index.js
@@ -1,85 +1,83 @@
-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]: {
- '@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()');
- passport.initialize()(req, res, () => {
- debugSilly('@flecks/web/server.request.route: passport.session()');
- passport.session()(req, res, () => {
- if (!req.user) {
- const {User} = flecks.get('$flecks/db.models');
- req.user = new User();
- req.user.id = 0;
- }
- next();
- });
- });
- },
- '@flecks/web.routes': () => [
- {
- method: 'get',
- path: '/auth/logout',
- middleware: (req, res) => {
- req.logout();
- res.redirect('/');
- },
- },
- ],
- '@flecks/server.up': (flecks) => {
- passport.serializeUser((user, fn) => fn(null, user.id));
- passport.deserializeUser(async (id, fn) => {
- const {User} = flecks.get('$flecks/db.models');
- try {
- fn(undefined, await User.findByPk(id));
- }
- catch (error) {
- fn(error);
+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()');
+ passport.initialize()(req, res, () => {
+ debugSilly('@flecks/web/server.request.route: passport.session()');
+ passport.session()(req, res, () => {
+ if (!req.user) {
+ const {User} = flecks.get('$flecks/db.models');
+ req.user = new User();
+ req.user.id = 0;
}
+ next();
});
- },
- '@flecks/socket.intercom': () => ({
- '@flecks/user/users': async (sids, server) => {
- const sockets = await server.sockets();
- return sids
- .filter((sid) => sockets.has(sid))
- .reduce(
- (r, sid) => ({
- ...r,
- [sid]: sockets.get(sid).handshake.user.id,
- }),
- {},
- );
+ });
+ },
+ '@flecks/web.routes': () => [
+ {
+ method: 'get',
+ path: '/auth/logout',
+ middleware: (req, res) => {
+ req.logout();
+ res.redirect('/');
},
- }),
- '@flecks/socket/server.request.socket': (flecks) => (socket, next) => {
- debugSilly('@flecks/socket/server.request.socket: passport.initialize()');
- passport.initialize()(socket.handshake, undefined, () => {
- debugSilly('@flecks/socket/server.request.socket: passport.session()');
- passport.session()(socket.handshake, undefined, async () => {
- /* eslint-disable no-param-reassign */
- if (!socket.handshake.user) {
- const {User} = flecks.get('$flecks/db.models');
- socket.handshake.user = new User();
- socket.handshake.user.id = 0;
- }
- socket.handshake.login = LogOps.logIn;
- socket.handshake.logIn = LogOps.logIn;
- socket.handshake.logout = LogOps.logOut;
- socket.handshake.logOut = LogOps.logOut;
- socket.handshake.isAuthenticated = LogOps.isAuthenticated;
- socket.handshake.isUnauthenticated = LogOps.isUnauthenticated;
- /* eslint-enable no-param-reassign */
- await socket.join(`/u/${socket.handshake.user.id}`);
- next();
- });
- });
},
+ ],
+ '@flecks/server.up': (flecks) => {
+ passport.serializeUser((user, fn) => fn(null, user.id));
+ passport.deserializeUser(async (id, fn) => {
+ const {User} = flecks.get('$flecks/db.models');
+ try {
+ fn(undefined, await User.findByPk(id));
+ }
+ catch (error) {
+ fn(error);
+ }
+ });
+ },
+ '@flecks/socket.intercom': () => ({
+ '@flecks/user/users': async (sids, server) => {
+ const sockets = await server.sockets();
+ return sids
+ .filter((sid) => sockets.has(sid))
+ .reduce(
+ (r, sid) => ({
+ ...r,
+ [sid]: sockets.get(sid).handshake.user.id,
+ }),
+ {},
+ );
+ },
+ }),
+ '@flecks/socket/server.request.socket': (flecks) => (socket, next) => {
+ debugSilly('@flecks/socket/server.request.socket: passport.initialize()');
+ passport.initialize()(socket.handshake, undefined, () => {
+ debugSilly('@flecks/socket/server.request.socket: passport.session()');
+ passport.session()(socket.handshake, undefined, async () => {
+ /* eslint-disable no-param-reassign */
+ if (!socket.handshake.user) {
+ const {User} = flecks.get('$flecks/db.models');
+ socket.handshake.user = new User();
+ socket.handshake.user.id = 0;
+ }
+ socket.handshake.login = LogOps.logIn;
+ socket.handshake.logIn = LogOps.logIn;
+ socket.handshake.logout = LogOps.logOut;
+ socket.handshake.logOut = LogOps.logOut;
+ socket.handshake.isAuthenticated = LogOps.isAuthenticated;
+ socket.handshake.isUnauthenticated = LogOps.isUnauthenticated;
+ /* eslint-enable no-param-reassign */
+ await socket.join(`/u/${socket.handshake.user.id}`);
+ next();
+ });
+ });
},
};
diff --git a/packages/user/src/session/server.js b/packages/user/src/session/server.js
index 7443289..fea4442 100644
--- a/packages/user/src/session/server.js
+++ b/packages/user/src/session/server.js
@@ -1,59 +1,57 @@
-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]: {
- '@flecks/core.config': () => ({
- /**
- * Set the cookie secret for session encryption.
- *
- * See: http://expressjs.com/en/resources/middleware/cookie-parser.html
- */
- cookieSecret: (
- 'Set the FLECKS_ENV_FLECKS_USER_SESSION_SERVER_cookieSecret environment variable!'
- ),
- }),
- '@flecks/web/server.request.route': (flecks) => {
- const urle = express.urlencoded({extended: true});
- return (req, res, next) => {
- debugSilly('@flecks/web/server.request.route: express.urlencoded()');
- urle(req, res, (error) => {
+export const hooks = {
+ '@flecks/core.config': () => ({
+ /**
+ * Set the cookie secret for session encryption.
+ *
+ * See: http://expressjs.com/en/resources/middleware/cookie-parser.html
+ */
+ cookieSecret: (
+ 'Set the FLECKS_ENV_FLECKS_USER_SESSION_SERVER_cookieSecret environment variable!'
+ ),
+ }),
+ '@flecks/web/server.request.route': (flecks) => {
+ const urle = express.urlencoded({extended: true});
+ return (req, res, next) => {
+ debugSilly('@flecks/web/server.request.route: express.urlencoded()');
+ urle(req, res, (error) => {
+ if (error) {
+ next(error);
+ return;
+ }
+ debugSilly('@flecks/web/server.request.route: session()');
+ flecks.get('$flecks/user.session')(req, res, (error) => {
if (error) {
next(error);
return;
}
- debugSilly('@flecks/web/server.request.route: session()');
- flecks.get('$flecks/user.session')(req, res, (error) => {
- if (error) {
- next(error);
- return;
- }
- debugSilly('session ID: %s', req.session.id);
- next();
- });
+ debugSilly('session ID: %s', req.session.id);
+ next();
});
- };
- },
- '@flecks/server.up': async (flecks) => {
- flecks.set('$flecks/user.session', expressSession({
- resave: false,
- sameSite: true,
- saveUninitialized: false,
- secret: flecks.get('@flecks/user/session/server.cookieSecret'),
- ...await flecks.invokeMergeAsync('@flecks/user.session'),
- }));
- },
- '@flecks/socket/server.request.socket': (flecks) => (socket, next) => {
- debugSilly('@flecks/socket/server.request.socket: session()');
- flecks.get('$flecks/user.session')(socket.handshake, {}, () => {
- const id = socket.handshake.session?.id;
- socket.join(id);
- next();
});
- },
+ };
+ },
+ '@flecks/server.up': async (flecks) => {
+ flecks.set('$flecks/user.session', expressSession({
+ resave: false,
+ sameSite: true,
+ saveUninitialized: false,
+ secret: flecks.get('@flecks/user/session/server.cookieSecret'),
+ ...await flecks.invokeMergeAsync('@flecks/user.session'),
+ }));
+ },
+ '@flecks/socket/server.request.socket': (flecks) => (socket, next) => {
+ debugSilly('@flecks/socket/server.request.socket: session()');
+ flecks.get('$flecks/user.session')(socket.handshake, {}, () => {
+ const id = socket.handshake.session?.id;
+ socket.join(id);
+ next();
+ });
},
};
diff --git a/packages/web/build/dox/hooks.js b/packages/web/build/dox/hooks.js
index c16dfa6..ece001a 100644
--- a/packages/web/build/dox/hooks.js
+++ b/packages/web/build/dox/hooks.js
@@ -1,62 +1,58 @@
-import {Hooks} from '@flecks/core';
-
-export default {
- [Hooks]: {
- /**
- * Define sequential actions to run when the client comes up.
- */
- '@flecks/web/client.up': async () => {
- await youCanDoAsyncThingsHere();
+export const hooks = {
+ /**
+ * Define sequential actions to run when the client comes up.
+ */
+ '@flecks/web/client.up': async () => {
+ await youCanDoAsyncThingsHere();
+ },
+ /**
+ * Override flecks configuration sent to client flecks.
+ * @param {http.ClientRequest} req The HTTP request object.
+ */
+ '@flecks/web.config': (req) => ({
+ someClientFleck: {
+ someConfig: req.someConfig,
},
- /**
- * Override flecks configuration sent to client flecks.
- * @param {http.ClientRequest} req The HTTP request object.
- */
- '@flecks/web.config': (req) => ({
- someClientFleck: {
- someConfig: req.someConfig,
+ }),
+ /**
+ * Define HTTP routes.
+ */
+ '@flecks/web.routes': () => [
+ {
+ method: 'get',
+ path: '/some-path',
+ middleware: (req, res, next) => {
+ // Express-style route middleware...
+ next();
},
- }),
- /**
- * Define HTTP routes.
- */
- '@flecks/web.routes': () => [
- {
- method: 'get',
- path: '/some-path',
- middleware: (req, res, next) => {
- // Express-style route middleware...
- next();
- },
- },
- ],
- /**
- * Define middleware to run when a route is matched.
- */
- '@flecks/web/server.request.route': () => (req, res, next) => {
- // Express-style route middleware...
- next();
- },
- /**
- * Define middleware to run when an HTTP socket connection is established.
- */
- '@flecks/web/server.request.socket': () => (req, res, next) => {
- // Express-style route middleware...
- next();
- },
- /**
- * Define composition functions to run over the HTML stream prepared for the client.
- * @param {stream.Readable} stream The HTML stream.
- * @param {http.ClientRequest} req The HTTP request object.
- */
- '@flecks/web/server.stream.html': (stream, req) => {
- return stream.pipe(myTransformStream);
- },
- /**
- * Define sequential actions to run when the HTTP server comes up.
- */
- '@flecks/web/server.up': async () => {
- await youCanDoAsyncThingsHere();
},
+ ],
+ /**
+ * Define middleware to run when a route is matched.
+ */
+ '@flecks/web/server.request.route': () => (req, res, next) => {
+ // Express-style route middleware...
+ next();
+ },
+ /**
+ * Define middleware to run when an HTTP socket connection is established.
+ */
+ '@flecks/web/server.request.socket': () => (req, res, next) => {
+ // Express-style route middleware...
+ next();
+ },
+ /**
+ * Define composition functions to run over the HTML stream prepared for the client.
+ * @param {stream.Readable} stream The HTML stream.
+ * @param {http.ClientRequest} req The HTTP request object.
+ */
+ '@flecks/web/server.stream.html': (stream, req) => {
+ return stream.pipe(myTransformStream);
+ },
+ /**
+ * Define sequential actions to run when the HTTP server comes up.
+ */
+ '@flecks/web/server.up': async () => {
+ await youCanDoAsyncThingsHere();
},
};
diff --git a/packages/web/src/server/build/entry.js b/packages/web/src/server/build/entry.js
index 085a7fb..61748a9 100644
--- a/packages/web/src/server/build/entry.js
+++ b/packages/web/src/server/build/entry.js
@@ -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!');
}
diff --git a/packages/web/src/server/index.js b/packages/web/src/server/index.js
index 9823061..6509a0e 100644
--- a/packages/web/src/server/index.js
+++ b/packages/web/src/server/index.js
@@ -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,199 +16,197 @@ const debug = D('@flecks/web/server');
export {augmentBuild};
-export default {
- [Hooks]: {
- '@flecks/core.build': augmentBuild,
- '@flecks/core.build.alter': async (neutrinoConfigs, flecks) => {
- // Don't build if there's a fleck target.
- if (neutrinoConfigs.fleck && !flecks.get('@flecks/web/server.forceBuildWithFleck')) {
+export const hooks = {
+ '@flecks/core.build': augmentBuild,
+ '@flecks/core.build.alter': async (neutrinoConfigs, flecks) => {
+ // Don't build if there's a fleck target.
+ if (neutrinoConfigs.fleck && !flecks.get('@flecks/web/server.forceBuildWithFleck')) {
+ // eslint-disable-next-line no-param-reassign
+ delete neutrinoConfigs.web;
+ return;
+ }
+ // Only build vendor in dev.
+ if (neutrinoConfigs['web-vendor']) {
+ if (process.argv.find((arg) => 'production' === arg)) {
// eslint-disable-next-line no-param-reassign
- delete neutrinoConfigs.web;
- return;
+ delete neutrinoConfigs['web-vendor'];
}
- // Only build vendor in dev.
- if (neutrinoConfigs['web-vendor']) {
- if (process.argv.find((arg) => 'production' === arg)) {
- // eslint-disable-next-line no-param-reassign
- delete neutrinoConfigs['web-vendor'];
+ // Only build if something actually changed.
+ const dll = flecks.get('@flecks/web/server.dll');
+ if (dll.length > 0) {
+ const manifest = join(
+ FLECKS_CORE_ROOT,
+ 'node_modules',
+ '.cache',
+ 'flecks',
+ 'web-vendor.manifest.json',
+ );
+ let timestamp = 0;
+ try {
+ const stats = await stat(manifest);
+ timestamp = stats.mtime;
}
- // Only build if something actually changed.
- const dll = flecks.get('@flecks/web/server.dll');
- if (dll.length > 0) {
- const manifest = join(
- FLECKS_CORE_ROOT,
- 'node_modules',
- '.cache',
- 'flecks',
- 'web-vendor.manifest.json',
- );
- let timestamp = 0;
+ // eslint-disable-next-line no-empty
+ catch (error) {}
+ let latest = 0;
+ for (let i = 0; i < dll.length; ++i) {
+ const path = dll[i];
try {
- const stats = await stat(manifest);
- timestamp = stats.mtime;
+ // eslint-disable-next-line no-await-in-loop
+ const stats = await stat(join(FLECKS_CORE_ROOT, 'node_modules', path));
+ if (stats.mtime > latest) {
+ latest = stats.mtime;
+ }
}
// eslint-disable-next-line no-empty
catch (error) {}
- let latest = 0;
- for (let i = 0; i < dll.length; ++i) {
- const path = dll[i];
- try {
- // eslint-disable-next-line no-await-in-loop
- const stats = await stat(join(FLECKS_CORE_ROOT, 'node_modules', path));
- if (stats.mtime > latest) {
- latest = stats.mtime;
- }
- }
- // eslint-disable-next-line no-empty
- catch (error) {}
- }
- if (timestamp > latest) {
- // eslint-disable-next-line no-param-reassign
- delete neutrinoConfigs['web-vendor'];
- }
- else if (timestamp > 0) {
- await unlink(manifest);
- }
+ }
+ if (timestamp > latest) {
+ // eslint-disable-next-line no-param-reassign
+ delete neutrinoConfigs['web-vendor'];
+ }
+ else if (timestamp > 0) {
+ await unlink(manifest);
}
}
- // Bail if there's no web build.
- if (!neutrinoConfigs.web) {
- return;
- }
- // Bail if the build isn't watching.
- if (!process.argv.find((arg) => '--watch' === arg)) {
- return;
- }
- // Otherwise, spawn `webpack-dev-server` (WDS).
- const cmd = [
- 'npx', 'webpack-dev-server',
- '--mode', 'development',
- '--hot',
- '--config', flecks.buildConfig('webpack.config.js'),
- ];
- spawnWith(
- cmd,
- {
- env: {
- FLECKS_CORE_BUILD_LIST: 'web',
- },
- },
- );
- // Remove the build config since we're handing off to WDS.
- // eslint-disable-next-line no-param-reassign
- delete neutrinoConfigs.web;
- },
- '@flecks/core.build.config': () => [
- /**
- * Template file used to generate the client HTML.
- *
- * See: https://github.com/jantimon/html-webpack-plugin/blob/main/docs/template-option.md
- */
- 'template.ejs',
- /**
- * PostCSS config file.
- *
- * See: https://github.com/postcss/postcss#usage
- */
- 'postcss.config.js',
- ],
- '@flecks/core.config': () => ({
- /**
- * (webpack-dev-server) Disable the host check.
- *
- * See: https://github.com/webpack/webpack-dev-server/issues/887
- */
- devDisableHostCheck: false,
- /**
- * (webpack-dev-server) Host to bind.
- */
- devHost: 'localhost',
- /**
- * (webpack-dev-server) Port to bind.
- */
- devPort: undefined,
- /**
- * (webpack-dev-server) Public path to serve.
- *
- * Defaults to `flecks.get('@flecks/web/server.public')`.
- */
- devPublic: undefined,
- /**
- * (webpack-dev-server) Webpack stats output.
- */
- devStats: {
- assets: false,
- chunks: false,
- colors: true,
- modules: false,
- },
- /**
- * Modules to externalize using `webpack.DllPlugin`.
- */
- dll: [],
- /**
- * Force building http target even if there's a fleck target.
- */
- forceBuildWithFleck: false,
- /**
- * Host to bind.
- */
- host: '0.0.0.0',
- /**
- * Build path.
- */
- output: 'web',
- /**
- * Port to bind.
- */
- port: 32340,
- /**
- * Public path to server.
- */
- public: 'localhost:32340',
- /**
- * Webpack stats configuration when building HTTP target.
- */
- stats: {
- children: false,
- chunks: false,
- colors: true,
- modules: false,
- },
- /**
- * Proxies to trust.
- *
- * See: https://www.npmjs.com/package/proxy-addr
- */
- trust: false,
- }),
- '@flecks/core.starting': (flecks) => {
- debug('bootstrapping flecks...');
- const webFlecks = Flecks.bootstrap({
- config: flecks.config,
- platforms: ['client', '!server'],
- });
- debug('bootstrapped');
- flecks.set('$flecks/web.flecks', webFlecks);
- },
- '@flecks/core.targets': (flecks) => [
- 'web',
- ...(flecks.get('@flecks/web/server.dll').length > 0 ? ['web-vendor'] : []),
- ],
- '@flecks/web.routes': (flecks) => [
+ }
+ // Bail if there's no web build.
+ if (!neutrinoConfigs.web) {
+ return;
+ }
+ // Bail if the build isn't watching.
+ if (!process.argv.find((arg) => '--watch' === arg)) {
+ return;
+ }
+ // Otherwise, spawn `webpack-dev-server` (WDS).
+ const cmd = [
+ 'npx', 'webpack-dev-server',
+ '--mode', 'development',
+ '--hot',
+ '--config', flecks.buildConfig('webpack.config.js'),
+ ];
+ spawnWith(
+ cmd,
{
- method: 'get',
- path: '/flecks.config.js',
- middleware: async (req, res) => {
- res.setHeader('Content-Type', 'application/javascript; charset=UTF-8');
- res.send(await configSource(flecks, req));
+ env: {
+ FLECKS_CORE_BUILD_LIST: 'web',
},
},
- ],
- '@flecks/web/server.stream.html': inlineConfig,
- '@flecks/server.up': (flecks) => createHttpServer(flecks),
- '@flecks/repl.context': (flecks) => ({
- httpServer: flecks.get('$flecks/web/server.instance'),
- }),
+ );
+ // Remove the build config since we're handing off to WDS.
+ // eslint-disable-next-line no-param-reassign
+ delete neutrinoConfigs.web;
},
+ '@flecks/core.build.config': () => [
+ /**
+ * Template file used to generate the client HTML.
+ *
+ * See: https://github.com/jantimon/html-webpack-plugin/blob/main/docs/template-option.md
+ */
+ 'template.ejs',
+ /**
+ * PostCSS config file.
+ *
+ * See: https://github.com/postcss/postcss#usage
+ */
+ 'postcss.config.js',
+ ],
+ '@flecks/core.config': () => ({
+ /**
+ * (webpack-dev-server) Disable the host check.
+ *
+ * See: https://github.com/webpack/webpack-dev-server/issues/887
+ */
+ devDisableHostCheck: false,
+ /**
+ * (webpack-dev-server) Host to bind.
+ */
+ devHost: 'localhost',
+ /**
+ * (webpack-dev-server) Port to bind.
+ */
+ devPort: undefined,
+ /**
+ * (webpack-dev-server) Public path to serve.
+ *
+ * Defaults to `flecks.get('@flecks/web/server.public')`.
+ */
+ devPublic: undefined,
+ /**
+ * (webpack-dev-server) Webpack stats output.
+ */
+ devStats: {
+ assets: false,
+ chunks: false,
+ colors: true,
+ modules: false,
+ },
+ /**
+ * Modules to externalize using `webpack.DllPlugin`.
+ */
+ dll: [],
+ /**
+ * Force building http target even if there's a fleck target.
+ */
+ forceBuildWithFleck: false,
+ /**
+ * Host to bind.
+ */
+ host: '0.0.0.0',
+ /**
+ * Build path.
+ */
+ output: 'web',
+ /**
+ * Port to bind.
+ */
+ port: 32340,
+ /**
+ * Public path to server.
+ */
+ public: 'localhost:32340',
+ /**
+ * Webpack stats configuration when building HTTP target.
+ */
+ stats: {
+ children: false,
+ chunks: false,
+ colors: true,
+ modules: false,
+ },
+ /**
+ * Proxies to trust.
+ *
+ * See: https://www.npmjs.com/package/proxy-addr
+ */
+ trust: false,
+ }),
+ '@flecks/core.starting': (flecks) => {
+ debug('bootstrapping flecks...');
+ const webFlecks = Flecks.bootstrap({
+ config: flecks.config,
+ platforms: ['client', '!server'],
+ });
+ debug('bootstrapped');
+ flecks.set('$flecks/web.flecks', webFlecks);
+ },
+ '@flecks/core.targets': (flecks) => [
+ 'web',
+ ...(flecks.get('@flecks/web/server.dll').length > 0 ? ['web-vendor'] : []),
+ ],
+ '@flecks/web.routes': (flecks) => [
+ {
+ method: 'get',
+ path: '/flecks.config.js',
+ middleware: async (req, res) => {
+ res.setHeader('Content-Type', 'application/javascript; charset=UTF-8');
+ res.send(await configSource(flecks, req));
+ },
+ },
+ ],
+ '@flecks/web/server.stream.html': inlineConfig,
+ '@flecks/server.up': (flecks) => createHttpServer(flecks),
+ '@flecks/repl.context': (flecks) => ({
+ httpServer: flecks.get('$flecks/web/server.instance'),
+ }),
};