The psionic protocol is kept intentionally minimal. These examples are shown using the psionic wire codec, designed to be easy to debug and interact with manually and in constrained environments, but the same protocol can be implemented over any transport layer and with any serialization format.
Each frame is a tuple of [type, id, ...args]
-
typeis a single character string indicating the frame type -
idis a string identifier that is written back in responses to the caller so they can correlate responses to their requests. -
argsis any additional data needed for the frame, which varies based on the frame type.
Example
D "1" {"name":"Alice","greet":{"@@psion":"f"},"worker":{"@@psion":"g"}} true
Send a new set of capabilities and state to the remote peer. The sender provides a description object, which is a structure that contains any combination of nested objects, arrays, primitive values, and functions. The receiver gets a live-updating view of this description as it changes over time, and can call functions within it as if they were local.
-
description: A nested object containing any combination of primitive values, objects, arrays, and functions.{"@@psion":"f"}indicates a callable function, and{"@@psion":"g"}for async generators. -
swap(optional): A boolean flag indicating whether the description should replace the existing remote description (true), or be merged with it (falseor omitted). Ifswapistrue, the provided description completely replaces the existing remote description. Ifswapisfalseor omitted, the provided description is merged with the existing remote description, allowing for incremental updates.
Example
C "2" ["greet"] ["Hello, %s!"]
Invoke a function on the remote peer. The caller provides the path to the function within the remote description, and an array of arguments to call it with. The callee receives the call, executes the function, and sends back a response frame with the result.
If the function is a generator, the callee instead sends an iterator ID, which can be iterated over the generator with . (next), < (return), and > (throw) frames.
-
name: An array of strings representing the path to the function within the remote description. For example,["user", "load"]would callpeer.remote.user.load(). -
args: An array of arguments to call the function with.
Example
K "2" "Hello, Alice!"
Send a successful response back to the caller after any frame that expects a response (e.g. D, C, ., >) The id field matches the id of the original call frame, so the caller can correlate the response to their request.
For iterators (., <, >), responses are an array where the first item is a yielded value, and the second is a 0 or 1 indicating if the generator is done. Continuing to call next on a completed generator will return an error.
result: The result of the call, which can be any value.
Example
E "2" 0 "Cannot greet user, server is shy."
Send an error response back to the caller after any frame that expects a response (e.g. D, C, ., >) The id field matches the id of the original call frame, so the caller can correlate the response to their request.
-
code: A numeric error code. -
error(optional): A string describing the error.
Example
. "3" "iterator-1" "Next value"
Advance a remote iterator to its next yield point. The operationId field matches the return of the original call frame that created the iterator. The callee responds with either an OK frame containing the yielded value, or an Error frame if the iterator is completed or throws.
-
operationId: The ID of the iterator to advance, as returned by the original call frame. -
value: An optional value to send into the generator, foryieldexpressions.
Example
< "3" "iterator-1" "Final value"
Stop the iteration of a remote iterator, completing it. The operationId field matches the return of the original call frame that created the iterator. The callee responds with an OK frame confirming completion, or an Error frame if the iterator is already completed or throws.
-
operationId: The ID of the iterator to close, as returned by the originalCallframe. -
value: An optional return value to send into the generator.
Example
> "3" "iterator-1" 0 "Something went wrong"
Used to throw an error into a remote iterator. The operationId field matches the return of the original call frame that created the iterator. The callee responds with either an OK frame containing the value returned by the generator, or an Error frame if the iterator is already completed or throws.
-
operationId: The ID of the iterator to advance, as returned by the originalCallframe. -
code: A numeric error code. -
error(optional): A string describing the error.
There are some standard errors defined by the protocol, but applications are free to define their own error codes and messages as well.
The generic remote error code indicates that an error occurred on the remote peer while processing a request. The error message may contain additional details about the error, but this is not guaranteed.
This error indicates that a call was attempted without an active remote peer connection. This can happen if the peer has not yet connected to a remote, or if the connection was lost.
This error indicates that the connection to the remote peer was lost while a request was in-flight. The request may or may not have been processed by the remote peer.
This error indicates that a message was received that does not conform to the expected protocol format. This can happen if someone is sending data leading to corrupted frames or missing arguments.
This error indicates that a call was made to a function that does not exist on the remote peer's description. This can happen if the caller has an outdated view of the remote description, or if the caller is trying to call a function that was never described.
This error indicates that a call was made with an ID that is no longer valid. This can happen if the caller is trying to respond to a call that has already been responded to, or if the caller is trying to interact with an iterator that has already completed.
This error indicates that a call was rejected by the remote peer. This can happen if the remote peer is overloaded, or if the remote peer has some application-level logic for rejecting certain calls.