Skip to main content
0.25 makes every function observable with worker.observe(source, target, options?), and runs the calls a worker makes to its own functions in-process. The trigger APIs keep working in 0.25, so most projects upgrade without code changes. This page lists the behavior changes 0.25 brings to existing workers.
0.26 removes triggers and trigger types. To move them to observers and observable functions with 0.26 SDKs, see Upgrading from 0.25.x to 0.26.x. To move them while you stay on 0.25, follow the 0.25 version of this guide, which uses the 0.25 APIs.

What changes for existing workers

  • The old APIs keep working. 0.24 SDKs run against a 0.25 engine unchanged, except that an unregisterTriggerType now takes effect, and their functions are observable. 0.25 SDKs run against a 0.24 engine too: observe fails with UNSUPPORTED, observe hooks never run, and calls are not dispatched locally.
  • registerTrigger and registerTriggerType are deprecated in every 0.25 SDK. They keep working in 0.25; 0.26 removes them.
  • One runtime warning per process per API. The first call logs, through the SDK’s logger (each SDK names its own API):
    registerTriggerType logs the equivalent message pointing to registerFunction with an observe descriptor.
  • Language markers. TypeScript marks the APIs @deprecated, Python emits a DeprecationWarning at the call site, Go marks them Deprecated: (reported by linters such as staticcheck), and Rust marks the methods #[deprecated(since = "0.25.0")].
Rust crates built with #![deny(warnings)] or -D warnings fail to compile on calls to register_trigger, register_trigger_type, or unregister_trigger_type. Put #[allow(deprecated)] on the calls you keep, or migrate them.

Behavior changes

Local dispatch

A worker that calls a function it registered itself now runs the handler in-process after the engine confirms the registration. The call does not go through the engine. Results, error shapes, and timeouts are the same. The differences:
  • No message size limit on that path, because nothing is serialized. Through the engine, a message can be at most 64 MiB (16 MiB for browsers and SDKs older than 0.25), and a larger one fails with payload_too_large. A local call never fails with this error. If a test expects payload_too_large on a call to a function in the same worker, send the call with dispatch: "remote".
  • No engine-side span or call metrics for the local call. The SDK still records its own execute span, and reports the number of local invocations every 10 seconds (localinvocationstats), which the engine adds to iii.invocations.total with dispatch="local".
  • Value semantics. The handler receives a copy of the payload and the caller a copy of the result, as over the wire.
  • Rust: a local call runs on the caller’s task, and a sync handler (RegisterFunction::new) runs inline on it, where the call’s timeout cannot interrupt it. For a handler that can block, use an async handler (RegisterFunction::new_async) or set local_dispatch(false) on it.
  • Recursion: a local call nested more than 64 local calls deep goes through the engine. In the browser while offline, local calls run up to 256 levels deep, and a deeper one fails at once with LOCAL_DEPTH_EXCEEDED.
  • Python: a sync handler called locally runs on a thread of its own and honors the caller’s timeout. Whatever a local handler raises, BaseExceptions such as KeyboardInterrupt and SystemExit included, reaches the caller as an InvocationError with code invocation_failed and the original exception as its __cause__.
  • Disconnects: once confirmed, local dispatch keeps serving calls to the worker’s own functions while the connection is down.
Local dispatch is off for a session with an RBAC middleware_function_id, or one that may not invoke the function, because a local call would bypass those checks. Enqueue calls always go through the engine. To keep the old behavior for a function, a call, or the whole engine: 0.26 renames TriggerRequest to CallRequest; see Upgrading from 0.25.x.

unregisterTriggerType now takes effect

Every SDK has always sent unregistertriggertype, but engines before 0.25 ignored it. The 0.25 engine removes the trigger type when the sending worker is its provider, and ignores the message otherwise. The type’s triggers then move to another provider of the type, or wait until a provider registers again. If your worker calls unregisterTriggerType and expected the type to stay, remove the call.

Smaller changes

  • RegisterFunction frames omit request_format and response_format when they are absent. Before, they sent null.
  • Loop guard. Every invocation carries an observe depth. When the depth of an event is more than observers.max_observe_depth (8 by default), the engine drops it. Workers on 0.24 SDKs do not send the depth. For their calls, the engine uses the largest depth of the invocations still in flight on that worker.
  • Observe hooks that hang. An onObserverAdded hook still running after 30 seconds fails the observer with OWNER_TIMEOUT, and the next hook of the same function may start while it runs.
  • Rust SDK:
    • public protocol structs gained fields and Message gained variants, so struct literals of them and exhaustive matches on Message need updating (prefer the constructors, such as TriggerRequest::new);
    • the connection state turns Reconnecting as soon as the link drops;
    • a handler that panics now returns an invocation_failed error to the caller. Before, the call timed out;
    • frames omit an invocation_id of None. Before, they sent null.
  • Browser SDK:
    • calls send metadata, traceparent, and baggage, and handlers receive the metadata as their second argument;
    • while the page is offline, functions it hosts run locally up to 256 nested calls, and a deeper call fails with LOCAL_DEPTH_EXCEEDED.

Migration checklist

  • Upgrade the engine and the SDKs
  • Remove unregisterTriggerType calls that expected the trigger type to stay
  • In Rust crates built with -D warnings, put #[allow(deprecated)] on the trigger calls you keep
  • Check functions that call themselves for local dispatch differences (large payloads, blocking Rust sync handlers, tests that expect engine spans)
  • Move triggers and trigger types to observers before you upgrade to 0.26: with 0.26 SDKs in Upgrading from 0.25.x to 0.26.x, or on 0.25 with the 0.25 version of this guide

Result

The project runs on 0.25. Calls a worker makes to its own functions run in-process, and every function can be observed with worker.observe.
For the observer model, see Observers. For the provider side, see Observable functions. For filter syntax, see the filter language reference.