Lifecycle Hooks
Two optional hooks in src/hooks.ws.js cover once-per-worker setup and teardown:
| Hook | When it fires | Use for |
|---|---|---|
init({ platform }) | Once per worker after the listen socket is bound, before any upgrade / open / message. | Capture platform, register cron platform, wire push registry, warm caches. |
shutdown({ platform }) | Once per worker before the listen socket closes. | Flush metrics, close DB / Redis clients, write final state. |
Both are async-allowed and awaited. start() (production) and createTestServer() (test harness) resolve only after init completes; a throwing init rejects the promise. Throwing shutdown is logged and ignored - shutdown should be best-effort.
Basic usage
// src/hooks.ws.js
import { live, message, close, unsubscribe, setCronPlatform } from 'svelte-realtime/server';
let metrics;
export async function init({ platform }) {
setCronPlatform(platform);
metrics = await connectMetrics();
}
export async function shutdown() {
await metrics?.flush();
await metrics?.close();
}
export { message, close, unsubscribe }; Why init exists
Pre-0.5, setCronPlatform, live.configurePush({ remoteRegistry }), and other framework-level setup happened from the open(ws, platform) hook on the first connection. There was a boot-to-first-connect window where:
- Cron ticks fired into a no-op platform.
live.push(userId, ...)could not reach cross-instance users because the registry had not been wired.init-shaped work in user code (DB pools, cache warm-up, leader election) had to be fudged intoopenand gated by a “first-call” boolean.
init fires before any of that and is awaited by the listen socket bind. By the time the server starts accepting upgrades, every once-per-worker primitive is in place.
Cluster fan-out
In clustered deployments, each worker fires its own init. The simplest recommended pattern is the realtime({ bus, leader }) factory (added in 0.5.6) - one call wires bus + leader + platform from a single declaration of cluster intent:
// src/hooks.ws.js
import { realtime } from 'svelte-realtime/server';
import { createRedis, createPubSubBus, createLeader } from 'svelte-adapter-uws-extensions/redis';
const redis = createRedis();
const bus = createPubSubBus(redis);
const leader = createLeader(redis);
export const { open, close, message, init } = realtime({
bus,
leader: leader.isLeader
});
export async function shutdown() {
await leader?.stop();
} For fine-grained control, drop down to the layer-1 primitives:
import { createLeader } from 'svelte-adapter-uws-extensions/redis/leader';
import { configureCron, setBus } from 'svelte-realtime/server';
let leader;
export async function init({ platform }) {
leader = await createLeader(redis, { key: 'cron-leader' });
setBus(bus); // process-wide bus
configureCron({ leader: () => leader.isLeader() });
}
export async function shutdown() {
await leader?.stop();
} See createLeader, the Cron docs, and the Distributed Pub/Sub guide for the full pattern.
Adapter requirement
init and shutdown require svelte-adapter-uws@^0.5.0. On 0.4 adapters the hooks are silently ignored; the legacy “first connect” pattern continues to work.
Push registry wiring
Migrate live.configurePush from open to init:
- export function open(ws, { platform }) {
- live.configurePush({ remoteRegistry: registry });
- }
+ export function init({ platform }) {
+ live.configurePush({ remoteRegistry: registry });
+ } live.configurePush accepts identify and remoteRegistry independently (one is required). Passing neither throws.
Was this page helpful?