@vielzeug/pulse 1.0.5 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -55,6 +55,7 @@ using _ = pulse;
55
55
  - **Typed event maps** — `TServer` and `TClient` generics enforce payload types at compile time
56
56
  - **`on()` / `once()` / `wait()`** — persistent, one-shot, and async-await subscriptions
57
57
  - **`channel()`** — isolated namespaces multiplexed over the shared connection
58
+ - **`presence()`** — room-scoped presence channels are memoized by room name
58
59
  - **`join()` / `leave()`** — room membership with server-confirmation promises
59
60
  - **`presence()`** — reactive `Signal<Map<memberId, T>>` with `onJoin`/`onLeave` callbacks
60
61
  - **Middleware** — intercept outgoing `send()` calls; omit `next()` to suppress
package/dist/channel.cjs CHANGED
@@ -1,2 +1,2 @@
1
- const e=require("./_dev.cjs"),t=require("./_utils.cjs"),n=require("./_wait.cjs");function r(r,i,a,o,s){let c=t.deriveAbortController(o),l=new Set,u=c.signal.aborted;return{get disposalSignal(){return c.signal},dispose(){if(!u){u=!0,c.abort();for(let e of l)e();l.clear(),s?.()}},get disposed(){return u},get name(){return r},on(t,n){if(u)return e.warn(`on('${String(t)}') called on a disposed channel '${r}' — listener ignored`),()=>{};let i=a(r,t,n);return l.add(i),()=>{i(),l.delete(i)}},once(t,n){if(u)return e.warn(`once('${String(t)}') called on a disposed channel '${r}' — listener ignored`),()=>{};let i=()=>{};return i=a(r,t,e=>{l.delete(i),i(),n(e)}),l.add(i),()=>{l.delete(i),i()}},send(t,n){if(u){e.warn(`send('${String(t)}') called on a disposed channel '${r}' — message dropped`);return}i(r,t,n)},[Symbol.dispose](){this.dispose()},wait(e,t){return n.createWaitPromise(e,c.signal,t,(e,t)=>a(r,e,t))}}}exports.createChannel=r;
1
+ const e=require("./_dev.cjs"),t=require("./_utils.cjs"),n=require("./_wait.cjs");function r(r,i,a,o,s){let c=t.deriveAbortController(o),l=new Set,u=c.signal.aborted;return c.signal.addEventListener(`abort`,()=>{u=!0},{once:!0}),{get disposalSignal(){return c.signal},dispose(){if(!u){u=!0,c.abort();for(let e of l)e();l.clear(),s?.()}},get disposed(){return u},get name(){return r},on(t,n){if(u)return e.warn(`on('${String(t)}') called on a disposed channel '${r}' — listener ignored`),()=>{};let i=a(r,t,n);return l.add(i),()=>{i(),l.delete(i)}},once(t,n){if(u)return e.warn(`once('${String(t)}') called on a disposed channel '${r}' — listener ignored`),()=>{};let i=()=>{};return i=a(r,t,e=>{l.delete(i),i(),n(e)}),l.add(i),()=>{l.delete(i),i()}},send(t,n){if(u){e.warn(`send('${String(t)}') called on a disposed channel '${r}' — message dropped`);return}i(r,t,n)},[Symbol.dispose](){this.dispose()},wait(e,t){return n.createWaitPromise(e,c.signal,t,(e,t)=>a(r,e,t))}}}exports.createChannel=r;
2
2
  //# sourceMappingURL=channel.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"channel.cjs","names":[],"sources":["../src/channel.ts"],"sourcesContent":["import type { EventKey, MessageMap, PulseChannel, Unsubscribe } from './types';\n\nimport { warn } from './_dev';\nimport { deriveAbortController } from './_utils';\nimport { createWaitPromise } from './_wait';\n\ntype SendFn = (channel: string, event: string, payload: unknown) => void;\ntype SubscribeFn = (channel: string, event: string, handler: (payload: unknown) => void) => () => void;\n\n/**\n * Create a typed channel object that scopes messaging to a named namespace.\n * @internal\n */\nexport function createChannel<TServer extends MessageMap, TClient extends MessageMap>(\n name: string,\n send: SendFn,\n subscribe: SubscribeFn,\n disposalSignal: AbortSignal,\n onDispose?: () => void,\n): PulseChannel<TServer, TClient> {\n const ctrl = deriveAbortController(disposalSignal);\n const tracked = new Set<() => void>();\n\n let disposed = ctrl.signal.aborted;\n\n const channel: PulseChannel<TServer, TClient> = {\n get disposalSignal() {\n return ctrl.signal;\n },\n\n dispose() {\n if (disposed) return;\n\n disposed = true;\n ctrl.abort();\n\n for (const unsub of tracked) unsub();\n\n tracked.clear();\n onDispose?.();\n },\n\n get disposed() {\n return disposed;\n },\n\n get name() {\n return name;\n },\n\n on<K extends EventKey<TServer>>(event: K, handler: (payload: TServer[K]) => void): Unsubscribe {\n if (disposed) {\n warn(`on('${String(event)}') called on a disposed channel '${name}' — listener ignored`);\n\n return () => {};\n }\n\n const unsub = subscribe(name, event, handler as (payload: unknown) => void);\n\n tracked.add(unsub);\n\n return () => {\n unsub();\n tracked.delete(unsub);\n };\n },\n\n once<K extends EventKey<TServer>>(event: K, handler: (payload: TServer[K]) => void): Unsubscribe {\n if (disposed) {\n warn(`once('${String(event)}') called on a disposed channel '${name}' — listener ignored`);\n\n return () => {};\n }\n\n let unsub: Unsubscribe = () => {};\n\n const wrapped = (payload: unknown): void => {\n tracked.delete(unsub);\n unsub();\n (handler as (payload: unknown) => void)(payload);\n };\n\n unsub = subscribe(name, event, wrapped);\n tracked.add(unsub);\n\n return () => {\n tracked.delete(unsub);\n unsub();\n };\n },\n\n send<K extends EventKey<TClient>>(event: K, payload: TClient[K]): void {\n if (disposed) {\n warn(`send('${String(event)}') called on a disposed channel '${name}' — message dropped`);\n\n return;\n }\n\n send(name, event, payload);\n },\n\n [Symbol.dispose]() {\n this.dispose();\n },\n\n wait<K extends EventKey<TServer>>(\n event: K,\n opts?: { signal?: AbortSignal; timeout?: number },\n ): Promise<TServer[K]> {\n return createWaitPromise<TServer[K]>(event as string, ctrl.signal, opts, (ev, handler) =>\n subscribe(name, ev, handler),\n );\n },\n };\n\n return channel;\n}\n"],"mappings":"iFAaA,SAAgB,EACd,EACA,EACA,EACA,EACA,EACgC,CAChC,IAAM,EAAO,EAAA,sBAAsB,CAAc,EAC3C,EAAU,IAAI,IAEhB,EAAW,EAAK,OAAO,QA4F3B,MAAO,CAzFL,IAAI,gBAAiB,CACnB,OAAO,EAAK,MACd,EAEA,SAAU,CACJ,MAGJ,CADA,EAAW,GACX,EAAK,MAAM,EAEX,IAAK,IAAM,KAAS,EAAS,EAAM,EAEnC,EAAQ,MAAM,EACd,IAAY,CALD,CAMb,EAEA,IAAI,UAAW,CACb,OAAO,CACT,EAEA,IAAI,MAAO,CACT,OAAO,CACT,EAEA,GAAgC,EAAU,EAAqD,CAC7F,GAAI,EAGF,OAFA,EAAA,KAAK,OAAO,OAAO,CAAK,EAAE,mCAAmC,EAAK,qBAAqB,MAE1E,CAAC,EAGhB,IAAM,EAAQ,EAAU,EAAM,EAAO,CAAqC,EAI1E,OAFA,EAAQ,IAAI,CAAK,MAEJ,CACX,EAAM,EACN,EAAQ,OAAO,CAAK,CACtB,CACF,EAEA,KAAkC,EAAU,EAAqD,CAC/F,GAAI,EAGF,OAFA,EAAA,KAAK,SAAS,OAAO,CAAK,EAAE,mCAAmC,EAAK,qBAAqB,MAE5E,CAAC,EAGhB,IAAI,MAA2B,CAAC,EAWhC,MAHA,GAAQ,EAAU,EAAM,EANP,GAA2B,CAC1C,EAAQ,OAAO,CAAK,EACpB,EAAM,EACN,EAAwC,CAAO,CACjD,CAEsC,EACtC,EAAQ,IAAI,CAAK,MAEJ,CACX,EAAQ,OAAO,CAAK,EACpB,EAAM,CACR,CACF,EAEA,KAAkC,EAAU,EAA2B,CACrE,GAAI,EAAU,CACZ,EAAA,KAAK,SAAS,OAAO,CAAK,EAAE,mCAAmC,EAAK,oBAAoB,EAExF,MACF,CAEA,EAAK,EAAM,EAAO,CAAO,CAC3B,EAEA,CAAC,OAAO,UAAW,CACjB,KAAK,QAAQ,CACf,EAEA,KACE,EACA,EACqB,CACrB,OAAO,EAAA,kBAA8B,EAAiB,EAAK,OAAQ,GAAO,EAAI,IAC5E,EAAU,EAAM,EAAI,CAAO,CAC7B,CACF,CAGK,CACT"}
1
+ {"version":3,"file":"channel.cjs","names":[],"sources":["../src/channel.ts"],"sourcesContent":["import type { EventKey, MessageMap, PulseChannel, Unsubscribe } from './types';\n\nimport { warn } from './_dev';\nimport { deriveAbortController } from './_utils';\nimport { createWaitPromise } from './_wait';\n\ntype SendFn = (channel: string, event: string, payload: unknown) => void;\ntype SubscribeFn = (channel: string, event: string, handler: (payload: unknown) => void) => () => void;\n\n/**\n * Create a typed channel object that scopes messaging to a named namespace.\n * @internal\n */\nexport function createChannel<TServer extends MessageMap, TClient extends MessageMap>(\n name: string,\n send: SendFn,\n subscribe: SubscribeFn,\n disposalSignal: AbortSignal,\n onDispose?: () => void,\n): PulseChannel<TServer, TClient> {\n const ctrl = deriveAbortController(disposalSignal);\n const tracked = new Set<() => void>();\n\n let disposed = ctrl.signal.aborted;\n\n ctrl.signal.addEventListener(\n 'abort',\n () => {\n disposed = true;\n },\n { once: true },\n );\n\n const channel: PulseChannel<TServer, TClient> = {\n get disposalSignal() {\n return ctrl.signal;\n },\n\n dispose() {\n if (disposed) return;\n\n disposed = true;\n ctrl.abort();\n\n for (const unsub of tracked) unsub();\n\n tracked.clear();\n onDispose?.();\n },\n\n get disposed() {\n return disposed;\n },\n\n get name() {\n return name;\n },\n\n on<K extends EventKey<TServer>>(event: K, handler: (payload: TServer[K]) => void): Unsubscribe {\n if (disposed) {\n warn(`on('${String(event)}') called on a disposed channel '${name}' — listener ignored`);\n\n return () => {};\n }\n\n const unsub = subscribe(name, event, handler as (payload: unknown) => void);\n\n tracked.add(unsub);\n\n return () => {\n unsub();\n tracked.delete(unsub);\n };\n },\n\n once<K extends EventKey<TServer>>(event: K, handler: (payload: TServer[K]) => void): Unsubscribe {\n if (disposed) {\n warn(`once('${String(event)}') called on a disposed channel '${name}' — listener ignored`);\n\n return () => {};\n }\n\n let unsub: Unsubscribe = () => {};\n\n const wrapped = (payload: unknown): void => {\n tracked.delete(unsub);\n unsub();\n (handler as (payload: unknown) => void)(payload);\n };\n\n unsub = subscribe(name, event, wrapped);\n tracked.add(unsub);\n\n return () => {\n tracked.delete(unsub);\n unsub();\n };\n },\n\n send<K extends EventKey<TClient>>(event: K, payload: TClient[K]): void {\n if (disposed) {\n warn(`send('${String(event)}') called on a disposed channel '${name}' — message dropped`);\n\n return;\n }\n\n send(name, event, payload);\n },\n\n [Symbol.dispose]() {\n this.dispose();\n },\n\n wait<K extends EventKey<TServer>>(\n event: K,\n opts?: { signal?: AbortSignal; timeout?: number },\n ): Promise<TServer[K]> {\n return createWaitPromise<TServer[K]>(event as string, ctrl.signal, opts, (ev, handler) =>\n subscribe(name, ev, handler),\n );\n },\n };\n\n return channel;\n}\n"],"mappings":"iFAaA,SAAgB,EACd,EACA,EACA,EACA,EACA,EACgC,CAChC,IAAM,EAAO,EAAA,sBAAsB,CAAc,EAC3C,EAAU,IAAI,IAEhB,EAAW,EAAK,OAAO,QAoG3B,OAlGA,EAAK,OAAO,iBACV,YACM,CACJ,EAAW,EACb,EACA,CAAE,KAAM,EAAK,CACf,EA4FO,CAzFL,IAAI,gBAAiB,CACnB,OAAO,EAAK,MACd,EAEA,SAAU,CACJ,MAGJ,CADA,EAAW,GACX,EAAK,MAAM,EAEX,IAAK,IAAM,KAAS,EAAS,EAAM,EAEnC,EAAQ,MAAM,EACd,IAAY,CALD,CAMb,EAEA,IAAI,UAAW,CACb,OAAO,CACT,EAEA,IAAI,MAAO,CACT,OAAO,CACT,EAEA,GAAgC,EAAU,EAAqD,CAC7F,GAAI,EAGF,OAFA,EAAA,KAAK,OAAO,OAAO,CAAK,EAAE,mCAAmC,EAAK,qBAAqB,MAE1E,CAAC,EAGhB,IAAM,EAAQ,EAAU,EAAM,EAAO,CAAqC,EAI1E,OAFA,EAAQ,IAAI,CAAK,MAEJ,CACX,EAAM,EACN,EAAQ,OAAO,CAAK,CACtB,CACF,EAEA,KAAkC,EAAU,EAAqD,CAC/F,GAAI,EAGF,OAFA,EAAA,KAAK,SAAS,OAAO,CAAK,EAAE,mCAAmC,EAAK,qBAAqB,MAE5E,CAAC,EAGhB,IAAI,MAA2B,CAAC,EAWhC,MAHA,GAAQ,EAAU,EAAM,EANP,GAA2B,CAC1C,EAAQ,OAAO,CAAK,EACpB,EAAM,EACN,EAAwC,CAAO,CACjD,CAEsC,EACtC,EAAQ,IAAI,CAAK,MAEJ,CACX,EAAQ,OAAO,CAAK,EACpB,EAAM,CACR,CACF,EAEA,KAAkC,EAAU,EAA2B,CACrE,GAAI,EAAU,CACZ,EAAA,KAAK,SAAS,OAAO,CAAK,EAAE,mCAAmC,EAAK,oBAAoB,EAExF,MACF,CAEA,EAAK,EAAM,EAAO,CAAO,CAC3B,EAEA,CAAC,OAAO,UAAW,CACjB,KAAK,QAAQ,CACf,EAEA,KACE,EACA,EACqB,CACrB,OAAO,EAAA,kBAA8B,EAAiB,EAAK,OAAQ,GAAO,EAAI,IAC5E,EAAU,EAAM,EAAI,CAAO,CAC7B,CACF,CAGK,CACT"}
package/dist/channel.js CHANGED
@@ -4,7 +4,9 @@ import { createWaitPromise as n } from "./_wait.js";
4
4
  //#region src/channel.ts
5
5
  function r(r, i, a, o, s) {
6
6
  let c = t(o), l = /* @__PURE__ */ new Set(), u = c.signal.aborted;
7
- return {
7
+ return c.signal.addEventListener("abort", () => {
8
+ u = !0;
9
+ }, { once: !0 }), {
8
10
  get disposalSignal() {
9
11
  return c.signal;
10
12
  },
@@ -1 +1 @@
1
- {"version":3,"file":"channel.js","names":[],"sources":["../src/channel.ts"],"sourcesContent":["import type { EventKey, MessageMap, PulseChannel, Unsubscribe } from './types';\n\nimport { warn } from './_dev';\nimport { deriveAbortController } from './_utils';\nimport { createWaitPromise } from './_wait';\n\ntype SendFn = (channel: string, event: string, payload: unknown) => void;\ntype SubscribeFn = (channel: string, event: string, handler: (payload: unknown) => void) => () => void;\n\n/**\n * Create a typed channel object that scopes messaging to a named namespace.\n * @internal\n */\nexport function createChannel<TServer extends MessageMap, TClient extends MessageMap>(\n name: string,\n send: SendFn,\n subscribe: SubscribeFn,\n disposalSignal: AbortSignal,\n onDispose?: () => void,\n): PulseChannel<TServer, TClient> {\n const ctrl = deriveAbortController(disposalSignal);\n const tracked = new Set<() => void>();\n\n let disposed = ctrl.signal.aborted;\n\n const channel: PulseChannel<TServer, TClient> = {\n get disposalSignal() {\n return ctrl.signal;\n },\n\n dispose() {\n if (disposed) return;\n\n disposed = true;\n ctrl.abort();\n\n for (const unsub of tracked) unsub();\n\n tracked.clear();\n onDispose?.();\n },\n\n get disposed() {\n return disposed;\n },\n\n get name() {\n return name;\n },\n\n on<K extends EventKey<TServer>>(event: K, handler: (payload: TServer[K]) => void): Unsubscribe {\n if (disposed) {\n warn(`on('${String(event)}') called on a disposed channel '${name}' — listener ignored`);\n\n return () => {};\n }\n\n const unsub = subscribe(name, event, handler as (payload: unknown) => void);\n\n tracked.add(unsub);\n\n return () => {\n unsub();\n tracked.delete(unsub);\n };\n },\n\n once<K extends EventKey<TServer>>(event: K, handler: (payload: TServer[K]) => void): Unsubscribe {\n if (disposed) {\n warn(`once('${String(event)}') called on a disposed channel '${name}' — listener ignored`);\n\n return () => {};\n }\n\n let unsub: Unsubscribe = () => {};\n\n const wrapped = (payload: unknown): void => {\n tracked.delete(unsub);\n unsub();\n (handler as (payload: unknown) => void)(payload);\n };\n\n unsub = subscribe(name, event, wrapped);\n tracked.add(unsub);\n\n return () => {\n tracked.delete(unsub);\n unsub();\n };\n },\n\n send<K extends EventKey<TClient>>(event: K, payload: TClient[K]): void {\n if (disposed) {\n warn(`send('${String(event)}') called on a disposed channel '${name}' — message dropped`);\n\n return;\n }\n\n send(name, event, payload);\n },\n\n [Symbol.dispose]() {\n this.dispose();\n },\n\n wait<K extends EventKey<TServer>>(\n event: K,\n opts?: { signal?: AbortSignal; timeout?: number },\n ): Promise<TServer[K]> {\n return createWaitPromise<TServer[K]>(event as string, ctrl.signal, opts, (ev, handler) =>\n subscribe(name, ev, handler),\n );\n },\n };\n\n return channel;\n}\n"],"mappings":";;;;AAaA,SAAgB,EACd,GACA,GACA,GACA,GACA,GACgC;CAChC,IAAM,IAAO,EAAsB,CAAc,GAC3C,oBAAU,IAAI,IAAgB,GAEhC,IAAW,EAAK,OAAO;CA4F3B,OAAO;EAzFL,IAAI,iBAAiB;GACnB,OAAO,EAAK;EACd;EAEA,UAAU;GACJ,QAGJ;IADA,IAAW,IACX,EAAK,MAAM;IAEX,KAAK,IAAM,KAAS,GAAS,EAAM;IAGnC,AADA,EAAQ,MAAM,GACd,IAAY;GALD;EAMb;EAEA,IAAI,WAAW;GACb,OAAO;EACT;EAEA,IAAI,OAAO;GACT,OAAO;EACT;EAEA,GAAgC,GAAU,GAAqD;GAC7F,IAAI,GAGF,OAFA,EAAK,OAAO,OAAO,CAAK,EAAE,mCAAmC,EAAK,qBAAqB,SAE1E,CAAC;GAGhB,IAAM,IAAQ,EAAU,GAAM,GAAO,CAAqC;GAI1E,OAFA,EAAQ,IAAI,CAAK,SAEJ;IAEX,AADA,EAAM,GACN,EAAQ,OAAO,CAAK;GACtB;EACF;EAEA,KAAkC,GAAU,GAAqD;GAC/F,IAAI,GAGF,OAFA,EAAK,SAAS,OAAO,CAAK,EAAE,mCAAmC,EAAK,qBAAqB,SAE5E,CAAC;GAGhB,IAAI,UAA2B,CAAC;GAWhC,OAHA,IAAQ,EAAU,GAAM,IANP,MAA2B;IAG1C,AAFA,EAAQ,OAAO,CAAK,GACpB,EAAM,GACN,EAAwC,CAAO;GACjD,CAEsC,GACtC,EAAQ,IAAI,CAAK,SAEJ;IAEX,AADA,EAAQ,OAAO,CAAK,GACpB,EAAM;GACR;EACF;EAEA,KAAkC,GAAU,GAA2B;GACrE,IAAI,GAAU;IACZ,EAAK,SAAS,OAAO,CAAK,EAAE,mCAAmC,EAAK,oBAAoB;IAExF;GACF;GAEA,EAAK,GAAM,GAAO,CAAO;EAC3B;EAEA,CAAC,OAAO,WAAW;GACjB,KAAK,QAAQ;EACf;EAEA,KACE,GACA,GACqB;GACrB,OAAO,EAA8B,GAAiB,EAAK,QAAQ,IAAO,GAAI,MAC5E,EAAU,GAAM,GAAI,CAAO,CAC7B;EACF;CAGK;AACT"}
1
+ {"version":3,"file":"channel.js","names":[],"sources":["../src/channel.ts"],"sourcesContent":["import type { EventKey, MessageMap, PulseChannel, Unsubscribe } from './types';\n\nimport { warn } from './_dev';\nimport { deriveAbortController } from './_utils';\nimport { createWaitPromise } from './_wait';\n\ntype SendFn = (channel: string, event: string, payload: unknown) => void;\ntype SubscribeFn = (channel: string, event: string, handler: (payload: unknown) => void) => () => void;\n\n/**\n * Create a typed channel object that scopes messaging to a named namespace.\n * @internal\n */\nexport function createChannel<TServer extends MessageMap, TClient extends MessageMap>(\n name: string,\n send: SendFn,\n subscribe: SubscribeFn,\n disposalSignal: AbortSignal,\n onDispose?: () => void,\n): PulseChannel<TServer, TClient> {\n const ctrl = deriveAbortController(disposalSignal);\n const tracked = new Set<() => void>();\n\n let disposed = ctrl.signal.aborted;\n\n ctrl.signal.addEventListener(\n 'abort',\n () => {\n disposed = true;\n },\n { once: true },\n );\n\n const channel: PulseChannel<TServer, TClient> = {\n get disposalSignal() {\n return ctrl.signal;\n },\n\n dispose() {\n if (disposed) return;\n\n disposed = true;\n ctrl.abort();\n\n for (const unsub of tracked) unsub();\n\n tracked.clear();\n onDispose?.();\n },\n\n get disposed() {\n return disposed;\n },\n\n get name() {\n return name;\n },\n\n on<K extends EventKey<TServer>>(event: K, handler: (payload: TServer[K]) => void): Unsubscribe {\n if (disposed) {\n warn(`on('${String(event)}') called on a disposed channel '${name}' — listener ignored`);\n\n return () => {};\n }\n\n const unsub = subscribe(name, event, handler as (payload: unknown) => void);\n\n tracked.add(unsub);\n\n return () => {\n unsub();\n tracked.delete(unsub);\n };\n },\n\n once<K extends EventKey<TServer>>(event: K, handler: (payload: TServer[K]) => void): Unsubscribe {\n if (disposed) {\n warn(`once('${String(event)}') called on a disposed channel '${name}' — listener ignored`);\n\n return () => {};\n }\n\n let unsub: Unsubscribe = () => {};\n\n const wrapped = (payload: unknown): void => {\n tracked.delete(unsub);\n unsub();\n (handler as (payload: unknown) => void)(payload);\n };\n\n unsub = subscribe(name, event, wrapped);\n tracked.add(unsub);\n\n return () => {\n tracked.delete(unsub);\n unsub();\n };\n },\n\n send<K extends EventKey<TClient>>(event: K, payload: TClient[K]): void {\n if (disposed) {\n warn(`send('${String(event)}') called on a disposed channel '${name}' — message dropped`);\n\n return;\n }\n\n send(name, event, payload);\n },\n\n [Symbol.dispose]() {\n this.dispose();\n },\n\n wait<K extends EventKey<TServer>>(\n event: K,\n opts?: { signal?: AbortSignal; timeout?: number },\n ): Promise<TServer[K]> {\n return createWaitPromise<TServer[K]>(event as string, ctrl.signal, opts, (ev, handler) =>\n subscribe(name, ev, handler),\n );\n },\n };\n\n return channel;\n}\n"],"mappings":";;;;AAaA,SAAgB,EACd,GACA,GACA,GACA,GACA,GACgC;CAChC,IAAM,IAAO,EAAsB,CAAc,GAC3C,oBAAU,IAAI,IAAgB,GAEhC,IAAW,EAAK,OAAO;CAoG3B,OAlGA,EAAK,OAAO,iBACV,eACM;EACJ,IAAW;CACb,GACA,EAAE,MAAM,GAAK,CACf,GA4FO;EAzFL,IAAI,iBAAiB;GACnB,OAAO,EAAK;EACd;EAEA,UAAU;GACJ,QAGJ;IADA,IAAW,IACX,EAAK,MAAM;IAEX,KAAK,IAAM,KAAS,GAAS,EAAM;IAGnC,AADA,EAAQ,MAAM,GACd,IAAY;GALD;EAMb;EAEA,IAAI,WAAW;GACb,OAAO;EACT;EAEA,IAAI,OAAO;GACT,OAAO;EACT;EAEA,GAAgC,GAAU,GAAqD;GAC7F,IAAI,GAGF,OAFA,EAAK,OAAO,OAAO,CAAK,EAAE,mCAAmC,EAAK,qBAAqB,SAE1E,CAAC;GAGhB,IAAM,IAAQ,EAAU,GAAM,GAAO,CAAqC;GAI1E,OAFA,EAAQ,IAAI,CAAK,SAEJ;IAEX,AADA,EAAM,GACN,EAAQ,OAAO,CAAK;GACtB;EACF;EAEA,KAAkC,GAAU,GAAqD;GAC/F,IAAI,GAGF,OAFA,EAAK,SAAS,OAAO,CAAK,EAAE,mCAAmC,EAAK,qBAAqB,SAE5E,CAAC;GAGhB,IAAI,UAA2B,CAAC;GAWhC,OAHA,IAAQ,EAAU,GAAM,IANP,MAA2B;IAG1C,AAFA,EAAQ,OAAO,CAAK,GACpB,EAAM,GACN,EAAwC,CAAO;GACjD,CAEsC,GACtC,EAAQ,IAAI,CAAK,SAEJ;IAEX,AADA,EAAQ,OAAO,CAAK,GACpB,EAAM;GACR;EACF;EAEA,KAAkC,GAAU,GAA2B;GACrE,IAAI,GAAU;IACZ,EAAK,SAAS,OAAO,CAAK,EAAE,mCAAmC,EAAK,oBAAoB;IAExF;GACF;GAEA,EAAK,GAAM,GAAO,CAAO;EAC3B;EAEA,CAAC,OAAO,WAAW;GACjB,KAAK,QAAQ;EACf;EAEA,KACE,GACA,GACqB;GACrB,OAAO,EAA8B,GAAiB,EAAK,QAAQ,IAAO,GAAI,MAC5E,EAAU,GAAM,GAAI,CAAO,CAC7B;EACF;CAGK;AACT"}
package/dist/presence.cjs CHANGED
@@ -1,2 +1,2 @@
1
- const e=require("./_dev.cjs"),t=require("./_utils.cjs"),n=require("./protocol.cjs");let r=require("@vielzeug/ripple");function i(i,a,o,s){let c=t.deriveAbortController(s),l=c.signal.aborted,u=(0,r.signal)(new Map),d=new Set,f=new Set,p=[o.onState(e=>{if(e.room!==i)return;let t=new Map;for(let[n,r]of Object.entries(e.members))t.set(n,r);u.value=t}),o.onJoin(e=>{if(e.room!==i)return;let t=new Map(u.value);t.set(e.id,e.state),u.value=t;for(let t of d)t(e.id,e.state)}),o.onLeave(e=>{if(e.room!==i)return;let t=new Map(u.value);t.delete(e.id),u.value=t;for(let t of f)t(e.id)})];return{get disposalSignal(){return c.signal},dispose(){if(!l){l=!0,a(n.encode({room:i,type:`leave`})),c.abort();for(let e of p)e();p.length=0,d.clear(),f.clear(),u.dispose()}},get disposed(){return l},onJoin(t){return l?(e.warn(`onJoin() called on a disposed presence channel '${i}' — listener ignored`),()=>{}):(d.add(t),()=>d.delete(t))},onLeave(t){return l?(e.warn(`onLeave() called on a disposed presence channel '${i}' — listener ignored`),()=>{}):(f.add(t),()=>f.delete(t))},get room(){return i},get state(){return u},[Symbol.dispose](){this.dispose()},update(e){l||a(n.encode({room:i,state:e,type:`presence`}))}}}exports.createPresence=i;
1
+ const e=require("./_dev.cjs"),t=require("./_utils.cjs"),n=require("./protocol.cjs");let r=require("@vielzeug/ripple");function i(i,a,o,s,c){let l=t.deriveAbortController(s),u=l.signal.aborted;l.signal.addEventListener(`abort`,()=>{u=!0},{once:!0});let d=(0,r.signal)(new Map),f=new Set,p=new Set,m=[o.onState(e=>{if(e.room!==i)return;let t=new Map;for(let[n,r]of Object.entries(e.members))t.set(n,r);d.value=t}),o.onJoin(e=>{if(e.room!==i)return;let t=new Map(d.value);t.set(e.id,e.state),d.value=t;for(let t of f)t(e.id,e.state)}),o.onLeave(e=>{if(e.room!==i)return;let t=new Map(d.value);t.delete(e.id),d.value=t;for(let t of p)t(e.id)})];return{get disposalSignal(){return l.signal},dispose(){if(!u){u=!0,a(n.encode({room:i,type:`leave`})),l.abort();for(let e of m)e();m.length=0,f.clear(),p.clear(),d.dispose(),c?.()}},get disposed(){return u},onJoin(t){return u?(e.warn(`onJoin() called on a disposed presence channel '${i}' — listener ignored`),()=>{}):(f.add(t),()=>f.delete(t))},onLeave(t){return u?(e.warn(`onLeave() called on a disposed presence channel '${i}' — listener ignored`),()=>{}):(p.add(t),()=>p.delete(t))},get room(){return i},get state(){return d},[Symbol.dispose](){this.dispose()},update(e){u||a(n.encode({room:i,state:e,type:`presence`}))}}}exports.createPresence=i;
2
2
  //# sourceMappingURL=presence.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"presence.cjs","names":[],"sources":["../src/presence.ts"],"sourcesContent":["import { type Signal, signal } from '@vielzeug/ripple';\n\nimport type { PresenceChannel, Unsubscribe } from './types';\n\nimport { warn } from './_dev';\nimport { deriveAbortController } from './_utils';\nimport { type InPresenceJoinFrame, type InPresenceLeaveFrame, type InPresenceStateFrame, encode } from './protocol';\n\ntype RawSendFn = (frame: string) => void;\n\nexport type PresenceEvents = {\n onJoin(handler: (frame: InPresenceJoinFrame) => void): Unsubscribe;\n onLeave(handler: (frame: InPresenceLeaveFrame) => void): Unsubscribe;\n onState(handler: (frame: InPresenceStateFrame) => void): Unsubscribe;\n};\n\n/**\n * Create a presence channel that reactively tracks room member states.\n * @internal\n */\nexport function createPresence<T>(\n room: string,\n rawSend: RawSendFn,\n events: PresenceEvents,\n disposalSignal: AbortSignal,\n): PresenceChannel<T> {\n const ctrl = deriveAbortController(disposalSignal);\n\n let disposed = ctrl.signal.aborted;\n\n const members: Signal<Map<string, T>> = signal(new Map<string, T>());\n const joinHandlers = new Set<(memberId: string, state: T) => void>();\n const leaveHandlers = new Set<(memberId: string) => void>();\n\n const unsubs: Array<() => void> = [\n events.onState((frame) => {\n if (frame.room !== room) return;\n\n const next = new Map<string, T>();\n\n for (const [id, state] of Object.entries(frame.members)) {\n next.set(id, state as T);\n }\n\n members.value = next;\n }),\n events.onJoin((frame) => {\n if (frame.room !== room) return;\n\n const next = new Map(members.value);\n\n next.set(frame.id, frame.state as T);\n members.value = next;\n\n for (const handler of joinHandlers) {\n handler(frame.id, frame.state as T);\n }\n }),\n events.onLeave((frame) => {\n if (frame.room !== room) return;\n\n const next = new Map(members.value);\n\n next.delete(frame.id);\n members.value = next;\n\n for (const handler of leaveHandlers) {\n handler(frame.id);\n }\n }),\n ];\n\n const presence: PresenceChannel<T> = {\n get disposalSignal() {\n return ctrl.signal;\n },\n\n dispose() {\n if (disposed) return;\n\n disposed = true;\n rawSend(encode({ room, type: 'leave' }));\n ctrl.abort();\n\n for (const unsub of unsubs) unsub();\n\n unsubs.length = 0;\n joinHandlers.clear();\n leaveHandlers.clear();\n members.dispose();\n },\n\n get disposed() {\n return disposed;\n },\n\n onJoin(handler: (memberId: string, state: T) => void): Unsubscribe {\n if (disposed) {\n warn(`onJoin() called on a disposed presence channel '${room}' — listener ignored`);\n\n return () => {};\n }\n\n joinHandlers.add(handler);\n\n return () => joinHandlers.delete(handler);\n },\n\n onLeave(handler: (memberId: string) => void): Unsubscribe {\n if (disposed) {\n warn(`onLeave() called on a disposed presence channel '${room}' — listener ignored`);\n\n return () => {};\n }\n\n leaveHandlers.add(handler);\n\n return () => leaveHandlers.delete(handler);\n },\n\n get room() {\n return room;\n },\n\n get state() {\n return members;\n },\n\n [Symbol.dispose]() {\n this.dispose();\n },\n\n update(state: T): void {\n if (disposed) return;\n\n rawSend(encode({ room, state, type: 'presence' }));\n },\n };\n\n return presence;\n}\n"],"mappings":"sHAoBA,SAAgB,EACd,EACA,EACA,EACA,EACoB,CACpB,IAAM,EAAO,EAAA,sBAAsB,CAAc,EAE7C,EAAW,EAAK,OAAO,QAErB,GAAA,EAAA,EAAA,OAAA,CAAyC,IAAI,GAAgB,EAC7D,EAAe,IAAI,IACnB,EAAgB,IAAI,IAEpB,EAA4B,CAChC,EAAO,QAAS,GAAU,CACxB,GAAI,EAAM,OAAS,EAAM,OAEzB,IAAM,EAAO,IAAI,IAEjB,IAAK,GAAM,CAAC,EAAI,KAAU,OAAO,QAAQ,EAAM,OAAO,EACpD,EAAK,IAAI,EAAI,CAAU,EAGzB,EAAQ,MAAQ,CAClB,CAAC,EACD,EAAO,OAAQ,GAAU,CACvB,GAAI,EAAM,OAAS,EAAM,OAEzB,IAAM,EAAO,IAAI,IAAI,EAAQ,KAAK,EAElC,EAAK,IAAI,EAAM,GAAI,EAAM,KAAU,EACnC,EAAQ,MAAQ,EAEhB,IAAK,IAAM,KAAW,EACpB,EAAQ,EAAM,GAAI,EAAM,KAAU,CAEtC,CAAC,EACD,EAAO,QAAS,GAAU,CACxB,GAAI,EAAM,OAAS,EAAM,OAEzB,IAAM,EAAO,IAAI,IAAI,EAAQ,KAAK,EAElC,EAAK,OAAO,EAAM,EAAE,EACpB,EAAQ,MAAQ,EAEhB,IAAK,IAAM,KAAW,EACpB,EAAQ,EAAM,EAAE,CAEpB,CAAC,CACH,EAqEA,MAAO,CAlEL,IAAI,gBAAiB,CACnB,OAAO,EAAK,MACd,EAEA,SAAU,CACJ,MAIJ,CAFA,EAAW,GACX,EAAQ,EAAA,OAAO,CAAE,OAAM,KAAM,OAAQ,CAAC,CAAC,EACvC,EAAK,MAAM,EAEX,IAAK,IAAM,KAAS,EAAQ,EAAM,EAElC,EAAO,OAAS,EAChB,EAAa,MAAM,EACnB,EAAc,MAAM,EACpB,EAAQ,QAAQ,CAPL,CAQb,EAEA,IAAI,UAAW,CACb,OAAO,CACT,EAEA,OAAO,EAA4D,CASjE,OARI,GACF,EAAA,KAAK,mDAAmD,EAAK,qBAAqB,MAErE,CAAC,IAGhB,EAAa,IAAI,CAAO,MAEX,EAAa,OAAO,CAAO,EAC1C,EAEA,QAAQ,EAAkD,CASxD,OARI,GACF,EAAA,KAAK,oDAAoD,EAAK,qBAAqB,MAEtE,CAAC,IAGhB,EAAc,IAAI,CAAO,MAEZ,EAAc,OAAO,CAAO,EAC3C,EAEA,IAAI,MAAO,CACT,OAAO,CACT,EAEA,IAAI,OAAQ,CACV,OAAO,CACT,EAEA,CAAC,OAAO,UAAW,CACjB,KAAK,QAAQ,CACf,EAEA,OAAO,EAAgB,CACjB,GAEJ,EAAQ,EAAA,OAAO,CAAE,OAAM,QAAO,KAAM,UAAW,CAAC,CAAC,CACnD,CAGK,CACT"}
1
+ {"version":3,"file":"presence.cjs","names":[],"sources":["../src/presence.ts"],"sourcesContent":["import { type Signal, signal } from '@vielzeug/ripple';\n\nimport type { PresenceChannel, Unsubscribe } from './types';\n\nimport { warn } from './_dev';\nimport { deriveAbortController } from './_utils';\nimport { type InPresenceJoinFrame, type InPresenceLeaveFrame, type InPresenceStateFrame, encode } from './protocol';\n\ntype RawSendFn = (frame: string) => void;\n\nexport type PresenceEvents = {\n onJoin(handler: (frame: InPresenceJoinFrame) => void): Unsubscribe;\n onLeave(handler: (frame: InPresenceLeaveFrame) => void): Unsubscribe;\n onState(handler: (frame: InPresenceStateFrame) => void): Unsubscribe;\n};\n\n/**\n * Create a presence channel that reactively tracks room member states.\n * @internal\n */\nexport function createPresence<T>(\n room: string,\n rawSend: RawSendFn,\n events: PresenceEvents,\n disposalSignal: AbortSignal,\n onDispose?: () => void,\n): PresenceChannel<T> {\n const ctrl = deriveAbortController(disposalSignal);\n\n let disposed = ctrl.signal.aborted;\n\n ctrl.signal.addEventListener(\n 'abort',\n () => {\n disposed = true;\n },\n { once: true },\n );\n\n const members: Signal<Map<string, T>> = signal(new Map<string, T>());\n const joinHandlers = new Set<(memberId: string, state: T) => void>();\n const leaveHandlers = new Set<(memberId: string) => void>();\n\n const unsubs: Array<() => void> = [\n events.onState((frame) => {\n if (frame.room !== room) return;\n\n const next = new Map<string, T>();\n\n for (const [id, state] of Object.entries(frame.members)) {\n next.set(id, state as T);\n }\n\n members.value = next;\n }),\n events.onJoin((frame) => {\n if (frame.room !== room) return;\n\n const next = new Map(members.value);\n\n next.set(frame.id, frame.state as T);\n members.value = next;\n\n for (const handler of joinHandlers) {\n handler(frame.id, frame.state as T);\n }\n }),\n events.onLeave((frame) => {\n if (frame.room !== room) return;\n\n const next = new Map(members.value);\n\n next.delete(frame.id);\n members.value = next;\n\n for (const handler of leaveHandlers) {\n handler(frame.id);\n }\n }),\n ];\n\n const presence: PresenceChannel<T> = {\n get disposalSignal() {\n return ctrl.signal;\n },\n\n dispose() {\n if (disposed) return;\n\n disposed = true;\n rawSend(encode({ room, type: 'leave' }));\n ctrl.abort();\n\n for (const unsub of unsubs) unsub();\n\n unsubs.length = 0;\n joinHandlers.clear();\n leaveHandlers.clear();\n members.dispose();\n onDispose?.();\n },\n\n get disposed() {\n return disposed;\n },\n\n onJoin(handler: (memberId: string, state: T) => void): Unsubscribe {\n if (disposed) {\n warn(`onJoin() called on a disposed presence channel '${room}' — listener ignored`);\n\n return () => {};\n }\n\n joinHandlers.add(handler);\n\n return () => joinHandlers.delete(handler);\n },\n\n onLeave(handler: (memberId: string) => void): Unsubscribe {\n if (disposed) {\n warn(`onLeave() called on a disposed presence channel '${room}' — listener ignored`);\n\n return () => {};\n }\n\n leaveHandlers.add(handler);\n\n return () => leaveHandlers.delete(handler);\n },\n\n get room() {\n return room;\n },\n\n get state() {\n return members;\n },\n\n [Symbol.dispose]() {\n this.dispose();\n },\n\n update(state: T): void {\n if (disposed) return;\n\n rawSend(encode({ room, state, type: 'presence' }));\n },\n };\n\n return presence;\n}\n"],"mappings":"sHAoBA,SAAgB,EACd,EACA,EACA,EACA,EACA,EACoB,CACpB,IAAM,EAAO,EAAA,sBAAsB,CAAc,EAE7C,EAAW,EAAK,OAAO,QAE3B,EAAK,OAAO,iBACV,YACM,CACJ,EAAW,EACb,EACA,CAAE,KAAM,EAAK,CACf,EAEA,IAAM,GAAA,EAAA,EAAA,OAAA,CAAyC,IAAI,GAAgB,EAC7D,EAAe,IAAI,IACnB,EAAgB,IAAI,IAEpB,EAA4B,CAChC,EAAO,QAAS,GAAU,CACxB,GAAI,EAAM,OAAS,EAAM,OAEzB,IAAM,EAAO,IAAI,IAEjB,IAAK,GAAM,CAAC,EAAI,KAAU,OAAO,QAAQ,EAAM,OAAO,EACpD,EAAK,IAAI,EAAI,CAAU,EAGzB,EAAQ,MAAQ,CAClB,CAAC,EACD,EAAO,OAAQ,GAAU,CACvB,GAAI,EAAM,OAAS,EAAM,OAEzB,IAAM,EAAO,IAAI,IAAI,EAAQ,KAAK,EAElC,EAAK,IAAI,EAAM,GAAI,EAAM,KAAU,EACnC,EAAQ,MAAQ,EAEhB,IAAK,IAAM,KAAW,EACpB,EAAQ,EAAM,GAAI,EAAM,KAAU,CAEtC,CAAC,EACD,EAAO,QAAS,GAAU,CACxB,GAAI,EAAM,OAAS,EAAM,OAEzB,IAAM,EAAO,IAAI,IAAI,EAAQ,KAAK,EAElC,EAAK,OAAO,EAAM,EAAE,EACpB,EAAQ,MAAQ,EAEhB,IAAK,IAAM,KAAW,EACpB,EAAQ,EAAM,EAAE,CAEpB,CAAC,CACH,EAsEA,MAAO,CAnEL,IAAI,gBAAiB,CACnB,OAAO,EAAK,MACd,EAEA,SAAU,CACJ,MAIJ,CAFA,EAAW,GACX,EAAQ,EAAA,OAAO,CAAE,OAAM,KAAM,OAAQ,CAAC,CAAC,EACvC,EAAK,MAAM,EAEX,IAAK,IAAM,KAAS,EAAQ,EAAM,EAElC,EAAO,OAAS,EAChB,EAAa,MAAM,EACnB,EAAc,MAAM,EACpB,EAAQ,QAAQ,EAChB,IAAY,CARD,CASb,EAEA,IAAI,UAAW,CACb,OAAO,CACT,EAEA,OAAO,EAA4D,CASjE,OARI,GACF,EAAA,KAAK,mDAAmD,EAAK,qBAAqB,MAErE,CAAC,IAGhB,EAAa,IAAI,CAAO,MAEX,EAAa,OAAO,CAAO,EAC1C,EAEA,QAAQ,EAAkD,CASxD,OARI,GACF,EAAA,KAAK,oDAAoD,EAAK,qBAAqB,MAEtE,CAAC,IAGhB,EAAc,IAAI,CAAO,MAEZ,EAAc,OAAO,CAAO,EAC3C,EAEA,IAAI,MAAO,CACT,OAAO,CACT,EAEA,IAAI,OAAQ,CACV,OAAO,CACT,EAEA,CAAC,OAAO,UAAW,CACjB,KAAK,QAAQ,CACf,EAEA,OAAO,EAAgB,CACjB,GAEJ,EAAQ,EAAA,OAAO,CAAE,OAAM,QAAO,KAAM,UAAW,CAAC,CAAC,CACnD,CAGK,CACT"}
package/dist/presence.js CHANGED
@@ -3,61 +3,65 @@ import { deriveAbortController as t } from "./_utils.js";
3
3
  import { encode as n } from "./protocol.js";
4
4
  import { signal as r } from "@vielzeug/ripple";
5
5
  //#region src/presence.ts
6
- function i(i, a, o, s) {
7
- let c = t(s), l = c.signal.aborted, u = r(/* @__PURE__ */ new Map()), d = /* @__PURE__ */ new Set(), f = /* @__PURE__ */ new Set(), p = [
6
+ function i(i, a, o, s, c) {
7
+ let l = t(s), u = l.signal.aborted;
8
+ l.signal.addEventListener("abort", () => {
9
+ u = !0;
10
+ }, { once: !0 });
11
+ let d = r(/* @__PURE__ */ new Map()), f = /* @__PURE__ */ new Set(), p = /* @__PURE__ */ new Set(), m = [
8
12
  o.onState((e) => {
9
13
  if (e.room !== i) return;
10
14
  let t = /* @__PURE__ */ new Map();
11
15
  for (let [n, r] of Object.entries(e.members)) t.set(n, r);
12
- u.value = t;
16
+ d.value = t;
13
17
  }),
14
18
  o.onJoin((e) => {
15
19
  if (e.room !== i) return;
16
- let t = new Map(u.value);
17
- t.set(e.id, e.state), u.value = t;
18
- for (let t of d) t(e.id, e.state);
20
+ let t = new Map(d.value);
21
+ t.set(e.id, e.state), d.value = t;
22
+ for (let t of f) t(e.id, e.state);
19
23
  }),
20
24
  o.onLeave((e) => {
21
25
  if (e.room !== i) return;
22
- let t = new Map(u.value);
23
- t.delete(e.id), u.value = t;
24
- for (let t of f) t(e.id);
26
+ let t = new Map(d.value);
27
+ t.delete(e.id), d.value = t;
28
+ for (let t of p) t(e.id);
25
29
  })
26
30
  ];
27
31
  return {
28
32
  get disposalSignal() {
29
- return c.signal;
33
+ return l.signal;
30
34
  },
31
35
  dispose() {
32
- if (!l) {
33
- l = !0, a(n({
36
+ if (!u) {
37
+ u = !0, a(n({
34
38
  room: i,
35
39
  type: "leave"
36
- })), c.abort();
37
- for (let e of p) e();
38
- p.length = 0, d.clear(), f.clear(), u.dispose();
40
+ })), l.abort();
41
+ for (let e of m) e();
42
+ m.length = 0, f.clear(), p.clear(), d.dispose(), c?.();
39
43
  }
40
44
  },
41
45
  get disposed() {
42
- return l;
46
+ return u;
43
47
  },
44
48
  onJoin(t) {
45
- return l ? (e(`onJoin() called on a disposed presence channel '${i}' — listener ignored`), () => {}) : (d.add(t), () => d.delete(t));
49
+ return u ? (e(`onJoin() called on a disposed presence channel '${i}' — listener ignored`), () => {}) : (f.add(t), () => f.delete(t));
46
50
  },
47
51
  onLeave(t) {
48
- return l ? (e(`onLeave() called on a disposed presence channel '${i}' — listener ignored`), () => {}) : (f.add(t), () => f.delete(t));
52
+ return u ? (e(`onLeave() called on a disposed presence channel '${i}' — listener ignored`), () => {}) : (p.add(t), () => p.delete(t));
49
53
  },
50
54
  get room() {
51
55
  return i;
52
56
  },
53
57
  get state() {
54
- return u;
58
+ return d;
55
59
  },
56
60
  [Symbol.dispose]() {
57
61
  this.dispose();
58
62
  },
59
63
  update(e) {
60
- l || a(n({
64
+ u || a(n({
61
65
  room: i,
62
66
  state: e,
63
67
  type: "presence"
@@ -1 +1 @@
1
- {"version":3,"file":"presence.js","names":[],"sources":["../src/presence.ts"],"sourcesContent":["import { type Signal, signal } from '@vielzeug/ripple';\n\nimport type { PresenceChannel, Unsubscribe } from './types';\n\nimport { warn } from './_dev';\nimport { deriveAbortController } from './_utils';\nimport { type InPresenceJoinFrame, type InPresenceLeaveFrame, type InPresenceStateFrame, encode } from './protocol';\n\ntype RawSendFn = (frame: string) => void;\n\nexport type PresenceEvents = {\n onJoin(handler: (frame: InPresenceJoinFrame) => void): Unsubscribe;\n onLeave(handler: (frame: InPresenceLeaveFrame) => void): Unsubscribe;\n onState(handler: (frame: InPresenceStateFrame) => void): Unsubscribe;\n};\n\n/**\n * Create a presence channel that reactively tracks room member states.\n * @internal\n */\nexport function createPresence<T>(\n room: string,\n rawSend: RawSendFn,\n events: PresenceEvents,\n disposalSignal: AbortSignal,\n): PresenceChannel<T> {\n const ctrl = deriveAbortController(disposalSignal);\n\n let disposed = ctrl.signal.aborted;\n\n const members: Signal<Map<string, T>> = signal(new Map<string, T>());\n const joinHandlers = new Set<(memberId: string, state: T) => void>();\n const leaveHandlers = new Set<(memberId: string) => void>();\n\n const unsubs: Array<() => void> = [\n events.onState((frame) => {\n if (frame.room !== room) return;\n\n const next = new Map<string, T>();\n\n for (const [id, state] of Object.entries(frame.members)) {\n next.set(id, state as T);\n }\n\n members.value = next;\n }),\n events.onJoin((frame) => {\n if (frame.room !== room) return;\n\n const next = new Map(members.value);\n\n next.set(frame.id, frame.state as T);\n members.value = next;\n\n for (const handler of joinHandlers) {\n handler(frame.id, frame.state as T);\n }\n }),\n events.onLeave((frame) => {\n if (frame.room !== room) return;\n\n const next = new Map(members.value);\n\n next.delete(frame.id);\n members.value = next;\n\n for (const handler of leaveHandlers) {\n handler(frame.id);\n }\n }),\n ];\n\n const presence: PresenceChannel<T> = {\n get disposalSignal() {\n return ctrl.signal;\n },\n\n dispose() {\n if (disposed) return;\n\n disposed = true;\n rawSend(encode({ room, type: 'leave' }));\n ctrl.abort();\n\n for (const unsub of unsubs) unsub();\n\n unsubs.length = 0;\n joinHandlers.clear();\n leaveHandlers.clear();\n members.dispose();\n },\n\n get disposed() {\n return disposed;\n },\n\n onJoin(handler: (memberId: string, state: T) => void): Unsubscribe {\n if (disposed) {\n warn(`onJoin() called on a disposed presence channel '${room}' — listener ignored`);\n\n return () => {};\n }\n\n joinHandlers.add(handler);\n\n return () => joinHandlers.delete(handler);\n },\n\n onLeave(handler: (memberId: string) => void): Unsubscribe {\n if (disposed) {\n warn(`onLeave() called on a disposed presence channel '${room}' — listener ignored`);\n\n return () => {};\n }\n\n leaveHandlers.add(handler);\n\n return () => leaveHandlers.delete(handler);\n },\n\n get room() {\n return room;\n },\n\n get state() {\n return members;\n },\n\n [Symbol.dispose]() {\n this.dispose();\n },\n\n update(state: T): void {\n if (disposed) return;\n\n rawSend(encode({ room, state, type: 'presence' }));\n },\n };\n\n return presence;\n}\n"],"mappings":";;;;;AAoBA,SAAgB,EACd,GACA,GACA,GACA,GACoB;CACpB,IAAM,IAAO,EAAsB,CAAc,GAE7C,IAAW,EAAK,OAAO,SAErB,IAAkC,kBAAO,IAAI,IAAe,CAAC,GAC7D,oBAAe,IAAI,IAA0C,GAC7D,oBAAgB,IAAI,IAAgC,GAEpD,IAA4B;EAChC,EAAO,SAAS,MAAU;GACxB,IAAI,EAAM,SAAS,GAAM;GAEzB,IAAM,oBAAO,IAAI,IAAe;GAEhC,KAAK,IAAM,CAAC,GAAI,MAAU,OAAO,QAAQ,EAAM,OAAO,GACpD,EAAK,IAAI,GAAI,CAAU;GAGzB,EAAQ,QAAQ;EAClB,CAAC;EACD,EAAO,QAAQ,MAAU;GACvB,IAAI,EAAM,SAAS,GAAM;GAEzB,IAAM,IAAO,IAAI,IAAI,EAAQ,KAAK;GAGlC,AADA,EAAK,IAAI,EAAM,IAAI,EAAM,KAAU,GACnC,EAAQ,QAAQ;GAEhB,KAAK,IAAM,KAAW,GACpB,EAAQ,EAAM,IAAI,EAAM,KAAU;EAEtC,CAAC;EACD,EAAO,SAAS,MAAU;GACxB,IAAI,EAAM,SAAS,GAAM;GAEzB,IAAM,IAAO,IAAI,IAAI,EAAQ,KAAK;GAGlC,AADA,EAAK,OAAO,EAAM,EAAE,GACpB,EAAQ,QAAQ;GAEhB,KAAK,IAAM,KAAW,GACpB,EAAQ,EAAM,EAAE;EAEpB,CAAC;CACH;CAqEA,OAAO;EAlEL,IAAI,iBAAiB;GACnB,OAAO,EAAK;EACd;EAEA,UAAU;GACJ,QAIJ;IAFA,IAAW,IACX,EAAQ,EAAO;KAAE;KAAM,MAAM;IAAQ,CAAC,CAAC,GACvC,EAAK,MAAM;IAEX,KAAK,IAAM,KAAS,GAAQ,EAAM;IAKlC,AAHA,EAAO,SAAS,GAChB,EAAa,MAAM,GACnB,EAAc,MAAM,GACpB,EAAQ,QAAQ;GAPL;EAQb;EAEA,IAAI,WAAW;GACb,OAAO;EACT;EAEA,OAAO,GAA4D;GASjE,OARI,KACF,EAAK,mDAAmD,EAAK,qBAAqB,SAErE,CAAC,MAGhB,EAAa,IAAI,CAAO,SAEX,EAAa,OAAO,CAAO;EAC1C;EAEA,QAAQ,GAAkD;GASxD,OARI,KACF,EAAK,oDAAoD,EAAK,qBAAqB,SAEtE,CAAC,MAGhB,EAAc,IAAI,CAAO,SAEZ,EAAc,OAAO,CAAO;EAC3C;EAEA,IAAI,OAAO;GACT,OAAO;EACT;EAEA,IAAI,QAAQ;GACV,OAAO;EACT;EAEA,CAAC,OAAO,WAAW;GACjB,KAAK,QAAQ;EACf;EAEA,OAAO,GAAgB;GACjB,KAEJ,EAAQ,EAAO;IAAE;IAAM;IAAO,MAAM;GAAW,CAAC,CAAC;EACnD;CAGK;AACT"}
1
+ {"version":3,"file":"presence.js","names":[],"sources":["../src/presence.ts"],"sourcesContent":["import { type Signal, signal } from '@vielzeug/ripple';\n\nimport type { PresenceChannel, Unsubscribe } from './types';\n\nimport { warn } from './_dev';\nimport { deriveAbortController } from './_utils';\nimport { type InPresenceJoinFrame, type InPresenceLeaveFrame, type InPresenceStateFrame, encode } from './protocol';\n\ntype RawSendFn = (frame: string) => void;\n\nexport type PresenceEvents = {\n onJoin(handler: (frame: InPresenceJoinFrame) => void): Unsubscribe;\n onLeave(handler: (frame: InPresenceLeaveFrame) => void): Unsubscribe;\n onState(handler: (frame: InPresenceStateFrame) => void): Unsubscribe;\n};\n\n/**\n * Create a presence channel that reactively tracks room member states.\n * @internal\n */\nexport function createPresence<T>(\n room: string,\n rawSend: RawSendFn,\n events: PresenceEvents,\n disposalSignal: AbortSignal,\n onDispose?: () => void,\n): PresenceChannel<T> {\n const ctrl = deriveAbortController(disposalSignal);\n\n let disposed = ctrl.signal.aborted;\n\n ctrl.signal.addEventListener(\n 'abort',\n () => {\n disposed = true;\n },\n { once: true },\n );\n\n const members: Signal<Map<string, T>> = signal(new Map<string, T>());\n const joinHandlers = new Set<(memberId: string, state: T) => void>();\n const leaveHandlers = new Set<(memberId: string) => void>();\n\n const unsubs: Array<() => void> = [\n events.onState((frame) => {\n if (frame.room !== room) return;\n\n const next = new Map<string, T>();\n\n for (const [id, state] of Object.entries(frame.members)) {\n next.set(id, state as T);\n }\n\n members.value = next;\n }),\n events.onJoin((frame) => {\n if (frame.room !== room) return;\n\n const next = new Map(members.value);\n\n next.set(frame.id, frame.state as T);\n members.value = next;\n\n for (const handler of joinHandlers) {\n handler(frame.id, frame.state as T);\n }\n }),\n events.onLeave((frame) => {\n if (frame.room !== room) return;\n\n const next = new Map(members.value);\n\n next.delete(frame.id);\n members.value = next;\n\n for (const handler of leaveHandlers) {\n handler(frame.id);\n }\n }),\n ];\n\n const presence: PresenceChannel<T> = {\n get disposalSignal() {\n return ctrl.signal;\n },\n\n dispose() {\n if (disposed) return;\n\n disposed = true;\n rawSend(encode({ room, type: 'leave' }));\n ctrl.abort();\n\n for (const unsub of unsubs) unsub();\n\n unsubs.length = 0;\n joinHandlers.clear();\n leaveHandlers.clear();\n members.dispose();\n onDispose?.();\n },\n\n get disposed() {\n return disposed;\n },\n\n onJoin(handler: (memberId: string, state: T) => void): Unsubscribe {\n if (disposed) {\n warn(`onJoin() called on a disposed presence channel '${room}' — listener ignored`);\n\n return () => {};\n }\n\n joinHandlers.add(handler);\n\n return () => joinHandlers.delete(handler);\n },\n\n onLeave(handler: (memberId: string) => void): Unsubscribe {\n if (disposed) {\n warn(`onLeave() called on a disposed presence channel '${room}' — listener ignored`);\n\n return () => {};\n }\n\n leaveHandlers.add(handler);\n\n return () => leaveHandlers.delete(handler);\n },\n\n get room() {\n return room;\n },\n\n get state() {\n return members;\n },\n\n [Symbol.dispose]() {\n this.dispose();\n },\n\n update(state: T): void {\n if (disposed) return;\n\n rawSend(encode({ room, state, type: 'presence' }));\n },\n };\n\n return presence;\n}\n"],"mappings":";;;;;AAoBA,SAAgB,EACd,GACA,GACA,GACA,GACA,GACoB;CACpB,IAAM,IAAO,EAAsB,CAAc,GAE7C,IAAW,EAAK,OAAO;CAE3B,EAAK,OAAO,iBACV,eACM;EACJ,IAAW;CACb,GACA,EAAE,MAAM,GAAK,CACf;CAEA,IAAM,IAAkC,kBAAO,IAAI,IAAe,CAAC,GAC7D,oBAAe,IAAI,IAA0C,GAC7D,oBAAgB,IAAI,IAAgC,GAEpD,IAA4B;EAChC,EAAO,SAAS,MAAU;GACxB,IAAI,EAAM,SAAS,GAAM;GAEzB,IAAM,oBAAO,IAAI,IAAe;GAEhC,KAAK,IAAM,CAAC,GAAI,MAAU,OAAO,QAAQ,EAAM,OAAO,GACpD,EAAK,IAAI,GAAI,CAAU;GAGzB,EAAQ,QAAQ;EAClB,CAAC;EACD,EAAO,QAAQ,MAAU;GACvB,IAAI,EAAM,SAAS,GAAM;GAEzB,IAAM,IAAO,IAAI,IAAI,EAAQ,KAAK;GAGlC,AADA,EAAK,IAAI,EAAM,IAAI,EAAM,KAAU,GACnC,EAAQ,QAAQ;GAEhB,KAAK,IAAM,KAAW,GACpB,EAAQ,EAAM,IAAI,EAAM,KAAU;EAEtC,CAAC;EACD,EAAO,SAAS,MAAU;GACxB,IAAI,EAAM,SAAS,GAAM;GAEzB,IAAM,IAAO,IAAI,IAAI,EAAQ,KAAK;GAGlC,AADA,EAAK,OAAO,EAAM,EAAE,GACpB,EAAQ,QAAQ;GAEhB,KAAK,IAAM,KAAW,GACpB,EAAQ,EAAM,EAAE;EAEpB,CAAC;CACH;CAsEA,OAAO;EAnEL,IAAI,iBAAiB;GACnB,OAAO,EAAK;EACd;EAEA,UAAU;GACJ,QAIJ;IAFA,IAAW,IACX,EAAQ,EAAO;KAAE;KAAM,MAAM;IAAQ,CAAC,CAAC,GACvC,EAAK,MAAM;IAEX,KAAK,IAAM,KAAS,GAAQ,EAAM;IAMlC,AAJA,EAAO,SAAS,GAChB,EAAa,MAAM,GACnB,EAAc,MAAM,GACpB,EAAQ,QAAQ,GAChB,IAAY;GARD;EASb;EAEA,IAAI,WAAW;GACb,OAAO;EACT;EAEA,OAAO,GAA4D;GASjE,OARI,KACF,EAAK,mDAAmD,EAAK,qBAAqB,SAErE,CAAC,MAGhB,EAAa,IAAI,CAAO,SAEX,EAAa,OAAO,CAAO;EAC1C;EAEA,QAAQ,GAAkD;GASxD,OARI,KACF,EAAK,oDAAoD,EAAK,qBAAqB,SAEtE,CAAC,MAGhB,EAAc,IAAI,CAAO,SAEZ,EAAc,OAAO,CAAO;EAC3C;EAEA,IAAI,OAAO;GACT,OAAO;EACT;EAEA,IAAI,QAAQ;GACV,OAAO;EACT;EAEA,CAAC,OAAO,WAAW;GACjB,KAAK,QAAQ;EACf;EAEA,OAAO,GAAgB;GACjB,KAEJ,EAAQ,EAAO;IAAE;IAAM;IAAO,MAAM;GAAW,CAAC,CAAC;EACnD;CAGK;AACT"}
package/dist/protocol.cjs CHANGED
@@ -1,2 +1,2 @@
1
- const e=require("./errors.cjs");function t(e){return JSON.stringify(e)}function n(t){if(typeof t!=`string`)throw new e.PulseProtocolError(`Expected string message data`,t);let n;try{n=JSON.parse(t)}catch{throw new e.PulseProtocolError(`JSON parse error`,t)}if(typeof n!=`object`||!n||!(`type`in n)||typeof n.type!=`string`)throw new e.PulseProtocolError(`Missing or invalid "type" field`,n);return n}exports.decode=n,exports.encode=t;
1
+ const e=require("./errors.cjs");function t(e){return JSON.stringify(e)}function n(t){if(typeof t!=`string`)throw new e.PulseProtocolError(`Expected string message data`,t);let n;try{n=JSON.parse(t)}catch{throw new e.PulseProtocolError(`JSON parse error`,t)}if(typeof n!=`object`||!n||!(`type`in n)||typeof n.type!=`string`)throw new e.PulseProtocolError(`Missing or invalid "type" field`,n);return n}function r(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function i(e,t){if(!r(e))return null;let n=e[t];return typeof n==`string`?n:null}function a(e){return i(e,`code`)!==null&&i(e,`message`)!==null}function o(e){return i(e,`room`)!==null}function s(e){return i(e,`room`)!==null}function c(e){return!r(e)||i(e,`event`)===null||!(`payload`in e)?!1:e.channel===void 0||typeof e.channel==`string`}function l(e){return r(e)&&typeof e.ts==`number`}function u(e){return r(e)&&i(e,`id`)!==null&&i(e,`room`)!==null&&`state`in e}function d(e){return i(e,`id`)!==null&&i(e,`room`)!==null}function f(e){return r(e)&&i(e,`room`)!==null&&r(e.members)}function p(e){return i(e,`channel`)!==null}function m(e){return i(e,`channel`)!==null}function h(t){let r=n(t);switch(r.type){case`error`:if(a(r))return{frame:r,kind:`known`};break;case`joined`:if(o(r))return{frame:r,kind:`known`};break;case`left`:if(s(r))return{frame:r,kind:`known`};break;case`message`:if(c(r))return{frame:r,kind:`known`};break;case`pong`:if(l(r))return{frame:r,kind:`known`};break;case`presence_join`:if(u(r))return{frame:r,kind:`known`};break;case`presence_leave`:if(d(r))return{frame:r,kind:`known`};break;case`presence_state`:if(f(r))return{frame:r,kind:`known`};break;case`subscribed`:if(p(r))return{frame:r,kind:`known`};break;case`unsubscribed`:if(m(r))return{frame:r,kind:`known`};break;default:return{kind:`unknown`,type:r.type}}throw new e.PulseProtocolError(`Malformed "${r.type}" frame`,r)}exports.decode=n,exports.decodeValidated=h,exports.encode=t,exports.isErrorFrame=a,exports.isJoinedFrame=o,exports.isLeftFrame=s,exports.isMessageFrame=c,exports.isPongFrame=l,exports.isPresenceJoinFrame=u,exports.isPresenceLeaveFrame=d,exports.isPresenceStateFrame=f;
2
2
  //# sourceMappingURL=protocol.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.cjs","names":[],"sources":["../src/protocol.ts"],"sourcesContent":["import { PulseProtocolError } from './errors';\n\n// ─── Outgoing frames (client → server) ────────────────────────────────────────\n\nexport type OutMessageFrame = { channel?: string; event: string; payload: unknown; type: 'message' };\nexport type OutSubscribeFrame = { channel: string; type: 'subscribe' };\nexport type OutUnsubscribeFrame = { channel: string; type: 'unsubscribe' };\nexport type OutJoinFrame = { room: string; type: 'join' };\nexport type OutLeaveFrame = { room: string; type: 'leave' };\nexport type OutPresenceFrame = { room: string; state: unknown; type: 'presence' };\nexport type OutPingFrame = { ts: number; type: 'ping' };\n\nexport type OutFrame =\n | OutJoinFrame\n | OutLeaveFrame\n | OutMessageFrame\n | OutPingFrame\n | OutPresenceFrame\n | OutSubscribeFrame\n | OutUnsubscribeFrame;\n\n// ─── Incoming frames (server → client) ────────────────────────────────────────\n\nexport type InMessageFrame = { channel?: string; event: string; payload: unknown; type: 'message' };\nexport type InSubscribedFrame = { channel: string; type: 'subscribed' };\nexport type InUnsubscribedFrame = { channel: string; type: 'unsubscribed' };\nexport type InJoinedFrame = { room: string; type: 'joined' };\nexport type InLeftFrame = { room: string; type: 'left' };\nexport type InPresenceStateFrame = { members: Record<string, unknown>; room: string; type: 'presence_state' };\nexport type InPresenceJoinFrame = { id: string; room: string; state: unknown; type: 'presence_join' };\nexport type InPresenceLeaveFrame = { id: string; room: string; type: 'presence_leave' };\nexport type InPongFrame = { ts: number; type: 'pong' };\nexport type InErrorFrame = { code: string; message: string; type: 'error' };\n\nexport type InFrame =\n | InErrorFrame\n | InJoinedFrame\n | InLeftFrame\n | InMessageFrame\n | InPongFrame\n | InPresenceJoinFrame\n | InPresenceLeaveFrame\n | InPresenceStateFrame\n | InSubscribedFrame\n | InUnsubscribedFrame;\n\n// ─── Codec ────────────────────────────────────────────────────────────────────\n\n/**\n * Encode an outgoing frame to a JSON string.\n * @internal\n */\nexport function encode(frame: OutFrame): string {\n return JSON.stringify(frame);\n}\n\n/**\n * Parse a raw MessageEvent data string into a typed InFrame.\n * Throws `PulseProtocolError` if the data is not valid JSON or missing the `type` field.\n * @internal\n */\nexport function decode(raw: unknown): InFrame {\n if (typeof raw !== 'string') {\n throw new PulseProtocolError('Expected string message data', raw);\n }\n\n let parsed: unknown;\n\n try {\n parsed = JSON.parse(raw);\n } catch (cause) {\n throw new PulseProtocolError('JSON parse error', raw);\n }\n\n if (\n parsed === null ||\n typeof parsed !== 'object' ||\n !('type' in parsed) ||\n typeof (parsed as { type: unknown }).type !== 'string'\n ) {\n throw new PulseProtocolError('Missing or invalid \"type\" field', parsed);\n }\n\n return parsed as InFrame;\n}\n"],"mappings":"gCAoDA,SAAgB,EAAO,EAAyB,CAC9C,OAAO,KAAK,UAAU,CAAK,CAC7B,CAOA,SAAgB,EAAO,EAAuB,CAC5C,GAAI,OAAO,GAAQ,SACjB,MAAM,IAAI,EAAA,mBAAmB,+BAAgC,CAAG,EAGlE,IAAI,EAEJ,GAAI,CACF,EAAS,KAAK,MAAM,CAAG,CACzB,MAAgB,CACd,MAAM,IAAI,EAAA,mBAAmB,mBAAoB,CAAG,CACtD,CAEA,GAEE,OAAO,GAAW,WADlB,GAEA,EAAE,SAAU,IACZ,OAAQ,EAA6B,MAAS,SAE9C,MAAM,IAAI,EAAA,mBAAmB,kCAAmC,CAAM,EAGxE,OAAO,CACT"}
1
+ {"version":3,"file":"protocol.cjs","names":[],"sources":["../src/protocol.ts"],"sourcesContent":["import { PulseProtocolError } from './errors';\n\n// ─── Outgoing frames (client → server) ────────────────────────────────────────\n\nexport type OutMessageFrame = { channel?: string; event: string; payload: unknown; type: 'message' };\nexport type OutSubscribeFrame = { channel: string; type: 'subscribe' };\nexport type OutUnsubscribeFrame = { channel: string; type: 'unsubscribe' };\nexport type OutJoinFrame = { room: string; type: 'join' };\nexport type OutLeaveFrame = { room: string; type: 'leave' };\nexport type OutPresenceFrame = { room: string; state: unknown; type: 'presence' };\nexport type OutPingFrame = { ts: number; type: 'ping' };\n\nexport type OutFrame =\n | OutJoinFrame\n | OutLeaveFrame\n | OutMessageFrame\n | OutPingFrame\n | OutPresenceFrame\n | OutSubscribeFrame\n | OutUnsubscribeFrame;\n\n// ─── Incoming frames (server → client) ────────────────────────────────────────\n\nexport type InMessageFrame = { channel?: string; event: string; payload: unknown; type: 'message' };\nexport type InSubscribedFrame = { channel: string; type: 'subscribed' };\nexport type InUnsubscribedFrame = { channel: string; type: 'unsubscribed' };\nexport type InJoinedFrame = { room: string; type: 'joined' };\nexport type InLeftFrame = { room: string; type: 'left' };\nexport type InPresenceStateFrame = { members: Record<string, unknown>; room: string; type: 'presence_state' };\nexport type InPresenceJoinFrame = { id: string; room: string; state: unknown; type: 'presence_join' };\nexport type InPresenceLeaveFrame = { id: string; room: string; type: 'presence_leave' };\nexport type InPongFrame = { ts: number; type: 'pong' };\nexport type InErrorFrame = { code: string; message: string; type: 'error' };\n\nexport type InFrame =\n | InErrorFrame\n | InJoinedFrame\n | InLeftFrame\n | InMessageFrame\n | InPongFrame\n | InPresenceJoinFrame\n | InPresenceLeaveFrame\n | InPresenceStateFrame\n | InSubscribedFrame\n | InUnsubscribedFrame;\n\ntype ParsedFrame = { type: string } & Record<string, unknown>;\nexport type DecodedInFrame = { frame: InFrame; kind: 'known' } | { kind: 'unknown'; type: string };\n\n// ─── Codec ────────────────────────────────────────────────────────────────────\n\n/**\n * Encode an outgoing frame to a JSON string.\n * @internal\n */\nexport function encode(frame: OutFrame): string {\n return JSON.stringify(frame);\n}\n\n/**\n * Parse a raw MessageEvent data string and ensure it has a string `type` field.\n * Throws `PulseProtocolError` if the data is not valid JSON or missing the `type` field.\n * @internal\n */\nexport function decode(raw: unknown): ParsedFrame {\n if (typeof raw !== 'string') {\n throw new PulseProtocolError('Expected string message data', raw);\n }\n\n let parsed: unknown;\n\n try {\n parsed = JSON.parse(raw);\n } catch (cause) {\n throw new PulseProtocolError('JSON parse error', raw);\n }\n\n if (\n parsed === null ||\n typeof parsed !== 'object' ||\n !('type' in parsed) ||\n typeof (parsed as { type: unknown }).type !== 'string'\n ) {\n throw new PulseProtocolError('Missing or invalid \"type\" field', parsed);\n }\n\n return parsed as ParsedFrame;\n}\n\nfunction isObjectRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction getStringProp(frame: unknown, key: string): string | null {\n if (!isObjectRecord(frame)) return null;\n\n const value = frame[key];\n\n return typeof value === 'string' ? value : null;\n}\n\nexport function isErrorFrame(frame: unknown): frame is InErrorFrame {\n return getStringProp(frame, 'code') !== null && getStringProp(frame, 'message') !== null;\n}\n\nexport function isJoinedFrame(frame: unknown): frame is InJoinedFrame {\n return getStringProp(frame, 'room') !== null;\n}\n\nexport function isLeftFrame(frame: unknown): frame is InLeftFrame {\n return getStringProp(frame, 'room') !== null;\n}\n\nexport function isMessageFrame(frame: unknown): frame is InMessageFrame {\n if (!isObjectRecord(frame) || getStringProp(frame, 'event') === null || !('payload' in frame)) return false;\n\n return frame.channel === undefined || typeof frame.channel === 'string';\n}\n\nexport function isPongFrame(frame: unknown): frame is InPongFrame {\n return isObjectRecord(frame) && typeof frame.ts === 'number';\n}\n\nexport function isPresenceJoinFrame(frame: unknown): frame is InPresenceJoinFrame {\n return (\n isObjectRecord(frame) &&\n getStringProp(frame, 'id') !== null &&\n getStringProp(frame, 'room') !== null &&\n 'state' in frame\n );\n}\n\nexport function isPresenceLeaveFrame(frame: unknown): frame is InPresenceLeaveFrame {\n return getStringProp(frame, 'id') !== null && getStringProp(frame, 'room') !== null;\n}\n\nexport function isPresenceStateFrame(frame: unknown): frame is InPresenceStateFrame {\n return isObjectRecord(frame) && getStringProp(frame, 'room') !== null && isObjectRecord(frame.members);\n}\n\nfunction isSubscribedFrame(frame: unknown): frame is InSubscribedFrame {\n return getStringProp(frame, 'channel') !== null;\n}\n\nfunction isUnsubscribedFrame(frame: unknown): frame is InUnsubscribedFrame {\n return getStringProp(frame, 'channel') !== null;\n}\n\n/**\n * Parse and validate known incoming frame shapes.\n * Unknown frame `type` values are passed through so callers can warn/drop them.\n * @internal\n */\nexport function decodeValidated(raw: unknown): DecodedInFrame {\n const frame = decode(raw) as ParsedFrame;\n\n switch (frame.type) {\n case 'error':\n if (isErrorFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'joined':\n if (isJoinedFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'left':\n if (isLeftFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'message':\n if (isMessageFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'pong':\n if (isPongFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'presence_join':\n if (isPresenceJoinFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'presence_leave':\n if (isPresenceLeaveFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'presence_state':\n if (isPresenceStateFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'subscribed':\n if (isSubscribedFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'unsubscribed':\n if (isUnsubscribedFrame(frame)) return { frame, kind: 'known' };\n\n break;\n default:\n return { kind: 'unknown', type: frame.type };\n }\n\n throw new PulseProtocolError(`Malformed \"${frame.type}\" frame`, frame);\n}\n"],"mappings":"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"}
@@ -78,4 +78,19 @@ export type InErrorFrame = {
78
78
  type: 'error';
79
79
  };
80
80
  export type InFrame = InErrorFrame | InJoinedFrame | InLeftFrame | InMessageFrame | InPongFrame | InPresenceJoinFrame | InPresenceLeaveFrame | InPresenceStateFrame | InSubscribedFrame | InUnsubscribedFrame;
81
+ export type DecodedInFrame = {
82
+ frame: InFrame;
83
+ kind: 'known';
84
+ } | {
85
+ kind: 'unknown';
86
+ type: string;
87
+ };
88
+ export declare function isErrorFrame(frame: unknown): frame is InErrorFrame;
89
+ export declare function isJoinedFrame(frame: unknown): frame is InJoinedFrame;
90
+ export declare function isLeftFrame(frame: unknown): frame is InLeftFrame;
91
+ export declare function isMessageFrame(frame: unknown): frame is InMessageFrame;
92
+ export declare function isPongFrame(frame: unknown): frame is InPongFrame;
93
+ export declare function isPresenceJoinFrame(frame: unknown): frame is InPresenceJoinFrame;
94
+ export declare function isPresenceLeaveFrame(frame: unknown): frame is InPresenceLeaveFrame;
95
+ export declare function isPresenceStateFrame(frame: unknown): frame is InPresenceStateFrame;
81
96
  //# sourceMappingURL=protocol.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,eAAe,GAAG;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AACrG,MAAM,MAAM,iBAAiB,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC;AACvE,MAAM,MAAM,mBAAmB,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,aAAa,CAAA;CAAE,CAAC;AAC3E,MAAM,MAAM,YAAY,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAC1D,MAAM,MAAM,aAAa,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAC5D,MAAM,MAAM,gBAAgB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC;AAClF,MAAM,MAAM,YAAY,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAExD,MAAM,MAAM,QAAQ,GAChB,YAAY,GACZ,aAAa,GACb,eAAe,GACf,YAAY,GACZ,gBAAgB,GAChB,iBAAiB,GACjB,mBAAmB,CAAC;AAIxB,MAAM,MAAM,cAAc,GAAG;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AACpG,MAAM,MAAM,iBAAiB,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,YAAY,CAAA;CAAE,CAAC;AACxE,MAAM,MAAM,mBAAmB,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,cAAc,CAAA;CAAE,CAAC;AAC5E,MAAM,MAAM,aAAa,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC;AAC7D,MAAM,MAAM,WAAW,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AACzD,MAAM,MAAM,oBAAoB,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,gBAAgB,CAAA;CAAE,CAAC;AAC9G,MAAM,MAAM,mBAAmB,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,eAAe,CAAA;CAAE,CAAC;AACtG,MAAM,MAAM,oBAAoB,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,gBAAgB,CAAA;CAAE,CAAC;AACxF,MAAM,MAAM,WAAW,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AACvD,MAAM,MAAM,YAAY,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAE5E,MAAM,MAAM,OAAO,GACf,YAAY,GACZ,aAAa,GACb,WAAW,GACX,cAAc,GACd,WAAW,GACX,mBAAmB,GACnB,oBAAoB,GACpB,oBAAoB,GACpB,iBAAiB,GACjB,mBAAmB,CAAC;AAGxB,MAAM,MAAM,cAAc,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAsDnG,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,YAAY,CAElE;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,aAAa,CAEpE;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,WAAW,CAEhE;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,cAAc,CAItE;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,WAAW,CAEhE;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,mBAAmB,CAOhF;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,oBAAoB,CAElF;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,oBAAoB,CAElF"}
package/dist/protocol.js CHANGED
@@ -14,7 +14,115 @@ function n(t) {
14
14
  if (typeof n != "object" || !n || !("type" in n) || typeof n.type != "string") throw new e("Missing or invalid \"type\" field", n);
15
15
  return n;
16
16
  }
17
+ function r(e) {
18
+ return typeof e == "object" && !!e && !Array.isArray(e);
19
+ }
20
+ function i(e, t) {
21
+ if (!r(e)) return null;
22
+ let n = e[t];
23
+ return typeof n == "string" ? n : null;
24
+ }
25
+ function a(e) {
26
+ return i(e, "code") !== null && i(e, "message") !== null;
27
+ }
28
+ function o(e) {
29
+ return i(e, "room") !== null;
30
+ }
31
+ function s(e) {
32
+ return i(e, "room") !== null;
33
+ }
34
+ function c(e) {
35
+ return !r(e) || i(e, "event") === null || !("payload" in e) ? !1 : e.channel === void 0 || typeof e.channel == "string";
36
+ }
37
+ function l(e) {
38
+ return r(e) && typeof e.ts == "number";
39
+ }
40
+ function u(e) {
41
+ return r(e) && i(e, "id") !== null && i(e, "room") !== null && "state" in e;
42
+ }
43
+ function d(e) {
44
+ return i(e, "id") !== null && i(e, "room") !== null;
45
+ }
46
+ function f(e) {
47
+ return r(e) && i(e, "room") !== null && r(e.members);
48
+ }
49
+ function p(e) {
50
+ return i(e, "channel") !== null;
51
+ }
52
+ function m(e) {
53
+ return i(e, "channel") !== null;
54
+ }
55
+ function h(t) {
56
+ let r = n(t);
57
+ switch (r.type) {
58
+ case "error":
59
+ if (a(r)) return {
60
+ frame: r,
61
+ kind: "known"
62
+ };
63
+ break;
64
+ case "joined":
65
+ if (o(r)) return {
66
+ frame: r,
67
+ kind: "known"
68
+ };
69
+ break;
70
+ case "left":
71
+ if (s(r)) return {
72
+ frame: r,
73
+ kind: "known"
74
+ };
75
+ break;
76
+ case "message":
77
+ if (c(r)) return {
78
+ frame: r,
79
+ kind: "known"
80
+ };
81
+ break;
82
+ case "pong":
83
+ if (l(r)) return {
84
+ frame: r,
85
+ kind: "known"
86
+ };
87
+ break;
88
+ case "presence_join":
89
+ if (u(r)) return {
90
+ frame: r,
91
+ kind: "known"
92
+ };
93
+ break;
94
+ case "presence_leave":
95
+ if (d(r)) return {
96
+ frame: r,
97
+ kind: "known"
98
+ };
99
+ break;
100
+ case "presence_state":
101
+ if (f(r)) return {
102
+ frame: r,
103
+ kind: "known"
104
+ };
105
+ break;
106
+ case "subscribed":
107
+ if (p(r)) return {
108
+ frame: r,
109
+ kind: "known"
110
+ };
111
+ break;
112
+ case "unsubscribed":
113
+ if (m(r)) return {
114
+ frame: r,
115
+ kind: "known"
116
+ };
117
+ break;
118
+ default: return {
119
+ kind: "unknown",
120
+ type: r.type
121
+ };
122
+ }
123
+ throw new e(`Malformed "${r.type}" frame`, r);
124
+ }
17
125
  //#endregion
18
- export { n as decode, t as encode };
126
+ export { n as decode, h as decodeValidated, t as encode, a as isErrorFrame, o as isJoinedFrame, s as isLeftFrame, c as isMessageFrame, l as isPongFrame, u as isPresenceJoinFrame, d as isPresenceLeaveFrame, f as isPresenceStateFrame };
19
127
 
20
128
  //# sourceMappingURL=protocol.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.js","names":[],"sources":["../src/protocol.ts"],"sourcesContent":["import { PulseProtocolError } from './errors';\n\n// ─── Outgoing frames (client → server) ────────────────────────────────────────\n\nexport type OutMessageFrame = { channel?: string; event: string; payload: unknown; type: 'message' };\nexport type OutSubscribeFrame = { channel: string; type: 'subscribe' };\nexport type OutUnsubscribeFrame = { channel: string; type: 'unsubscribe' };\nexport type OutJoinFrame = { room: string; type: 'join' };\nexport type OutLeaveFrame = { room: string; type: 'leave' };\nexport type OutPresenceFrame = { room: string; state: unknown; type: 'presence' };\nexport type OutPingFrame = { ts: number; type: 'ping' };\n\nexport type OutFrame =\n | OutJoinFrame\n | OutLeaveFrame\n | OutMessageFrame\n | OutPingFrame\n | OutPresenceFrame\n | OutSubscribeFrame\n | OutUnsubscribeFrame;\n\n// ─── Incoming frames (server → client) ────────────────────────────────────────\n\nexport type InMessageFrame = { channel?: string; event: string; payload: unknown; type: 'message' };\nexport type InSubscribedFrame = { channel: string; type: 'subscribed' };\nexport type InUnsubscribedFrame = { channel: string; type: 'unsubscribed' };\nexport type InJoinedFrame = { room: string; type: 'joined' };\nexport type InLeftFrame = { room: string; type: 'left' };\nexport type InPresenceStateFrame = { members: Record<string, unknown>; room: string; type: 'presence_state' };\nexport type InPresenceJoinFrame = { id: string; room: string; state: unknown; type: 'presence_join' };\nexport type InPresenceLeaveFrame = { id: string; room: string; type: 'presence_leave' };\nexport type InPongFrame = { ts: number; type: 'pong' };\nexport type InErrorFrame = { code: string; message: string; type: 'error' };\n\nexport type InFrame =\n | InErrorFrame\n | InJoinedFrame\n | InLeftFrame\n | InMessageFrame\n | InPongFrame\n | InPresenceJoinFrame\n | InPresenceLeaveFrame\n | InPresenceStateFrame\n | InSubscribedFrame\n | InUnsubscribedFrame;\n\n// ─── Codec ────────────────────────────────────────────────────────────────────\n\n/**\n * Encode an outgoing frame to a JSON string.\n * @internal\n */\nexport function encode(frame: OutFrame): string {\n return JSON.stringify(frame);\n}\n\n/**\n * Parse a raw MessageEvent data string into a typed InFrame.\n * Throws `PulseProtocolError` if the data is not valid JSON or missing the `type` field.\n * @internal\n */\nexport function decode(raw: unknown): InFrame {\n if (typeof raw !== 'string') {\n throw new PulseProtocolError('Expected string message data', raw);\n }\n\n let parsed: unknown;\n\n try {\n parsed = JSON.parse(raw);\n } catch (cause) {\n throw new PulseProtocolError('JSON parse error', raw);\n }\n\n if (\n parsed === null ||\n typeof parsed !== 'object' ||\n !('type' in parsed) ||\n typeof (parsed as { type: unknown }).type !== 'string'\n ) {\n throw new PulseProtocolError('Missing or invalid \"type\" field', parsed);\n }\n\n return parsed as InFrame;\n}\n"],"mappings":";;AAoDA,SAAgB,EAAO,GAAyB;CAC9C,OAAO,KAAK,UAAU,CAAK;AAC7B;AAOA,SAAgB,EAAO,GAAuB;CAC5C,IAAI,OAAO,KAAQ,UACjB,MAAM,IAAI,EAAmB,gCAAgC,CAAG;CAGlE,IAAI;CAEJ,IAAI;EACF,IAAS,KAAK,MAAM,CAAG;CACzB,QAAgB;EACd,MAAM,IAAI,EAAmB,oBAAoB,CAAG;CACtD;CAEA,IAEE,OAAO,KAAW,aADlB,KAEA,EAAE,UAAU,MACZ,OAAQ,EAA6B,QAAS,UAE9C,MAAM,IAAI,EAAmB,qCAAmC,CAAM;CAGxE,OAAO;AACT"}
1
+ {"version":3,"file":"protocol.js","names":[],"sources":["../src/protocol.ts"],"sourcesContent":["import { PulseProtocolError } from './errors';\n\n// ─── Outgoing frames (client → server) ────────────────────────────────────────\n\nexport type OutMessageFrame = { channel?: string; event: string; payload: unknown; type: 'message' };\nexport type OutSubscribeFrame = { channel: string; type: 'subscribe' };\nexport type OutUnsubscribeFrame = { channel: string; type: 'unsubscribe' };\nexport type OutJoinFrame = { room: string; type: 'join' };\nexport type OutLeaveFrame = { room: string; type: 'leave' };\nexport type OutPresenceFrame = { room: string; state: unknown; type: 'presence' };\nexport type OutPingFrame = { ts: number; type: 'ping' };\n\nexport type OutFrame =\n | OutJoinFrame\n | OutLeaveFrame\n | OutMessageFrame\n | OutPingFrame\n | OutPresenceFrame\n | OutSubscribeFrame\n | OutUnsubscribeFrame;\n\n// ─── Incoming frames (server → client) ────────────────────────────────────────\n\nexport type InMessageFrame = { channel?: string; event: string; payload: unknown; type: 'message' };\nexport type InSubscribedFrame = { channel: string; type: 'subscribed' };\nexport type InUnsubscribedFrame = { channel: string; type: 'unsubscribed' };\nexport type InJoinedFrame = { room: string; type: 'joined' };\nexport type InLeftFrame = { room: string; type: 'left' };\nexport type InPresenceStateFrame = { members: Record<string, unknown>; room: string; type: 'presence_state' };\nexport type InPresenceJoinFrame = { id: string; room: string; state: unknown; type: 'presence_join' };\nexport type InPresenceLeaveFrame = { id: string; room: string; type: 'presence_leave' };\nexport type InPongFrame = { ts: number; type: 'pong' };\nexport type InErrorFrame = { code: string; message: string; type: 'error' };\n\nexport type InFrame =\n | InErrorFrame\n | InJoinedFrame\n | InLeftFrame\n | InMessageFrame\n | InPongFrame\n | InPresenceJoinFrame\n | InPresenceLeaveFrame\n | InPresenceStateFrame\n | InSubscribedFrame\n | InUnsubscribedFrame;\n\ntype ParsedFrame = { type: string } & Record<string, unknown>;\nexport type DecodedInFrame = { frame: InFrame; kind: 'known' } | { kind: 'unknown'; type: string };\n\n// ─── Codec ────────────────────────────────────────────────────────────────────\n\n/**\n * Encode an outgoing frame to a JSON string.\n * @internal\n */\nexport function encode(frame: OutFrame): string {\n return JSON.stringify(frame);\n}\n\n/**\n * Parse a raw MessageEvent data string and ensure it has a string `type` field.\n * Throws `PulseProtocolError` if the data is not valid JSON or missing the `type` field.\n * @internal\n */\nexport function decode(raw: unknown): ParsedFrame {\n if (typeof raw !== 'string') {\n throw new PulseProtocolError('Expected string message data', raw);\n }\n\n let parsed: unknown;\n\n try {\n parsed = JSON.parse(raw);\n } catch (cause) {\n throw new PulseProtocolError('JSON parse error', raw);\n }\n\n if (\n parsed === null ||\n typeof parsed !== 'object' ||\n !('type' in parsed) ||\n typeof (parsed as { type: unknown }).type !== 'string'\n ) {\n throw new PulseProtocolError('Missing or invalid \"type\" field', parsed);\n }\n\n return parsed as ParsedFrame;\n}\n\nfunction isObjectRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction getStringProp(frame: unknown, key: string): string | null {\n if (!isObjectRecord(frame)) return null;\n\n const value = frame[key];\n\n return typeof value === 'string' ? value : null;\n}\n\nexport function isErrorFrame(frame: unknown): frame is InErrorFrame {\n return getStringProp(frame, 'code') !== null && getStringProp(frame, 'message') !== null;\n}\n\nexport function isJoinedFrame(frame: unknown): frame is InJoinedFrame {\n return getStringProp(frame, 'room') !== null;\n}\n\nexport function isLeftFrame(frame: unknown): frame is InLeftFrame {\n return getStringProp(frame, 'room') !== null;\n}\n\nexport function isMessageFrame(frame: unknown): frame is InMessageFrame {\n if (!isObjectRecord(frame) || getStringProp(frame, 'event') === null || !('payload' in frame)) return false;\n\n return frame.channel === undefined || typeof frame.channel === 'string';\n}\n\nexport function isPongFrame(frame: unknown): frame is InPongFrame {\n return isObjectRecord(frame) && typeof frame.ts === 'number';\n}\n\nexport function isPresenceJoinFrame(frame: unknown): frame is InPresenceJoinFrame {\n return (\n isObjectRecord(frame) &&\n getStringProp(frame, 'id') !== null &&\n getStringProp(frame, 'room') !== null &&\n 'state' in frame\n );\n}\n\nexport function isPresenceLeaveFrame(frame: unknown): frame is InPresenceLeaveFrame {\n return getStringProp(frame, 'id') !== null && getStringProp(frame, 'room') !== null;\n}\n\nexport function isPresenceStateFrame(frame: unknown): frame is InPresenceStateFrame {\n return isObjectRecord(frame) && getStringProp(frame, 'room') !== null && isObjectRecord(frame.members);\n}\n\nfunction isSubscribedFrame(frame: unknown): frame is InSubscribedFrame {\n return getStringProp(frame, 'channel') !== null;\n}\n\nfunction isUnsubscribedFrame(frame: unknown): frame is InUnsubscribedFrame {\n return getStringProp(frame, 'channel') !== null;\n}\n\n/**\n * Parse and validate known incoming frame shapes.\n * Unknown frame `type` values are passed through so callers can warn/drop them.\n * @internal\n */\nexport function decodeValidated(raw: unknown): DecodedInFrame {\n const frame = decode(raw) as ParsedFrame;\n\n switch (frame.type) {\n case 'error':\n if (isErrorFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'joined':\n if (isJoinedFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'left':\n if (isLeftFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'message':\n if (isMessageFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'pong':\n if (isPongFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'presence_join':\n if (isPresenceJoinFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'presence_leave':\n if (isPresenceLeaveFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'presence_state':\n if (isPresenceStateFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'subscribed':\n if (isSubscribedFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'unsubscribed':\n if (isUnsubscribedFrame(frame)) return { frame, kind: 'known' };\n\n break;\n default:\n return { kind: 'unknown', type: frame.type };\n }\n\n throw new PulseProtocolError(`Malformed \"${frame.type}\" frame`, frame);\n}\n"],"mappings":"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"}
package/dist/pulse.cjs CHANGED
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=class e extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}static is(t){return t instanceof e}},t=class extends e{url;constructor(e,t,n){super(e,n),this.url=t}},n=class extends e{event;constructor(e,t){super(`Timed out waiting for "${e}"`,t),this.event=e}},r=class extends e{constructor(e){super(`Aborted`,e)}},i=class extends e{constructor(e=`Pulse`,t){super(`${e} instance is disposed`,t)}},a=class extends e{raw;constructor(e,t,n){super(e,n),this.raw=t}},o=null,s=()=>o,c=class e extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}static is(t){return t instanceof e}},l=class extends c{},u=0,d=()=>++u,f={scheduling:{activeDirty:`a`,batchDepth:0,dirtyWithEffectSubsA:new Set,dirtyWithEffectSubsB:new Set,pendingSubscribers:new Set},scopeCleanups:null,tracking:null},p=null,m=()=>p===null?f:p.get(),h=()=>m().scheduling,g=()=>p!==null,_=()=>m().tracking,v=e=>{let t=_();if(t!==null){if(t.sourceObserver?.(e),t.kind===`computed`)t.depCollector.push({source:e,version:e.version});else if(t.kind===`effect`){let n=t.effect;e.addEffectSub(n),t.subscriptions.add(()=>e.removeEffectSub(n)),t.deps.set(e,e.version)}}},y=e=>e instanceof Error?e:Error(`Non-Error thrown: ${String(e)}`),b=e=>{let t=[];for(let n of e)try{n()}catch(e){t.push(y(e))}return t},x=(e,t)=>{let n=b(e);if(n.length===1)throw n[0];if(n.length>0)throw AggregateError(n,t)},S=!globalThis.__RIPPLE_PROD__;function C(e){S&&console.warn(`[@vielzeug/ripple] ${e}`)}var w=!1,T=0,E=[],D=e=>e.activeDirty===`a`?e.dirtyWithEffectSubsA:e.dirtyWithEffectSubsB,O=(e,t)=>{let n=++T;for(let n of t.effectSubs())e.pendingSubscribers.add(n);for(let e of t.computedSubs())E.push(e);try{for(;E.length>0;){let t=E.pop();if(t.lastPropEpoch_!==n&&(t.lastPropEpoch_=n,t.markDirty())){t.effectSubs().size>0&&D(e).add(t);for(let e of t.computedSubs())E.push(e)}}}finally{E.length=0}},k=e=>{for(;D(e).size>0;){let t=D(e);e.activeDirty=e.activeDirty===`a`?`b`:`a`,D(e).clear();for(let n of t)if(n.hasSubscribers()&&n.refreshIfDirty()){for(let t of n.effectSubs())e.pendingSubscribers.add(t);for(let t of n.computedSubs())t.markDirty()&&t.effectSubs().size>0&&D(e).add(t)}t.clear()}},A=e=>{let t=0;for(;e.pendingSubscribers.size>0||D(e).size>0;){if(++t>100)throw new l(`infinite flush loop (> 100 iterations)`);if(D(e).size>0&&k(e),e.pendingSubscribers.size===0)continue;let n=[...e.pendingSubscribers];e.pendingSubscribers.clear(),x(n,`subscriber errors`)}},j=globalThis.process?.versions!=null,M=e=>{if(!e.hasSubscribers())return;!w&&j&&!g()&&(w=!0,C(`Signal updated in a Node.js-like environment. The module-level flush queue is shared across concurrent requests — use per-request worker isolation or the @vielzeug/ripple/ssr sub-path for request-isolated scheduling.`));let t=h();O(t,e),t.batchDepth===0&&A(t)},N=class{fn_;disposed_=!1;constructor(e){this.fn_=e}get disposed(){return this.disposed_}dispose(){if(this.disposed_)return;this.disposed_=!0;let e=this.fn_;this.fn_=null,e()}[Symbol.dispose](){this.dispose()}},P=Symbol(`ripple.is-signal`),F=new FinalizationRegistry(({key:e,map:t})=>{t.delete(e)}),I=class{version=0;name;[P]=!0;computedSubs_=new Map;effectSubs_=new Set;constructor(e){this.name=e}addComputedSub(e){let t=new WeakRef(e);this.computedSubs_.set(e,t),F.register(e,{key:e,map:this.computedSubs_},t)}removeComputedSub(e){let t=this.computedSubs_.get(e);t!==void 0&&(this.computedSubs_.delete(e),F.unregister(t))}addEffectSub(e){this.effectSubs_.add(e)}removeEffectSub(e){this.effectSubs_.delete(e)}clearSubscribers(){for(let e of this.computedSubs_.values())F.unregister(e);this.computedSubs_.clear(),this.effectSubs_.clear()}hasSubscribers(){if(this.effectSubs_.size>0)return!0;for(let e of this.computedSubs_.values())if(e.deref()!==void 0)return!0;return!1}*computedSubs(){for(let[e,t]of this.computedSubs_){let n=t.deref();n===void 0?(this.computedSubs_.delete(e),F.unregister(t)):yield n}}effectSubs(){return this.effectSubs_}},L=class extends I{value_;equals_;disposed_;constructor(e,t,n){super(n),this.value_=e,this.equals_=t??Object.is,this.disposed_=!1}get value(){return this.disposed_||v(this),this.value_}set value(e){if(this.disposed_||this.equals_(this.value_,e))return;let t=this.value_;this.value_=e,this.version=d(),s()?.write?.({name:this.name,newValue:e,oldValue:t}),M(this)}peek(){return this.value_}subscribe=e=>{if(this.disposed_){let e=new N(()=>{});return e.dispose(),e}return this.addEffectSub(e),new N(()=>{this.removeEffectSub(e)})};get disposed(){return this.disposed_}dispose(){this.disposed_||(this.disposed_=!0,this.clearSubscribers(),s()?.dispose?.({kind:`signal`,name:this.name}))}[Symbol.dispose](){this.dispose()}},R=(e,t)=>new L(e,t?.equals,t?.name),z=!globalThis.__PULSE_PROD__;function B(e){z&&console.warn(`[@vielzeug/pulse] ${e}`)}function V(e,...t){return t.length===0?e:t.reduce(H,e)}function H(e,t){if(e.aborted)return e;if(t.aborted)return t;let n=new AbortController,r=()=>n.abort(e.reason),i=()=>n.abort(t.reason);return e.addEventListener(`abort`,r,{once:!0}),t.addEventListener(`abort`,i,{once:!0}),n.signal.addEventListener(`abort`,()=>{e.removeEventListener(`abort`,r),t.removeEventListener(`abort`,i)},{once:!0}),n.signal}function U(e){let t=new AbortController;return e.aborted?t.abort(e.reason):e.addEventListener(`abort`,()=>t.abort(e.reason),{once:!0}),t}function W(e,t){return new Promise(n=>{if(t?.aborted){n();return}let r=setTimeout(n,e);t?.addEventListener(`abort`,()=>{clearTimeout(r),n()},{once:!0})})}function G(e,t=3e4){let n=Math.min(1e3*2**e,t);return Math.random()*n}function K(e,t,i,a){return new Promise((o,s)=>{let c=[t],l;if(i?.signal&&c.push(i.signal),i?.timeout!==void 0){let t=new AbortController;l=setTimeout(()=>{t.abort(new n(e))},i.timeout),c.push(t.signal)}let u=c.length===1?c[0]:V(c[0],...c.slice(1));if(u.aborted){clearTimeout(l),s(u.reason instanceof n?u.reason:new r);return}let d=()=>{},f=()=>{clearTimeout(l),d(),s(u.reason instanceof n?u.reason:new r)};u.addEventListener(`abort`,f,{once:!0}),d=a(e,e=>{clearTimeout(l),u.removeEventListener(`abort`,f),o(e)})})}function q(e,t,n,r,i){let a=U(r),o=new Set,s=a.signal.aborted;return{get disposalSignal(){return a.signal},dispose(){if(!s){s=!0,a.abort();for(let e of o)e();o.clear(),i?.()}},get disposed(){return s},get name(){return e},on(t,r){if(s)return B(`on('${String(t)}') called on a disposed channel '${e}' — listener ignored`),()=>{};let i=n(e,t,r);return o.add(i),()=>{i(),o.delete(i)}},once(t,r){if(s)return B(`once('${String(t)}') called on a disposed channel '${e}' — listener ignored`),()=>{};let i=()=>{};return i=n(e,t,e=>{o.delete(i),i(),r(e)}),o.add(i),()=>{o.delete(i),i()}},send(n,r){if(s){B(`send('${String(n)}') called on a disposed channel '${e}' — message dropped`);return}t(e,n,r)},[Symbol.dispose](){this.dispose()},wait(t,r){return K(t,a.signal,r,(t,r)=>n(e,t,r))}}}function J(e){return JSON.stringify(e)}function Y(e){if(typeof e!=`string`)throw new a(`Expected string message data`,e);let t;try{t=JSON.parse(e)}catch{throw new a(`JSON parse error`,e)}if(typeof t!=`object`||!t||!(`type`in t)||typeof t.type!=`string`)throw new a(`Missing or invalid "type" field`,t);return t}var X=3e4,Z=5e3;function Q(e,t,n){if(!e)return{onPong(){},start(){},stop(){}};let r=e===!0?{}:e,i=r.interval??X,a=r.timeout??Z,o,s,c=!1;function l(){o=setTimeout(()=>{t(J({ts:Date.now(),type:`ping`})),s=setTimeout(()=>{B(`Heartbeat timed out after ${a}ms — treating connection as dead`),n()},a)},i)}return{onPong(){clearTimeout(s),s=void 0,c&&l()},start(){c=!0,clearTimeout(o),clearTimeout(s),s=void 0,l()},stop(){c=!1,clearTimeout(o),clearTimeout(s),o=void 0,s=void 0}}}function $(e,t,n,r){let i=U(r),a=i.signal.aborted,o=R(new Map),s=new Set,c=new Set,l=[n.onState(t=>{if(t.room!==e)return;let n=new Map;for(let[e,r]of Object.entries(t.members))n.set(e,r);o.value=n}),n.onJoin(t=>{if(t.room!==e)return;let n=new Map(o.value);n.set(t.id,t.state),o.value=n;for(let e of s)e(t.id,t.state)}),n.onLeave(t=>{if(t.room!==e)return;let n=new Map(o.value);n.delete(t.id),o.value=n;for(let e of c)e(t.id)})];return{get disposalSignal(){return i.signal},dispose(){if(!a){a=!0,t(J({room:e,type:`leave`})),i.abort();for(let e of l)e();l.length=0,s.clear(),c.clear(),o.dispose()}},get disposed(){return a},onJoin(t){return a?(B(`onJoin() called on a disposed presence channel '${e}' — listener ignored`),()=>{}):(s.add(t),()=>s.delete(t))},onLeave(t){return a?(B(`onLeave() called on a disposed presence channel '${e}' — listener ignored`),()=>{}):(c.add(t),()=>c.delete(t))},get room(){return e},get state(){return o},[Symbol.dispose](){this.dispose()},update(n){a||t(J({room:e,state:n,type:`presence`}))}}}var ee=5;function te(e){if(!e)return{attempt:async()=>!1,reset(){}};let t=e===!0?{}:e,n=t.maxAttempts??ee,r=typeof t.delay==`function`?t.delay:typeof t.delay==`number`?()=>t.delay:G,i=0;return{async attempt(e,t,a){if(i>=n)return!1;let o=r(i);if(i++,a?.(i),await W(o,t),t.aborted)return!1;try{return await e(),i=0,!0}catch{return!1}},reset(){i=0}}}var ne=class{map=new Map;add(e,t,n){let r=this.map.get(e);r||(r=new Map,this.map.set(e,r));let i=r.get(t);return i||(i=new Set,r.set(t,i)),i.add(n),()=>{i.delete(n),i.size===0&&r.delete(t),r.size===0&&this.map.delete(e)}}dispatch(e,t,n){this.map.get(e)?.get(t)?.forEach(e=>e(n))}clear(){this.map.clear()}};function re(e,a={}){let o=new AbortController,s=R(`connecting`),c=R(new Set),l=new ne,u=a.middleware??[],d=new Map,f=new Map,p=null,m=!1,h=!1,g=[],_=new Map,v=a.buffer,y=v?[]:null,b=typeof v==`object`&&v?v.maxSize??50:50,x=te(a.reconnect),S=Q(a.heartbeat,e=>{p?.readyState===WebSocket.OPEN&&p.send(e)},()=>{B(`Heartbeat dead — closing socket for reconnect`),p?.close(4e3,`heartbeat timeout`)});function C(e){p?.readyState===WebSocket.OPEN?p.send(e):y===null?B(`send() called while connection is ${s.value} — message dropped`):(y.length>=b&&y.shift(),y.push(e))}function w(e,t,n){let r=0;function i(){if(r>=u.length){n();return}let a=u[r++];a(e,t,i)}i()}function T(){m||(p=new WebSocket(e,a.protocols),p.onopen=()=>{h=!1,s.value=`open`,x.reset(),S.start(),a.onOpen?.();let e=g.splice(0);for(let t of e)t.resolve();if(y&&y.length>0){let e=y.splice(0);for(let t of e)p.send(t)}for(let e of _.keys())p.send(J({channel:e,type:`subscribe`}))},p.onmessage=e=>{a.onMessage?.(e);let t;try{t=Y(e.data)}catch(e){B(`Protocol error: ${String(e)}`);return}try{D(t)}catch(e){B(`Frame handling error: ${String(e)}`)}},p.onerror=()=>{a.onError?a.onError(new t(`WebSocket error`,e)):B(`WebSocket error — pass onError to observe it (see onClose for recovery logic)`);let n=g.splice(0);for(let r of n)r.reject(new t(`WebSocket error`,e))},p.onclose=n=>{S.stop(),a.onClose?.(n.code,n.reason);let r=g.splice(0);for(let n of r)n.reject(new t(`Connection closed before open`,e));if(h||m){s.value=`closed`;return}E()})}async function E(){s.value=`reconnecting`,!await x.attempt(()=>new Promise((e,t)=>{g.push({reject:t,resolve:e}),T()}),o.signal,a.onReconnect)&&!m&&(B(`Reconnect budget exhausted — connection permanently closed`),s.value=`closed`)}function D(e){switch(e.type){case`error`:B(`Server error [${e.code}]: ${e.message}`);break;case`joined`:{let t=new Set(c.value);t.add(e.room),c.value=t;let n=d.get(e.room);if(n){d.delete(e.room);for(let e of n)e()}break}case`left`:{let t=new Set(c.value);t.delete(e.room),c.value=t;let n=f.get(e.room);if(n){f.delete(e.room);for(let e of n)e()}break}case`message`:l.dispatch(e.channel??null,e.event,e.payload);break;case`pong`:S.onPong();break;case`presence_join`:l.dispatch(null,e.type,e);break;case`presence_leave`:l.dispatch(null,e.type,e);break;case`presence_state`:l.dispatch(null,e.type,e);break;case`subscribed`:case`unsubscribed`:break;default:B(`Received frame with an unrecognized type: ${String(e.type)}`);break}}function O(e,t,a,s){return m?Promise.reject(new i):new Promise((i,c)=>{let l=[];s?.signal&&l.push(s.signal);let u;if(s?.timeout!==void 0){let e=new AbortController;u=setTimeout(()=>e.abort(new n(a)),s.timeout),l.push(e.signal)}let d=l.length===0?o.signal:V(o.signal,...l);if(d.aborted){clearTimeout(u),c(d.reason instanceof n?d.reason:new r);return}let f=()=>{},m=()=>{clearTimeout(u),d.removeEventListener(`abort`,f),i()};f=()=>{clearTimeout(u);let i=e.get(t);i?.delete(m),i?.size===0&&e.delete(t),c(d.reason instanceof n?d.reason:new r)};let h=e.get(t);h||(h=new Set,e.set(t,h)),h.add(m),d.addEventListener(`abort`,f,{once:!0});let g=()=>C(J({room:t,type:a}));p?.readyState===WebSocket.OPEN?g():k.connect().then(g).catch(n=>{clearTimeout(u),d.removeEventListener(`abort`,f);let r=e.get(t);r?.delete(m),r?.size===0&&e.delete(t),c(n)})})}let k={channel(e){let t=_.get(e);if(t)return t;m&&B(`channel('${e}') called on a disposed Pulse — returning an already-disposed channel`);let n=q(e,(e,t,n)=>{w(t,n,()=>{C(J({channel:e,event:t,payload:n,type:`message`}))})},(e,t,n)=>l.add(e,t,n),o.signal,()=>{_.delete(e),C(J({channel:e,type:`unsubscribe`}))});return m||_.set(e,n),p?.readyState===WebSocket.OPEN&&p.send(J({channel:e,type:`subscribe`})),n},connect(){return m?Promise.reject(new i):p?.readyState===WebSocket.OPEN?Promise.resolve():new Promise((e,t)=>{g.push({reject:t,resolve:e}),(!p||p.readyState>WebSocket.OPEN)&&T()})},disconnect(e=1e3,t=``){h=!0,p?.close(e,t)},get disposalSignal(){return o.signal},dispose(){if(m)return;m=!0,h=!0,S.stop(),p?.close(1e3,`disposed`),p=null;let e=g.splice(0);for(let t of e)t.reject(new i);_.clear(),d.clear(),f.clear(),s.dispose(),c.dispose(),l.clear(),o.abort()},get disposed(){return m},join(e,t){return O(d,e,`join`,t)},leave(e,t){return O(f,e,`leave`,t)},on(e,t){return m?(B(`on('${String(e)}') called on a disposed Pulse — listener ignored`),()=>{}):l.add(null,e,t)},once(e,t){if(m)return B(`once('${String(e)}') called on a disposed Pulse — listener ignored`),()=>{};let n=()=>{};return n=l.add(null,e,e=>{n(),t(e)}),n},presence(e){let t=$(e,C,{onJoin:e=>l.add(null,`presence_join`,t=>e(t)),onLeave:e=>l.add(null,`presence_leave`,t=>e(t)),onState:e=>l.add(null,`presence_state`,t=>e(t))},o.signal);return m?B(`presence('${e}') called on a disposed Pulse — returning an already-disposed presence channel`):k.join(e).catch(t=>{B(`presence() join failed for room '${e}': ${String(t)}`)}),t},get rooms(){return c},send(e,t){if(m){B(`send('${String(e)}') called on a disposed Pulse — message dropped`);return}w(e,t,()=>{C(J({event:e,payload:t,type:`message`}))})},get status(){return s},[Symbol.dispose](){this.dispose()},wait(e,t){return K(e,o.signal,t,(e,t)=>l.add(null,e,t))}};return a.lazy||T(),k}exports.PulseAbortError=r,exports.PulseConnectionError=t,exports.PulseDisposedError=i,exports.PulseError=e,exports.PulseProtocolError=a,exports.PulseTimeoutError=n,exports.createPulse=re;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@vielzeug/ripple");var t=class e extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}static is(t){return t instanceof e}},n=class extends t{url;constructor(e,t,n){super(e,n),this.url=t}},r=class extends t{event;constructor(e,t){super(`Timed out waiting for "${e}"`,t),this.event=e}},i=class extends t{constructor(e){super(`Aborted`,e)}},a=class extends t{constructor(e=`Pulse`,t){super(`${e} instance is disposed`,t)}},o=class extends t{raw;constructor(e,t,n){super(e,n),this.raw=t}},s=!globalThis.__PULSE_PROD__;function c(e){s&&console.warn(`[@vielzeug/pulse] ${e}`)}function l(e,...t){return t.length===0?e:t.reduce(u,e)}function u(e,t){if(e.aborted)return e;if(t.aborted)return t;let n=new AbortController,r=()=>n.abort(e.reason),i=()=>n.abort(t.reason);return e.addEventListener(`abort`,r,{once:!0}),t.addEventListener(`abort`,i,{once:!0}),n.signal.addEventListener(`abort`,()=>{e.removeEventListener(`abort`,r),t.removeEventListener(`abort`,i)},{once:!0}),n.signal}function d(e){let t=new AbortController;return e.aborted?t.abort(e.reason):e.addEventListener(`abort`,()=>t.abort(e.reason),{once:!0}),t}function f(e,t){return new Promise(n=>{if(t?.aborted){n();return}let r=setTimeout(n,e);t?.addEventListener(`abort`,()=>{clearTimeout(r),n()},{once:!0})})}function p(e,t=3e4){let n=Math.min(1e3*2**e,t);return Math.random()*n}function m(e,t,n,a){return new Promise((o,s)=>{let c=[t],u;if(n?.signal&&c.push(n.signal),n?.timeout!==void 0){let t=new AbortController;u=setTimeout(()=>{t.abort(new r(e))},n.timeout),c.push(t.signal)}let d=c.length===1?c[0]:l(c[0],...c.slice(1));if(d.aborted){clearTimeout(u),s(d.reason instanceof r?d.reason:new i);return}let f=()=>{},p=()=>{clearTimeout(u),f(),s(d.reason instanceof r?d.reason:new i)};d.addEventListener(`abort`,p,{once:!0}),f=a(e,e=>{clearTimeout(u),d.removeEventListener(`abort`,p),o(e)})})}function h(e,t,n,r,i){let a=d(r),o=new Set,s=a.signal.aborted;return a.signal.addEventListener(`abort`,()=>{s=!0},{once:!0}),{get disposalSignal(){return a.signal},dispose(){if(!s){s=!0,a.abort();for(let e of o)e();o.clear(),i?.()}},get disposed(){return s},get name(){return e},on(t,r){if(s)return c(`on('${String(t)}') called on a disposed channel '${e}' — listener ignored`),()=>{};let i=n(e,t,r);return o.add(i),()=>{i(),o.delete(i)}},once(t,r){if(s)return c(`once('${String(t)}') called on a disposed channel '${e}' — listener ignored`),()=>{};let i=()=>{};return i=n(e,t,e=>{o.delete(i),i(),r(e)}),o.add(i),()=>{o.delete(i),i()}},send(n,r){if(s){c(`send('${String(n)}') called on a disposed channel '${e}' — message dropped`);return}t(e,n,r)},[Symbol.dispose](){this.dispose()},wait(t,r){return m(t,a.signal,r,(t,r)=>n(e,t,r))}}}function g(e){return JSON.stringify(e)}function _(e){if(typeof e!=`string`)throw new o(`Expected string message data`,e);let t;try{t=JSON.parse(e)}catch{throw new o(`JSON parse error`,e)}if(typeof t!=`object`||!t||!(`type`in t)||typeof t.type!=`string`)throw new o(`Missing or invalid "type" field`,t);return t}function v(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function y(e,t){if(!v(e))return null;let n=e[t];return typeof n==`string`?n:null}function b(e){return y(e,`code`)!==null&&y(e,`message`)!==null}function x(e){return y(e,`room`)!==null}function S(e){return y(e,`room`)!==null}function C(e){return!v(e)||y(e,`event`)===null||!(`payload`in e)?!1:e.channel===void 0||typeof e.channel==`string`}function w(e){return v(e)&&typeof e.ts==`number`}function T(e){return v(e)&&y(e,`id`)!==null&&y(e,`room`)!==null&&`state`in e}function E(e){return y(e,`id`)!==null&&y(e,`room`)!==null}function D(e){return v(e)&&y(e,`room`)!==null&&v(e.members)}function O(e){return y(e,`channel`)!==null}function k(e){return y(e,`channel`)!==null}function A(e){let t=_(e);switch(t.type){case`error`:if(b(t))return{frame:t,kind:`known`};break;case`joined`:if(x(t))return{frame:t,kind:`known`};break;case`left`:if(S(t))return{frame:t,kind:`known`};break;case`message`:if(C(t))return{frame:t,kind:`known`};break;case`pong`:if(w(t))return{frame:t,kind:`known`};break;case`presence_join`:if(T(t))return{frame:t,kind:`known`};break;case`presence_leave`:if(E(t))return{frame:t,kind:`known`};break;case`presence_state`:if(D(t))return{frame:t,kind:`known`};break;case`subscribed`:if(O(t))return{frame:t,kind:`known`};break;case`unsubscribed`:if(k(t))return{frame:t,kind:`known`};break;default:return{kind:`unknown`,type:t.type}}throw new o(`Malformed "${t.type}" frame`,t)}var j=3e4,M=5e3;function N(e,t,n){if(!e)return{onPong(){},start(){},stop(){}};let r=e===!0?{}:e,i=r.interval??j,a=r.timeout??M,o,s,l=!1;function u(){o=setTimeout(()=>{t(g({ts:Date.now(),type:`ping`})),s=setTimeout(()=>{c(`Heartbeat timed out after ${a}ms — treating connection as dead`),n()},a)},i)}return{onPong(){clearTimeout(s),s=void 0,l&&u()},start(){l=!0,clearTimeout(o),clearTimeout(s),s=void 0,u()},stop(){l=!1,clearTimeout(o),clearTimeout(s),o=void 0,s=void 0}}}function P(t,n,r,i,a){let o=d(i),s=o.signal.aborted;o.signal.addEventListener(`abort`,()=>{s=!0},{once:!0});let l=(0,e.signal)(new Map),u=new Set,f=new Set,p=[r.onState(e=>{if(e.room!==t)return;let n=new Map;for(let[t,r]of Object.entries(e.members))n.set(t,r);l.value=n}),r.onJoin(e=>{if(e.room!==t)return;let n=new Map(l.value);n.set(e.id,e.state),l.value=n;for(let t of u)t(e.id,e.state)}),r.onLeave(e=>{if(e.room!==t)return;let n=new Map(l.value);n.delete(e.id),l.value=n;for(let t of f)t(e.id)})];return{get disposalSignal(){return o.signal},dispose(){if(!s){s=!0,n(g({room:t,type:`leave`})),o.abort();for(let e of p)e();p.length=0,u.clear(),f.clear(),l.dispose(),a?.()}},get disposed(){return s},onJoin(e){return s?(c(`onJoin() called on a disposed presence channel '${t}' — listener ignored`),()=>{}):(u.add(e),()=>u.delete(e))},onLeave(e){return s?(c(`onLeave() called on a disposed presence channel '${t}' — listener ignored`),()=>{}):(f.add(e),()=>f.delete(e))},get room(){return t},get state(){return l},[Symbol.dispose](){this.dispose()},update(e){s||n(g({room:t,state:e,type:`presence`}))}}}var F=5;function I(e){if(!e)return{attempt:async()=>({ok:!1}),reset(){}};let t=e===!0?{}:e,n=t.maxAttempts??F,r=typeof t.delay==`function`?t.delay:typeof t.delay==`number`?()=>t.delay:p,i=0;return{async attempt(e,t,a){if(i>=n)return{ok:!1};let o=r(i);if(i++,a?.(i),await f(o,t),t.aborted)return{ok:!1};try{return await e(),i=0,{ok:!0}}catch(e){return{error:e,ok:!1}}},reset(){i=0}}}var L=class{map=new Map;add(e,t,n){let r=this.map.get(e);r||(r=new Map,this.map.set(e,r));let i=r.get(t);return i||(i=new Set,r.set(t,i)),i.add(n),()=>{i.delete(n),i.size===0&&r.delete(t),r.size===0&&this.map.delete(e)}}dispatch(e,t,n){this.map.get(e)?.get(t)?.forEach(e=>e(n))}clear(){this.map.clear()}};function R(t,o={}){let s=new AbortController,u=(0,e.signal)(`connecting`),d=(0,e.signal)(new Set),f=new L,p=o.middleware??[],_=new Map,v=new Map,y=null,b=!1,x=!1,S=[],C=new Map,w=new Map,T=o.buffer,E=T?[]:null,D=typeof T==`object`&&T?T.maxSize??50:50,O=I(o.reconnect),k=N(o.heartbeat,e=>{y?.readyState===WebSocket.OPEN&&y.send(e)},()=>{c(`Heartbeat dead — closing socket for reconnect`),y?.close(4e3,`heartbeat timeout`)});function j(e){y?.readyState===WebSocket.OPEN?y.send(e):E===null?c(`send() called while connection is ${u.value} — message dropped`):(E.length>=D&&E.shift(),E.push(e))}function M(e,t,n){let r=0;function i(){if(r>=p.length){n();return}let a=p[r++];a(e,t,i)}i()}function F(){b||(y=new WebSocket(t,o.protocols),y.onopen=()=>{x=!1,u.value=`open`,O.reset(),k.start(),o.onOpen?.();let e=S.splice(0);for(let t of e)t.resolve();if(E&&E.length>0){let e=E.splice(0);for(let t of e)y.send(t)}for(let e of C.keys())y.send(g({channel:e,type:`subscribe`}))},y.onmessage=e=>{o.onMessage?.(e);let t;try{t=A(e.data)}catch(e){c(`Protocol error: ${String(e)}`);return}try{z(t)}catch(e){c(`Frame handling error: ${String(e)}`)}},y.onerror=()=>{o.onError?o.onError(new n(`WebSocket error`,t)):c(`WebSocket error — pass onError to observe it (see onClose for recovery logic)`);let e=S.splice(0);for(let r of e)r.reject(new n(`WebSocket error`,t))},y.onclose=e=>{k.stop(),o.onClose?.(e.code,e.reason);let r=S.splice(0);for(let e of r)e.reject(new n(`Connection closed before open`,t));if(x||b){u.value=`closed`;return}R()})}async function R(){u.value=`reconnecting`;let e=await O.attempt(()=>new Promise((e,t)=>{S.push({reject:t,resolve:e}),F()}),s.signal,o.onReconnect);!e.ok&&!b&&(e.error&&c(`Reconnect attempt failed: ${String(e.error)}`),c(`Reconnect budget exhausted — connection permanently closed`),u.value=`closed`)}function z(e){if(e.kind===`unknown`){c(`Received frame with an unrecognized type: ${e.type}`);return}let t=e.frame;switch(t.type){case`error`:c(`Server error [${t.code}]: ${t.message}`);break;case`joined`:{let e=new Set(d.value);e.add(t.room),d.value=e;let n=_.get(t.room);if(n){_.delete(t.room);for(let e of n)e()}break}case`left`:{let e=new Set(d.value);e.delete(t.room),d.value=e;let n=v.get(t.room);if(n){v.delete(t.room);for(let e of n)e()}break}case`message`:f.dispatch(t.channel??null,t.event,t.payload);break;case`pong`:k.onPong();break;case`presence_join`:f.dispatch(null,t.type,t);break;case`presence_leave`:f.dispatch(null,t.type,t);break;case`presence_state`:f.dispatch(null,t.type,t);break;case`subscribed`:case`unsubscribed`:break}}function B(e,t,n,o){return b?Promise.reject(new a):new Promise((a,c)=>{let u=[];o?.signal&&u.push(o.signal);let d;if(o?.timeout!==void 0){let e=new AbortController;d=setTimeout(()=>e.abort(new r(n)),o.timeout),u.push(e.signal)}let f=u.length===0?s.signal:l(s.signal,...u);if(f.aborted){clearTimeout(d),c(f.reason instanceof r?f.reason:new i);return}let p=()=>{},m=()=>{clearTimeout(d),f.removeEventListener(`abort`,p),a()};p=()=>{clearTimeout(d);let n=e.get(t);n?.delete(m),n?.size===0&&e.delete(t),c(f.reason instanceof r?f.reason:new i)};let h=e.get(t),_=!h;h||(h=new Set,e.set(t,h)),h.add(m),f.addEventListener(`abort`,p,{once:!0});let v=()=>j(g({room:t,type:n}));_&&(y?.readyState===WebSocket.OPEN?v():V.connect().then(v).catch(n=>{clearTimeout(d),f.removeEventListener(`abort`,p);let r=e.get(t);r?.delete(m),r?.size===0&&e.delete(t),c(n)}))})}let V={channel(e){let t=C.get(e);if(t)return t;b&&c(`channel('${e}') called on a disposed Pulse — returning an already-disposed channel`);let n=h(e,(e,t,n)=>{M(t,n,()=>{j(g({channel:e,event:t,payload:n,type:`message`}))})},(e,t,n)=>f.add(e,t,n),s.signal,()=>{C.delete(e),j(g({channel:e,type:`unsubscribe`}))});return b||C.set(e,n),y?.readyState===WebSocket.OPEN&&y.send(g({channel:e,type:`subscribe`})),n},connect(){return b?Promise.reject(new a):y?.readyState===WebSocket.OPEN?Promise.resolve():new Promise((e,t)=>{S.push({reject:t,resolve:e}),(!y||y.readyState>WebSocket.OPEN)&&F()})},disconnect(e=1e3,t=``){x=!0,y?.close(e,t)},get disposalSignal(){return s.signal},dispose(){if(b)return;b=!0,x=!0,k.stop(),y?.close(1e3,`disposed`),y=null;let e=S.splice(0);for(let t of e)t.reject(new a);C.clear(),w.clear(),_.clear(),v.clear(),u.dispose(),d.dispose(),f.clear(),s.abort()},get disposed(){return b},join(e,t){return B(_,e,`join`,t)},leave(e,t){return B(v,e,`leave`,t)},on(e,t){return b?(c(`on('${String(e)}') called on a disposed Pulse — listener ignored`),()=>{}):f.add(null,e,t)},once(e,t){if(b)return c(`once('${String(e)}') called on a disposed Pulse — listener ignored`),()=>{};let n=()=>{};return n=f.add(null,e,e=>{n(),t(e)}),n},presence(e){let t=w.get(e);if(t)return t;let n=P(e,j,{onJoin:e=>f.add(null,`presence_join`,t=>e(t)),onLeave:e=>f.add(null,`presence_leave`,t=>e(t)),onState:e=>f.add(null,`presence_state`,t=>e(t))},s.signal,()=>{w.delete(e)});return b||w.set(e,n),b?c(`presence('${e}') called on a disposed Pulse — returning an already-disposed presence channel`):V.join(e).catch(t=>{t instanceof i||t instanceof a||c(`presence() join failed for room '${e}': ${String(t)}`)}),n},get rooms(){return d},send(e,t){if(b){c(`send('${String(e)}') called on a disposed Pulse — message dropped`);return}M(e,t,()=>{j(g({event:e,payload:t,type:`message`}))})},get status(){return u},[Symbol.dispose](){this.dispose()},wait(e,t){return m(e,s.signal,t,(e,t)=>f.add(null,e,t))}};return o.lazy||F(),V}exports.PulseAbortError=i,exports.PulseConnectionError=n,exports.PulseDisposedError=a,exports.PulseError=t,exports.PulseProtocolError=o,exports.PulseTimeoutError=r,exports.createPulse=R;
2
2
  //# sourceMappingURL=pulse.cjs.map