An extension package can expose methods that code on the other side of the boundary between the host and its clients calls directly. Mark the method as Remote, bind its owner to a service, and the runtime carries calls, streams and cancellation between the two sides. This page is the reference for that model: the authoring walkthrough is on Remote calls for extension authors, the wire messages are on The Oda Remote API, and what a build generates for such a package is on Plugin artifacts and the catalog.
Four markers exist, written on the method itself in TypeScript:
| Marker | What it declares |
|---|---|
@Remote | A direct call, under the method's own name. |
@Remote('name') | A direct call, exported under another name. |
@Remote({ mode: 'stream' }) | A streaming call. |
@RemoteScope('context') | A contextual call, whose receiver comes from the named context, with an optional second name. |
Remote method "<method>" has conflicting invocation markers.{ mode: 'stream' }; anything else fails with Remote options must contain exactly mode: "stream". Use the plain or named marker for a method that does not stream.T | undefined still counts as required; only an optional parameter may be left out of a call.A marked method becomes callable once its owner is bound:
1. Markers read back in declaration order, and a descriptor under another version is refused.ctx. invocation describes the call in progress: the request it belongs to (its namespace, its method and the arguments as received), the service it reached, the peer that made it and the signal. Outside a Remote call the same property reads as undefined.signal: AbortSignal parameter to cooperate with cancellation; the runtime provides the signal, and it never travels as a call argument. React to it to stop your work when the caller goes away.@Remote({ mode: 'stream' }) and returns an Iterable<Out>, an AsyncIterable<Out> or a RemoteStream<Out, In>. Each yielded item travels on the logical stream as the method produces it, so a caller sees items while the method still runs.send(item), end() and dispose(): send adds one item to the caller's side of the stream, end half-closes it so no further items follow, and dispose cancels the stream and ends the caller's iteration.ctx. invocation. uplink(). It is available once per call, and asking for it a second time throws.{ ok: true, value } on success or { ok: false, error } on failure.RemoteError class carrying a stable code of the form <domain>/ <reason>.gateway/ bad-request when the request does not satisfy the endpoint, and that failure can carry an issues list describing what was wrong; gateway/ cancelled when the call was cancelled; and gateway/ internal for a failure the layer could not classify.code, never by an instance check on the error's class.Uint8Array) may cross only as a single result of a unary call, directly or nested inside it; parameters and stream items stay JSON only.<namespace>/ <method>; a registered invocation carries the full id <package>#<namespace>/ <method>.<package>#<name>.<package>#<side>, where the side is host or client._, $, . and -. A lone . or . . is refused, and a # is refused inside a name because # separates the package part of an identity; a name outside the set is refused with must contain only RPC endpoint segment characters.Each key points at a different thing. An endpoint addresses one callable as <namespace>/, and a registered invocation carries the full id <package>#<namespace>/. A generated schema is addressed as <package>#<name>, and one package's contribution per side as <package>#<side>; the # marks where the package part ends.
On the class. The markers record one descriptor on the class, frozen and non-enumerable, and instances read it from there; the class declares its callable surface once, and the markers read back in declaration order.
Inside a method, ctx. carries the request the call made (its namespace, its method and the arguments as received), the service it reached, the peer that made the call and the cancellation signal. Outside a Remote call the property reads as undefined.
Yes. Each item travels on the stream as the method produces it, so the caller sees items while the method still runs. There is no wait for the method to finish.
A handle stands for one connection generation. When the connection is lost, iteration fails with the connection error and the handle is finished; start the call again for a new stream.
When a call does not satisfy the endpoint, it fails with gateway/. That failure can carry an issues list describing what was wrong, so read it before you retry.
A method takes one marker. A second marker that disagrees with the first fails with Remote method "<method>" has conflicting invocation markers; keep the single marker that says how the method is called and remove the other.
send() call throw?send throws when the item is not a lossless JSON value, when the uplink has already been ended, or when the stream has terminated. Nothing is delivered when it throws, so fix the value or stop sending to an ended or finished stream.