insite-subscriptions-client
Version:
Client-side subscriptions/publications for inSite
255 lines (175 loc) • 9.1 kB
Markdown
# insite-subscriptions-client
Client-side library for subscribing to inSite publications over WebSocket. Provides reactive containers for three data shapes — object, array, and map — that apply server updates and notify your handlers.
Part of [inSite](../../README.md) — requires [insite-ws](../ws/README.md). Pairs with [insite-subscriptions-server](../subscriptions-server/README.md).
## Installation
```sh
npm install insite-subscriptions-client
```
Or:
```sh
bun add insite-subscriptions-client
```
## Setup
Before creating any subscriptions, bind the library to your WebSocket instance:
```ts
import { WS } from "insite-ws/client";
import { Subscription } from "insite-subscriptions-client";
const ws = new WS({ url: "wss://example.com" });
Subscription.bindTo(ws);
```
`Subscription.bindTo(ws)` wires the subscription protocol to the given `WS` instance. Without it, creating subscriptions will throw.
## Core Concepts
**Subscription** — Low-level subscription. Sends `s-s` (subscribe) and `s-u` (unsubscribe) messages, receives `s-c` (changed) updates. You typically use the higher-level containers instead.
**SubscriptionObject**, **SubscriptionArray**, **SubscriptionMap** — Reactive containers. Each applies incoming updates to its data and invokes an optional `handleUpdate` callback. Can be used standalone (you feed updates manually) or with built-in subscription.
**WithSubscription variants** — Combine container + subscription. `SubscriptionObjectWithSubscription`, `SubscriptionArrayWithSubscription`, `SubscriptionMapWithSubscription` subscribe on creation and feed updates into the container.
**SubscriptionGroup** — Manages multiple subscriptions with shared lifecycle (load, init, unload). Supports target binding (assign values to an object), debounced updates, and definition-based configuration.
## Public API
### Subscription
```ts
Subscription.bindTo(ws: WS): void
```
Binds all subscriptions to the given WebSocket. Call once before creating any subscription.
```ts
new Subscription(
type: "array" | "map" | "object",
publicationName: string,
args?: unknown[],
handler?: (updates: unknown) => void,
immediately?: boolean
)
```
| Property | Description |
|----------|-------------|
| `i` | Internal subscription id |
| `type` | `"array"` \| `"map"` \| `"object"` |
| `publicationName` | Publication name to subscribe to |
| `args` | Arguments passed to the publication |
| `handler` | Called when updates arrive |
| `immediately` | Request initial data on subscribe (default `true`) |
| `isActive` | `true` when data is loaded, `false` when connection is closed |
| Method | Description |
|--------|-------------|
| `cancel()` | Unsubscribes and removes the subscription |
| `start` | Sends subscribe message (called on reconnect) |
---
### SubscriptionObject
Plain object container. Updates merge into the object; `null` clears it.
```ts
new SubscriptionObject(updates?: Updates, onUpdate?: UpdateHandler)
SubscriptionObject.WithSubscription(publicationName: string, params: unknown[], onUpdate: UpdateHandler, immediately?: boolean)
```
**Types:**
- `Updated` — `Record<string, unknown>` (the current object state)
- `Updates` — `Record<string, unknown> | null` (incoming update payload)
- `UpdateHandler` — `(object, updated, updates) => void`
**Methods (via symbols):** `update`, `clear`, `getAsUpdates`, `getAsInitialUpdated`, `getHandleUpdate` / `setHandleUpdate`
---
### SubscriptionArray
Array container. Supports add, delete, initial load, and optional sort direction.
```ts
new SubscriptionArray<I>(updates?: Updates<I>, onUpdate?: UpdateHandler<I>)
SubscriptionArray.WithSubscription(publicationName: string, params: unknown[], onUpdate: UpdateHandler<I>, immediately?: boolean)
```
**Types:**
- `Updated<I>` — `I[]` with `added: I[]` and `deleted: I[]`
- `Updates<I>` — Array of `["i", items[], sort?] | ["a", item] | ["d", item] | null`
- `UpdateHandler<I>` — `(array, updated, updates) => void`
**Properties:** `sorted`, `sortDirection` (-1 | 0 | 1), `sortCompareFunction`, `handleUpdate`
**Methods:** `update`, `clear`, `getAsUpdates`, `getAsInitialUpdated`. WithSubscription adds `subscribe`, `unsubscribe`, `renew`
---
### SubscriptionMap
Map container keyed by `_id`. Supports create, update, delete, initial load, and multi-field sorting.
```ts
new SubscriptionMap<I>(updates?: Updates<I>, onUpdate?: UpdateHandler<I>)
SubscriptionMap.WithSubscription(publicationName: string, params: unknown[], onUpdate: UpdateHandler<I>, immediately?: boolean)
```
**Types:**
- `Updated<I>` — `I[]` with `added: I[]` and `deleted: string[]` (ids)
- `Updates<I>` — Array of `["i", items[], sortList?] | ["c", item] | ["u", item, fields] | ["d", _id] | null`
- `UpdateHandler<I>` — `(map, updated, updates) => void`
- `SubscriptionMapItem` — Base class for custom map items (has `_id`, `update`, `delete`)
**Properties:** `sorted`, `sortList`, `sortCompareFunction`, `handleUpdate`, `Item` (setter for custom item class)
**Methods:** `update`, `clear`, `getAsUpdates`, `getAsInitialUpdated`, `sort`. WithSubscription adds `subscribe`, `unsubscribe`, `renew`
**Custom items:** Set `map.Item = MyItemClass` before or after first update. `MyItemClass` receives `(map, updates)` in constructor and must have `_id`.
---
### SubscriptionGroup
Manages multiple subscriptions with shared lifecycle and optional target binding.
```ts
new SubscriptionGroup(definitions, options?)
```
**Options:**
| Option | Type | Description |
|--------|------|-------------|
| `target` | object | Object to bind subscription values to (`target[name] = value`) |
| `debounce` | number | Debounce ms for `update` events (default: `SubscriptionGroup.debounceLimit` = 8) |
| `immediately` | boolean | Subscribe immediately on attach (default `true`) |
**Methods:**
| Method | Description |
|--------|-------------|
| `attach(definitions, shouldReload?, immediately?)` | Attach definitions, returns `Promise` of load |
| `detach(names, shouldUpdate?)` | Detach subscriptions by name |
| `redefine(definitions)` | Replace definitions, reattach changed, detach removed |
| `subscribe()` | Subscribe all items |
| `unsubscribe()` | Unsubscribe all items |
| `applyOptions({ target?, debounceLimit? })` | Update target or debounce |
| `loaded()` | Returns `StatefulPromise` resolving when all items loaded |
| `inited()` | Returns `StatefulPromise` resolving when all items inited |
| `unloaded()` | Returns `StatefulPromise` resolving when all items unloaded |
**Events:** `load`, `init`, `unload`, `update`, `update.{name}`
**Properties:** `items`, `values`, `isLoaded`, `isInited`, `target`, `emitUpdate`
---
### SubscriptionGroup Definition Format
Each definition describes one subscription in the group.
**Object form:**
```ts
{
name: string;
type?: "object" | "array" | "map";
publicationName?: string; // defaults to name
params?: unknown[];
value?: SubscriptionObjectWithSubscription | SubscriptionArrayWithSubscription | SubscriptionMapWithSubscription;
handle?: (updated, group) => void;
onBeforeInit?: (value) => void;
debounce?: number;
preventBind?: boolean; // skip binding to target
}
```
**Tuple form:** `[name, type?, publicationName?, params?, handle?, onBeforeInit?, debounce?, preventBind?]`
---
### Update Protocol
**SubscriptionObject:** `Updates` is `Record<string, unknown> | null`. Non-null merges into the object; null clears.
**SubscriptionArray:** `Updates` is an array of:
| Op | Format | Description |
|----|--------|-------------|
| `"i"` | `["i", items[], sort?]` | Initial load. `sort`: -1 (desc), 0 (none), 1 (asc) |
| `"a"` | `["a", item]` | Add item |
| `"d"` | `["d", item]` | Delete item |
| null | — | Clear |
**SubscriptionMap:** `Updates` is an array of:
| Op | Format | Description |
|----|--------|-------------|
| `"i"` | `["i", items[], sortList?]` | Initial load. `sortList`: `{ field: -1|1 }` |
| `"c"` | `["c", item]` | Create item |
| `"u"` | `["u", item, fields]` | Update item. `fields`: array of changed keys or `true` (atomic) |
| `"d"` | `["d", _id]` | Delete by id |
| null | — | Clear |
---
### Symbols
Exported for extension and integration with other inSite packages:
| Symbol | Purpose |
|--------|---------|
| `updateSymbol` | Method to apply updates |
| `getHandleUpdateSymbol` / `setHandleUpdateSymbol` | Get/set update handler |
| `getAsUpdatesSymbol` | Serialize current state as updates |
| `getAsInitialUpdatedSymbol` | Get current state as "initial updated" shape |
| `clearSymbol` | Clear container |
| `subscribeSymbol` / `unsubscribeSymbol` / `renewSymbol` | Subscription lifecycle |
| `getSubscriptionSymbol` | Get underlying Subscription |
| `getHandleSubscriptionSymbol` / `setHandleSubscriptionSymbol` | Handler wiring |
---
## Related
- [insite-ws](../ws/README.md) — provides WS
- [insite-subscriptions-server](../subscriptions-server/README.md) — server counterpart
- [insite-client](../client/README.md), [insite-users-client](../users-client/README.md) — use subscriptions
## License
MIT