From walkeros
Explains walkerOS destinations: interface methods like push and init, env dependency injection with mocking for testing, lifecycle, config schema, paths, and consent mechanisms.
npx claudepluginhub elbwalker/walkerosThis skill uses the workspace's default tool permissions.
Destinations receive processed events from the collector and deliver them to
Creates isolated Git worktrees for feature branches with prioritized directory selection, gitignore safety checks, auto project setup for Node/Python/Rust/Go, and baseline verification.
Executes implementation plans in current session by dispatching fresh subagents per independent task, with two-stage reviews: spec compliance then code quality.
Dispatches parallel agents to independently tackle 2+ tasks like separate test failures or subsystems without shared state or dependencies.
Destinations receive processed events from the collector and deliver them to third-party tools (analytics, marketing, data warehouses).
Core principle: Destinations transform and deliver. They don't capture or process—that's sources and collector.
See packages/core/src/types/destination.ts for canonical interface.
| Method | Purpose | Required |
|---|---|---|
init(context) | Load scripts, authenticate | Optional |
push(event, context) | Transform and send event | Required |
pushBatch(batch, context) | Batch processing | Optional |
destroy(context) | Cleanup on shutdown | Optional |
config | Settings, mapping, consent | Required |
destroy?: DestroyFn — Optional cleanup method. Called during
command('shutdown'). Use to close DB connections, flush buffers, or release
SDK clients. Receives { id, config, env, logger }.
Destinations use dependency injection via env for external APIs. This enables
testing without mocking.
// Destination defines its env type
export interface Env extends DestinationWeb.Env {
window: {
gtag: Gtag.Gtag;
dataLayer: unknown[];
};
}
// Destination uses env, not globals
async function push(event, context) {
const { env } = context;
env.window.gtag('event', mappedName, mappedData);
}
REQUIRED SKILL: See testing-strategy for full testing patterns.
import { mockEnv } from '@walkeros/core';
import { examples } from '../dev';
const calls: Array<{ path: string[]; args: unknown[] }> = [];
const testEnv = mockEnv(examples.env.push, (path, args) => {
calls.push({ path, args });
});
await destination.push(event, { ...context, env: testEnv });
expect(calls).toContainEqual({
path: ['window', 'gtag'],
args: ['event', 'purchase', expect.any(Object)],
});
config: {
settings: { /* destination-specific */ },
mapping: { /* event transformation rules */ },
data: { /* global data mapping */ },
consent: { /* required consent states */ },
policy: { /* processing rules */ },
queue: boolean, // queue events
dryRun: boolean, // test mode
}
Two separate mechanisms control when destinations receive events:
| Mechanism | Purpose | Scope | Effect |
|---|---|---|---|
require | Delay initialization | Whole destination | Destination stays in pending until all required events fire (e.g., walker consent) |
consent | Filter events | Per-event | Events without matching consent are silently skipped or queued |
Require gates the destination lifecycle. A destination with
require: ["consent"] does not exist in the collector until a
"walker consent" event fires. Until then, events are queued internally.
Consent gates individual event delivery. A destination with
consent: { marketing: true } only receives events where the collector's
consent state (or the event's own consent field) includes
{ marketing: true }.
State refresh on flush: When queued events are flushed to a destination,
they receive the current collector state (consent, user, globals) — not
the stale state from when they were originally captured. Any state-mutation
command (walker consent, walker user, walker globals, etc.) triggers a
flush attempt. The consent gate still applies: events without required consent
simply return to the queue.
Both can be combined:
{
"config": {
"require": ["consent"],
"consent": { "marketing": true }
}
}
This means: don't initialize until consent fires, then only accept events with marketing consent.
Simulation impact: require causes "destination not found" errors in
flow_simulate because the destination stays pending. Remove require
temporarily for simulation testing.
Policy modifies the event BEFORE mapping rules run. Defined at config level (applies to all events) or rule level (applies to specific events):
{
"config": {
"policy": {
"user_data.email": {
"key": "user.email",
"consent": { "marketing": true }
}
}
}
}
Policy supports consent-gated field injection — fields are only added when the required consent is present in the event.
| Type | Path | Examples |
|---|---|---|
| Web | packages/web/destinations/ | gtag, meta, api, piwikpro, plausible |
| Server | packages/server/destinations/ | aws, gcp, meta |
Use as starting point: packages/web/destinations/plausible/
Destinations can wire to post-collector transformer chains via the before
property:
destinations: {
gtag: {
code: destinationGtag,
before: 'redact' // First transformer to run before this destination
}
}
The transformer chain runs after collector enrichment, before the destination receives events. Each destination can have its own chain. See understanding-transformers for chain details.
Destinations that integrate vendor SDKs typically need two consent layers:
Layer 1: config.consent — gates walkerOS event delivery. If consent is not
granted, events don't reach the destination. This is the primary barrier.
Layer 2: on('consent') — controls vendor SDK internals. Even when walkerOS
stops sending events, the vendor SDK may still run its own behaviors (DOM
capture, polling, fetching configs). Use on('consent') to pause/resume these.
on(type, context) {
if (type !== 'consent') return;
const consent = context.data;
// Derive from config.consent keys — don't hardcode consent names
const granted = Object.keys(config.consent || {}).every(k => consent[k]);
vendorSdk.setOptOut(!granted);
}
Both layers are needed for complete consent compliance. config.consent
prevents data flow. on('consent') prevents vendor SDK side effects.
Destinations can customize HTTP responses by calling
context.env.respond?.({ body, status?, headers? }). This is useful for
destinations that need to signal success/failure back to the HTTP caller. First
call wins (idempotent). The respond function is optional — only present when the
source provides one.
Source Files:
Package READMEs:
Documentation: