Releases: elbwalker/walkerOS
walkerOS v4.1.1
Changes
The simulate functions (simulateSource, simulateTransformer,
simulateDestination) now return the unified Simulation.Result shape with
captured events and intercepted calls, instead of the internal push result.
PushResult no longer carries the simulate-only captured, usage, and
perDestination fields.
Rename the contract inheritance key from extends to extend for consistency
with the rest of the flow config vocabulary. Contracts that inherit from another
named contract now use "extend": "<name>".
Add the d8a.tech web destination for a GA4-compatible and warehouse-native
analytics platform.
Export the $-ref completion builders (getVariableCompletions,
getEnvCompletions, getStoreCompletions, getFlowCompletions,
getSecretCompletions) and the CompletionEntry type from the package barrel
so custom inputs can reuse them outside Monaco.
Export getMappingPathCompletions and getContractCompletions so custom form
inputs can seed contract-driven path and $contract reference suggestions
outside Monaco. Add allowedRefKinds (the cursor-scoped $-ref gate, now
covering $contract) and getJsonPathAtOffset, and gate the open $
completion fallback by the same scope rule so Monaco offers only the ref kinds
valid at the cursor.
Add Google Ads Enhanced Conversions support. Set enhancedConversions on the
ads settings to map event fields to Google's user_data, sent before each
conversion.
The project, flow, and deployment list operations now accept optional cursor
and limit arguments and return a nextCursor to fetch the next page. Listing
without these arguments is unchanged and returns all results. In the MCP, the
project_manage, flow_manage, and deploy_manage tools expose cursor and
limit on their list action.
Add extend and remove to mapping rules. extend deep-merges a partial rule
onto a package-shipped default (a null value clears an inherited field);
remove strips fields from the produced payload. Rules without either keyword
keep the existing replace behavior.
Send the Conversions API access token in an Authorization: Bearer header
instead of the URL query string, so the credential no longer appears in server,
proxy, or APM request logs.
The collector exposes observers: Set<ObserverFn> so any subscriber can watch
every step of the pipeline. Each source, transformer, destination, and store
call emits a FlowState record with timings, mapping match, consent state, and
skip reasons. createTelemetryObserver from @walkeros/core batches emissions
to an HTTP endpoint, and the CLI runtime picks up the traceUntil flag from its
heartbeat so trace mode toggles take effect without a redeploy.
Fix latent type errors in Panel props and attribute tree builder, and enable
typecheck script so the package participates in CI type coverage.
New server-side bot and AI-agent detection transformer. Annotates events with
user.botScore, user.agentScore, and optionally user.agentProduct (the
matched user-agent, e.g. 'ChatGPT-User'). It wraps isbot and a curated
AI-agent UA map. Annotate-only — events are never dropped; destinations filter
via mapping.
Web requests sent via fetch now support a configurable timeout, defaulting to 10
seconds. A stalled network request is aborted instead of hanging indefinitely.
The browser source now releases its DOM event listeners, pulse intervals, and
wait timeouts when the source is destroyed. This prevents memory growth and
avoids duplicate events when the source is torn down or re-initialized.
Published Packages
- @walkeros/cli
- @walkeros/collector
- @walkeros/core
- @walkeros/explorer
- @walkeros/mcp
- @walkeros/server-destination-meta
- @walkeros/server-transformer-bot
- @walkeros/storybook-addon
- @walkeros/transformer-ga4
- @walkeros/web-core
- @walkeros/web-destination-d8a
- @walkeros/web-destination-gtag
- @walkeros/web-source-browser
walkerOS v4.1.0
Changes
Cache reads through checkCache are now correct against async stores
(filesystem, Redis, any store with an async get). Previously a custom async
store could silently miss the cache.
checkCache returns a Promise. External callers must add await.
The BigQuery Storage Write API data plane now authenticates from
settings.bigquery (e.g. keyFilename, credentials) instead of always
falling back to Application Default Credentials. Service-account auth configured
for setup now also applies to event ingestion.
Fix getByPath silently returning undefined for objects created in a different
realm (Node http.IncomingMessage, vm contexts, worker threads, iframes). The
internal instanceof Object guard is replaced with a cross-realm-safe check, so
dot-notation paths now extract fields from native request objects and other
cross-realm sources.
Collector and destination buffers are now size-bounded with FIFO drop-oldest
eviction. Defaults: collector queueMax: 1000, destination queueMax: 1000,
destination dlqMax: 100. Set either knob to override per scope. Drop counts
surface in collector.status.dropped and collector.status.destinations[id].
Fix resolveContracts so a child contract that uses extends inherits the
parent's tagging when it does not redeclare it. Previously the parent's
tagging was silently dropped, which corrupted contract version tracking for
anyone building on a base contract.
Flow v4 routing & cache cleanup.
Cache:
cache.fullis renamed tocache.stop. Search-and-replace.cacheRule.matchis now optional. Omitted means always-match. The literal
'*'is dropped from the schema and the TypeScript types;compileMatcher
still tolerates the string at runtime for migration.- New
cache.namespace?: stringfield. Omit to write keys directly to the
store. Same store + same key + same namespace = same cache entry. - Implicit per-step namespace prefixes (
s:,t:,d:) are removed. If you
relied on them to separate same-keyed caches across
sources/transformers/destinations using the same store, setcache.namespace
explicitly.
Routing: - Unified recursive
Routetype. A Route isstring | Route[] | RouteConfig. - New
caseoperator replaces the legacyRoute[]first-match shape. The
legacy shape is compiled as an implicit{ case: [...] }for runtime
compatibility, but new configs should usecaseexplicitly. RouteConfigis a disjoint union enforced at the TypeScript type level via
neverfields: a single RouteConfig sets at most one ofnext/case. A
bare{ match }is a gate (pass-through when the match fires, fall-through
when it fails). JSON Schema validation currently emitsanyOfand does not
enforce disjointness at runtime — see follow-up notes.- Sequence sugar (
next: [A, B, C]) is preserved.
Path: - A transformer entry with no
codeis apath— a code-less passthrough. The
engine synthesizes(e) => ({ event: e }). Use paths to name and share
beforechains across destinations. Validation: a path must declare at least
one ofpackage,before,next, orcache.
Schema & tooling: - Updated Zod schemas (cache, route, matcher).
- Updated MCP tool descriptions and resource references.
- Updated
flow_validateto enforce the new constraints (EMPTY_TRANSFORMER
error code added).
Migration: Hard cut at the schema/type level. Configs usingcache.full
will fail validation — rename tostop. Configs usingmatch: "*"will fail
validation — omitmatch. Configs usingRoute[]first-match still work at
runtime (compiled as implicitcase) but new configs should usecase
explicitly.
$schema: "v4"is preserved. No version bump.
Breaking: code: "<exportName>" is no longer accepted on any step. Replace
with import: "<exportName>" alongside package.
New: Every step (source, transformer, destination, store) accepts
import?: string. With package, it selects a named export. package alone
still loads the default export. Inline code stays
code: { push, type?, init? }. Empty steps are valid no-ops. flow_validate
and the CLI bundler raise OBSOLETE_CODE_STRING on the legacy shape with a
precise rename hint.
Pass-through transformer steps + closed-schema validation.
Validation: validateTransformerEntry in @walkeros/core is now the single
source of truth. Bundler, flow_validate, and collector runtime all delegate.
Closed schema: unknown top-level keys are errors. code + package together is
a CONFLICT.
Pass-through steps: A transformer entry with no code and no package is
valid; the collector synthesizes its push. Three variants:
- before/next chain only (named hop)
- cache only (e.g. dedup)
- mapping only (event-to-event transform via
Mapping.Config)
Mapping at the transformer position: newmapping?: Mapping.Configfield on
Transformer.Config/InitTransformer. Same shape as
Destination.Config.mapping, event-to-event semantic.data/silentare
ignored at the transformer position with a one-time warning.
Engine tag: synthesized instance now usestype: 'pass'(was'path').
Hard cut.
Runtime fixes: compileNexthandles mixed-shapenextarrays (["a", { case }]) via a new
'sequence'variant.- A destination's
beforereferencing a pass-through transformer now walks that
transformer's ownbefore/next. cache.stop: trueat a pre-collector transformer halts the pipeline (matches
cache.mdx).
Migration: Typo keys on a step now fail validation.
instance.type === 'path'consumers must read'pass'.runTransformerChain
consumers should branch on the newstoppedflag.
Remove unused legacy fields batchFn and batched from Mapping.Rule. Batch
state lives on the destination via BatchRegistry, never on mapping rules. No
runtime impact.
Route grammar: rename case to one (first-match dispatch) and add many
(all-match parallel fan-out, pre-collector only). many terminates the main
chain and is rejected at post-collector positions (destination.before,
destination.next); use multiple destinations for post-collector fan-out.
RouteCaseConfig is renamed to RouteOneConfig; no aliases.
Source.Context no longer exposes setIngest or setRespond. Server sources
handling concurrent inbound requests must call
context.withScope(rawScope, respond, body) to bind per-request ingest and
respond. Browser and other single-scope sources keep working without changes.
Status.dropped is now keyed by stepId, so operators can see at a glance which
step dropped events. Read with status.dropped["collector"]?.queue or
status.dropped["destination.<id>"]?.queue / .dlq, or build the key with the
new stepId() helper exported from @walkeros/core. Breaking change: the
previous flat shape (status.dropped.queue / .queuePush / .dlq) and the
per-destination dropped field on DestinationStatus are removed.
Add Flow.Store.cache for store-level caching: read-through + write-through
wrapper with single-flight dedup, recursive composition via cache.store, and
per-wrapper counters. CacheRule is now a discriminated union
(EventCacheRule | StoreCacheRule); schema rejects inert fields in store
contexts.
Built-in __cache upgraded with LRU, maxEntries: 10000, batched eviction, and
active TTL sweep.
Breaking: @walkeros/store-memory is removed. Its logic is absorbed into
__cache. Migration: drop the store declaration, or omit cache.store to use
the built-in tier. flow_validate flags legacy references.
Add @walkeros/transformer-ga4: GA4 Measurement Protocol v2 decoder transformer
with default mappings for 33 standard events. Server-side use via
source-express in the before chain.
Also: fix collector to preserve fan-out in source.before chains. Previously,
when a before-transformer returned an array of events, only the first survived.
This enables vendor-protocol decoders (GA4, Segment, Snowplow, etc.) to fan a
batched request into N walkerOS events.
Internal pipeline failures in mapping, source startup, transformer init, and
destination init now log via the scoped logger and increment
collector.status.failed. Previously silent. User-supplied callbacks (mapping
condition/fn/validate, on subscriptions) log on throw but do not affect
status.failed. A source whose init() throws now stays
config.init === false instead of being marked initialized.
Add typed accessors Source.getSource, Destination.getDestination,
Transformer.getTransformer, Store.getStore. Each takes a collector and a
step id and returns the registered instance with its declared generic recovered,
replacing the Elb.Fn-collapsed shape that the bag's index signature exposes on
read.
Callers (mainly tests and integrations that invoke a step's raw push through
the collector) no longer need as any / as (rawData: X) => ... casts at this
boundary. Each helper throws <Kind> not found: <id> for unknown ids. No
runtime behavior change.
Add validateJsonSchema / validateEventsJsonSchema exports for step-level
validation config, promote the validate and no-many route schemas to direct
exports, and add optional nodeType / subPath cursor fields to
IntelliSenseContext for context-scoped autocomplete.
Add step-level validate? primitive on every walkerOS step. validate: is a
declarative description of validation intent, like cache or consent.
Consumers decide how to enforce.
Restructure Flow.ContractRule to a uniform
{ extends?, tagging?, description?, events?, schema? } shape. A single
agnostic JSON Schema replaces the typed section fields (globals, context,
custom, user, consent); standard event field names live inside
schema.properties.<name>. extends resolves schema via additive deep-merge.
Contracts are a description and governance concept: tooling, MCP, and humans
read them. Runtime enforcement is the consumer's cal...
walkerOS v4.0.2
Changes
Move from packages/web/destinations/demo to packages/destinations/demo so
the package can serve web and server flows from a single location, alongside
packages/transformers/. The published package name and exports are unchanged;
only the in-repo path moves. walkerOS.platform widens from ["web"] to
["web", "server"]. No consumer code changes required.
Rename routing types: NextRule to Route, Next to RouteSpec (Zod schemas
and MatcherNext* IDs renamed in step). Widen Flow.*.before/next to
RouteSpec so Route[] conditional routing type-checks at the JSON layer. Fix
the CLI bundler dropping Route[] data via a narrowing cast on the inline path.
Hard cut, no aliases; flow.json shape unchanged.
Published Packages
walkerOS v4.0.1
Changes
Fix events being silently dropped when posted via navigator.sendBeacon. The
browser forces Content-Type: text/plain;charset=UTF-8 for beacon requests even
when the payload is JSON, which previously caused the express middleware to skip
body parsing and the GCP Cloud Functions handler to treat the body as an opaque
string, both falling through to an empty-event push. Express now accepts
text/plain bodies through express.json(), and the Cloud Functions handler
attempts JSON.parse on string bodies before classifying the request.
BigQuery destination: migrate from legacy tabledata.insertAll to the BigQuery
Storage Write API (~2x cheaper at volume, 2 TiB/month free tier), add the
setup() lifecycle for one-shot dataset and table provisioning via
walkeros setup destination.bigquery, and implement pushBatch so the
collector's batch: <ms> mapping setting actually batches into a single
appendRows call.
Breaking changes:
- The 15-column table schema is now using walkerOS event v4 schema.
Runwalkeros setup destination.bigqueryto provision the dataset and table
with day partitioning ontimestampand clustering on(name, entity, action).
@walkeros/cli: Server bundles now use @vercel/nft to trace dependencies and copy
only files actually used into dist/node_modules/. Pacote remains the install
layer (driven by flow.json's config.bundle.packages field; users do not run npm
install for step packages). The walkerOS.bundle.external annotation field on
package manifests is no longer recognized (deprecation warning if seen). The
flow..config.bundle.external sub-field on flow configs is also no longer
supported (warned and stripped during load). The
flow..config.bundle.traceInclude field is the escape hatch for cases nft
cannot statically trace. Server output is always a directory: dist/{flow.mjs,
package.json, node_modules/}. Default output filename changed from bundle.mjs to
flow.mjs. The runtime image expects /app/flow/flow.mjs. flow.json schema is
unchanged (still v4); only @walkeros/cli bumps. Migration: see
https://walkeros.io/docs/migrate/cli-4x.
@walkeros/server-destination-gcp: removed obsolete walkerOS.bundle.external
annotation from package manifest. nft handles externalization automatically. No
behavior change for consumers.
Bundler honors walkerOS.bundle.external declared in step-package package.json
files. Listed packages are externalized from the ESM bundle and the bundler
always installs them (plus their full transitive deps) into
<outputDir>/node_modules/ via pacote — no npm install shell-out, no manual
deploy step. When externals is empty, output remains a single bundle.mjs
(backward compatible). When non-empty, output is a self-contained directory:
bundle.mjs, package.json, package-lock.json, node_modules/.
The bundler reads npm config (registry, scope tokens) from .npmrc,
parallelizes manifest fetches with retry, atomically stages each package
extraction (no half-populated node_modules/ on failure), and reuses the
closure resolution from the existing collectAllSpecs BFS so peerDependencies
are honored.
Hard-errors when:
- A package in the install closure declares a
pre/install/postinstallscript
(pacote.extract does not run them). - A step package names an external in
walkerOS.bundle.externalbut does not
list it independenciesorpeerDependencies. - Two step packages declare the same external and the resolved version does not
satisfy all consumers' constraints.
Warns (not errors) when: - Bundle output contains unresolved
__dirname/__filenamereferences (with
package attribution by hit count). - A step package's
walkerOS.bundle.*block contains unknown keys (typo guard).
New sibling exportdownloadPackagesWithResolutionreturns both the package
paths and the fullResolutionResult. ExistingdownloadPackageskeeps its
return shape unchanged.
Surface destination init errors in logs at ERROR level. Previously, two layers
swallowed errors silently: the gcp destination's init catch only logged for
isNotFound errors and re-threw everything else without logging; the collector
wrapped destinationInit with tryCatchAsync (no onError), which silently
returned undefined on a thrown error and treated the destination as
not-initialized. Combined effect: a real init failure (e.g., the recent
streamType regression in BigQuery Storage Write API call) showed only
[gcp-bigquery] init in DEBUG logs and nothing else, regardless of log level.
Now: gcp's init catch logs every error at ERROR before re-throwing (with
consistent error: context key), AND the collector logs at ERROR via
logger.scope(destType).error('Destination init threw', { error }) if init
throws or rejects. Failures are never silent. Mocks updated to enforce the new
shapes; tests cover both sync-throw and async-rejection variants.
Fix walkeros bundle failing on Windows when stage 2 import paths contained
backslashes that JS parsed as escape sequences.
Declare @google-cloud/bigquery-storage as a bundle external via
walkerOS.bundle.external. Fixes __dirname is not defined in ES module scope
when bundling a flow that uses BigQuery Storage Write API. The bundler's closure
walker pulls in the transitive gRPC stack (@grpc/grpc-js,
@grpc/proto-loader, protobufjs, google-gax) automatically via
bigquery-storage's own dependencies and peerDependencies, so only the one
entry needs to be declared. Requires @walkeros/cli >= the version shipping the
bundler-externals feature.
Add Pub/Sub sub-destination to the GCP server package. Publishes walkerOS events
to a Pub/Sub topic with optional per-key ordering and dynamic attributes, plus
idempotent topic provisioning via walkeros setup destination.<id>. EU region
default for at-rest storage. Three auth modes: ADC, service account JSON,
pre-configured client.
Adopt the setup lifecycle for one-shot Kafka topic provisioning. Operators can
now run walkeros setup destination.<id> to create topics idempotently with
explicit numPartitions, replicationFactor, and configEntries. Drift on
partition count, replication factor, and config entries emits warnings without
auto-mutating. Optional Confluent Schema Registry binding registers a schema and
(optionally) sets the per-subject compatibility level.
No safe defaults. Kafka topic creation requires cluster-specific decisions.
The boolean form setup: true is rejected with an error listing the required
fields. Only the object form
(setup: { numPartitions, replicationFactor, ... }) is valid. This is the
canonical example of the "no safe default" pattern in the walkerOS
create-destination skill.
When the topic is missing at runtime, push() now logs an actionable error
pointing at walkeros setup destination.<id>.
Add an optional setup lifecycle to destinations, sources, and stores.
Each package may now implement setup?: SetupFn to provision external resources
(BigQuery datasets and tables, Pub/Sub topics and subscriptions, SQLite tables,
webhook registrations, etc.). Setup is triggered only by the new
walkeros setup <kind>.<name> CLI command, never automatically by the runtime,
push, or deploy. Idempotency, ordering, and error semantics are the package's
responsibility; the framework provides the type slot, the CLI invocation, and a
resolveSetup(value, defaults) helper.
LifecycleContext<C, E> is the new shared context type used by both setup and
destroy. DestroyContext remains as a deprecated type alias for one minor
cycle. The Types bundle on Destination, Source, and Store gains a
5th/6th/4th positional slot for setup options; existing aliases compile
unchanged because the slot defaults to unknown.
Config<T>.setup?: boolean | SetupOptions<T> is added across all three kinds
and validated by the corresponding Zod ConfigSchema plus the flow component
schemas in @walkeros/core/schemas/flow.ts.
CLI:
walkeros setup <kind>.<name>runs a single component'ssetup()function.<kind>issource,destination, orstore(transformers have no
provisioning).--config <path>(default./flow.json),--flow <name>for multi-flow
configs, plus standard--json/--verbose/--silent.- Exit 0 on success or skip; non-zero on failure. Skip narration covers three
cases: nosetup()on the package,config.setup === false, or
config.setupunset. - When the package's
setup()returns a non-undefined value, the CLI emits it
as JSON on stdout forjqpiping.
Source lifecycle redesign: factory + eager init + collector-gated on()
Source factories must now be side-effect-free. The collector calls
Instance.init() on each source eagerly after all factories register. require
no longer gates code execution. It gates on(type) delivery (events queue in
Instance.queueOn until the source is started, then replay).
collector.pending.sources has been removed; per-source state lives on
Source.Instance (queueOn) and Source.Config (init, require).
Migration: any source factory with side effects (queue draining, walker command
emission, listener attachment) should move those into the returned Instance's
optional init method. Tests asserting on collector.pending.sources should
read collector.sources[id] and inspect config.init / config.require
instead.
Fixes the elbLayer queue replay clobbering fresh consent/user state,
late-activated sources missing walker run, and inter-source require chains
racing when a non-required source's init fired a state-mutating walker command
before later require-gated sources had been registered.
Add the setup() lifecycle. Run walkeros setup destination.<name> to create
the events table with the canonical walkerOS Event v4 schema and apply pragmas
(journal_mode=WAL, synchronous=NORMAL, foreign_keys=ON,
temp_store=MEMORY). Setup is idempotent and detects drift via
PRAGMA table_info (logs WARN setup.drift, never auto-mutat...
walkerOS v4.0.0
Changes
Remove dead bundleRemote() and add OpenAPI drift detection.
Breaking changes:
- Removed
bundleRemote()export from@walkeros/cli. The corresponding
/api/bundleendpoint was removed from the walkerOS app on 2026-04-08, so
this function had been silently broken in production for ~3 weeks. Local
bundling viabundle()is unaffected. - Removed
remoteandcontentoptions from the MCPflow_bundletool. The
tool now bundles locally only.
New: - Added
npm run -w @walkeros/cli validate:openapi-specscript that diffs the
checked-inpackages/cli/openapi/spec.jsonagainst the live app's OpenAPI
document. Detects drift between the walkerOS-side type contract and the actual
API. Wired into PR-time CI, daily cron, and a pre-commit lint-staged hook. All
layers are gated on aWALKEROS_APP_URLsecret and skip silently when unset,
so the change ships safely without configuration. To activate: set
WALKEROS_APP_URLin repo secrets pointing to a deployed app instance.
Fix walkeros push deadlock for web flows whose destinations await real timers
during init.
Previously, async-drain timer interception captured every setTimeout into a
pending map and only fired them via a post-fn flush. If a destination's init
awaited one of those captured timers (e.g.,
@walkeros/web-destination-amplitude's engagement plugin awaits a 10s
setTimeout to give up on a CDN script load), init never resolved,
await collector.push deadlocked, and Node exited with
Detected unsettled top-level await (exit 13).
A drain pump now runs alongside fn(flowModule) for non---simulate runs: each
tick fires every captured non-cleared timer using a real setImmediate
reference. Timers fire in delay-ascending order, intervals re-register, callback
errors are reported via console.warn. Bounded by max-iterations (1000) and
wall-clock (30s) caps.
--simulate <step> continues to use the post-fn flush path so snapshot
ordering remains stable.
Behavior change (edge case): a destination using setTimeout for retry backoff
under walkeros push (real, non-simulate) now sees its timer fire instantly.
This was already the documented contract for --simulate <step> snapshots; it
now extends to real push for consistency.
Validate device-code and token responses from the auth server with Zod schemas
at the trust boundary in login/index.ts. Malformed responses now surface as
structured errors instead of being trusted into config writes or browser
launches. Replaces the hand-rolled type guards for Items 1 and 3 of the cli-auth
feedback review. No public API change.
Explicit opt-in anonymous usage telemetry for CLI and MCP. Telemetry is off by
default; users opt in with walkeros telemetry enable and out with
walkeros telemetry disable. No persistent identifier is written before opt-in.
No ingest endpoint ships in this release: opting in records consent locally;
emission begins when a managed endpoint is released. The data contract lives at
packages/cli/src/telemetry/flow.json.
Add flowId filter to CLI listDeployments and redesign the MCP
deploy_manage tool around it.
CLI (@walkeros/cli):
listDeployments({ projectId?, type?, status?, flowId? })now forwards
flowIdas a query parameter toGET /api/projects/{id}/deployments.- New helper
deleteDeploymentByFlowId({ projectId?, flowId, slug? })deletes
the active deployment for a flow, surfacing aDeploymentAmbiguityError(code
MULTIPLE_DEPLOYMENTS, with adetails[]list) when a flow has more than one
active deployment and no slug was supplied.
MCP (@walkeros/mcp) breaking: deploy_manage'sget,delete, andlistactions now take
{ projectId?, flowId, slug? }. The oldidparameter has been removed.
flowIdis required forget/deleteand optional forlist. Soft-deleted
deployments are always excluded.- When a flow has multiple active deployments and
slugis not provided,
get/deletereturn aMULTIPLE_DEPLOYMENTSerror with adetails[]list
of{ slug, type, status, updatedAt }entries so the caller can pick one.
deployaction is unchanged.
Event model v4: breaking changes to the Event, Source, and Entity shapes.
event.idis now a W3C span_id (16 lowercase hex chars), generated by the
collector. Reference: W3C Trace Context (W3C Recommendation, January 2020).event.version,event.group,event.countare removed.source.typeis now the source kind (e.g.browser,gtag,mcp,cli).
Newsource.platformholds the runtime (web|server|app| ...).source.idandsource.previous_idare removed.- Browser source now sets
source.urlandsource.referrer. - MCP source sets
source.toolper emission. CLI source setssource.command. Entity.nestedandEntity.contextare now optional. Rootevent.nestedand
event.contextremain required.- Each source self-registers via TypeScript module augmentation of
SourceMap
in@walkeros/core. - App-side coordination (
/workspaces/developer/app) is a follow-up plan, not
part of this release. Telemetry from v4 CLI/MCP will not validate against the
existing app schema until that follow-up ships. Mapping.Rule.skipis renamed toMapping.Rule.silent. Customer flow.json
configs usingskip: truein mapping rules must rename tosilent: true.
Hard cut: no legacy alias, the field is gone.
Add CodeDiff atom and CodeDiffBox molecule — read-only, theme-aware Monaco
DiffEditor wrappers for side-by-side / inline code diff viewing. CodeDiffBox
mirrors CodeBox's API (header, actions, traffic lights, footer) and adds an
opt-in summary strip, split/inline toggle, and copy button. Supports any Monaco
language; walkerOS $var: / $secret: decorations are applied to both sides
automatically.
IntelliSense improvements for flow.json editors:
- Chain references (
next/before) now autocomplete in all forms: scalar,
inline array, multi-line array, and Route[] innernext. Previously only the
scalar form triggered. $store.completions, hover, and validation added. Fed by a new optional
storesfield onIntelliSenseContext; the flow extractor collects store IDs
from the active flow.$env.completions and hover added. OptionalenvNamesinventory on
IntelliSenseContextenables validation; when absent,$env.still gets a
generic hover.$contract.completion now only triggers when the cursor starts a new string
value, matching runtime semantics (whole-string refs only).packagecompletion detection is JSON-path aware — multi-line"package":
values now surface completions.- Variables and definitions are collected at config / flow / step levels with
correct cascade priority (step > flow > config). - Markers validate chain references in all forms via a JSON walk instead of a
scalar-only regex. - Internals now import the shared
REF_*regex constants from@walkeros/core
— single source of truth, no inline duplicates.
PropertyTable — responsive card-view fallback via CSS container queries
(triggered below 420px), graceful empty-state rendering with an optional
emptyMessage prop (default: "No specific properties available."), and improved
column-width handling so the Description column no longer forces horizontal
overflow in narrow containers.
Add Monaco IntelliSense for $flow.X cross-flow references in Code/CodeBox.
Completion offers known sibling flow names from the parsed flow document, hover
describes the resolved target, decorations style matches the other reference
prefixes, and unknown flow names emit a warning marker. Re-export REF_FLOW
from @walkeros/core so consumers can build inline regex tooling without
reaching into the subpath.
Flow v4: type redesign and cross-flow references.
Breaking changes:
- Renamed
Flow.Settings(single-flow shape) toFlow. The newFlow.Settings
is the arbitrary kv-bag insideFlow.Config(matchesDestination.Settings
semantics). - Renamed
Flow.Config(root file shape) toFlow.Json. - Removed
Flow.WebandFlow.Server. Replaced by
config.platform: 'web' | 'server'(a string discriminator). - Renamed
Flow.InlineCodetoFlow.Code. - Renamed
Flow.SourceReference/DestinationReference/
TransformerReference/StoreReferencetoFlow.Source/Destination/
Transformer/Store(Reference suffix dropped). - Renamed
Flow.ContractEntrytoFlow.ContractRule. - Lifted
bundleand platform fields into the per-flowconfigblock. flow.jsonversionbumped from 3 to 4. v3 input is rejected (no compat
shim).
New:$flow.X.Yreference resolves toflows.X.config.Yin the same file. Useful
for linking a web flow's API destination to a server flow's deployed URL
without duplicating values.- Per-flow
Flow.Configblock:{ platform, url, settings, bundle }. walkeros validatewarns on unresolved$flow.X.Y(use--strictto error).
walkeros bundleandwalkeros deployalways error on unresolved refs.- See
docs/migrating/v3-to-v4.mdxon the website for the manual migration
steps. No automated codemod is shipped.
Add flowCanvasResult helper + FlowCanvasToolResult / FlowCanvasPayload /
SuggestionTile types for UI-renderable tool outputs. flow_manage actions
get / create / update now return a kind: 'flow-canvas' payload with
optional suggestion tiles so chat clients can render the flow graph inline.
Split @walkeros/mcp into a library entry (server factory plus ToolClient
abstraction) and a thin stdio binary. The package now exports
createWalkerOSMcpServer, HttpToolClient, createStreamableHttpHandler,
TOOL_DEFINITIONS, and the ToolClient interface so host applications can
mount walkerOS MCP tools over HTTP (e.g. from a Next.js Route Handler) or
consume them directly from non-MCP runtimes (e.g. Vercel AI SDK adapters). The
walkeros-mcp stdio binary is unchanged for end ...
walkerOS v3.4.1
Changes
Add walkeros previews {list|get|create|delete} commands for managing preview
bundles. create supports --flow <name> or --settings-id <id> to target a
flow settings entry, and --url <siteUrl> to produce a ready-to-open activation
URL. Use --open to launch it in your default browser.
Add preview.list, preview.get, preview.create, and preview.delete
actions to the api tool. When siteUrl is provided to preview.create, the
response includes a ready-to-open activationUrl and deactivationUrl.
Preview preflight now self-heals when a preview bundle is deleted. Instead of
injecting the preview script directly and letting it 404, the preflight does a
fetch(HEAD) first. If the bundle is missing, it clears the elbPreview cookie
and loads the production walker, so visitors never see silent analytics
breakage.
Step examples can now carry a title, description, and public flag.
Non-public examples stay hidden from the docs and AI tools so first-time
visitors see only the canonical ones.
useHooks now isolates hook failures. A pre-hook that throws no longer crashes
the pipeline — the wrapped function is called directly and a warning is logged.
A post-hook that throws leaves the original result in place. Added optional 4th
logger parameter so warnings route through the walkerOS Logger (falls back to
console.warn when no logger is provided). All collector call sites now pass
collector.logger.
Wrap localStorage/sessionStorage/cookie operations in try/catch. Storage
access in private browsing (Safari), sandboxed iframes, or when quota is
exceeded throws SecurityError/QuotaExceededError — previously these crashed
the event pipeline at the call site. Reads now return empty, writes return empty
and do not persist, deletes are silently ignored.
Published Packages
walkerOS v3.4.0
Changes
Add target option to bundle():
cdn | cdn-skeleton | runner | simulate | push. Replaces
buildOverrides.skipWrapper (deprecated) to stop dev schemas leaking into
production CDN bundles. Stage 2 entry generators gain platform option and
inject env.window/env.document for browser targets, fixing window.elbLayer
in deployed walker.js.
Clients now send User-Agent, X-WalkerOS-Client, and
X-WalkerOS-Client-Version on every request to the walkerOS app. When the app
returns 426 Upgrade Required, the CLI prints the required version + upgrade
instruction and exits with code 2; the MCP surfaces the same info in tool
errors. Set WALKEROS_CLIENT_TYPE=runner to have the CLI binary identify as a
long-lived runner instead of an interactive CLI (used by the runtime image so
runners are distinguishable from interactive sessions).
<CodeBox> and <LiveCode> now run with Monaco configured to target: ES2022,
module: ESNext, moduleDetection: 'force', and a registered ambient
declarations file exposing walkerOS runtime globals (elb, getMappingEvent,
getMappingValue). Mapping snippets can be plain object literals or top-level
await calls — no import / export boilerplate required — while keeping full
IntelliSense via the existing @walkeros/core type registration.
<LiveCode> now renders its result panel as JSON (it's always vendor output,
regardless of the input language) and its config panel as JSON (it's always
data). Only the input panel respects the language prop.
Add CodeView (Shiki-backed read-only code display) with matching Box frame,
plus a CodeStatic atom as the underlying highlighter. Also suppress the Monaco
loader's {type: 'cancelation'} unhandled rejections globally via a single
window-level listener, fixing the dev-console noise that fired on every unmount
of a <CodeBox> consumer.
Add TransformerSchemas and StoreSchemas namespaces with ConfigSchema /
configJsonSchema exports. Mirrors the existing DestinationSchemas /
SourceSchemas pattern so every component type has a documented Config schema
available via @walkeros/core/dev.
Reconcile pre-existing drift between TS Config interfaces and their Zod schemas:
DestinationSchemas.ConfigSchemanow includesbefore,next,cache,
disabled,mock,include(matchingDestination.Config) and drops
phantomonError/onLogfields that were never wired in the TS type or
consumed at runtime.SourceSchemas.ConfigSchemanow includesdisabledand drops a phantom
onErrorfield.MappingSchemas.ConfigSchemanow includesinclude, matching
Mapping.Config(the baseSource.Configextends).CollectorSchemas.ConfigSchemanow includesloggerand drops phantom
verbose/onError/onLogfields not present inCollector.Config.CollectorSchemas.InitConfigSchemanow includestransformers,stores,
hooks, matchingCollector.InitConfig.
Add a compile-time drift guard at
packages/core/src/schemas/__tests__/config-drift.test-d.ts. Any future
divergence between a Config TS interface and its Zod schema failstsc --noEmit
(already wired into the project'stypecheckscript and CI). Keys-only check;
value types may still differ for recursive or generic-slot fields where Zod
cannot express TS-side precision.
Add FullStory web destination with trackEvent, setIdentity, setProperties
(user/page), consent start/shutdown, and clean teardown via @fullstory/browser
SDK v2.
Add Heap web destination (product analytics, identity, consent)
Add Hotjar web destination (session replay, heatmaps, surveys)
Add Matomo web destination (self-hosted/cloud privacy-first analytics) via _paq
command queue with support for page views, custom events, ecommerce, goals, site
search, content tracking, and custom dimensions
Harden the Monaco / CodeBox integration. Fix moduleDetection (Force), add
<LiveCode> configLanguage prop, guard ScriptTarget.ES2022 fallback, warn
on loader.init() failures in dev, drop dead code. No API change for existing
callers.
Add Optimizely Feature Experimentation web destination with conversion tracking
via trackEvent(), revenue/value event tags, user context management with
attribute targeting, and consent-based client lifecycle.
Add Microsoft Advertising (Bing UET CAPI) server destination
Add Criteo Events API server destination
Add server-side Customer.io destination with Track, Identify, Page View,
Transactional Messaging (sendEmail/sendPush), and Customer Lifecycle management
(destroy/suppress/merge) via customerio-node SDK. Auto-fallback to
trackAnonymous() for anonymous visitors, state-diffed identify to avoid
redundant calls.
Add @walkeros/server-destination-file: local filesystem sink for walkerOS
server flows. Appends events to a file as JSONL (default), TSV, or CSV with
per-event filename resolution via the standard Mapping.Value DSL (tenant
sharding via key, daily rotation via $code: in fn). Opens one
WriteStream per resolved filename and keeps it open until destroy(). No
third-party SDK — uses node:fs built-ins.
Add server-side HubSpot CRM destination with custom event tracking via
events.send API, contact upsert via CRM API with state-based dedup, optional
batch mode (up to 500 events/flush), defaultProperties for attribution, and
graceful shutdown with queue flush.
Add server-side Apache Kafka destination via kafkajs. Supports JSON
serialization, configurable compression (gzip, snappy, lz4, zstd), per-rule
topic and message key overrides, SASL/SSL authentication (Confluent Cloud, AWS
MSK, SCRAM, OAuthBearer), and graceful producer shutdown via destroy().
Add server-side Klaviyo marketing automation destination with event tracking via
EventsApi.createEvent() and profile management via
ProfilesApi.createOrUpdateProfile(). Supports revenue tracking with
value/valueCurrency, ecommerce metric name mapping, and identify state diffing
to avoid redundant upserts.
Add server-side mParticle CDP destination. Events are packaged into batches and
POSTed to the regional mParticle pod via the HTTP Events API using Basic auth
(apiKey / apiSecret). Supports user identities, user attributes, consent state,
pod selection, and environment targeting.
Add Reddit Conversions API server destination
Add server-side Redis Streams destination via ioredis. Supports XADD append with
auto-generated entry IDs, JSON and flat serialization modes, approximate and
exact MAXLEN trimming, per-rule stream key overrides, env-injected client
pattern for testing, and graceful shutdown via client.quit().
Add server-side RudderStack CDP destination with full Segment Spec support
(Track, Identify, Group, Page, Screen, Alias) via @rudderstack/rudder-sdk-node
SDK. Includes graceful shutdown via flush(), identity resolution per-call, alias
support for identity merging, and state diffing for identify/group.
Add Snapchat Conversions API server destination.
Add @walkeros/server-destination-sqlite: server destination that persists
walkerOS events to SQLite. One destination, two drivers behind a single
interface: better-sqlite3 for local files and :memory:, @libsql/client for
remote Turso / libSQL / sqld. Driver is auto-selected from the connection URL.
Both SDKs are optional peer dependencies. Auto-creates a canonical events table
on init (opt-out via schema: 'manual'), caches a prepared INSERT, closes the
connection on destroy(). Per-rule mapping.settings.table override supported.
Add X (Twitter) Conversions API server destination
Migrate every step example in every walkerOS package to the standardized
[callable, ...args][] shape introduced in @walkeros/core. Every step
example's out is now an array of effect tuples whose first element is the
callable's public SDK name ('gtag', 'analytics.track', 'fbq',
'dataLayer.push', 'sendServer', 'fetch', 'trackClient.track',
'amplitude.track', 'fs.writeFile', 'producer.send', 'client.xadd',
'client.send', 'dataset.table.insert', etc.). Source examples use 'elb' as
the callable; transformer examples use the reserved 'return' keyword; store
examples use store-operation callables ('get', 'set'). Tests capture real
calls on each component's spy and assert against example.out directly — the
hardcoded PACKAGE_CALLS registry in the app is no longer consulted (emptied;
plan #3 removes it structurally).
Introduce the standardized StepExample.out shape: [callable, ...args][]
where each tuple is a function call (first element is the callable name) or a
['return', value] tuple for transformer-style returns. Every effect is
self-describing; docs and tools can render it uniformly without a per-package
registry.
Ship the shared formatOut renderer from @walkeros/core for docs + app. Also
exports StepEffect and StepOut types. Migrate
@walkeros/web-destination-gtag to the new shape as the canary — its multi-tool
outputs (GA4 + Ads + GTM) now flatten into a single array of gtag(...) and
dataLayer.push(...) tuples in observed execution order. Remaining destination
packages ship the old shape until the bulk migration (separate plan).
Published Packages
walkerOS v3.3.1
Changes
Collector auto-generated destination keys now use lowercase letters only (a-z,
length 5) instead of base-36 (0-9a-z, length 4). getId gains an optional
charset parameter; default behavior is unchanged so session IDs and other
existing callers stay bit-for-bit identical.
Force collector.run=true during push and simulate so flows with run:false work
in CLI
Fix infinite recursion when registering on('consent', ...) handlers. The
collector's on() helper previously re-broadcast to all source on handlers,
causing self-re-registering consent handlers to recurse unbounded and crash the
tab. on() now fires only the newly-registered callback against current state.
Fix release pipeline to embed the correct __VERSION__ in published packages.
Fix race in source cache MISS wrapper: applyUpdate promise was
fire-and-forget, so a source fallback (e.g. express GIF default) could win
createRespond's first-call-wins race on the first request. wrappedPush now
awaits the pending update before returning.
Published Packages
walkerOS v3.3.0
Changes
Add Amplitude web destination (@walkeros/web-destination-amplitude) —
analytics, identity with full operation vocabulary, revenue (single and
multi-product via loop), groups, consent opt-out, and three optional plugins
(Session Replay, Feature Experiments, Guides & Surveys) via the official
@amplitude/* npm packages.
- Default event forwarding: every walkerOS event becomes
amplitude.track(name, event_properties) - Custom event properties:
settings.includeflattens walkerOS event sections
with prefix (data_*,globals_*, etc.) - Identity: destination-level + per-event
settings.identify, resolving to
user/device/session plus the full Identify operation vocabulary (set,
setOnce,add,append,prepend,preInsert,postInsert,remove,
unset,clearAll). Runtime state diffing skips redundant setter calls. - Revenue:
settings.revenuesupports both single-object andloop-based
multi-product orders. Currency defaults to"EUR". - Groups:
settings.groupandsettings.groupIdentifyfor B2B flows - Reset:
settings.reset: truecallsamplitude.reset()on logout - Consent:
on('consent')handler derives the consent keys from
config.consentand togglesamplitude.setOptOut()(strict: all keys must be
granted for opt-in) - Plugins (all npm-bundled, opt-in via settings): Session Replay, Feature
Experiments (viainitializeWithAmplitudeAnalytics), Engagement (Guides &
Surveys) - Async init: awaits
amplitude.init(...).promiseso the destination is truly
ready before returning - SDK resolution follows the
env?.amplitude ?? amplitudepattern (mirrors
@walkeros/web-destination-clarityand@walkeros/server-destination-gcp
BigQuery)
BREAKING CHANGE: The packages block has moved from flow.<name>.packages
to flow.<name>.bundle.packages. Flow files using the old shape fail fast with
a migration error pointing to the new location.
Also adds flow.<name>.bundle.overrides — a Record<string, string> for
pinning transitive dependency versions, matching npm's overrides semantics.
Use this to resolve version conflicts when a transitive dependency's declared
range conflicts with another required version in the same tree (the original
motivating case: @amplitude/engagement-browser pins
@amplitude/analytics-types@^1.0.0 while @amplitude/analytics-browser
transitively requires analytics-types@2.11.1 exact — previously an
unresolvable bundler conflict).
Migration: move the existing packages block one level deeper into a new
bundle wrapper.
{
"version": 3,
"flows": {
"default": {
"web": {},
- "packages": {
- "@walkeros/collector": {}
- },
+ "bundle": {
+ "packages": {
+ "@walkeros/collector": {}
+ }
+ },
"sources": { },
"destinations": { }
}
}
}Overrides example:
{
"flows": {
"default": {
"web": {},
"bundle": {
"packages": {
"@walkeros/web-destination-amplitude": {}
},
"overrides": {
"@amplitude/analytics-types": "2.11.1"
}
}
}
}
}Overrides only substitute transitive dependencies during resolution — direct
package specs declared in bundle.packages always win. Overrides targeting a
direct local-path package emit a warning and are ignored. Peer constraint
mismatches against the chosen override emit a warning but do not error (the
override is an explicit user directive).
Add Microsoft Clarity web destination (@walkeros/web-destination-clarity) —
session replay, heatmaps, custom tags, identity, session priority, and consent
translation via the official @microsoft/clarity SDK.
- Default event forwarding: every walkerOS event becomes
Clarity.event(name) - Custom tags: flatten sections with
settings.includeor define explicit maps
withmapping.settings.set(supportsstringandstring[]values natively) - Identity: mapping values resolve to positional
Clarity.identify(...)args.
Destination-levelsettings.identifyfires on every push, matching Clarity's
"identify on every page load" recommendation - Session priority:
mapping.settings.upgradefiresClarity.upgrade(reason) - Consent: explicit
settings.consenttable translates walkerOS consent keys to
ClarityConsentV2categories (analytics_Storage,ad_Storage). All
consent state is forwarded viaClarity.consentV2(...)— the legacy
Clarity.consent(...)API is intentionally not used. - Honours
mapping.skipto run side effects without the default event call - Push execution order: identify → tags → upgrade → event, matching Clarity's
own guidance - SDK resolution follows the
env?.clarity ?? Claritypattern (mirrors
@walkeros/server-destination-gcpBigQuery wiring), so production uses the
real imported SDK while tests inject a mock viaenv.clarity
Expand getMarketingParameters to recognise 25+ ad platform click IDs
(Pinterest, Reddit, Quora, Yandex, Outbrain, Taboola, Mailchimp, Klaviyo,
HubSpot, Adobe, Impact, CJ, Branch, plus Google's wbraid/gbraid). Add a new
platform field that resolves the click ID to a canonical platform identifier
(e.g. gclid → google, fbclid → meta). Multi-click-ID URLs are resolved
deterministically via a priority order.
Custom click-ID registries can be passed as the third argument to
getMarketingParameters, or via the new clickIds field in the session source
settings — so flow.json users can extend or override defaults without touching
code.
Fix bare filename resolution in bundle command — walkeros bundle flow.json now resolves relative to cwd instead of CLI examples directory. Add TTY hint when writing to stdout
BREAKING: settings.include and mapping.settings.*.include have been
removed. Use config.include (destination-level) and mapping.include
(per-event rule-level) instead. The include logic is now handled by the walkerOS
core/collector — the destination receives pre-flattened properties in
context.data automatically.
Migration:
Before:
"config": {
"settings": { "ga4": { "include": ["data"] } }
}After:
"config": {
"include": ["data"]
}For per-event overrides:
Before:
"mapping": { "order": { "complete": { "settings": { "ga4": { "include": ["data", "globals"] } } } } }After:
"mapping": { "order": { "complete": { "include": ["data", "globals"] } } }Add include as a first-class field on Destination.Config (destination-level)
and Mapping.Rule (per-event override). The collector resolves include in
processEventMapping before calling push(), flattening specified event
sections into prefixed key-value pairs (e.g. data_price: 420) and merging them
as the bottom layer of context.data.
Rule-level include replaces config-level (not additive). Merge priority:
include (bottom) → config.data → rule.data (top, wins on conflict). The
context section correctly extracts [0] from OrderedProperties tuples.
New export: flattenIncludeSections(event, sections) from @walkeros/core.
Add LinkedIn Insight Tag web destination (@walkeros/web-destination-linkedin)
— opt-in conversion forwarding via window.lintrk('track', ...).
- Opt-in conversion model: Events without
mapping.settings.conversionare
silently ignored. Each conversion references a pre-created Conversion Rule ID
from LinkedIn Campaign Manager. - Per-event conversion mapping with short keys:
id,value,currency,
eventId— translated at call time to LinkedIn'sconversion_id/
conversion_value/currency/event_id. - Currency fallback via walkerOS
{ key, value }syntax — defaults to
"EUR"whendata.currencyis absent. - Deduplication ready — maps walkerOS
event.idto LinkedIn'sevent_id
field, ready for future cross-channel deduplication with a server (Conversions
API) destination. - Consent-gated:
marketing(notanalytics). The collector's
config.consentgate is the sole mechanism — the Insight Tag has no vendor
opt-out API.config.loadScript: truesupports deferred script injection
after consent grant. - No npm SDK — the destination injects the official Insight Tag from
https://snap.licdn.com/li.lms-analytics/insight.min.jsat runtime. - No identity tracking — LinkedIn identity is cookie-based and managed
entirely by the Insight Tag.li_fat_idcapture is the session source's
responsibility (future Conversions API destination will consume it). - Covered features: 8 step-example fixtures including unmapped-event
ignoring, simple conversion ID, full e-commerce conversion (value + currency +
eventId), page view key-page-view, LEAD conversion,mapping.skip, falsy id
guard, and partial-fields omission.
Add skip?: boolean to Mapping.Rule as a universal sibling of ignore.
Destinations can now honor a rule-level skip to process settings.* side
effects (identify, revenue, group, etc.) while omitting their default forwarding
call (track(), capture(), event()). Replaces destination-specific
settings.skipTrack / settings.skipEvent toggles.
processEventMapping() now returns an explicit skip: boolean field alongside
ignore. The collector does not short-circuit on skip — it still calls
destination.push() so the destination can run its side effects. The
destination implementation reads context.rule?.skip and gates its default
forwarding call on !skip.
ignore: true still wins when both flags are set on the same rule.
Add Mixpanel web destination (@walkeros/web-destination-mixpanel) — events,
identity, the full 8-operation people vocabulary, group association, group
profiles, reset, and consent opt-in/opt-out via the official mixpanel-browser
npm package.
- Default event forwarding: every walkerOS event becomes
mixpanel.track(name, properties) - Custom event properties: `settings.incl...
walkerOS v3.2.0
Changes
Add chainPath to ingest metadata and support path-specific mocks via --mock destination.ga4.before.redact='...'
Unify duplicated CLI patterns for reliability and consistency
- Add unified event validator with graduated levels (strict/standard/minimal)
- Fix package resolution in simulate to respect packages.path from flow config
- Extract shared readStdinToTempFile utility, remove copy-paste dynamic imports
- Standardize duration output to milliseconds (matching MCP schema contract)
- Fix temp file cleanup in run command (hot-swap accumulation, shutdown handler)
- Fix simulator bare /tmp cleanup bug
- Unify URL fetching into shared fetchContentString, eliminate temp file
roundtrip - Refactor loadJsonFromSource as thin wrapper around loadJsonConfig
- Remove unused downloadFromUrl function
Replace externalServer hack with typed sourceSettings override in bundle wrapper
Flow graph architecture: symmetric before/next hooks, mutable Ingest, per-destination isolation.
- Add symmetric
before/nextto all step types (sources, transformers, destinations) - Add
Ingestinterface with mutable_metatracking (hops, path) - Parameterize
Transformer.Fn<T, E>andResult<E>on event type - Support
Result[]return from transformers for fan-out - Remove
Object.freeze(ingest)— ingest is fully mutable - Upgrade
setIngestto create typedIngestwith_meta - Clone ingest per destination to prevent cross-contamination
- Add
createMockContexttest utility for context construction
Generalize queue flush: refresh user/globals/consent on queued events and flush
after any state mutation command
Wire initConfig.hooks into collector instance. Simulation uses
prePush/postDestinationPush hooks for event capture. Hooks are wired by
startFlow before events fire.
Polyfill fetch and navigator.sendBeacon in JSDOM during web simulation to prevent throws and capture network calls
Resolve transitive dependencies from local path packages automatically
Improve flow_simulate tool description, warnings, and prompts to explain require, consent, mapping, and policy behavior. Agents using the MCP now get actionable guidance when destinations are pending or events are silently skipped.
Add conditional routing and native cache as built-in config properties on
sources, transformers, and destinations.
Routing: NextRule[] in next/before properties enables conditional step
chaining, replacing @walkeros/transformer-router.
Cache:
- Cache rules use the same match syntax as routing (MatchExpression)
- Source cache: full pipeline caching with respond interception
- Transformer cache: step-level memoization, chain continues
- Destination cache: event deduplication
- Update rules modify cached results on read via getMappingValue
- Default per-collector memory store with namespaced keys
compileMatcher upgraded to use getByPath for scoped dot-paths (ingest.method,
event.name). Removed @walkeros/server-transformer-cache (replaced by native
cache).
Support path-based package: references on flow config components
Add include/exclude destination filter to collector.push PushOptions.
Sources can now control which destinations receive their events.
Destination simulation uses the full collector pipeline with include filter,
giving production-identical event enrichment, consent, and mapping.
Transformer respond wrappers now propagate through the pipeline
Accept non-JSON POST bodies in all server sources
Server sources no longer reject non-JSON bodies with HTTP 400. Instead, they push an empty event {} to the collector, enabling source.before transformers to process raw input via ingest. Raw body is available through ingest mapping (e.g., "rawBody": "body").
Support single .ts files and directories without package.json as local packages in flow.json
Add --snapshot flag to push command for setting up global state before bundle execution
Add useHooks wrapping to store get/set/delete operations
Refactor bundler to two-step compilation: ESM code compilation + platform
wrapper. Config changes no longer require full rebuilds. Production bundles
carry zero dev/simulate code.
Unify simulation for sources, destinations, and transformers through the push
command.
- All step types simulate via
pushwith auto-env loading and call tracking - Add
--simulate transformer.Xto invoke a transformer directly with an event - Before chains run as mandatory preparation; next chains are skipped
- Source simulation captures at the collector.push boundary, preserving the
full before chain - Hooks (prePush/postDestinationPush) capture events instead of manual overrides
- Timer interception flushes setTimeout/setInterval deterministically for
async patterns (debounced batches, detached Promise chains) - MCP migrated to the push-based simulation pipeline
- Legacy simulate code removed