Coordination strategies

Orbit provides another layer of abstraction on top of direct event observation and handling: a Coordinator. A coordinator manages a set of sources to which it applies a set of coordination strategies.

Why use a coordinator?

Since configuring event handlers is so straightforward, what’s the point of using a coordinator? There are several benefits:

Creating a coordinator

A coordinator is typically shared across an application and manages all of the coordination strategies between sources.

A coordinator can be created with sources and strategies:

import Coordinator from '@orbit/coordinator';
const coordinator = new Coordinator({
sources: [store, backup],
strategies: [backupStoreSync]
});

Or sources and strategies can be added / removed any time the coordinator is inactive:

import Coordinator from '@orbit/coordinator';
const coordinator = new Coordinator();
coordinator.addSource(store);
coordinator.addSource(backup);
coordinator.addStrategy(backupStoreSync);

Activating a coordinator

A coordinator won’t actually do anything until it’s been “activated”, which is an async process that activates all of the coordinator’s strategies:

coordinator.activate()
.then(() => {
console.log('Coordinator is active');
});

Note that you can assign a log-level when activating a coordinator, and it will be applied to all of the coordinator’s strategies:

import { LogLevel } from '@orbit/coordinator';
coordinator.activate({ logLevel: LogLevel.Info })
.then(() => {
console.log('Coordinator will be chatty');
});

Possible log levels include None, Errors, Warnings, and Info.

Deactivating a coordinator

If you want to temporarily disable a coordinator or change its settings, you can deactivate it:

coordinator.deactivate()
.then(() => {
console.log('Coordinator is inactive');
});

At this point you can add/remove strategies and/or sources.

Coordination strategies

Every Strategy has certain properties, including a name, the names of sources to which it applies, a log level, and a log prefix.

@orbit/coordinator ships with several standard strategies, which are discussed below. It’s also straightforward to create your own custom strategies.

Request strategies

Request strategies participate in the request flow. Every request strategy should be defined with:

Here are some example strategies that query / update a remote server pessimistically whenever a store is queried / updated:

import { RequestStrategy } from '@orbit/coordinator';
// Query the remote server whenever the store is queried
coordinator.addStrategy(new RequestStrategy({
source: 'store',
on: 'beforeQuery',
target: 'remote',
action: 'pull',
blocking: true
}));
// Update the remote server whenever the store is updated
coordinator.addStrategy(new RequestStrategy({
source: 'store',
on: 'beforeUpdate',
target: 'remote',
action: 'push',
blocking: true
}));

It’s possible to apply a filter function to a strategy so that it only applies to certain data. For instance, the following filter limits which queries should be handled by a remote server:

import { RequestStrategy } from '@orbit/coordinator';
// Only forward requests for planets on to the remote server
coordinator.addStrategy(new RequestStrategy({
source: 'store',
on: 'beforeQuery',
target: 'remote',
action: 'pull',
blocking: true,
filter(query) {
return query.expression.op === 'findRecords' &&
query.expression.type === 'planet'
}
}));

Sync strategies

Sync strategies participate in the sync flow. Every sync strategy should be defined with:

Sync strategies only observe the transform event and apply the sync method on the target.

The following strategy synchronizes any changes to the remote source with a store:

import { SyncStrategy } from '@orbit/coordinator';
// Sync all changes received from the remote server to the store
coordinator.addStrategy(new SyncStrategy({
source: 'remote',
target: 'store',
blocking: true
}));

As described above for request strategies, sync strategies can also accept a filter function to limit the applicability of a strategy. This can be useful to, say, only backup certain types of data to browser storage.

Event logging strategies

An event logging strategy can be applied to observe events on sources. By default, all events will be logged on all sources registered to a coordinator:

import { EventLoggingStrategy } from '@orbit/coordinator';
coordinator.addStrategy(new EventLoggingStrategy());

You may wish to only observe events on certain interfaces, which can be specified as follows:

coordinator.addStrategy(new EventLoggingStrategy({
interfaces: ['updatable', 'pushable', 'syncable']
}));

Valid interfaces include updatable, queryable, pushable, pullable, and syncable (note the lower case).

Furthermore, you may wish to only observe certain sources, which can be specified by name:

coordinator.addStrategy(new EventLoggingStrategy({
sources: ['remote', 'store']
}));

The event logging strategy will respect the log level that is specified when the coordinator is activated.

Log truncation strategies

As changes are applied to sources, their transform logs grow in size. A log truncation strategy will keep the size in check by observing the sources associated with the strategy and truncating their logs when a common transform has been applied to them all.

To add a log truncation strategy that applies to all sources:

import { LogTruncationStrategy } from '@orbit/coordinator';
coordinator.addStrategy(new LogTruncationStrategy());

To limit the strategy to apply to only specific sources:

coordinator.addStrategy(new LogTruncationStrategy({
sources: ['backup', 'store']
}));