@polytric/openws-spec
v0.0.4
Published
Polytric OpenWS Specification
Maintainers
Readme
OpenWS Specification
OpenWS is a specification for describing WebSocket-based systems in the same way OpenAPI describes HTTP APIs. It models WebSocket communication as an asymmetrical 2-way request-response mesh: multiple components exchange named messages, and "responses" are simply messages sent back through the same network.
An OpenWS document describes:
- Networks: logical WebSocket systems where
messages are exchanged - Roles:
role-based API surfaces with statically definedmessagecontracts - Messages: named
messagedefinitions that contain apayload - Payload: the shape of application data transmitted through WebSocket messages, defined with JSON Schema
- Metadata & connection hints: document metadata (title, version, description) and optional connection hints (endpoints)
Conceptually (at runtime), we also use the following terms throughout this document:
- Participants: runtime instances/connections that assume a
roleon anetwork - Handlers: runtime message-processing functions invoked when a participant receives a
message
Participants and handlers are not serialized in the OpenWS JSON document. They are explanatory terms used to describe how role/message contracts are typically implemented.
This spec intentionally does not describe behavior or execution.
Conventions
The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, MAY, and OPTIONAL in this document are to be interpreted as described in RFC 2119.
Unless otherwise noted:
- OpenWS documents are JSON objects.
- Examples are written as valid JSON (no comments, no trailing commas).
payloadschemas use JSON Schema. A specific JSON Schema dialect may be chosen by downstream tooling; OpenWS itself describes shapes rather than enforcing a single validator implementation.
Note: The OpenWS document describes contracts. Runtime details such as routing, correlation, authentication, and connection lifecycle are intentionally left to runtime layers.
Document Structure
An OpenWS document is a JSON object with the following top-level fields:
openws(REQUIRED): the OpenWS specification version as a string.title(OPTIONAL): a human-readable name for the document.version(OPTIONAL): the version of the described system/API (not the OpenWS spec version).description(OPTIONAL): a longer explanation of the document.networks(REQUIRED): a map of network names to network definitions.
An implementation:
- MUST ignore unknown fields (to allow forward compatibility and extensions).
- SHOULD preserve unknown fields when round-tripping documents (when feasible).
- MAY support custom extension fields anywhere in the document (see Metadata & Connection Hints).
Minimal skeleton:
{
"openws": "0.0.2",
"title": "My WebSocket System",
"version": "1.0.0",
"description": "Optional, human-readable description.",
"networks": {
"example": {
"roles": {
"server": {
"messages": {}
}
}
}
}
}Ecosystem
OpenWS is designed as a tooling ecosystem, similar in spirit to OpenAPI: a spec, plus runtimes, adapters, generators, and framework integrations.
This repository/spec is intended to support a full "fleet" of packages, including (but not limited to):
Spec & validation
@polytric/openws-spec- This library
Runtime framework
@polytric/openws- OpenWS framework for JavaScript/TypeScript@polytric/fastify-openws- Fastify WebSocket integration
UI & tooling
@polytric/openws-ui- Swagger-like spec explorer@polytric/fastify-openws-ui- Mount OpenWS UI in Fastify
SDK generation
@polytric/openws-sdkgen- CLI for generating typed SDKs
The spec intentionally stays framework-agnostic; framework integrations and SDKs belong to the ecosystem layer.
Core Concepts
Networks
A network is a logical WebSocket system, with multiple participants on the network forming a mesh. It is common to run multiple isolated features over WebSocket on a single host. For example, a chess game client may have a chat room, a tournament entry, and the actual chess game. They are logically separated like this in the spec:
{
"networks": {
"chat": {},
"tournament": {},
"chess": {}
}
}[!NOTE] Examples in the core concepts sections have omitted details for brevity; they are not valid standalone. Consult the complete example towards the bottom of the doc for a fully working example.
On the backend side, there may be a dedicated service for each of these components, so each service would typically define just one network:
Chat service:
{
"networks": {
"chat": {}
}
}Tournament service:
{
"networks": {
"tournament": {}
}
}Chess service:
{
"networks": {
"chess": {}
}
}A network definition:
- MUST define
roles. - MAY include metadata such as
description. - MAY include custom extension fields.
Roles
In the OpenWS document, we define roles. At runtime, a participant is a connected instance that assumes a role on a network.
Multiple participants of the same role may exist on the same network. For example, in a chat service, a server is normally connected to many clients. OpenWS defers modeling one-to-one, one-to-many, or many-to-many relationships to the application/runtime.
{
"networks": {
"chat": {
"roles": {
"server": {},
"client": {}
}
}
}
}It is common for different components of a system to expose different APIs and provide different behaviors. This is naturally modeled by participants on the same network having different roles. Continuing the chat example, imagine we now have a mobile client, a customer web portal, and an admin console. To serve both customer and admin needs, the server is split into two separate roles, while a single backend may implement both roles. Distinct roles can be set up like this:
{
"roles": {
"adminServer": {},
"customerServer": {},
"mobile": {},
"portal": {},
"console": {}
}
}IMPORTANT: this is not a role-based access control system, but the clean API boundary is intended to make layering an auth system trivial.
A role definition:
- MUST define
messages(it MAY be empty). - MAY include metadata such as
descriptionor connection hints such asendpoints. - MAY include custom extension fields.
Messages
In the OpenWS document, roles declare the data shapes they accept as messages. Messages are indexed by name.
Message definitions are scoped to the receiving role. At runtime:
- A participant receives a message by name, and dispatches it to a handler.
- A participant sends a message by targeting some other participant(s) whose role defines that message name and payload shape.
OpenWS intentionally describes contracts, not delivery semantics. Routing (broadcast, directed, room-based, etc.) is runtime-specific.
For example, a chat server may accept message, join, createRoom, while the customer portal may accept channelStats. The meaning of messages differs depending on perspective: the chat server accepts join as an incoming message and handles it to update its state, while the portal can send join to the server. Similarly, the portal accepts channelStats as an incoming message, while the server can send channelStats to the portal.
{
"roles": {
"server": {
"messages": {
"message": {},
"join": {},
"createRoom": {}
}
},
"portal": {
"messages": {
"channelStats": {}
}
}
}
}A message definition:
- MUST define
payload. - MAY include metadata such as
description. - MUST be uniquely named within its role (message names under the same role MUST NOT collide).
- MAY include custom extension fields.
Request/response modeling
OpenWS supports request/response patterns, but does not mandate a single correlation mechanism. If your system uses request/response semantics, runtimes SHOULD define a correlation convention (for example, an ID field inside the payload or an envelope-level correlation ID).
Payload
Each message declares its payload shape using JSON Schema:
{
"message": {
"payload": {
"type": "string",
"minLength": 1,
"maxLength": 2048
}
}
}Payload schemas:
- MUST be valid JSON Schema objects (as interpreted by the runtime/tooling).
- SHOULD be compatible with your chosen JSON Schema dialect across toolchains.
- MAY use common JSON Schema keywords such as
type,properties,required,enum, andformat.
A role's handlers are the runtime functions that process incoming messages whose payloads match the message definitions in the spec.
Metadata & Connection Hints
OpenWS includes a small set of well-known metadata fields for product-grade tooling, while still allowing custom metadata anywhere in the document.
Document metadata
OpenWS supports a small set of well-known metadata fields at the root of the document:
title(SHOULD): a human-readable name for the document.version(SHOULD): the version of the described system/API (not the OpenWS spec version).description(MAY): a longer explanation of the document.
Tooling MAY use these fields for documentation, generation, and display.
Connection hints
Endpoints are optional connection hints that help participants determine how to establish connections. They do not prescribe deployment topology, discovery, authentication, or load-balancing.
An endpoint is typically attached to a role definition:
{
"networks": {
"chat": {
"roles": {
"server": {
"endpoints": [
{ "scheme": "wss", "host": "localhost", "port": 8082, "path": "/abc" }
]
}
}
}
}
}Endpoint objects:
- MAY include
scheme(wsorwss),host,port, andpath. - SHOULD be treated as hints. Implementations MAY connect using other configuration (service discovery, environment variables, user choice, etc.).
- Dynamic endpoint resolution is outside the scope of this spec.
Custom metadata and extensions
Custom metadata fields MAY be added anywhere in the JSON structure. Implementations:
- MUST ignore fields they do not understand.
- SHOULD encourage a consistent prefix such as
x-...for extension fields to reduce the chance of collisions with future spec fields.
Complete Example
The following example is a minimal but complete OpenWS document modeling a chat system. It uses:
- a single
chatnetwork - three roles (
server,client,portal) - message contracts scoped to the receiving role
- connection hints via
endpoints - one extension field (
x-routingNotes) to document intended flows
{
"openws": "0.0.2",
"title": "Example Chat Service",
"version": "1.0.0",
"description": "A minimal OpenWS document modeling a chat network.",
"networks": {
"chat": {
"description": "Realtime chat network.",
"roles": {
"server": {
"description": "Backend that hosts rooms and broadcasts messages.",
"endpoints": [
{
"scheme": "wss",
"host": "chat.example.com",
"port": 443,
"path": "/ws/chat"
}
],
"messages": {
"join": {
"description": "Request to join a room.",
"payload": {
"type": "object",
"additionalProperties": false,
"required": ["userId", "roomId"],
"properties": {
"userId": { "type": "string", "minLength": 1 },
"roomId": { "type": "string", "minLength": 1 }
}
}
},
"message": {
"description": "Send a chat message to a room.",
"payload": {
"type": "object",
"additionalProperties": false,
"required": ["userId", "roomId", "text"],
"properties": {
"userId": { "type": "string", "minLength": 1 },
"roomId": { "type": "string", "minLength": 1 },
"text": { "type": "string", "minLength": 1, "maxLength": 2048 }
}
}
},
"createRoom": {
"description": "Create a new room.",
"payload": {
"type": "object",
"additionalProperties": false,
"required": ["userId", "name"],
"properties": {
"userId": { "type": "string", "minLength": 1 },
"name": { "type": "string", "minLength": 1, "maxLength": 128 }
}
}
},
"requestStats": {
"description": "Request channel statistics.",
"payload": {
"type": "object",
"additionalProperties": false,
"required": ["roomId"],
"properties": {
"roomId": { "type": "string", "minLength": 1 }
}
}
}
}
},
"client": {
"description": "End-user client that receives events from the server.",
"messages": {
"roomJoined": {
"description": "Emitted after a join succeeds.",
"payload": {
"type": "object",
"additionalProperties": false,
"required": ["roomId"],
"properties": {
"roomId": { "type": "string", "minLength": 1 }
}
}
},
"messageReceived": {
"description": "Broadcast of a message to participants in a room.",
"payload": {
"type": "object",
"additionalProperties": false,
"required": ["roomId", "text", "senderId", "sentAt"],
"properties": {
"roomId": { "type": "string", "minLength": 1 },
"text": { "type": "string", "minLength": 1, "maxLength": 2048 },
"senderId": { "type": "string", "minLength": 1 },
"sentAt": { "type": "integer" }
}
}
}
}
},
"portal": {
"description": "Internal web portal that receives aggregated stats.",
"messages": {
"channelStats": {
"description": "Room-level metrics snapshot.",
"payload": {
"type": "object",
"additionalProperties": false,
"required": ["roomId", "members", "messagesLastMinute"],
"properties": {
"roomId": { "type": "string", "minLength": 1 },
"members": { "type": "integer", "minimum": 0 },
"messagesLastMinute": { "type": "integer", "minimum": 0 }
}
}
}
}
}
},
"x-routingNotes": "Clients send server.join/server.message. Server emits client.roomJoined/client.messageReceived and portal.channelStats."
}
}
}