wspulse

package module
v0.6.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 18, 2026 License: MIT Imports: 2 Imported by: 1

README

wspulse/core

CI Go Reference Go

Shared types for the wspulse WebSocket ecosystem.

This module provides Message, Codec, JSONCodec, wire-protocol types (MessageType, StatusCode), and sentinel errors used by both wspulse/hub and wspulse/client-go. It has zero production dependencies (Go stdlib only).

Status: v0 --- API is being stabilized. Module path: github.com/wspulse/core.


Install

go get github.com/wspulse/core

Quick Start

import wspulse "github.com/wspulse/core"

// Create a message
msg := wspulse.Message{
    Event:   "msg",
    Payload: []byte(`{"text":"hello"}`),
}

// Encode with the default JSON codec
data, err := wspulse.JSONCodec.Encode(msg)
if err != nil {
    log.Fatal(err)
}

// Decode
decoded, err := wspulse.JSONCodec.Decode(data)
if err != nil {
    log.Fatal(err)
}

fmt.Println(decoded.Event) // "msg"

// Check the codec's WebSocket wire type
wspulse.JSONCodec.WireType() // returns wspulse.TextMessage
Sentinel errors
if errors.Is(err, wspulse.ErrConnectionClosed) {
    // connection was already closed
}
if errors.Is(err, wspulse.ErrSendBufferFull) {
    // outbound buffer full, message was dropped
}

Packages

github.com/wspulse/core (root)

Core shared types used across the wspulse ecosystem.

github.com/wspulse/core/router

Gin-style event router for dispatching incoming wspulse.Message values to registered handlers. Features global middleware, per-event handler chains, a configurable fallback for unmatched messages, and a built-in Recovery() middleware.

Routing key --- the "event" JSON field

Every message is encoded on the wire as a JSON object. The "event" field is what the router uses to select the handler:

{
  "event": "chat.message",
  "payload": { "text": "hello" }
}

msg.Event on the Go side maps directly to "event" in JSON. Register handlers with r.On("chat.message", ...) to match that value. The first parameter to On is named event to make this correspondence explicit.

Usage
import (
    "encoding/json"

    wspulse "github.com/wspulse/core"
    "github.com/wspulse/core/router"
)

r := router.New()

// Global middleware --- runs before every handler
r.Use(router.Recovery())
r.Use(func(c *router.Context) {
    // authenticate, rate-limit, set metadata ...
    c.Set("userID", authenticate(c.Connection))
    c.Next()
})

// Per-event handlers --- matched against msg.Event ("event" in JSON)
r.On("chat.message", func(c *router.Context) {
    userID := c.GetString("userID")
    payload, _ := json.Marshal(map[string]any{"ok": true, "from": userID})
    _ = c.Connection.Send(wspulse.Message{
        Event:   "chat.ack",
        Payload: payload,
    })
})
r.On("ping", func(c *router.Context) {
    _ = c.Connection.Send(wspulse.Message{Event: "pong"})
})

// Dispatch --- call this from WithOnMessage in wspulse/hub
r.Dispatch(connection, msg)

Key properties:

  • Routing key is msg.Event, which maps to the "event" field in the JSON wire format
  • Context.Next() / Abort() / IsAborted() flow control (same as Gin)
  • Context.Set / Get / MustGet / GetString typed key-value metadata
  • sync.Pool-backed Context recycling --- 0 steady-state allocations per dispatch (metadata map allocated once per pooled Context on first Set; preserved across pool reuses)
  • Lazy chain building: Use or On can be called in any order before the first Dispatch
  • Panics at startup on empty event name or duplicate registration
  • Max chain length: 62 handlers (middleware + route handlers combined)

Development

make fmt        # auto-format source files (gofmt + goimports)
make check      # validate format, lint, test with race detector (pre-commit gate)
make test       # go test -race -count=50 ./... (override: TEST_COUNT=N)
make test-cover # go test with coverage report -> coverage.html
make bench      # run benchmarks with memory allocation stats
make tidy       # go mod tidy (GOWORK=off)
make clean      # remove build artifacts and test cache

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrConnectionClosed is returned when sending to a connection that has already been closed.
	ErrConnectionClosed = errors.New("wspulse: connection is closed")

	// ErrSendBufferFull is returned when the outbound buffer is full.
	// The message is dropped; handle backpressure at the application layer.
	ErrSendBufferFull = errors.New("wspulse: send buffer full, message dropped")
)

Sentinel errors shared across the wspulse ecosystem.

Functions

This section is empty.

Types

type Codec

type Codec interface {
	// Encode serializes m into bytes ready to be sent over a WebSocket connection.
	Encode(m Message) ([]byte, error)

	// Decode deserializes received WebSocket bytes into a Message.
	Decode(data []byte) (Message, error)

	// WireType returns the WebSocket message type to use when sending.
	WireType() MessageType
}

Codec encodes and decodes Messages for transmission over a WebSocket connection.

var JSONCodec Codec = jsonCodec{}

JSONCodec is the default Codec. Messages are encoded as JSON text frames. Message.Payload must be valid JSON bytes (e.g. the output of json.Marshal).

type Message added in v0.6.0

type Message struct {
	// Event identifies the message purpose. wspulse does not interpret this value.
	// Conventional values: "msg" (user data), "sys" (system event), "ack" (acknowledgement).
	Event string

	// Payload is the encoded message body. Its format is determined by the Codec.
	Payload []byte
}

Message is the minimal transport unit for all WebSocket communication. Payload bytes are opaque to the wspulse layer; their format depends on the Codec in use. When using JSONCodec (the default), Payload must be valid JSON bytes (e.g. output of json.Marshal). When using a binary codec, Payload is the codec-encoded bytes.

type MessageType added in v0.4.0

type MessageType int

MessageType indicates the WebSocket message type used in read and write operations. Values follow RFC 6455 §11.8 and match those used by github.com/coder/websocket and gorilla/websocket — numeric values are identical, so only a type cast is needed at module boundaries (no runtime calculation).

const (
	// TextMessage denotes a UTF-8 encoded text frame (opcode 1).
	TextMessage MessageType = 1

	// BinaryMessage denotes a binary frame (opcode 2).
	BinaryMessage MessageType = 2
)

type StatusCode added in v0.4.0

type StatusCode int

StatusCode is a WebSocket close status code as defined by RFC 6455 §7.4. Values match those used by github.com/coder/websocket — numeric values are identical, so only a type cast is needed at module boundaries (no runtime calculation).

const (
	// StatusNormalClosure indicates a normal, intentional close (1000).
	StatusNormalClosure StatusCode = 1000

	// StatusGoingAway indicates the endpoint is going away, e.g. server shutdown
	// or browser tab close (1001).
	StatusGoingAway StatusCode = 1001

	// StatusProtocolError indicates a protocol error was detected (1002).
	StatusProtocolError StatusCode = 1002

	// StatusUnsupportedData indicates the received data type cannot be handled,
	// e.g. a binary frame sent to an endpoint that only accepts text (1003).
	StatusUnsupportedData StatusCode = 1003

	// StatusInvalidFramePayloadData indicates the received message contains data
	// inconsistent with the message type, e.g. non-UTF-8 bytes in a text frame
	// (1007).
	StatusInvalidFramePayloadData StatusCode = 1007

	// StatusPolicyViolation indicates a message that violates an endpoint policy
	// was received (1008).
	StatusPolicyViolation StatusCode = 1008

	// StatusMessageTooBig indicates the received message is too large to process
	// (1009).
	StatusMessageTooBig StatusCode = 1009

	// StatusMandatoryExtension indicates the client required a WebSocket
	// extension that the server did not negotiate (1010). Valid on the wire,
	// but not used by the wspulse ecosystem — included for completeness when
	// classifying close frames received from peers.
	StatusMandatoryExtension StatusCode = 1010

	// StatusInternalError indicates the server encountered an unexpected condition
	// that prevented it from fulfilling the request (1011).
	StatusInternalError StatusCode = 1011
)

Standard WebSocket close status codes from RFC 6455 §7.4.1. These are valid on the wire — safe to send in a WebSocket close frame.

const (
	// StatusNoStatusReceived indicates a close frame was received but contained
	// no status code (1005).
	StatusNoStatusReceived StatusCode = 1005

	// StatusAbnormalClosure indicates the connection was closed without any close
	// frame, e.g. an abrupt TCP drop (1006).
	StatusAbnormalClosure StatusCode = 1006

	// StatusTLSHandshake indicates a TLS handshake failure (1015).
	StatusTLSHandshake StatusCode = 1015
)

Local-only WebSocket close status codes from RFC 6455 §7.4.1. These MUST NOT be sent in a WebSocket close frame. Use them only to classify locally observed error conditions (logging, metrics).

Directories

Path Synopsis
cmd
benchsync command
Command benchsync regenerates the benchmark tables in docs from a fresh `go test -bench` output file.
Command benchsync regenerates the benchmark tables in docs from a fresh `go test -bench` output file.
Package router provides Gin-style event routing for wspulse messages.
Package router provides Gin-style event routing for wspulse messages.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL