Gathering and Providing
Gathering is useful when your fleck defines some sort of specification class, and then expects its sibling flecks to extend it. Some common examples:
- Database models, defined through
@flecks/db.models
. - Socket packets, defined through
@flecks/socket.packets
.
Basics
One constraint of using flecks.gather()
is that whatever you are gathering must be able to be
extended as a class. You can't flecks.gather()
plain objects, numbers, strings... you get the
idea.
Suppose the flecks implementing the hook @my/fleck.whatever
above returned two classes, Foo
and
Bar
. In this case, Gathered
would be structured as:
import {ById, ByType} from '@flecks/core';
const Gathered = flecks.gather('@my/fleck.whatever');
const Gathered = {
1: Bar,
2: Foo,
Bar,
Foo,
[ById]: {
1: Bar,
2: Foo,
},
[ByType]: {
Bar,
Foo,
},
};
IDs and types
flecks.gather()
gives each of your classes a numeric (nonzero) ID as well as a type name. It also
merges all numeric keys and type labels together into the result, so Gathered[1] === Gathered.Bar
would evaluate to true
in the example above.
The symbol keys ById
and ByType
are useful if you need to iterate over either all IDs or all
types. Since the numeric IDs and types are merged, iterating over the entire Gathered
object
would otherwise result in duplicates.
Each class gathered by flecks.gather()
will be extended with two properties by default: id
and type
. These correspond to the ID and type referenced above.
Following from the example above:
const foo = new Gathered.Foo();
assert(foo.id === 2);
assert(foo.type === 'Foo');
Options
flecks.gather()
also supports some options:
{
// The property added when extending the class to return the numeric ID.
idProperty = 'id',
// The property added when extending the class to return the type.
typeProperty = 'type',
// A function called with the `Gathered` object to allow checking validity.
check = () => {},
}
As an example, when @flecks/db
gathers models, typeProperty
is set to name
, because
Sequelize requires its model classes to have a unique name
property.
Gathering with @flecks/core.gathered
Instead of using flecks.gather()
yourself, your fleck may implement the hook
@flecks/core.gathered
which will automatically gather for you.
Let's assume we gather using:
const Gathered = flecks.gather('@my/fleck.whatever', {typeProperty: 'whateverType'});
The equivalent of this using @flecks/core.gathered
would be:
export const hooks = {
'@flecks/core.gathered': () => ({
whatever: {typeProperty: 'whateverType'},
}),
}
and then accessing the result through:
const Gathered = flecks.gathered('@my/fleck.whatever');
Notice that we only specified whatever
. We assume the hook above is implemented in a fleck
called @my/fleck
. flecks automatically builds the full gather hook name by combining your fleck's
name and the key.
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 (or @flecks/core.gathered
invoked) equivalently on both the client and the server.
If you have serializable Gathered
s (e.g. Socket packets), they should be invoked
and defined in @your/fleck
, and not in e.g. @your/fleck/server
, so that they are included for
every platform.
If each platform must implement the hook differently, be vigilant!
Providing through Flecks.provide(context, options)
with require.context
Complementary to above, Flecks.provide()
helps you to ergonomically provide your
flecks' implementations to a gather hook.
Here's an example of how you could manually provide @flecks/db.models
in your own fleck:
import SomeModel from './models/some-model';
import AnotherModel from './models/another-model';
export const hooks = {
'@flecks/db.models': () => ({
SomeModel,
AnotherModel,
}),
}
It will become a lot of typing to keep adding new models over time. Flecks.provide()
exists to
reduce this maintenance burden for you.
Webpack provides an API called require.context which imports many files with a pattern.
Supposing our fleck is structured like so:
src
├─ index.js
└─ models/
├─ some-model.js
└─ another-model.js
then, this src/index.js
:
import {Flecks} from '@flecks/core';
export const hooks = {
'@flecks/db.models': Flecks.provide(require.context('./models')),
};
is exactly equivalent to the src/index.js
above. By default, Flecks.provide()
CamelCases
the paths, so e.g. some-model
becomes SomeModel
.
Options
Flecks.provide()
also supports some options:
{
// Whether to invoke the default export as a function.
invoke = true,
// The transformation used on the path.
transformer = camelCase,
}
There is no hard requirement to use Flecks.provide()
, it is merely a convenience.
Decorating with Flecks.decorate(context, options)
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:
export const hooks = {
'@flecks/db.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!');
}
},
};
},
};
As with above, there exists an API for making the maintenance of decorators more ergonomic.
Supposing our fleck is structured like so:
src
├─ index.js
└─ models/
└─ decorators/
└─ user.js
and supposing that src/models/decorators/user.js
is written like so:
export default (User) => {
return class extends User {
// Let's mix in some logging...
constructor(...args) {
super(...args);
console.log ('Another user decorated!');
}
};
};
then, this src/index.js
:
import {Flecks} from '@flecks/core';
export const hooks = {
'@flecks/db.models.decorate': (
Flecks.decorate(require.context('./models/decorators'))
),
};
is exactly equivalent to the src/index.js
above.
If you have defined models in src/models
and model decorators in src/model/decorators
as
illustrated, be mindful of how
require.context
is gathering your files: particularly the useSubdirectories
flag which is true
by default.
Options
Flecks.decorate()
also supports some options:
{
// The transformation used on the class path.
transformer = camelCase,
}