بنقرة واحدة
walkeros-understanding-mapping
// Use when transforming walkerOS events in the flow (source→collector or collector→destination), configuring data/map/loop/set/condition/policy, or using $code: syntax in JSON configs.
// Use when transforming walkerOS events in the flow (source→collector or collector→destination), configuring data/map/loop/set/condition/policy, or using $code: syntax in JSON configs.
| name | walkeros-understanding-mapping |
| description | Use when transforming walkerOS events in the flow (source→collector or collector→destination), configuring data/map/loop/set/condition/policy, or using $code: syntax in JSON configs. |
Mapping transforms data at multiple points in the walkerOS flow:
Core principle: Mapping is the universal transformation layer. Same strategies work everywhere in the flow.
See packages/core/src/mapping.ts for implementation.
| Function | Purpose |
|---|---|
getMappingEvent(event, rules) | Find mapping rule for an event |
getMappingValue(value, data, options) | Transform a value using mapping config |
processEventMapping(event, config, collector) | Unified processing for sources/destinations |
1. Apply config.policy (modifies event)
2. Find matching rule via getMappingEvent()
3. Apply rule.policy (modifies event)
4. Transform config.data (global)
5. Check rule.ignore (short-circuits if true)
5b. Read rule.silent (informational - destination honors it)
6. Override event.name if rule.name
7. Transform rule.data (event-specific)
interface Config {
consent?: Consent; // Required consent for ALL events
data?: Value; // Global data transformation
include?: string[]; // Event sections to flatten into context.data
policy?: Policy; // Pre-processing for ALL events
mapping?: Rules; // Event-specific rules
}
include lists event sections to flatten into the destination's
PushContext.data before the rule runs. Sections are top-level keys of the
event (globals, user, consent, data, context, nested, source,
custom). The runtime helper flattenIncludeSections (in
packages/core/src/include.ts) handles the merge.
Use this when your mapping needs values from multiple event sections without
plumbing each path through a key:/map: config. include is also available
on Mapping.Rule (rule-level overrides config-level).
{
include: ['globals', 'user'],
mapping: {
page: {
view: {
// user.id and globals.* are now reachable via top-level paths in
// data/key/map values, e.g. `key: 'id'` for user.id.
data: { map: { user_id: 'id', site: 'language' } },
},
},
},
}
interface Rule {
name?: string; // Override event name
data?: Value; // Event-specific data transformation
include?: string[]; // Event sections to flatten into context.data (rule-level)
ignore?: boolean; // Skip this event entirely (no processing, no push)
silent?: boolean; // Process settings side effects, skip destination default push
policy?: Policy; // Event-specific pre-processing
condition?: Function; // Match condition (for arrays)
consent?: Consent; // Required consent for this rule
settings?: unknown; // Custom event configuration
batch?: number; // Batch size for grouping
extend?: RulePatch; // Config-layer merge onto a package-shipped default rule
remove?: string[]; // Output-layer: dotted paths stripped from the final payload
}
extend and remove (patching package-shipped rules)Some packages ship their own default mapping rules. A user rule at the same key normally replaces the default in full. Two keywords change that:
extend (config layer): a partial rule deep-merged onto the
package-shipped default at init, before any event is evaluated. A null value
clears an inherited field. Lets you add or override one field while keeping
the rest.remove (output layer): dotted paths stripped from the produced payload
after evaluation, applied last. Useful for dropping PII or vendor-reserved
fields without rewriting the full rule.A rule with neither keyword keeps the standard replace behavior.
See the website mapping docs for the authoritative reference and the GA4 example.
{
"purchase": {
"extend": {
"data": { "map": { "affiliation": "params.ep.affiliation" } }
},
"remove": ["currency"]
}
}
Both flags control rule behavior but have different semantics:
ignore: true - the rule matched but nothing happens. No data transform,
no destination call, no side effects. Use for suppression.silent: true - the rule matched and the destination push() is called.
settings.identify, settings.revenue, settings.group, etc. still run.
Only the destination's default forwarding call (e.g. track(), capture(),
event()) is suppressed. Use for "identify without an event" style flows.If both flags are set on the same rule, ignore wins.
Common use case: a user login event that should call amplitude.identify()
but should not create a separate track("user login") event in Amplitude.
interface ValueConfig {
key?: string; // Extract from path
value?: Primitive; // Static fallback value
fn?: Function; // Custom transformation
map?: Record; // Object transformation
loop?: [path, config]; // Array transformation
set?: Value[]; // Create array from values
condition?: Function; // Conditional extraction
consent?: Consent; // Consent-gated extraction
validate?: Function; // Value validation
}
Match events to transformation rules by entity and action.
const mapping = {
// Exact match: "product view" → view_item
product: {
view: { name: 'view_item' },
add: { name: 'add_to_cart' },
},
// Wildcard action: "foo *" → foo_interaction
foo: {
'*': { name: 'foo_interaction' },
},
// Wildcard entity: "* click" → generic_click
'*': {
click: { name: 'generic_click' },
},
};
Array of rules - first matching condition wins:
order: {
complete: [
{
condition: (event) => event.data?.value > 100,
name: 'high_value_purchase',
},
{ name: 'purchase' }, // Fallback (no condition)
],
}
JSON with $code:
{
"order": {
"complete": [
{
"condition": "$code:(event) => event.data?.value > 100",
"name": "high_value_purchase"
},
{ "name": "purchase" }
]
}
}
Common patterns shown below. For detailed examples of all 12 strategies, see value-strategies.md.
// Key extraction (string shorthand)
'data.price' // → event.data.price
// Key with fallback
{ key: 'data.currency', value: 'USD' } // Use USD if missing
// Static value
{ value: 'USD' }
// Function transform
{ fn: (event) => event.data.price * 100 } // Convert to cents
// Object map
{ map: { item_id: 'data.id', item_name: 'data.name' } }
// Array loop
{ loop: ['nested', { map: { item_id: 'data.id' } }] }
// Loop with "this" (single item as array)
{ loop: ['this', { map: { item_id: 'data.id' } }] }
// Set (create array)
{ set: ['data.id'] } // → ["SKU-123"]
// Fallback chain: Value[] at any value position (first defined value wins)
[{ key: 'data.sku' }, { key: 'data.id' }, { value: 'unknown' }]
// Consent-gated
{ key: 'user.email', consent: { marketing: true } }
// Validate
{ key: 'data.email', validate: (v) => v.includes('@') }
Policy modifies the event BEFORE mapping rules are applied. Use for:
Applied to ALL events:
config: {
policy: {
'user_data.external_id': 'user.id',
'custom_data.server_processed': { value: true },
},
mapping: { /* ... */ }
}
Applied after config policy, only for specific event:
mapping: {
order: {
complete: {
policy: {
'enriched.total_cents': {
fn: (event) => Math.round(event.data.total * 100)
}
},
name: 'purchase',
data: { /* ... */ }
}
}
}
{
"policy": {
"user_data.em": {
"key": "user.email",
"consent": { "marketing": true }
}
}
}
mapping: {
test: { '*': { ignore: true } }, // Ignore all test events
}
mapping: {
'*': {
'*': {
batch: 5, // Send after 5 events
}
}
}
mapping: {
order: {
complete: {
name: 'purchase',
settings: { priority: 'high', retryCount: 3 }
}
}
}
The $code: prefix enables JavaScript functions in JSON configurations:
{
"fn": "$code:(event) => event.data.price * 100",
"condition": "$code:(event) => event.data?.value > 100",
"validate": "$code:(value) => value > 0"
}
Important: The $code: prefix is processed by the CLI bundler. It converts
JSON strings to actual JavaScript functions during build.
All mapping callbacks share (value, context). The second argument is a
Mapping.Context object (see "Context object" below).
| Context | Signature |
|---|---|
fn | (value, context) => result |
condition (value) | (value, context) => boolean |
condition (rule) | (event, context) => boolean |
validate | (value, context) => boolean |
loop condition | (value, context) => boolean |
One-arg signatures like (value) => value.toUpperCase() continue to work,
TypeScript ignores the unused second arg.
| Field | Type | Required | Description |
|---|---|---|---|
event | WalkerOS.DeepPartialEvent | yes | The root event being mapped |
mapping | Mapping.Value | Mapping.Rule | yes | The surrounding mapping config (or rule) |
collector | Collector.Instance | yes | Active collector, use for push and queue |
logger | Logger.Instance | yes | Use for info/warn/error/debug |
consent | WalkerOS.Consent (optional) | no | Resolved consent at this evaluation point |
| Pattern | Result |
|---|---|
"data.id" | Extract event.data.id |
{ value: "USD" } | Static "USD" |
{ key: "x", value: "y" } | Extract x, fallback to "y" |
{ fn: (e) => ... } | Custom function |
{ map: {...} } | Object transformation |
{ loop: ["nested", {...}] } | Array transformation |
{ loop: ["this", {...}] } | Single-item as array |
{ set: ["a", "b"] } | Create array [valA, valB] |
[m1, m2, m3] | Fallback chain |
{ consent: {...} } | Consent-gated |
{ condition: fn } | Conditional |
{ validate: fn } | Validated |
| Feature | Purpose |
|---|---|
name | Override event name |
data | Transform event data |
ignore | Skip event entirely (no processing, no push) |
silent | Run settings side effects, skip default forwarding |
policy | Pre-process event |
condition | Match condition (arrays) |
consent | Required consent |
settings | Custom configuration |
batch | Batch size |
extend | Config-layer merge onto a package-shipped default (null clears a field) |
remove | Output-layer: dotted paths stripped from the final payload |
| Feature | Purpose |
|---|---|
consent | Required consent (all events) |
data | Global data transformation |
policy | Global pre-processing |
mapping | Event-specific rules |
For full destination configuration examples (TypeScript + JSON), see complete-examples.md.
Mapping.Config accepts the same shape at three positions in the flow, but the
semantic differs by position:
| Position | What the mapping produces |
|---|---|
| Source | A walkerOS event from raw input |
| Transformer | A mutated walkerOS event that continues through the chain |
| Destination | A vendor-shaped payload that the destination consumes |
When a transformer step declares only a mapping (no code, no package), the
collector synthesizes a push that runs processEventMapping against each event.
Same keyword as the destination field, different semantic at this step position.
See
walkeros-understanding-transformers
for the pass-through-step model.
Only event-mutating fields run; vendor-payload fields are no-ops with a one-time init warning:
| Field | Transformer position |
|---|---|
policy | Applies, pre-processes the event before rule matching |
include | Applies, flattens event sections into mapping context |
mapping[].policy | Applies, per-event policy |
mapping[].name | Applies, renames the event (mutation is observable downstream) |
mapping[].ignore | Applies, drops the event from the chain entirely (no downstream step sees it) |
mapping[].consent | Applies, consent gate |
data, mapping[].data | Ignored at this position (event mutation does not produce a vendor payload) |
mapping[].silent | Ignored at this position (destination-only concept) |
Note the ignore: true semantic shift: at a destination it skips delivery
to that destination only; at a transformer step it drops the event from the
chain so no downstream step (transformer or destination) sees it.
{
"transformers": {
"redactPII": {
"mapping": {
"policy": {
"user.email": { "value": "[redacted]" }
},
"mapping": {
"test": {
"*": { "ignore": true }
},
"order": {
"complete": { "name": "purchase" }
}
}
}
}
}
}
| Location | Purpose |
|---|---|
| Source config | Transform raw input → walkerOS events |
| Transformer step config | Mutate walkerOS events in-flight |
| Destination config | Transform walkerOS events → vendor format |
packages/core/src/mapping.ts | Core mapping functions |
packages/core/src/types/mapping.ts | Type definitions |
packages/cli/examples/flow-complete.json | Comprehensive example (53 features) |
Source Files:
Detailed References:
Examples:
Use when adding read-through caching to a walkerOS store, memoizing a slow API/Sheets backing, composing multi-tier cache chains, or deduplicating concurrent store reads. Covers recipes, TTL choice, error policy, and observability counters.
Use when configuring walkerOS event mappings for specific use cases. Provides recipes for GA4, Meta, custom APIs, and common transformation patterns.
Use when bundling walkerOS flows, testing events with simulate/push, running local servers, validating configs, or configuring Flow JSON files.
Use when wiring `@walkeros/transformer-ga4` into a server flow, overriding default GA4 event mappings, dropping events, adding custom event keys, or troubleshooting GA4 Measurement Protocol decoding. Covers the `before`-chain wiring contract, configuration recipes, and per-field patching with extend/remove.
Use when writing or updating walkerOS documentation - README, website docs, or skills. Covers quality standards, example validation, and DRY patterns.
Use when learning walkerOS architecture, understanding data flow, or designing composable event pipelines. Covers Source→Collector→Destination pattern and separation of concerns.