@cioky/remult-partykit
v0.1.1
Published
Realtime pub/sub engine + Svelte 5 reactive layer for Remult on Cloudflare Workers (partyserver).
Readme
@cioky/remult-partykit
Realtime for Remult on Cloudflare Durable Objects + PartyServer. A single-socket WebSocket transport with per-channel authorization, remote subscriptions, and an optional Svelte 5 reactive layer.
Agnostic by design: room sharding, subscription authorization, and client publish policy are consumer-supplied hooks. No application knowledge ships in the package — bring your own channel map and authz.
Install
bun add @cioky/remult-partykitPeer dependency: svelte (only needed for the /svelte entry).
What you get
| Entry | Contents |
|---|---|
| . | RemultPartySubscriptionClient (browser), RemultPartySubscriptionServer (SSR publisher, ResultAsync<void, PublishError>), DurableObjectLiveQueryStorage, typed error taxonomy (PublishError, SubscribeRejectedError) |
| /client | RemultPartySubscriptionClient |
| /durable-object | RemultPartyRoom, RemultLiveQueryStorageRoom (Cloudflare DO classes) |
| /server | RemultPartySubscriptionServer + RemultPartyServerOptions |
| /storage | DurableObjectLiveQueryStorage (Remult LiveQueryStorage) |
| /svelte | Svelte 5 runes wiring: initRemultClient, createLiveQuery, reactive connection state |
Configuration surface
Everything is a hook on RemultPartyServerOptions (server publisher) or by
subclassing RemultPartyRoom (DO side):
resolveRoomId(channel)— channel → room sharding. Default: one sharedglobalroom.validateSubscription(channel, roomName)— per-subscription authz. Default: allow.allowClientPublish(channel, connectionId, payload)— clientdataframe policy. Default: deny (server is authoritative).clientSenderId(room, connectionId)— trusted sender stamping. Default: none.
The DO also exposes resolveRoom(channel) and an overridable roomBinding()
for multi-binding setups.
Quick start
Worker entry — export the DO classes and route party requests:
import { routePartykitRequest } from 'partyserver'
import { RemultPartyRoom, RemultLiveQueryStorageRoom } from '@cioky/remult-partykit/durable-object'
export { RemultPartyRoom, RemultLiveQueryStorageRoom }
export default {
async fetch(request, env, ctx) {
return (await routePartykitRequest(request, env, { prefix: 'parties' })) ??
new Response('Not Found', { status: 404 })
},
} satisfies ExportedHandler<Env>Subclass the room with your sharding + authz:
import { RemultPartyRoom, type RemultPartyServerOptions } from '@cioky/remult-partykit'
export class MyRoom extends RemultPartyRoom {
protected options: RemultPartyServerOptions = {
resolveRoomId: (channel) => (channel.startsWith('user:') ? `user:${channel}` : 'global'),
validateSubscription: (channel, room) => room === this.name,
}
}Server publisher (SSR):
import { RemultPartySubscriptionServer } from '@cioky/remult-partykit/server'
const server = new RemultPartySubscriptionServer(env.REMULT_ROOM, {
resolveRoomId: myChannelMap,
})
const result = await server.publish('channel', payload) // ResultAsync<void, PublishError>Client (browser):
import { RemultPartySubscriptionClient } from '@cioky/remult-partykit/client'
const client = new RemultPartySubscriptionClient({
getSocketUrl: (room) => `wss://backend.example.com/parties/remult-room/${room}`,
})Tests
bun run test— client + Svelte reactivity with real WebSockets (Bun.serve).bun run test:do— DO rooms against real workerd via@cloudflare/vitest-pool-workers(Hibernation API,storage.sql, D1).bun run check— biome.bun run fallow— fallow analysis (advisory).
License
MIT
