Skip to main content

Create Your Own Source

A source captures events from an environment (browser, server, third-party API) and forwards them to the walkerOS collector.

The source interface

Sources are async functions that receive a context object and return a source instance:

Loading...

The context contains:

Loading...

The returned instance must implement:

Loading...

Types bundle

Sources use a Types interface to bundle all TypeScript types:

Loading...

The 4 type parameters:

  1. Settings: Source configuration options
  2. Mapping: Event mapping (usually never for sources)
  3. Push: External push signature (what source.push exposes)
  4. Env: Internal dependencies (what source calls via env.elb)

Context destructuring

The context parameter contains everything your source needs:

Loading...

The collector provides env.push (formerly env.elb). You provide other dependencies (like window, document, custom APIs) when configuring the source.

Per-scope context (server sources)

A single source factory instance handles many concurrent invocations: an Express server processes overlapping requests, a Lambda may be reused across invocations, a queue consumer processes batches. Each logical unit of work, an HTTP request, a queue message, a websocket frame, is a scope. To keep ingest metadata and response delegates isolated between concurrent scopes, server sources wrap each invocation with context.withScope:

Loading...

Inside the body, env.push carries the scope's ingest and respond all the way to destinations. Transformers and destinations read env.respond to delegate the HTTP response; context.ingest for source-extracted metadata. The scope ends when the body resolves.

Browser sources (and other single-scope sources) skip withScope. A browser tab is a single logical scope for its entire lifetime; calling env.push directly on the factory env is correct. Only server sources handling concurrent inbound work need withScope.

Minimal example

Loading...

Complete example: Event API source

Capturing events from a third-party API with event listeners:

Loading...

Using your source

Loading...

Testing your source

Test Utilities

Loading...

Test Example

Loading...

Reading a step back from the collector

When an integration test needs to call a step's raw push directly through the collector (a source's push(req, res), a destination's push(event, ctx), etc.), the bag types (collector.sources, collector.destinations, collector.transformers, collector.stores) erase the per-step generic. The push signature collapses to Elb.Fn on read.

Use the typed accessors from @walkeros/core to recover the narrow type without casts:

Loading...

Symmetric helpers exist for every step kind: Source.getSource, Destination.getDestination, Transformer.getTransformer, Store.getStore. Each throws <Kind> not found: <id> when the id is unknown. The helpers are opt-in and have no runtime cost.

Setup lifecycle (optional)

Sources may implement an optional setup() lifecycle for one-time, operator-time provisioning, for example registering a webhook callback or creating a Pub/Sub subscription. Setup runs only when an operator explicitly invokes walkeros setup source.<name>; the runtime never auto-invokes it. Opt in via config.setup in the flow config (true or an object).

See Setup lifecycle on the destinations page for the full concept, and walkeros setup CLI command for the operator-facing command.

Conditional activation with require

Sources can declare dependencies on collector events. A source with require won't receive lifecycle events (via on()) until all specified events have fired:

Loading...

How it works:

  • Sources are registered in collector.sources immediately, and the collector runs Instance.init() on each one eagerly after registration. Source.Config.init flips to true once init has run.
  • require does not gate code execution. It gates on() delivery. Lifecycle events targeted at a source whose require is unmet are buffered in the source's Instance.queueOn array.
  • After each collector event (consent, user, session, run, etc.), the collector decrements the matching entry from each source's require list. When a source's require is empty (and config.init === true), its queued lifecycle events are replayed by calling source.on(type, data) for each entry, then the queue is cleared.
  • Chains work naturally: consent clears the session source's require, which fires user, which clears the dataLayer source's require, which then receives any queued events.

Common patterns:

RequireUse case
['consent']Wait for CMP consent before tracking
['user']Wait for identity resolution
['session']Wait for session detection
['consent', 'run']Wait for both consent and collector run

Common patterns

Polling for API Readiness

When the external API isn't immediately available:

Loading...

Source as adapter pattern

Sources bridge external systems and the collector:

External System  ←→  Source (Adapter)  ←→  Collector

Two interfaces:

  1. External (source.push): Platform-specific signature

    • Browser: push(elem, data, options) → Returns Promise<PushResult>
    • Server: push(req, res) → Returns Promise<void> (writes HTTP response)
    • Your choice: Match your environment's needs
  2. Internal (env.elb): Standard collector interface

    • Always Elb.Fn - same across all sources
    • Sources translate external inputs → standard events → env.elb(event)

Example signatures:

Loading...

The Push type parameter defines what your source exposes externally. Internally, all sources use env.elb to forward to the collector.

Key concepts

  • Context pattern: Sources receive a single context object with config, env, logger, id
  • Validate custom dependencies: Only check optional env properties your source needs
  • Types bundle: Use 4-parameter pattern for full type safety
  • Adapter pattern: External push adapts to environment, internal env.push stays standard
  • Cleanup: Implement destroy() to remove listeners
  • Stateless: Let collector manage state

Package convention

Every walkerOS package includes machine-readable metadata for tooling and discovery.

walkerOS field in package.json

Loading...
FieldRequiredDescription
walkerOSYesObject with type and platform metadata

Build-time generation

Use buildDev() from the shared tsup config to auto-generate walkerOS.json:

Loading...

This file contains your package's JSON Schemas and examples, enabling MCP tools and the CLI to validate configurations without installing your package.

Optional: Hints

Packages can export a hints record from src/dev.ts to provide lightweight, actionable context beyond schemas and examples, such as capture timing, event formats, or troubleshooting tips. Hints are serialized into walkerOS.json and surfaced via MCP tools. See the walkeros-create-source skill for details.

Publishing checklist

  • walkerOS field in package.json
  • Keywords include walkeros and walkeros-source
  • buildDev() in tsup.config.ts
  • dist/walkerOS.json generated on build
  • npm run test passes
  • npm run lint passes

Next steps

💡 Need implementation support?
elbwalker offers hands-on support: setup review, measurement planning, destination mapping, and live troubleshooting. Book a 2-hour session (€399)