flow:
dox, hook registration, ensureUniqueReduction, middleware, ...
This commit is contained in:
parent
23f2fae001
commit
c3910ba5f0
|
@ -1,7 +1,7 @@
|
|||
<div align="center">
|
||||
<h1>flecks</h1>
|
||||
<p>
|
||||
Flecks is a dynamic, configuration-driven, fullstack application production system. Its purpose
|
||||
Flecks is an exceptionally extensible fullstack application production system. Its true purpose
|
||||
is to make application development a more joyful endeavor. Intelligent defaults combined with
|
||||
a highly dynamic structure encourage consistency while allowing you to easily express your own
|
||||
opinions.
|
||||
|
|
3
TODO.md
3
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
|
||||
|
|
1
packages/core/QUIRKS.md
Normal file
1
packages/core/QUIRKS.md
Normal file
|
@ -0,0 +1 @@
|
|||
- I use the variable `r` a lot when referencing a reducer's accumulator value
|
|
@ -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')
|
||||
|
|
|
@ -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).
|
||||
|
||||
|
|
|
@ -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: [
|
||||
'<somearg>',
|
||||
],
|
||||
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: [
|
||||
'<somearg>',
|
||||
],
|
||||
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';
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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};
|
||||
}, {});
|
||||
};
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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: [],
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
51
packages/core/test/middleware.js
Normal file
51
packages/core/test/middleware.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
import {expect} from 'chai';
|
||||
|
||||
import {Flecks} from '@flecks/core';
|
||||
|
||||
const testOne = require('./one');
|
||||
const testTwo = require('./two');
|
||||
|
||||
it('can make middleware', (done) => {
|
||||
let flecks;
|
||||
let foo;
|
||||
let mw;
|
||||
flecks = new Flecks({
|
||||
config: {
|
||||
'@flecks/core/test': {
|
||||
middleware: [
|
||||
'@flecks/core/one',
|
||||
'@flecks/core/two',
|
||||
],
|
||||
},
|
||||
},
|
||||
flecks: {
|
||||
'@flecks/core/one': testOne,
|
||||
'@flecks/core/two': testTwo,
|
||||
},
|
||||
});
|
||||
foo = {bar: 1};
|
||||
mw = flecks.makeMiddleware('@flecks/core/test.middleware');
|
||||
mw(foo, () => {
|
||||
expect(foo.bar).to.equal(4);
|
||||
flecks = new Flecks({
|
||||
config: {
|
||||
'@flecks/core/test': {
|
||||
middleware: [
|
||||
'@flecks/core/two',
|
||||
'@flecks/core/one',
|
||||
],
|
||||
},
|
||||
},
|
||||
flecks: {
|
||||
'@flecks/core/one': testOne,
|
||||
'@flecks/core/two': testTwo,
|
||||
},
|
||||
});
|
||||
foo = {bar: 1};
|
||||
mw = flecks.makeMiddleware('@flecks/core/test.middleware');
|
||||
mw(foo, () => {
|
||||
expect(foo.bar).to.equal(3);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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();
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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$/))
|
||||
),
|
||||
};
|
||||
|
||||
|
|
|
@ -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'),
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -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},
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
|
|
|
@ -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)),
|
||||
);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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: {},
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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'],
|
||||
};
|
||||
|
|
|
@ -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(`<pre>${error.message}</pre>`);
|
||||
return;
|
||||
}
|
||||
req.ban = async (keys, ttl = 0) => {
|
||||
const ban = Ban.fromRequest(req, keys, ttl);
|
||||
await Ban.create({...ban});
|
||||
res.status(403).send(`<pre>${Ban.format([ban])}</pre>`);
|
||||
};
|
||||
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(`<pre>${Ban.format([ban])}</pre>`);
|
||||
}
|
||||
};
|
||||
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(`<pre>${error.message}</pre>`);
|
||||
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(`<pre>${Ban.format([ban])}</pre>`);
|
||||
};
|
||||
},
|
||||
'@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(`<pre>${Ban.format([ban])}</pre>`);
|
||||
}
|
||||
};
|
||||
},
|
||||
'@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]),
|
||||
]),
|
||||
)
|
||||
),
|
||||
};
|
||||
|
|
|
@ -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 `<Component />`, 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 `<Component />`, but `Component`.
|
||||
return [
|
||||
Component,
|
||||
[SomeOtherComponent, {prop: 'value'}]
|
||||
];
|
||||
// You can also just:
|
||||
return Component;
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -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');
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -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}]
|
||||
),
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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}] : []
|
||||
),
|
||||
};
|
||||
|
|
|
@ -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
|
||||
),
|
||||
};
|
||||
|
|
|
@ -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),
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -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),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -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$/))
|
||||
),
|
||||
};
|
||||
|
|
|
@ -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$/)),
|
||||
};
|
||||
|
|
|
@ -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}],
|
||||
};
|
||||
|
|
|
@ -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',
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -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),
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
import {Hooks} from '@flecks/core';
|
||||
|
||||
export default {
|
||||
[Hooks]: {
|
||||
'@flecks/core.targets': () => ['server'],
|
||||
},
|
||||
export const hooks = {
|
||||
'@flecks/core.targets': () => ['server'],
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -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`,
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -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`,
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
}),
|
||||
};
|
||||
|
||||
|
|
|
@ -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),
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
));
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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!');
|
||||
}
|
||||
|
|
|
@ -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'),
|
||||
}),
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue
Block a user