ObjectStackObjectStack

Websocket schema — API Protocol reference

Defines the schema for WebSocket-based real-time communication in ObjectStack.

WebSocket Event Protocol

Defines the schema for WebSocket-based real-time communication in ObjectStack. Supports event subscriptions, filtering, presence tracking, and collaborative editing.

Industry alignment: Firebase Realtime Database, Socket.IO, Pusher

⚠️ NOT YET SERVED — this protocol is declared but no WebSocket server is mounted anywhere in the runtime (#2462, #3197): IRealtimeService.handleUpgrade is deliberately unimplemented and discovery advertises websockets: false. These schemas define the future wire contract; nothing consumes them at runtime today.

Source: packages/spec/src/api/websocket.zod.ts

TypeScript Usage

import { AckMessageSchema, CursorMessageSchema, CursorPositionSchema, DocumentStateSchema, EditMessageSchema, EditOperationSchema, EditOperationType, ErrorMessageSchema, EventMessageSchema, EventPatternSchema, EventSubscriptionSchema, PingMessageSchema, PongMessageSchema, PresenceMessageSchema, PresenceStateSchema, PresenceUpdateSchema, SimpleCursorPositionSchema, SimplePresenceStateSchema, SubscribeMessageSchema, UnsubscribeMessageSchema, UnsubscribeRequestSchema, WebSocketConfigSchema, WebSocketEventSchema, WebSocketMessageSchema, WebSocketMessageType, WebSocketPresenceStatus, WebSocketServerConfigSchema } from '@objectstack/spec/api';
import type { AckMessage, CursorMessage, CursorPosition, DocumentState, EditMessage, EditOperation, EditOperationType, ErrorMessage, EventMessage, EventPattern, EventSubscription, PingMessage, PongMessage, PresenceMessage, PresenceState, PresenceUpdate, SimpleCursorPosition, SimplePresenceState, SubscribeMessage, UnsubscribeMessage, UnsubscribeRequest, WebSocketConfig, WebSocketEvent, WebSocketMessage, WebSocketMessageType, WebSocketPresenceStatus, WebSocketServerConfig } from '@objectstack/spec/api';

// Validate data
const result = AckMessageSchema.parse(data);

AckMessage

Properties

PropertyTypeRequiredDescription
messageIdstring✅Unique message identifier
type'ack'✅
timestampstring✅ISO 8601 datetime when message was sent
ackMessageIdstring✅ID of the message being acknowledged
successboolean✅Whether the operation was successful
errorstringoptionalError message if operation failed

CursorMessage

Properties

PropertyTypeRequiredDescription
messageIdstring✅Unique message identifier
type'cursor'✅
timestampstring✅ISO 8601 datetime when message was sent
cursor{ userId: string; sessionId: string; documentId: string; position?: object; … }✅Cursor position

Nested Shape: CursorMessage.cursor

PropertyTypeRequiredDescription
userIdstring✅User identifier
sessionIdstring✅Session identifier
documentIdstring✅Document identifier being edited
position{ line: integer; column: integer }optionalCursor position in document
selection{ start: object; end: object }optionalSelection range (if text is selected)
colorstringoptionalCursor color for visual representation
userNamestringoptionalDisplay name of user
lastUpdatestring✅ISO 8601 datetime of last cursor update

CursorPosition

Properties

PropertyTypeRequiredDescription
userIdstring✅User identifier
sessionIdstring✅Session identifier
documentIdstring✅Document identifier being edited
position{ line: integer; column: integer }optionalCursor position in document
selection{ start: object; end: object }optionalSelection range (if text is selected)
colorstringoptionalCursor color for visual representation
userNamestringoptionalDisplay name of user
lastUpdatestring✅ISO 8601 datetime of last cursor update

Nested Shape: CursorPosition.position

PropertyTypeRequiredDescription
lineinteger✅Line number (0-indexed)
columninteger✅Column number (0-indexed)

DocumentState

Properties

PropertyTypeRequiredDescription
documentIdstring✅Document identifier
versioninteger✅Current document version
contentstring✅Current document content
lastModifiedstring✅ISO 8601 datetime of last modification
activeSessionsstring[]✅Active editing session IDs
checksumstringoptionalContent checksum for integrity verification

EditMessage

Properties

PropertyTypeRequiredDescription
messageIdstring✅Unique message identifier
type'edit'✅
timestampstring✅ISO 8601 datetime when message was sent
operation{ operationId: string; documentId: string; userId: string; sessionId: string; … }✅Edit operation

Nested Shape: EditMessage.operation

PropertyTypeRequiredDescription
operationIdstring✅Unique operation identifier
documentIdstring✅Document identifier
userIdstring✅User who performed the edit
sessionIdstring✅Session identifier
typeEnum<'insert' | 'delete' | 'replace'>✅Type of edit operation
position{ line: integer; column: integer }✅Starting position of the operation
endPosition{ line: integer; column: integer }optionalEnding position (for delete/replace operations)
contentstringoptionalContent to insert/replace
versioninteger✅Document version before this operation
timestampstring✅ISO 8601 datetime when operation was created
baseOperationIdstringoptionalPrevious operation ID this builds upon (for OT)

EditOperation

Properties

PropertyTypeRequiredDescription
operationIdstring✅Unique operation identifier
documentIdstring✅Document identifier
userIdstring✅User who performed the edit
sessionIdstring✅Session identifier
typeEnum<'insert' | 'delete' | 'replace'>✅Type of edit operation
position{ line: integer; column: integer }✅Starting position of the operation
endPosition{ line: integer; column: integer }optionalEnding position (for delete/replace operations)
contentstringoptionalContent to insert/replace
versioninteger✅Document version before this operation
timestampstring✅ISO 8601 datetime when operation was created
baseOperationIdstringoptionalPrevious operation ID this builds upon (for OT)

Nested Shape: EditOperation.position

PropertyTypeRequiredDescription
lineinteger✅Line number (0-indexed)
columninteger✅Column number (0-indexed)

EditOperationType

Allowed Values

  • insert
  • delete
  • replace

ErrorMessage

Properties

PropertyTypeRequiredDescription
messageIdstring✅Unique message identifier
type'error'✅
timestampstring✅ISO 8601 datetime when message was sent
codestring✅Error code
messagestring✅Error message
detailsanyoptionalAdditional error details

EventMessage

Properties

PropertyTypeRequiredDescription
messageIdstring✅Unique message identifier
type'event'✅
timestampstring✅ISO 8601 datetime when message was sent
subscriptionIdstring✅Subscription ID this event belongs to
eventNamestring✅Event name (dot notation by convention; the platform-checked event vocabulary is the closed DataEventType / BulkDataEventType enums)
objectstringoptionalObject name the event relates to
payloadany✅Event payload data
userIdstringoptionalUser who triggered the event

EventPattern

Event pattern (supports wildcards like "record." or ".created")

Type: string


EventSubscription

Properties

PropertyTypeRequiredDescription
subscriptionIdstring✅Unique subscription identifier
eventsstring[]✅Event patterns to subscribe to (supports wildcards, e.g., "record.*", "user.created")
objectsstring[]optionalObject names to filter events by (e.g., ["account", "contact"])
filtersanyoptionalFilter conditions for event payloads (not yet enforced — the runtime filters by object name and event type only)
channelsstring[]optionalChannel names for scoped subscriptions

PingMessage

Properties

PropertyTypeRequiredDescription
messageIdstring✅Unique message identifier
type'ping'✅
timestampstring✅ISO 8601 datetime when message was sent

PongMessage

Properties

PropertyTypeRequiredDescription
messageIdstring✅Unique message identifier
type'pong'✅
timestampstring✅ISO 8601 datetime when message was sent
pingMessageIdstringoptionalID of ping message being responded to

PresenceMessage

Properties

PropertyTypeRequiredDescription
messageIdstring✅Unique message identifier
type'presence'✅
timestampstring✅ISO 8601 datetime when message was sent
presence{ userId: string; sessionId: string; status: Enum<'online' | 'away' | 'busy' | 'offline'>; lastSeen: string; … }✅Presence state

Nested Shape: PresenceMessage.presence

PropertyTypeRequiredDescription
userIdstring✅User identifier
sessionIdstring✅Unique session identifier
statusEnum<'online' | 'away' | 'busy' | 'offline'>✅Current presence status
lastSeenstring✅ISO 8601 datetime of last activity
currentLocationstringoptionalCurrent page/route user is viewing
deviceEnum<'desktop' | 'mobile' | 'tablet' | 'other'>optionalDevice type
customStatusstringoptionalCustom user status message
metadataRecord<string, any>optionalAdditional custom presence data

PresenceState

Properties

PropertyTypeRequiredDescription
userIdstring✅User identifier
sessionIdstring✅Unique session identifier
statusEnum<'online' | 'away' | 'busy' | 'offline'>✅Current presence status
lastSeenstring✅ISO 8601 datetime of last activity
currentLocationstringoptionalCurrent page/route user is viewing
deviceEnum<'desktop' | 'mobile' | 'tablet' | 'other'>optionalDevice type
customStatusstringoptionalCustom user status message
metadataRecord<string, any>optionalAdditional custom presence data

PresenceUpdate

Properties

PropertyTypeRequiredDescription
statusEnum<'online' | 'away' | 'busy' | 'offline'>optionalUpdated presence status
currentLocationstringoptionalUpdated current location
customStatusstringoptionalUpdated custom status message
metadataRecord<string, any>optionalUpdated metadata

SimpleCursorPosition

Properties

PropertyTypeRequiredDescription
userIdstring✅User identifier
recordIdstring✅Record identifier being edited
fieldNamestring✅Field name being edited
positionnumber✅Cursor position (character offset from start)
selection{ start: number; end: number }optionalText selection range (if text is selected)

Nested Shape: SimpleCursorPosition.selection

PropertyTypeRequiredDescription
startnumber✅Selection start position
endnumber✅Selection end position

SimplePresenceState

Properties

PropertyTypeRequiredDescription
userIdstring✅User identifier
userNamestring✅User display name
statusEnum<'online' | 'away' | 'offline'>✅User presence status
lastSeenAtinteger✅Unix timestamp of last activity in milliseconds
lastSeenneveroptional[REMOVED] SimplePresenceState.lastSeen was renamed to lastSeenAt in @objectstack/spec 17 — the last-activity INSTANT now carries the shared EpochMs schema, which declares the epoch-millisecond unit. Rename the key to lastSeenAt; the value is unchanged (Date.now()). Note the neighbouring PresenceState.lastSeen (api/realtime-shared.zod.ts) is a different key with a different type — an ISO-8601 datetime STRING — and is untouched.
metadataRecord<string, any>optionalAdditional presence metadata (e.g., current page, custom status)

SubscribeMessage

Properties

PropertyTypeRequiredDescription
messageIdstring✅Unique message identifier
type'subscribe'✅
timestampstring✅ISO 8601 datetime when message was sent
subscription{ subscriptionId: string; events: string[]; objects?: string[]; filters?: any; … }✅Subscription configuration

Nested Shape: SubscribeMessage.subscription

PropertyTypeRequiredDescription
subscriptionIdstring✅Unique subscription identifier
eventsstring[]✅Event patterns to subscribe to (supports wildcards, e.g., "record.*", "user.created")
objectsstring[]optionalObject names to filter events by (e.g., ["account", "contact"])
filtersanyoptionalFilter conditions for event payloads (not yet enforced — the runtime filters by object name and event type only)
channelsstring[]optionalChannel names for scoped subscriptions

UnsubscribeMessage

Properties

PropertyTypeRequiredDescription
messageIdstring✅Unique message identifier
type'unsubscribe'✅
timestampstring✅ISO 8601 datetime when message was sent
request{ subscriptionId: string }✅Unsubscribe request

Nested Shape: UnsubscribeMessage.request

PropertyTypeRequiredDescription
subscriptionIdstring✅Subscription ID to unsubscribe from

UnsubscribeRequest

Properties

PropertyTypeRequiredDescription
subscriptionIdstring✅Subscription ID to unsubscribe from

WebSocketConfig

Properties

PropertyTypeRequiredDescription
urlstring✅WebSocket server URL
protocolsstring[]optionalWebSocket sub-protocols
reconnectbooleanoptional (default: true)Enable automatic reconnection
reconnectIntervalMsintegeroptional (default: 1000)Reconnection interval in milliseconds
maxReconnectAttemptsintegeroptional (default: 5)Maximum reconnection attempts
pingIntervalMsintegeroptional (default: 30000)Ping interval in milliseconds
timeoutMsintegeroptional (default: 5000)Message timeout in milliseconds
reconnectIntervalneveroptional[REMOVED] WebSocketConfig.reconnectInterval was renamed to reconnectIntervalMs in @objectstack/spec 17 — the unit of a duration-shaped number lives in the key name, not only in the describe prose. Rename the key to reconnectIntervalMs; the value (milliseconds) is unchanged.
pingIntervalneveroptional[REMOVED] WebSocketConfig.pingInterval was renamed to pingIntervalMs in @objectstack/spec 17 — the unit of a duration-shaped number lives in the key name, not only in the describe prose. Rename the key to pingIntervalMs; the value (milliseconds) is unchanged.
timeoutneveroptional[REMOVED] WebSocketConfig.timeout was renamed to timeoutMs in @objectstack/spec 17 — the unit of a duration-shaped number lives in the key name, not only in the describe prose. Rename the key to timeoutMs; the value (milliseconds) is unchanged.
headersRecord<string, string>optionalCustom headers for WebSocket handshake

WebSocketEvent

Properties

PropertyTypeRequiredDescription
typeEnum<'subscribe' | 'unsubscribe' | 'data-change' | 'presence-update' | 'cursor-update' | 'error'>✅Event type
channelstring✅Channel identifier (e.g., "record.account.123", "user.456")
payloadany✅Event payload data
occurredAtinteger✅Unix timestamp in milliseconds when the event occurred
timestampneveroptional[REMOVED] WebSocketEvent.timestamp was renamed to occurredAt in @objectstack/spec 17 — the event INSTANT now carries the shared EpochMs schema, which declares the epoch-millisecond unit the bare key name left to the describe prose. Rename the key to occurredAt; the value is unchanged (Date.now()).

WebSocketMessage

Union Options

This schema accepts one of the following structures:

Option 1

Type: subscribe

Properties

PropertyTypeRequiredDescription
messageIdstring✅Unique message identifier
type'subscribe'✅
timestampstring✅ISO 8601 datetime when message was sent
subscription{ subscriptionId: string; events: string[]; objects?: string[]; filters?: any; … }✅Subscription configuration

Nested Shape: WebSocketMessage[type='subscribe'].subscription

PropertyTypeRequiredDescription
subscriptionIdstring✅Unique subscription identifier
eventsstring[]✅Event patterns to subscribe to (supports wildcards, e.g., "record.*", "user.created")
objectsstring[]optionalObject names to filter events by (e.g., ["account", "contact"])
filtersanyoptionalFilter conditions for event payloads (not yet enforced — the runtime filters by object name and event type only)
channelsstring[]optionalChannel names for scoped subscriptions

Option 2

Type: unsubscribe

Properties

PropertyTypeRequiredDescription
messageIdstring✅Unique message identifier
type'unsubscribe'✅
timestampstring✅ISO 8601 datetime when message was sent
request{ subscriptionId: string }✅Unsubscribe request

Nested Shape: WebSocketMessage[type='unsubscribe'].request

PropertyTypeRequiredDescription
subscriptionIdstring✅Subscription ID to unsubscribe from

Option 3

Type: event

Properties

PropertyTypeRequiredDescription
messageIdstring✅Unique message identifier
type'event'✅
timestampstring✅ISO 8601 datetime when message was sent
subscriptionIdstring✅Subscription ID this event belongs to
eventNamestring✅Event name (dot notation by convention; the platform-checked event vocabulary is the closed DataEventType / BulkDataEventType enums)
objectstringoptionalObject name the event relates to
payloadany✅Event payload data
userIdstringoptionalUser who triggered the event

Option 4

Type: presence

Properties

PropertyTypeRequiredDescription
messageIdstring✅Unique message identifier
type'presence'✅
timestampstring✅ISO 8601 datetime when message was sent
presence{ userId: string; sessionId: string; status: Enum<'online' | 'away' | 'busy' | 'offline'>; lastSeen: string; … }✅Presence state

Nested Shape: WebSocketMessage[type='presence'].presence

PropertyTypeRequiredDescription
userIdstring✅User identifier
sessionIdstring✅Unique session identifier
statusEnum<'online' | 'away' | 'busy' | 'offline'>✅Current presence status
lastSeenstring✅ISO 8601 datetime of last activity
currentLocationstringoptionalCurrent page/route user is viewing
deviceEnum<'desktop' | 'mobile' | 'tablet' | 'other'>optionalDevice type
customStatusstringoptionalCustom user status message
metadataRecord<string, any>optionalAdditional custom presence data

Option 5

Type: cursor

Properties

PropertyTypeRequiredDescription
messageIdstring✅Unique message identifier
type'cursor'✅
timestampstring✅ISO 8601 datetime when message was sent
cursor{ userId: string; sessionId: string; documentId: string; position?: object; … }✅Cursor position

Nested Shape: WebSocketMessage[type='cursor'].cursor

PropertyTypeRequiredDescription
userIdstring✅User identifier
sessionIdstring✅Session identifier
documentIdstring✅Document identifier being edited
position{ line: integer; column: integer }optionalCursor position in document
selection{ start: object; end: object }optionalSelection range (if text is selected)
colorstringoptionalCursor color for visual representation
userNamestringoptionalDisplay name of user
lastUpdatestring✅ISO 8601 datetime of last cursor update

Option 6

Type: edit

Properties

PropertyTypeRequiredDescription
messageIdstring✅Unique message identifier
type'edit'✅
timestampstring✅ISO 8601 datetime when message was sent
operation{ operationId: string; documentId: string; userId: string; sessionId: string; … }✅Edit operation

Nested Shape: WebSocketMessage[type='edit'].operation

PropertyTypeRequiredDescription
operationIdstring✅Unique operation identifier
documentIdstring✅Document identifier
userIdstring✅User who performed the edit
sessionIdstring✅Session identifier
typeEnum<'insert' | 'delete' | 'replace'>✅Type of edit operation
position{ line: integer; column: integer }✅Starting position of the operation
endPosition{ line: integer; column: integer }optionalEnding position (for delete/replace operations)
contentstringoptionalContent to insert/replace
versioninteger✅Document version before this operation
timestampstring✅ISO 8601 datetime when operation was created
baseOperationIdstringoptionalPrevious operation ID this builds upon (for OT)

Option 7

Type: ack

Properties

PropertyTypeRequiredDescription
messageIdstring✅Unique message identifier
type'ack'✅
timestampstring✅ISO 8601 datetime when message was sent
ackMessageIdstring✅ID of the message being acknowledged
successboolean✅Whether the operation was successful
errorstringoptionalError message if operation failed

Option 8

Type: error

Properties

PropertyTypeRequiredDescription
messageIdstring✅Unique message identifier
type'error'✅
timestampstring✅ISO 8601 datetime when message was sent
codestring✅Error code
messagestring✅Error message
detailsanyoptionalAdditional error details

Option 9

Type: ping

Properties

PropertyTypeRequiredDescription
messageIdstring✅Unique message identifier
type'ping'✅
timestampstring✅ISO 8601 datetime when message was sent

Option 10

Type: pong

Properties

PropertyTypeRequiredDescription
messageIdstring✅Unique message identifier
type'pong'✅
timestampstring✅ISO 8601 datetime when message was sent
pingMessageIdstringoptionalID of ping message being responded to


WebSocketMessageType

Allowed Values

  • subscribe
  • unsubscribe
  • event
  • ping
  • pong
  • ack
  • error
  • presence
  • cursor
  • edit

WebSocketPresenceStatus

Allowed Values

  • online
  • away
  • busy
  • offline

WebSocketServerConfig

Properties

PropertyTypeRequiredDescription
enabledbooleanoptional (default: false)Enable WebSocket server
pathstringoptional (default: "/ws")WebSocket endpoint path
heartbeatIntervalMsnumberoptional (default: 30000)Heartbeat interval in milliseconds
reconnectAttemptsnumberoptional (default: 5)Maximum reconnection attempts for clients
presencebooleanoptional (default: false)Enable presence tracking
cursorSharingbooleanoptional (default: false)Enable collaborative cursor sharing
heartbeatIntervalneveroptional[REMOVED] WebSocketServerConfig.heartbeatInterval was renamed to heartbeatIntervalMs in @objectstack/spec 17 — the unit of a duration-shaped number lives in the key name, not only in the describe prose. Rename the key to heartbeatIntervalMs; the value (milliseconds) is unchanged.

On this page