Skip to main content

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');
note

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.

Platform order

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 Gathereds (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:

src/index.js
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:

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,
}
Batteries included

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:

src/models/decorators/user.js
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:

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.

Be aware of your context

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,
}