router

package module
v0.1.5 Latest Latest
Warning

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

Go to latest
Published: Jul 12, 2026 License: MIT Imports: 1 Imported by: 0

README

tinywasm/router

Isomorphic routing contract — Context, Router, HandlerFunc identical on native and edge/wasm targets. Defines request handling, streaming (SSE), WebSocket upgrade, and middleware. Modules mount their API via APIModule. The router defines the shape; concrete servers implement it.

Quick Start

import "github.com/tinywasm/router"

type MyModule struct{ name string }

func (m MyModule) ModelName() string { return m.name }
func (m MyModule) MountAPI(r router.Router) {
    r.Get("/api/data", func(ctx router.Context) {
        ctx.WriteStatus(200)
        ctx.Write([]byte("hello"))
    })
}

Caller — the call-side contract

func (v *MyView) Refresh() {
    v.Caller.Call("list_services", nil, func(res []byte, err error) {
        if err != nil {
            v.HandleError(err)
            return
        }
        v.Update(res)
    })
}

Modules and views depend on Caller to invoke server operations without knowing the wire protocol or transport. Adapters live with each transport (e.g. mcp.NewCaller in tinywasm/mcp adapts a JSON-RPC client), while tests use a mock.Caller.

Contracts

  • Context: minimal I/O (read method/path/body, write headers/status) + cookies (SetCookie/Cookie) + identity (SetUserID/UserID)
  • Cookie: isomorphic HTTP cookie type with SameSite policy (SameSiteDefault/Lax/Strict/None)
  • HandlerFunc: func(Context) — the unit of dispatch
  • Route: registration token; supports Requires(resource, action) for RBAC and Public() for explicit public access
  • RouteInfo: read-only view of a registered route with method, path, resource, action, and public flag
  • Router: register routes (Get/Post/Put/Delete/Handle) returning Route + streaming (Stream/Socket) + middleware (Use) + Routes() for introspection
  • Streamer: Context + Flush() for SSE/streaming responses
  • Socket: bidirectional connection (WebSocket)
  • Middleware: func(HandlerFunc) HandlerFunc — transversal logic (auth, logging)
  • APIModule: module + MountAPI(Router) — how modules publish APIs
  • Caller: call-side contract — how a client-side view invokes a named server operation
  • mock: subpackage with canonical test doubles (Router, Context, Route, Caller) — no net/http, WASM-safe

Design

No net/http in the public API. Handlers never import Go's standard library HTTP types. All routing is self-describing via signatures — no runtime type assertions, no hidden machinery.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type APIModule added in v0.1.0

type APIModule interface {
	model.ModuleNaming // provides ModelName() — identity
	MountAPI(r Router)
}

APIModule is a module that exposes a server API. It is consumed by the server entry point (!wasm): which passes it the host's Router, and the module registers its own routes/handlers. Since Router is isomorphic, the module never imports net/http to describe its API. The concrete transport (binary upload, another protocol mounted as a route) is the module's internal decision.

type Caller added in v0.1.4

type Caller interface {
	// Call invokes op; result/err arrive asynchronously via callback
	// (works for wasm fetch and for in-process test doubles alike).
	// Implementations MUST propagate every error — never swallow.
	Call(op string, args model.Encodable, callback func(result []byte, err error))

	// Dispatch is fire-and-forget (no response expected).
	Dispatch(op string, args model.Encodable)
}

Caller is how a client-side view invokes a named server operation without knowing the wire protocol or transport. It mirrors APIModule: APIModule is the mount-side contract, Caller is the call-side contract.

op is the logical operation name (e.g. "list_services") — NEVER a wire-level method. Translating op to the concrete envelope is the adapter's job (e.g. mcp.NewCaller adapts *mcp.Client). Test doubles satisfy Caller with canned bytes and no transport.

type Context added in v0.1.0

type Context interface {
	Method() string
	Path() string
	Body() []byte
	GetHeader(key string) string
	SetHeader(key, value string)
	WriteStatus(code int)
	Write(b []byte) (int, error)
	// Request-scoped values (middleware passes data to the next handler).
	SetValue(key string, v any)
	Value(key string) any
	// Isomorphic cookies.
	SetCookie(c Cookie)                // writes a cookie to the response
	Cookie(name string) (Cookie, bool) // reads a cookie from the request; ok=false if not found
	// Request-scoped identity. An auth middleware records the caller;
	// handlers and mounted modules read it.
	SetUserID(id string) // records the authenticated identity (id "" = anonymous)
	UserID() string      // reads the identity; "" if no valid session
}

Context is the minimal abstraction seen by a handler: request → response. Same interface signature for both native (!wasm) and edge/wasm targets.

Ownership: a Context belongs to ONE goroutine (the handler's); implementations are not required to be safe for concurrent use (same contract as http.ResponseWriter). To feed it from other goroutines, send the data over a channel to the owning goroutine — never share the Context itself.

type Cookie struct {
	Name     string   // e.g. "session_id", "user_pref"
	Value    string   // e.g. "abc123xyz789"
	Path     string   // e.g. "/", "/api"; omit for "/"
	Domain   string   // e.g. "example.com"; omit for current domain
	MaxAge   int      // >0 seconds; 0 = session; <0 = delete now
	Secure   bool     // true = HTTPS only
	HttpOnly bool     // true = no JavaScript access
	SameSite SameSite // SameSiteLax, SameSiteStrict, SameSiteNone
}

Cookie is the isomorphic representation of an HTTP cookie. It does not reference net/http: each concrete implementer maps it to its transport (net/http.Cookie on native; Set-Cookie header on edge/wasm).

type HandlerFunc added in v0.1.0

type HandlerFunc func(Context)

HandlerFunc is the dispatch unit: receives a Context and responds to it.

type Middleware added in v0.1.0

type Middleware func(HandlerFunc) HandlerFunc

Middleware wraps a handler to add cross-cutting logic (auth, logging). Operate ONLY on Context — never on concrete transport types.

type Route added in v0.1.1

type Route interface {
	// Requires binds an RBAC permission to the route: the (resource, action) pair.
	// action is a string, matching the source of truth user.Permission.Action string.
	// Readable and extensible: "write", "read", "orders:export" — not a cryptic byte.
	Requires(resource string, action string) Route
	// Public marks the route as accessible without identity. The absence of this
	// marker (and Requires) means the route is private by default.
	Public() Route
}

Route describes a registered route and allows annotating it. It is returned by each Router registration method. Annotations are declarative: the contract does not enforce them — each concrete implementer (native server, edge runtime) enforces them.

type RouteInfo added in v0.1.1

type RouteInfo struct {
	Method   string // e.g. "GET", "POST"
	Path     string // e.g. "/api/users", "/api/orders/:id"
	Resource string // e.g. "users", "orders"; "" = public route (no RBAC)
	Action   string // e.g. "read", "write", "orders:export"
	Public   bool   // true = accessible without identity
}

RouteInfo is the read-only view of a registered route — for introspection.

type Router

type Router interface {
	Get(path string, h HandlerFunc) Route
	Post(path string, h HandlerFunc) Route
	Put(path string, h HandlerFunc) Route
	Delete(path string, h HandlerFunc) Route
	Options(path string, h HandlerFunc) Route
	Handle(method, path string, h HandlerFunc) Route
	Stream(path string, h StreamFunc) Route
	Socket(path string, h SocketFunc) Route
	Use(m ...Middleware)
	// Routes enumerates the registered routes and their metadata.
	Routes() []RouteInfo
}

Router is what a module registers its routes on. A concrete implementer (native server, edge runtime) satisfies this interface; modules and hosts only consume it.

type SameSite added in v0.1.1

type SameSite int

SameSite types the SameSite policy — illegal state not representable (not a string).

const (
	SameSiteDefault SameSite = iota // browser default behavior
	SameSiteLax                     // cross-site requests send cookie (default modern behavior)
	SameSiteStrict                  // never send cookie cross-site
	SameSiteNone                    // send cookie in all contexts (requires Secure=true)
)

type Socket added in v0.1.0

type Socket interface {
	Read() ([]byte, error)
	Write(b []byte) error
	Close() error
}

Socket is the bidirectional upgraded connection (WebSocket). Isomorphic abstraction: does not touch concrete upgrade mechanisms.

type SocketFunc added in v0.1.0

type SocketFunc func(Socket)

SocketFunc is a handler that receives a typed Socket.

type StreamFunc added in v0.1.0

type StreamFunc func(Streamer)

StreamFunc is a handler that receives a typed Streamer.

type Streamer added in v0.1.0

type Streamer interface {
	Context
	Flush() // sends to the client what has been written so far, without closing the response
}

Streamer is a Context that also flushes writes immediately. Used for incremental responses (SSE, streaming).

Ownership: same single-goroutine contract as Context. A push loop (SSE hub, broker) must deliver messages to the handler's goroutine via a channel; only that goroutine calls Write/Flush.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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