Runtime Design
For runtime maintainers, this page documents runtime responsibilities and execution-flow boundaries.
Goals
- Same runtime composition API on frontend and backend.
- Deterministic event lifecycle hooks.
- Clear separation between runtime, schema, and transport concerns.
Lifecycle hooks
All envelopes flow through:
onReceivefor inbound events.onSendfor outbound events.onErrorfor failure events.
Context boundaries
LIVON uses three different context models:
RuntimeContext(ctxin runtime hooks) for emit/state/room APIs.SchemaBuildContextfor AST build resolution (schema.ast(...)).SchemaRequestContextfor parse/exec/publish runtime flow.
RuntimeContext is not the same as SchemaRequestContext, and neither is the same as envelope.context.
See @livon/runtime and Schema Context for field-level details.
Registry API (on*) in modules
Every module registers hooks via onReceive, onSend, and onError (destructured from the registry input).
import type {RuntimeModule} from '@livon/runtime';
export const traceModule = (): RuntimeModule => ({
name: 'traceModule',
register: ({onReceive, onSend, onError}) => {
onReceive(async (envelope, ctx, next) => {
return next();
});
onSend(async (envelope, ctx, next) => {
return next();
});
onError((error, envelope, ctx) => {
console.error('runtime error', {
event: envelope.event,
status: envelope.status,
message: error instanceof Error ? error.message : String(error),
});
});
},
});
onReceive and onSend are middleware-style chains (next(update?)).
onError is a listener list and runs without next.
Parameters in this example
onReceive((envelope, ctx, next) => ...) / onSend((envelope, ctx, next) => ...):
envelope(EventEnvelope): current event envelope in the hook chain.ctx(RuntimeContext): emit APIs, room scoping, shared runtime state.next((update?) => Promise<EventEnvelope>): continues chain; optionalupdatemerges into envelope.
onError((error, envelope, ctx) => ...):
error(unknown): thrown/normalized hook error.envelope(EventEnvelope): failed event envelope.ctx(RuntimeContext): runtime context for recovery/logging side effects.
Runtime composition
Server:
runtime(
nodeWsTransport(wsConfig),
schemaModule(serverSchema),
);
Client:
runtime(
clientWsTransport(clientWsConfig),
api,
);
Parameters in this example
runtime(...modules):
modules(RuntimeModule[]): ordered module list; order defines hook execution order.
nodeWsTransport(wsConfig):
wsConfig(NodeWsTransportInput): websocket server transport configuration.
schemaModule(serverSchema):
serverSchema(Api | ComposedApi): schema schema object returned byapi(...)orcomposeApi(...).
clientWsTransport(clientWsConfig):
clientWsConfig(ClientWsTransportInput): browser websocket transport configuration.
api:
- generated client runtime module from schema AST.
Low-level call order
Runtime call order is deterministic and follows module registration order from left to right.
runtime(moduleA, moduleB, moduleC);
Registration order:
moduleA.register(registry)moduleB.register(registry)moduleC.register(registry)
Hook execution order for one emitReceive call:
- first
onReceivehook registered - second
onReceivehook registered - ...
- chain end
Same rule applies to onSend.
For onError, handlers run in registration order with forEach.
Schema behavior
- Operations can define
input,output,exec,publish,rooms, andack. - Subscriptions define
payloadand optionalinput,output,filter,exec. - Generated client keeps operation names symmetric with server.
Envelope basics
type EventEnvelope = {
id: string;
event: string;
status: 'sending' | 'receiving' | 'failed';
metadata?: Readonly<Record<string, unknown>>;
context?: Readonly<Record<string, unknown>>;
payload?: Uint8Array;
error?: {
message: string;
name?: string;
stack?: string;
};
};
Boundaries
- Runtime orchestrates hooks and event forwarding.
- Schema module validates, executes, and encodes/decodes schema payloads.
- Transport maps wire frames to/from envelopes.
- Reliability behavior belongs to dedicated modules, not runtime or transport.