[ AxiomCore · Documentation ]
Streams and WebSockets
Declare HTTP streams, server-sent events, WebSockets and file streams on an endpoint, and bound how a frontend view keeps stream data.
Attach one StreamingDef subtype to an endpoint to declare an HTTP stream,
server-sent events (SSE), a WebSocket or a file stream. This page lists those
declarations, the options that bound a frontend stream view, and the durable
batch-sink profile. Backend examples were checked with axiom check from CLI
0.149.1.
The executable action host supports typed SSE, JSON or message HTTP streams and bidirectional JSON WebSockets with explicit limits and per-message authorization. See Integrations and transports for source policies, provider bindings and transport boundaries.
Declarations
streaming(transport: StreamingDef) goes inside an endpoint. Pass one of these
constructors.
| Constructor | Arguments and defaults |
|---|---|
HttpStreamDef | responseType: TypeRef?, framing = "chunk" (or "event", "message", "file"), encoding = "json" (or "text", "binary") |
SseStreamDef | responseType: TypeRef?. The kind is fixed to sse. |
WebSocketDef | direction = "bidirectional" (or "server_to_client"), reconnect = "auto" (or "manual", "none"), client: WsMessageDef?, server: WsMessageDef? |
WsMessageDef | model, required: a model reference or Json |
FileStreamDef | contentType: String?, filename: String?, disposition = "attachment" (or "inline") |
An unsupported value is rejected with AXB002, for example
Expected "chunk" | "event" | "message" | "file".
project(id: "stream-example", version: "1.0.0")
model Event { id: String }
model ChatMessage { text: String }
model ChatResponse { text: String }
endpoint events(method: GET, path: "/events") {
response(type: Models.Event)
streaming(HttpStreamDef(responseType: Models.Event, framing: "event", encoding: "json"))
}
endpoint sse(method: GET, path: "/sse") {
response(type: Models.Event)
streaming(SseStreamDef(responseType: Models.Event))
}
endpoint chat(method: WS, path: "/chat") {
response(type: Models.ChatResponse)
streaming(WebSocketDef(
direction: "bidirectional",
reconnect: "auto",
client: WsMessageDef(model: Models.ChatMessage),
server: WsMessageDef(model: Models.ChatResponse)
))
}
endpoint report(method: GET, path: "/report") {
response(type: Bytes)
streaming(FileStreamDef(
contentType: "application/pdf", filename: "report.pdf", disposition: "attachment"
))
}client and server each take a single WsMessageDef, not a list.
Bounded frontend views
An Acore UI stream can bound how much data its view keeps. The CLI 0.149.1
compiler validates these options. Use a matching compiler and host: new builds
require acore-data/v3, and an older host cannot silently ignore the options.
Editor and mounted-host qualification are separate.
| Option | Allowed values | Behavior |
|---|---|---|
buffer | 1 to 10,000 items | Items the view keeps. The reduce mode is append, latest or latest_by; latest_by needs a typed String or Int event key. |
overflow | "error" or "drop_oldest" | What happens at capacity |
max_bytes | 64 to 1,048,576 | Bound on the UTF-8 JSON size of retained items |
idle_timeout | 10ms to 1h | Owner-scoped deadline, reset by each event and cleared on close, disposal or session change |
sequence | A field name string | A public Int event field holding the source's increasing sequence |
map, filter | A pure function name | map(Event) -> Event, filter(Event) -> Bool |
fold | A pure function name | (List<Event>, List<Event>) -> List<Event> over the current view and a batch |
window_items | 1 to 256 | Flush a fold window after this many items |
window_time | 10ms to 1m | Optional flush deadline; needs window_items |
overflow: "error"stops before the view exceeds its item capacity.overflow: "drop_oldest"makes the retained view lossy on purpose:droppedcounts removed items andgaprecords the loss. This is a presentation policy, not a durable event-processing or sink policy. Required business events cannot use it. Older policies keep their earlier bounded-view behavior.sequencemust not name a task ID or other business key. After the first event, a duplicate, out-of-order or missing sequence number stops the subscription before publication and setsgap. Automatic reconnect cannot repair missing history: refresh from an authoritative snapshot before you open a new subscription.mapandfilterreceive explicit event values with the normal pure evaluator's fuel and value limits. Sequence and cursor progress follow the original event even when a filter drops it; a map cannot rewrite transport progress.
Folds and windows
A fold reduces the current view and an ordered batch of events. The initial view is an empty typed list, and the fold returns the same list type. It cannot capture page, session or store state.
module advanced.windows.ui
use contract Workflow from "workflow" audience "mobile"
model Change { id: Int; title: String; cursor: String }
pure fn replaceWindow(state: List<Change>, batch: List<Change>): List<Change> = batch
page Home {
stream updates = Workflow.updates(after: null) options(
fold: replaceWindow,
window_items: 2,
window_time: 100ms,
buffer: 4,
max_bytes: 65536,
overflow: "error",
sequence: "id"
)
view { Text(updates.status) Text(to_string(updates.items.length)) }
}This excerpt needs an updates(after: String?) -> Change operation in the
contract. Its id stands in for a contiguous transport sequence; in your
application, select a real transport sequence field, because an entity ID does
not establish stream ordering.
- A fold requires an explicit
max_bytesandoverflow: "error". Window options require a fold. - A count window flushes at
window_items. An optionalwindow_timestarts when the first item enters an empty window and stays fixed as more items arrive. The host owns the timer. - A natural close flushes the remaining batch. Cancellation, session replacement and disposal discard unpublished items and clear the timer.
- The queue and the resulting view each respect
max_bytes, and the view also respectsbuffer. Fold overflow is terminal and never silently drops required events.
Count and time windows are frontend presentation windows. They do not start background workers or provide durable upstream history. Test subscription ordering, overflow, cancellation, reconnect and disposal against your selected transport; synthetic transport checks do not prove mounted native delivery or durable provider behavior.
Durable batch sinks
The native PostgreSQL profile reuses the event, publisher and consumer
declarations, a managed transaction
and typed resource operations. There is no separate sink or pipeline
keyword.
- A source publishes an immutable, bounded batch with a stable receipt key, and a trusted worker consumes it through a guarded action.
- In the reference profile a batch has 1 to 32 items and each title has a 200-character bound. The private runtime specification binds the batch write to checked batch-insert steps and one receipt in the same serializable transaction. See Integrations and transports for the private provider contract.
- A worker acknowledges the queue only after the action succeeds. If the process dies after the database commit but before the acknowledgment, the queue may deliver the same message again; the unchanged batch key and input replay the database receipt. Changed input for an existing key is a conflict. Keep keys stable across redelivery.
- Queue retention and database receipt retention are separate responsibilities. This does not make an arbitrary external publication transactional or give a general exactly-once guarantee.
- Activation needs a signed, qualified database schema, a least-privilege application role, exact resource grants, explicit service credentials and the durable Redis configuration. Queue ordering applies to the declared binding.
Related
Managed logic and concurrency
Write bounded business logic in managed actions, declare typed failures, private setup, transactions, parallel groups, operation and client policies, and durable jobs.
Standard library
The acore modules, built-in functions and value members that the Axiom CLI 0.149.1 evaluator actually provides.