An flexible aggregator component for your backend (or even frontend).
An implementation for entity source with cache (CachedEntitySource
) is also shipped in the library.
You can freely define how to gather your data by implementing your own lookup function.
Using npm
npm install @necrobits/aggregator
or using yarn
yarn add @necrobits/aggregator
Given the following data and getters
const users = [
{ id: 'A', name: 'Andy' },
{ id: 'B', name: 'Hai' }
];
const findUsers = ids => users.filter(user => ids.includes(user.id));
const todos = [
{ id: 'T1', task: 'Study' },
{ id: 'T2', task: 'Code' }
];
const findTodos = ids => todos.filter(todo => ids.includes(todo.id));
// You want aggregation for this data
const data = [
{
date: '22-02-2022',
tasks: [
{assigneeId: 'A', taskId: 'T1'}
{assigneeId: 'B', taskId: 'T2'}
],
},
// {...},
]
The code for the aggregation looks like this:
const userSource = new SimpleEntitySource('user', {
lookupUsing: findUsers,
entityIdBy: 'id',
});
const todoSource = new SimpleEntitySource('todo', {
lookupUsing: findTodos,
entityIdBy: 'id',
});
const aggregator = new Aggregator({
user: userSource,
todo: todoSource,
});
// or
const aggregator = new Aggregator();
aggregator.register('user', userSource).register('todo', todoSource);
Use the aggregator
const opts = {
'tasks.*.assigneeId': {
source: 'user',
to: {
key: 'assignee',
},
removeIdKey: true,
},
'tasks.*.taskId': {
source: 'todo',
removeIdKey: true,
},
};
const aggregatedData = await aggregator.aggregate(data, opts);
console.log(aggregatedData);
Output:
[{
date: '22-02-2022',
tasks: [
{
id: 'T1',
name: 'Study'
assignee: {
id: 'A',
name: 'Andy'
}
},
{
id: 'T2',
name: 'Code',
assignee: {
id: 'B',
name: 'Hai'
}
}
]
},
{...}]
The syntax to define a aggregation process is as follows:
{
"<path to the object's ID>": {
//<Aggregation Options>
}
}
While declare a path to the object's ID, sometimes you have to access to an array. You can simply use *
to tell the aggregator to process every element in that array (see example above).
However, if the data itself is an array, you don't need to use the asterik *
at the beginning. The aggregator can recognize that automatically. Meaning, don't write *.userId
if you have an array of multiple objects that contains userId
,
[{ userId: '1' }, { userId: '2' }];
you can simply use userId
directly.
{
"userId":{
// options
}
}
Name | Type | Description | Required | Default |
---|---|---|---|---|
source | string | Name of the entity source to gather the data | Yes | |
to | - | If specified, the result is put into another key | ||
to.key | string | The name of the new field to inject the data into | If to is specified |
|
to.omitNull | boolean | If the lookuped object is null, remove the key | No | false |
removeIdKey | boolean | Remove the id field after injecting the data | No | false |
transform | (any) => (any) | A function to transform the data before the injection | No | Identity function (v) => v |
You can implement an adapter that implements the EntityCache
interface to use cache in CachedEntitySource
.
This is an example for node-cache
. You can also use Typescript if you want to.
export class NodeCacheAdapter<T> implements EntityCache<T> {
constructor(private nodeCache: NodeCache) {}
async invalidate(keys: string[]): Promise<void> {
this.nodeCache.del(keys);
return;
}
async get(key: string): Promise<T> {
return this.nodeCache.get(key);
}
async setBatch(batch: { key: string; value: any }[]): Promise<void> {
this.nodeCache.mset(
batch.map((b) => ({
key: b.key,
val: b.value,
}))
);
}
}
const cache = new NodeCache();
const userSource = new CachedEntitySource<User>("user",{
cache: new NodeCacheAdapter<User>(cache);
lookupUsing: findUsers,
entityIdBy: "id"
});
Name | Type | Description |
---|---|---|
cache | EntityCache | The cache instance that implements the EntityCache interface |
lookupUsing | EntityLookupFunction (string[]) => (T[] | Promise<T[]>) |
A function that receives an array of IDs and returns an array of entities (or an Promise) |
entityIdBy | string | (T) => string | The name of the ID field in the entity, or a function that receives an entity and returns its ID. |
MIT