Collector commands
The collector provides a core event processing engine that manages destinations,
consent, user data, and custom properties. Commands are executed through the
elb function, which the collector also exposes as collector.elb.
Unknown walker commands log a warning and return { ok: false }. Commands like
walker init are browser-specific and handled by the browser source before
they reach the collector.
For browser-specific commands like DOM initialization and elbLayer communication, see Browser Source Commands.
destination
Add destinations to the collector. The recommended approach is to configure
destinations during initialization with startFlow():
import { startFlow } from '@walkeros/collector';
import { destinationGtag } from '@walkeros/web-destination-gtag';
const { collector, elb } = await startFlow({
destinations: {
gtag: {
code: destinationGtag,
config: {
settings: { /* custom config */ },
},
},
},
});For dynamic scenarios requiring runtime destination addition, use
elb('walker destination') command or collector.addDestination(). See destination-specific documentation for
configuration options.
consent
Manage consent states for the collector. Names can be defined arbitrarily, but
common groups are functional, analytics, and marketing. Values are booleans, and
once a value is set to true it's treated as consent being granted.
elb('walker consent', { marketing: true, analytics: true });Setting a consent state to false will immediately stop a destination from
processing any events. Previously pushed events during the run are shared with
existing destinations once consent is granted.
Learn more about consent management in detail.
on
Add event listeners to the collector. They get called when specific events occur
like run or consent changes.
elb('walker on', { type, rules });rules depends on type and can also be an array for multiple listeners
at once. Every callback receives (data, context) where context exposes
collector and logger.
Callback signatures
| Action | Callback signature |
|---|---|
| consent | { [key]: (consent, context) => void | Promise<void> } |
| ready | (_, context) => void | Promise<void> |
| run | (_, context) => void | Promise<void> |
| session | (session, context) => void | Promise<void> |
| user | (user, context) => void | Promise<void> |
| (other) | (data, context) => void | Promise<void> |
The context object has the same shape for every action:
| Field | Type | Description |
|---|---|---|
collector | Collector.Instance | Active collector, use for push/queue |
logger | Logger.Instance | Use for info/warn/error/debug |
run
With each run, the on-event will be called. Use context.collector to access
the running instance.
elb('walker on', {
type: 'run',
rules: function (_, context) {
console.log('run with', { instance: context.collector });
},
});Every time the run command is called, the function will be executed:
// Setup collector with browser source
const { collector } = await startFlow({ run: true });
// Output: run with { instance: { ... } }
elb('walker run');
// Output: run with { instance: { ... } }consent
Every time the consent changes, the rules-matching function(s) will be called
with consent as the first argument and context (carrying collector and
logger) as the second.
function onConsent(consent, context) {
console.log('consent with', { consent, instance: context.collector });
if (consent.marketing) context.collector.push('walker user', readFromStorage());
}
elb('walker on', {
type: 'consent',
rules: { marketing: onConsent },
});The onConsent function will only be called when the marketing consent
changes:
elb('walker consent', { functional: true }); // Won't trigger the onConsent function
elb('walker consent', { marketing: true }); // Will trigger the onConsent functionuser
Set user identification data for the collector. There are three levels: user (company's internal ID), device (longer-term identifier), and session (temporary identification).
elb('walker user', { id: 'us3r', device: 'c00k13', session: 's3ss10n' });User IDs are added to each event.
{
"event": "entity action",
"user": {
"id": "us3r",
"device": "c00k13",
"session": "s3ss10n"
}
// other properties omitted
}Use fully anonymized & arbitrary IDs by default and check your options with persistent user IDs with your data protection officer.
Learn more about identification and user stitching
You can also set user identity declaratively from the DOM with the
data-elbuser attribute. See the browser source's
HTML attributes.
custom
Set custom properties that are added to each event processed by the collector.
elb('walker custom', { key: 'value' });globals
Set global properties that are added to each event processed by the collector.
elb('walker globals', { key: 'value' });These values are the base for every event the collector processes, including
events from server sources and any source with no DOM to read. A browser
source adds what it finds in data-elbglobals on top, and those values win
per key. Defaults that should survive a walker run belong in the collector's
globalsStatic config instead: it seeds the base at startup, and a run that
supplies its own globals resets the base to it. An event carries the globals
known when it was created, so a global set after an event is captured does not
reach one already waiting, whether it waits for consent, for a destination's
require gate, or for a destination to be added at all.
A transformer that runs before the collector, in a source's next chain, sees
the event before enrichment and therefore before the base globals are merged.
State delivery
State commands (consent, user, globals, custom) set values on the
collector. The collector records every state change immediately, even when it
arrives before run. Side-effecting delivery to subscribers (the on
callbacks above, and source on handlers) happens at or after run: when the
collector starts, it delivers the current state once to every subscriber that
has not yet seen it.
This gives three practical guarantees:
- Exactly-once. Each subscriber is invoked once per state change. The collector tracks what each subscriber has already received, so re-running or re-registering never double-fires a reaction.
- Order-independent. A consent-gated reaction fires correctly whether the
state was set before or after
run, and regardless of the order in which sources initialize. You don't have to set state and start the collector in a particular sequence. requireis a timing hint, not a correctness dependency. A source'srequireonly delays when its firstondelivery lands. A dependent source reacts to state correctly whether or not it declaresrequire.
This means sources don't need their own deduplication for state deliveries: the collector enforces exactly-once.
Pre-run events
The same record-immediately, deliver-at-run rule applies to events. An event pushed while the collector has not run yet is held in a bounded FIFO buffer instead of being discarded, and replayed once the collector starts:
const { collector, elb } = await startFlow({ run: false, destinations: { ... } });
await elb('walker consent', { essential: true });
await elb({ name: 'page view' }); // held, not delivered yet
await elb('walker run'); // 'page view' is delivered now- Replayed in push order. Held events reach destinations in the order they
were pushed, after the run-barrier state delivery, so each one carries the
consent, user, globals and custom state that is current at
run. - The pipeline runs once, at replay. Mapping, transformer chains and enrichment are applied when the event is replayed, not when it is held.
- Exactly-once. A second
rundoes not re-deliver events an earlier run already replayed. - Bounded. The buffer holds at most
queueMaxevents (default1000, shared with the destination replay buffer) and drops the oldest entries on overflow. Each hold and drop is logged atdebuglevel.
Commands are unaffected: they continue to apply immediately, as described above.
hook
Hooks customize the default behavior of the collector. Available hooks include
Push, DestinationInit, DestinationPush, StoreGet, StoreSet, and
StoreDelete. Hooks allow for validation, manipulation, or cancellation of
default behavior.
Add hooks to the collector to customize or enhance default processing.
elb('walker hook', { name: '<moment>', fn: hookFn });Moments
The overall function execution order is as follows:
- prePush
- preDestinationInit
- postDestinationInit
- preDestinationPush or preDestinationPushBatch
- postDestinationPush or postDestinationPushBatch
- postPush
Others are:
- preSessionStart
- postSessionStart
- preStoreGet / postStoreGet
- preStoreSet / postStoreSet
- preStoreDelete / postStoreDelete
Function signatures
In general, params will be prefixed as a parameter, containing fn which is
the original function and result for the post-hooks. Use the following
function signatures:
// Push
function prePush(params, event, data, options, context, nested) {
return params.fn(event, data, options, context, nested);
}
function postPush(params, event, data, trigger, context, nested) {
console.log('default return result', params.result);
return;
}
// DestinationInit
function preDestinationInit(params, config) {
return params.fn(config);
}
function postDestinationInit(params, config) {
console.log('default return result', params.result);
return params.result;
}
// DestinationPush
function preDestinationPush(params, event, config, mapping, runState) {
console.log('default return result', params.result);
return params.fn(event, config, mapping, runState);
}
function postDestinationPush(params) {
// Custom code with a void return
return;
}
// DestinationPushBatch
function preDestinationPushBatch(params, event, config, mapping, runState) {
console.log('default return result', params.result);
return params.fn(event, config, mapping, runState);
}
function postDestinationPushBatch(params) {
// Custom code with a void return
return;
}
// StoreGet
function preStoreGet(params, key) {
return params.fn(key);
}
function postStoreGet(params, key) {
return params.result;
}
// StoreSet
function preStoreSet(params, key, value, ttl) {
return params.fn(key, value, ttl);
}
function postStoreSet(params, key, value, ttl) {
return params.result;
}
// StoreDelete
function preStoreDelete(params, key) {
return params.fn(key);
}
function postStoreDelete(params, key) {
return params.result;
}Adding a hook
Add hooks during collector initialization or via the hook command:
// Add hooks during initialization
const { collector } = await startFlow({
hooks: {
prePush: (params, ...args) => {
window.elbTimer = Date.now();
return params.fn(...args);
},
},
});
// Add hooks via command
elb('walker hook', {
name: 'postPush',
fn: function (params, ...args) {
console.log('walker exec time', Date.now() - window.elbTimer);
},
});
elb('entity action');
// Output:
// walker exec time 1