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
unregisterTriggerTypenow takes effect, and their functions are observable. 0.25 SDKs run against a 0.24 engine too:observefails withUNSUPPORTED, observe hooks never run, and calls are not dispatched locally. -
registerTriggerandregisterTriggerTypeare 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):
registerTriggerTypelogs the equivalent message pointing toregisterFunctionwith anobservedescriptor. -
Language markers. TypeScript marks the APIs
@deprecated, Python emits aDeprecationWarningat the call site, Go marks themDeprecated:(reported by linters such as staticcheck), and Rust marks the methods#[deprecated(since = "0.25.0")].
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 expectspayload_too_largeon a call to a function in the same worker, send the call withdispatch: "remote". - No engine-side span or call metrics for the local call. The SDK still records its own
executespan, and reports the number of local invocations every 10 seconds (localinvocationstats), which the engine adds toiii.invocations.totalwithdispatch="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 setlocal_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 asKeyboardInterruptandSystemExitincluded, reaches the caller as anInvocationErrorwith codeinvocation_failedand 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.
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
RegisterFunctionframes omitrequest_formatandresponse_formatwhen they are absent. Before, they sentnull.- 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
onObserverAddedhook still running after 30 seconds fails the observer withOWNER_TIMEOUT, and the next hook of the same function may start while it runs. - Rust SDK:
- public protocol structs gained fields and
Messagegained variants, so struct literals of them and exhaustivematches onMessageneed updating (prefer the constructors, such asTriggerRequest::new); - the connection state turns
Reconnectingas soon as the link drops; - a handler that panics now returns an
invocation_failederror to the caller. Before, the call timed out; - frames omit an
invocation_idofNone. Before, they sentnull.
- public protocol structs gained fields and
- Browser SDK:
- calls send
metadata,traceparent, andbaggage, 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.
- calls send
Migration checklist
- Upgrade the engine and the SDKs
- Remove
unregisterTriggerTypecalls 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 withworker.observe.
For the observer model, see Observers. For the provider side, see
Observable functions. For filter syntax, see the
filter language reference.