This commit is contained in:
nvms 2025-04-21 11:56:42 -04:00
parent c448abfecc
commit 960616b3fb

View File

@ -2,48 +2,49 @@
Mesh is a command-based WebSocket framework for real-time applications. It uses Redis to coordinate connections, rooms, presence, and shared state across application instances, with built-in support for structured commands, latency tracking, and automatic reconnection. Mesh is a command-based WebSocket framework for real-time applications. It uses Redis to coordinate connections, rooms, presence, and shared state across application instances, with built-in support for structured commands, latency tracking, and automatic reconnection.
- [Quickstart](#quickstart) * [Quickstart](#quickstart)
- [Server](#server) * [Server](#server)
- [Client](#client) * [Client](#client)
- [Next steps](#next-steps) * [Next steps](#next-steps)
- [Who is this for?](#who-is-this-for) * [Who is this for?](#who-is-this-for)
- [Distributed messaging architecture](#distributed-messaging-architecture) * [Distributed messaging architecture](#distributed-messaging-architecture)
- [Redis channel subscriptions](#redis-channel-subscriptions) * [Redis channel subscriptions](#redis-channel-subscriptions)
- [Server configuration](#server-configuration) * [Server configuration](#server-configuration)
- [Server publishing](#server-publishing) * [Server publishing](#server-publishing)
- [Client usage](#client-usage) * [Client usage](#client-usage)
- [Rooms](#rooms) * [Rooms](#rooms)
- [Joining a room](#joining-a-room) * [Joining a room](#joining-a-room)
- [Leaving a room](#leaving-a-room) * [Leaving a room](#leaving-a-room)
- [Server API](#server-api) * [Server API](#server-api)
- [Access control](#access-control) * [Access control](#access-control)
- [Presence](#presence) * [Presence](#presence)
- [Server configuration](#server-configuration-1) * [Server configuration](#server-configuration-1)
- [Client usage](#client-usage-1) * [Client usage](#client-usage-1)
- [Getting presence information (server-side)](#getting-presence-information-server-side) * [Getting presence information (server-side)](#getting-presence-information-server-side)
- [Disabling auto-cleanup (optional)](#disabling-auto-cleanup-optional) * [Disabling auto-cleanup (optional)](#disabling-auto-cleanup-optional)
- [Combining presence with user info](#combining-presence-with-user-info) * [Combining presence with user info](#combining-presence-with-user-info)
- [Presence state](#presence-state) * [Presence is per connection, not per user](#presence-is-per-connection-not-per-user)
- [Client API](#client-api) * [Presence state](#presence-state)
- [Server behavior](#server-behavior) * [Client API](#client-api)
- [Receiving presence state updates](#receiving-presence-state-updates) * [Server behavior](#server-behavior)
- [Metadata](#metadata) * [Receiving presence state updates](#receiving-presence-state-updates)
- [Room metadata](#room-metadata) * [Metadata](#metadata)
- [Record subscriptions](#record-subscriptions) * [Room metadata](#room-metadata)
- [Server configuration](#server-configuration-2) * [Record subscriptions](#record-subscriptions)
- [Server configuration (writable)](#server-configuration-writable) * [Server configuration](#server-configuration-2)
- [Updating records (server-side)](#updating-records-server-side) * [Server configuration (writable)](#server-configuration-writable)
- [Updating records (client-side)](#updating-records-client-side) * [Updating records (server-side)](#updating-records-server-side)
- [Client usage — full mode (default)](#client-usage--full-mode-default) * [Updating records (client-side)](#updating-records-client-side)
- [Client usage — patch mode](#client-usage--patch-mode) * [Client usage — full mode (default)](#client-usage--full-mode-default)
- [Unsubscribing](#unsubscribing) * [Client usage — patch mode](#client-usage--patch-mode)
- [Versioning and resync](#versioning-and-resync) * [Unsubscribing](#unsubscribing)
- [Why you probably don't need client-side diffing](#why-you-probably-dont-need-client-side-diffing) * [Versioning and resync](#versioning-and-resync)
- [Command middleware](#command-middleware) * [Why you probably don't need client-side diffing](#why-you-probably-dont-need-client-side-diffing)
- [Latency tracking and connection liveness](#latency-tracking-and-connection-liveness) * [Command middleware](#command-middleware)
- [Server-side configuration](#server-side-configuration) * [Latency tracking and connection liveness](#latency-tracking-and-connection-liveness)
- [Client-side configuration](#client-side-configuration) * [Server-side configuration](#server-side-configuration)
- [Comparison](#comparison) * [Client-side configuration](#client-side-configuration)
* [Comparison](#comparison)
## Quickstart ## Quickstart
@ -456,18 +457,25 @@ const allMetadata = await Promise.all(
// [{ userId: "user123", username: "Alice", avatar: "..." }, ...] // [{ userId: "user123", username: "Alice", avatar: "..." }, ...]
``` ```
> [!NOTE] ### Presence is per connection, not per user
> Mesh tracks presence and presence state **per connection**, not per user.
> Mesh tracks presence and presence state **per connection**, not per user.
> If a user opens multiple browser tabs (or devices), each will be assigned a **separate connection ID** and have an **independent presence state**.
> When a user opens your app in multiple browser tabs or across multiple devices, each instance creates a separate WebSocket connection. These connections are treated independently in Mesh, each with its own connection ID, room membership, and presence state.
> Even if both tabs authenticate with the same user ID and you store these values in connection metadata, Mesh does not merge or deduplicate those connections.
> Even if those connections authenticate using the same user ID and you store that user ID in connection metadata, Mesh will not automatically group or deduplicate them. Each connection remains logically distinct.
> If you want to show a single presence indicator per user (e.g. only one "typing" icon), you should:
> This is intentional. Mesh is designed as a low-level framework that gives you real-time building blocks — like rooms, presence, state sync, and messaging — without dictating your data model, authentication flow, or application logic.
> 1. Attach a `userId` to each connection's metadata
> 2. Resolve metadata for each `connectionId` client-side We dont assume what a "user" is in your system, how they should be identified, or whether multiple sessions should be merged. Thats entirely up to you.
> 3. Deduplicate by `userId` in your UI logic
If you want to implement user-level presence behavior (such as "only show one typing indicator per user"), you can do so easily:
1. Assign a `userId` to each connections metadata during authentication.
2. On the client, resolve metadata for each `connectionId` received in presence events.
3. In your UI, deduplicate by `userId` rather than `connectionId`.
This gives you full control over what “presence” means in your app, without locking you into a particular structure.
> [!TIP] > [!TIP]
> You can expose a `get-user-metadata` command on the server that reads from `connectionManager.getMetadata(...)` to support this. > You can expose a `get-user-metadata` command on the server that reads from `connectionManager.getMetadata(...)` to support this.