@vielzeug/pulse 2.1.2 → 2.2.1

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
@@ -1,6 +1,6 @@
1
1
  # @vielzeug/pulse
2
2
 
3
- Typed WebSocket sessions with explicit connection ownership, scoped channels, reactive presence, reconnect restoration, and heartbeat support.
3
+ Typed WebSocket sessions with explicit connection ownership, scoped channels, ref-counted rooms with reactive presence, reconnect restoration, and heartbeat support.
4
4
 
5
5
  ## Install
6
6
 
@@ -13,28 +13,33 @@ pnpm add @vielzeug/pulse @vielzeug/ripple
13
13
  ```ts
14
14
  import { createPulse } from '@vielzeug/pulse';
15
15
 
16
- type ServerEvents = { 'chat:message': { text: string } };
17
- type ClientEvents = { 'chat:send': { text: string } };
18
- type Channels = {
19
- chat: {
20
- client: { send: { text: string } };
21
- server: { message: { text: string } };
16
+ type Schema = {
17
+ server: { 'chat:message': { text: string } };
18
+ client: { 'chat:send': { text: string } };
19
+ channels: {
20
+ chat: {
21
+ client: { send: { text: string } };
22
+ server: { message: { text: string } };
23
+ };
24
+ };
25
+ rooms: {
26
+ lobby: { presence: { name: string } };
22
27
  };
23
28
  };
24
- type Presence = { lobby: { name: string } };
25
29
 
26
- const pulse = createPulse<ServerEvents, ClientEvents, Channels, Presence>('wss://api.example.com/ws', {
30
+ const pulse = createPulse<Schema>('wss://api.example.com/ws', {
27
31
  reconnect: true,
28
32
  onError: (error) => console.error(error),
29
33
  });
30
34
 
31
35
  const chat = pulse.channel('chat');
32
- const lobby = pulse.presence('lobby');
36
+ const lobby = pulse.room('lobby');
33
37
 
34
38
  try {
35
39
  await pulse.connect();
36
40
  chat.send('send', { text: 'Hello!' });
37
- lobby.update({ name: 'Ada' });
41
+ await lobby.joined;
42
+ lobby.updatePresence({ name: 'Ada' });
38
43
  } catch (error) {
39
44
  console.error('Pulse connection failed:', error);
40
45
  }
@@ -44,11 +49,12 @@ pulse.dispose();
44
49
 
45
50
  ## Key Behavior
46
51
 
47
- - Call `connect()` before sending, joining rooms, or publishing presence.
48
- - Define root events, channel events, and presence state at `createPulse()` so named scopes are type-safe.
49
- - Each `channel()` and `presence()` call returns an independent disposable scope. Server subscriptions and rooms use reference counting.
50
- - Reconnect restores channels, desired rooms, and the last successfully published local presence state.
52
+ - Call `connect()` before sending messages or publishing presence. Room scopes can be created before connecting — joins are sent after the connection opens.
53
+ - Define server events, client events, channel schemas, and room schemas at `createPulse()` so named scopes are type-safe.
54
+ - Each `channel()` and `room()` call returns an independent disposable scope. Server subscriptions and room memberships use reference counting.
55
+ - Reconnect restores channels, room memberships, and the last successfully published local presence state.
51
56
  - `send()` throws `PulseConnectionError` while disconnected; Pulse never silently drops or buffers application messages.
57
+ - `room()` returns a `RoomScope` with a `joined` promise. When the room definition includes `presence`, the scope also exposes reactive presence state, `updatePresence()`, and `onJoin()`/`onLeave()` handlers.
52
58
  - `onError` receives typed transport and protocol errors.
53
59
 
54
60
  ## Migration
@@ -61,4 +67,4 @@ See the [Pulse migration guide](https://vielzeug.dev/pulse/migration).
61
67
  - [Usage](https://vielzeug.dev/pulse/usage)
62
68
  - [API](https://vielzeug.dev/pulse/api)
63
69
  - [Examples](https://vielzeug.dev/pulse/examples)
64
- - [Pulse 3.0 Migration](https://vielzeug.dev/pulse/migration)
70
+ - [Pulse Migration](https://vielzeug.dev/pulse/migration)
package/dist/errors.cjs CHANGED
@@ -1,2 +1,2 @@
1
- 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}};exports.PulseAbortError=r,exports.PulseConnectionError=t,exports.PulseDisposedError=i,exports.PulseError=e,exports.PulseProtocolError=a,exports.PulseTimeoutError=n;
1
+ 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{room;constructor(e,t){super(`Timed out waiting for room "${e}"`,t),this.room=e}},i=class extends e{constructor(e){super(`Aborted`,e)}},a=class extends e{constructor(e=`Pulse`,t){super(`${e} instance is disposed`,t)}},o=class extends e{raw;constructor(e,t,n){super(e,n),this.raw=t}};exports.PulseAbortError=i,exports.PulseConnectionError=t,exports.PulseDisposedError=a,exports.PulseError=e,exports.PulseProtocolError=o,exports.PulseRoomTimeoutError=r,exports.PulseTimeoutError=n;
2
2
  //# sourceMappingURL=errors.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.cjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * Base class for all pulse errors.\n * Use `instanceof PulseError` to catch any pulse-originated error in one branch.\n */\nexport class PulseError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n static is(err: unknown): err is PulseError {\n return err instanceof PulseError;\n }\n}\n\n/**\n * Thrown when a WebSocket connection cannot be established or is lost\n * without triggering auto-reconnect (budget exhausted, or reconnect disabled).\n */\nexport class PulseConnectionError extends PulseError {\n readonly url: string;\n\n constructor(message: string, url: string, opts?: ErrorOptions) {\n super(message, opts);\n this.url = url;\n }\n}\n\n/**\n * Thrown when `wait()` or `join()`/`leave()` times out before receiving a\n * server response, or when an AbortSignal fires before the event arrives.\n */\nexport class PulseTimeoutError extends PulseError {\n readonly event: string;\n\n constructor(event: string, opts?: ErrorOptions) {\n super(`Timed out waiting for \"${event}\"`, opts);\n this.event = event;\n }\n}\n\n/**\n * Thrown when `wait()` is aborted via an AbortSignal before the event fires.\n */\nexport class PulseAbortError extends PulseError {\n constructor(opts?: ErrorOptions) {\n super('Aborted', opts);\n }\n}\n\n/**\n * Thrown when a method is called on a disposed Pulse instance or channel.\n */\nexport class PulseDisposedError extends PulseError {\n constructor(target = 'Pulse', opts?: ErrorOptions) {\n super(`${target} instance is disposed`, opts);\n }\n}\n\n/**\n * Thrown when the server sends a frame that cannot be parsed or violates\n * the wire protocol schema.\n */\nexport class PulseProtocolError extends PulseError {\n readonly raw: unknown;\n\n constructor(message: string, raw?: unknown, opts?: ErrorOptions) {\n super(message, opts);\n this.raw = raw;\n }\n}\n"],"mappings":"AAIA,IAAa,EAAb,MAAa,UAAmB,KAAM,CACpC,YAAY,EAAiB,EAAqB,CAChD,MAAM,EAAS,CAAI,EACnB,KAAK,KAAO,WAAW,KACvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CAEA,OAAO,GAAG,EAAiC,CACzC,OAAO,aAAe,CACxB,CACF,EAMa,EAAb,cAA0C,CAAW,CACnD,IAEA,YAAY,EAAiB,EAAa,EAAqB,CAC7D,MAAM,EAAS,CAAI,EACnB,KAAK,IAAM,CACb,CACF,EAMa,EAAb,cAAuC,CAAW,CAChD,MAEA,YAAY,EAAe,EAAqB,CAC9C,MAAM,0BAA0B,EAAM,GAAI,CAAI,EAC9C,KAAK,MAAQ,CACf,CACF,EAKa,EAAb,cAAqC,CAAW,CAC9C,YAAY,EAAqB,CAC/B,MAAM,UAAW,CAAI,CACvB,CACF,EAKa,EAAb,cAAwC,CAAW,CACjD,YAAY,EAAS,QAAS,EAAqB,CACjD,MAAM,GAAG,EAAO,uBAAwB,CAAI,CAC9C,CACF,EAMa,EAAb,cAAwC,CAAW,CACjD,IAEA,YAAY,EAAiB,EAAe,EAAqB,CAC/D,MAAM,EAAS,CAAI,EACnB,KAAK,IAAM,CACb,CACF"}
1
+ {"version":3,"file":"errors.cjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * Base class for all pulse errors.\n * Use `instanceof PulseError` to catch any pulse-originated error in one branch.\n */\nexport class PulseError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n static is(err: unknown): err is PulseError {\n return err instanceof PulseError;\n }\n}\n\n/**\n * Thrown when a WebSocket connection cannot be established or is lost\n * without triggering auto-reconnect (budget exhausted, or reconnect disabled).\n */\nexport class PulseConnectionError extends PulseError {\n readonly url: string;\n\n constructor(message: string, url: string, opts?: ErrorOptions) {\n super(message, opts);\n this.url = url;\n }\n}\n\n/**\n * Thrown when `wait()` times out before the server event arrives.\n */\nexport class PulseTimeoutError extends PulseError {\n readonly event: string;\n\n constructor(event: string, opts?: ErrorOptions) {\n super(`Timed out waiting for \"${event}\"`, opts);\n this.event = event;\n }\n}\n\n/**\n * Thrown when a room scope's `joined` promise times out before the server\n * confirms membership, or when an AbortSignal fires before confirmation.\n */\nexport class PulseRoomTimeoutError extends PulseError {\n readonly room: string;\n\n constructor(room: string, opts?: ErrorOptions) {\n super(`Timed out waiting for room \"${room}\"`, opts);\n this.room = room;\n }\n}\n\n/**\n * Thrown when `wait()` is aborted via an AbortSignal before the event fires,\n * or when a room scope's join is aborted before the server confirms.\n */\nexport class PulseAbortError extends PulseError {\n constructor(opts?: ErrorOptions) {\n super('Aborted', opts);\n }\n}\n\n/**\n * Thrown when a method is called on a disposed Pulse instance or channel.\n */\nexport class PulseDisposedError extends PulseError {\n constructor(target = 'Pulse', opts?: ErrorOptions) {\n super(`${target} instance is disposed`, opts);\n }\n}\n\n/**\n * Thrown when the server sends a frame that cannot be parsed or violates\n * the wire protocol schema.\n */\nexport class PulseProtocolError extends PulseError {\n readonly raw: unknown;\n\n constructor(message: string, raw?: unknown, opts?: ErrorOptions) {\n super(message, opts);\n this.raw = raw;\n }\n}\n"],"mappings":"AAIA,IAAa,EAAb,MAAa,UAAmB,KAAM,CACpC,YAAY,EAAiB,EAAqB,CAChD,MAAM,EAAS,CAAI,EACnB,KAAK,KAAO,WAAW,KACvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CAEA,OAAO,GAAG,EAAiC,CACzC,OAAO,aAAe,CACxB,CACF,EAMa,EAAb,cAA0C,CAAW,CACnD,IAEA,YAAY,EAAiB,EAAa,EAAqB,CAC7D,MAAM,EAAS,CAAI,EACnB,KAAK,IAAM,CACb,CACF,EAKa,EAAb,cAAuC,CAAW,CAChD,MAEA,YAAY,EAAe,EAAqB,CAC9C,MAAM,0BAA0B,EAAM,GAAI,CAAI,EAC9C,KAAK,MAAQ,CACf,CACF,EAMa,EAAb,cAA2C,CAAW,CACpD,KAEA,YAAY,EAAc,EAAqB,CAC7C,MAAM,+BAA+B,EAAK,GAAI,CAAI,EAClD,KAAK,KAAO,CACd,CACF,EAMa,EAAb,cAAqC,CAAW,CAC9C,YAAY,EAAqB,CAC/B,MAAM,UAAW,CAAI,CACvB,CACF,EAKa,EAAb,cAAwC,CAAW,CACjD,YAAY,EAAS,QAAS,EAAqB,CACjD,MAAM,GAAG,EAAO,uBAAwB,CAAI,CAC9C,CACF,EAMa,EAAb,cAAwC,CAAW,CACjD,IAEA,YAAY,EAAiB,EAAe,EAAqB,CAC/D,MAAM,EAAS,CAAI,EACnB,KAAK,IAAM,CACb,CACF"}
package/dist/errors.d.ts CHANGED
@@ -15,15 +15,23 @@ export declare class PulseConnectionError extends PulseError {
15
15
  constructor(message: string, url: string, opts?: ErrorOptions);
16
16
  }
17
17
  /**
18
- * Thrown when `wait()` or `join()`/`leave()` times out before receiving a
19
- * server response, or when an AbortSignal fires before the event arrives.
18
+ * Thrown when `wait()` times out before the server event arrives.
20
19
  */
21
20
  export declare class PulseTimeoutError extends PulseError {
22
21
  readonly event: string;
23
22
  constructor(event: string, opts?: ErrorOptions);
24
23
  }
25
24
  /**
26
- * Thrown when `wait()` is aborted via an AbortSignal before the event fires.
25
+ * Thrown when a room scope's `joined` promise times out before the server
26
+ * confirms membership, or when an AbortSignal fires before confirmation.
27
+ */
28
+ export declare class PulseRoomTimeoutError extends PulseError {
29
+ readonly room: string;
30
+ constructor(room: string, opts?: ErrorOptions);
31
+ }
32
+ /**
33
+ * Thrown when `wait()` is aborted via an AbortSignal before the event fires,
34
+ * or when a room scope's join is aborted before the server confirms.
27
35
  */
28
36
  export declare class PulseAbortError extends PulseError {
29
37
  constructor(opts?: ErrorOptions);
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,qBAAa,UAAW,SAAQ,KAAK;gBACvB,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,YAAY;IAMhD,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,UAAU;CAG3C;AAED;;;GAGG;AACH,qBAAa,oBAAqB,SAAQ,UAAU;IAClD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;gBAET,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,YAAY;CAI9D;AAED;;;GAGG;AACH,qBAAa,iBAAkB,SAAQ,UAAU;IAC/C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;gBAEX,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,YAAY;CAI/C;AAED;;GAEG;AACH,qBAAa,eAAgB,SAAQ,UAAU;gBACjC,IAAI,CAAC,EAAE,YAAY;CAGhC;AAED;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,UAAU;gBACpC,MAAM,SAAU,EAAE,IAAI,CAAC,EAAE,YAAY;CAGlD;AAED;;;GAGG;AACH,qBAAa,kBAAmB,SAAQ,UAAU;IAChD,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;gBAEV,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,YAAY;CAIhE"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,qBAAa,UAAW,SAAQ,KAAK;gBACvB,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,YAAY;IAMhD,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,UAAU;CAG3C;AAED;;;GAGG;AACH,qBAAa,oBAAqB,SAAQ,UAAU;IAClD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;gBAET,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,YAAY;CAI9D;AAED;;GAEG;AACH,qBAAa,iBAAkB,SAAQ,UAAU;IAC/C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;gBAEX,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,YAAY;CAI/C;AAED;;;GAGG;AACH,qBAAa,qBAAsB,SAAQ,UAAU;IACnD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,YAAY;CAI9C;AAED;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,UAAU;gBACjC,IAAI,CAAC,EAAE,YAAY;CAGhC;AAED;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,UAAU;gBACpC,MAAM,SAAU,EAAE,IAAI,CAAC,EAAE,YAAY;CAGlD;AAED;;;GAGG;AACH,qBAAa,kBAAmB,SAAQ,UAAU;IAChD,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;gBAEV,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,YAAY;CAIhE"}
package/dist/errors.js CHANGED
@@ -17,20 +17,25 @@ var e = class e extends Error {
17
17
  super(`Timed out waiting for "${e}"`, t), this.event = e;
18
18
  }
19
19
  }, r = class extends e {
20
+ room;
21
+ constructor(e, t) {
22
+ super(`Timed out waiting for room "${e}"`, t), this.room = e;
23
+ }
24
+ }, i = class extends e {
20
25
  constructor(e) {
21
26
  super("Aborted", e);
22
27
  }
23
- }, i = class extends e {
28
+ }, a = class extends e {
24
29
  constructor(e = "Pulse", t) {
25
30
  super(`${e} instance is disposed`, t);
26
31
  }
27
- }, a = class extends e {
32
+ }, o = class extends e {
28
33
  raw;
29
34
  constructor(e, t, n) {
30
35
  super(e, n), this.raw = t;
31
36
  }
32
37
  };
33
38
  //#endregion
34
- export { r as PulseAbortError, t as PulseConnectionError, i as PulseDisposedError, e as PulseError, a as PulseProtocolError, n as PulseTimeoutError };
39
+ export { i as PulseAbortError, t as PulseConnectionError, a as PulseDisposedError, e as PulseError, o as PulseProtocolError, r as PulseRoomTimeoutError, n as PulseTimeoutError };
35
40
 
36
41
  //# sourceMappingURL=errors.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * Base class for all pulse errors.\n * Use `instanceof PulseError` to catch any pulse-originated error in one branch.\n */\nexport class PulseError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n static is(err: unknown): err is PulseError {\n return err instanceof PulseError;\n }\n}\n\n/**\n * Thrown when a WebSocket connection cannot be established or is lost\n * without triggering auto-reconnect (budget exhausted, or reconnect disabled).\n */\nexport class PulseConnectionError extends PulseError {\n readonly url: string;\n\n constructor(message: string, url: string, opts?: ErrorOptions) {\n super(message, opts);\n this.url = url;\n }\n}\n\n/**\n * Thrown when `wait()` or `join()`/`leave()` times out before receiving a\n * server response, or when an AbortSignal fires before the event arrives.\n */\nexport class PulseTimeoutError extends PulseError {\n readonly event: string;\n\n constructor(event: string, opts?: ErrorOptions) {\n super(`Timed out waiting for \"${event}\"`, opts);\n this.event = event;\n }\n}\n\n/**\n * Thrown when `wait()` is aborted via an AbortSignal before the event fires.\n */\nexport class PulseAbortError extends PulseError {\n constructor(opts?: ErrorOptions) {\n super('Aborted', opts);\n }\n}\n\n/**\n * Thrown when a method is called on a disposed Pulse instance or channel.\n */\nexport class PulseDisposedError extends PulseError {\n constructor(target = 'Pulse', opts?: ErrorOptions) {\n super(`${target} instance is disposed`, opts);\n }\n}\n\n/**\n * Thrown when the server sends a frame that cannot be parsed or violates\n * the wire protocol schema.\n */\nexport class PulseProtocolError extends PulseError {\n readonly raw: unknown;\n\n constructor(message: string, raw?: unknown, opts?: ErrorOptions) {\n super(message, opts);\n this.raw = raw;\n }\n}\n"],"mappings":";AAIA,IAAa,IAAb,MAAa,UAAmB,MAAM;CACpC,YAAY,GAAiB,GAAqB;EAGhD,AAFA,MAAM,GAAS,CAAI,GACnB,KAAK,OAAO,WAAW,MACvB,OAAO,eAAe,MAAM,WAAW,SAAS;CAClD;CAEA,OAAO,GAAG,GAAiC;EACzC,OAAO,aAAe;CACxB;AACF,GAMa,IAAb,cAA0C,EAAW;CACnD;CAEA,YAAY,GAAiB,GAAa,GAAqB;EAE7D,AADA,MAAM,GAAS,CAAI,GACnB,KAAK,MAAM;CACb;AACF,GAMa,IAAb,cAAuC,EAAW;CAChD;CAEA,YAAY,GAAe,GAAqB;EAE9C,AADA,MAAM,0BAA0B,EAAM,IAAI,CAAI,GAC9C,KAAK,QAAQ;CACf;AACF,GAKa,IAAb,cAAqC,EAAW;CAC9C,YAAY,GAAqB;EAC/B,MAAM,WAAW,CAAI;CACvB;AACF,GAKa,IAAb,cAAwC,EAAW;CACjD,YAAY,IAAS,SAAS,GAAqB;EACjD,MAAM,GAAG,EAAO,wBAAwB,CAAI;CAC9C;AACF,GAMa,IAAb,cAAwC,EAAW;CACjD;CAEA,YAAY,GAAiB,GAAe,GAAqB;EAE/D,AADA,MAAM,GAAS,CAAI,GACnB,KAAK,MAAM;CACb;AACF"}
1
+ {"version":3,"file":"errors.js","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * Base class for all pulse errors.\n * Use `instanceof PulseError` to catch any pulse-originated error in one branch.\n */\nexport class PulseError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n static is(err: unknown): err is PulseError {\n return err instanceof PulseError;\n }\n}\n\n/**\n * Thrown when a WebSocket connection cannot be established or is lost\n * without triggering auto-reconnect (budget exhausted, or reconnect disabled).\n */\nexport class PulseConnectionError extends PulseError {\n readonly url: string;\n\n constructor(message: string, url: string, opts?: ErrorOptions) {\n super(message, opts);\n this.url = url;\n }\n}\n\n/**\n * Thrown when `wait()` times out before the server event arrives.\n */\nexport class PulseTimeoutError extends PulseError {\n readonly event: string;\n\n constructor(event: string, opts?: ErrorOptions) {\n super(`Timed out waiting for \"${event}\"`, opts);\n this.event = event;\n }\n}\n\n/**\n * Thrown when a room scope's `joined` promise times out before the server\n * confirms membership, or when an AbortSignal fires before confirmation.\n */\nexport class PulseRoomTimeoutError extends PulseError {\n readonly room: string;\n\n constructor(room: string, opts?: ErrorOptions) {\n super(`Timed out waiting for room \"${room}\"`, opts);\n this.room = room;\n }\n}\n\n/**\n * Thrown when `wait()` is aborted via an AbortSignal before the event fires,\n * or when a room scope's join is aborted before the server confirms.\n */\nexport class PulseAbortError extends PulseError {\n constructor(opts?: ErrorOptions) {\n super('Aborted', opts);\n }\n}\n\n/**\n * Thrown when a method is called on a disposed Pulse instance or channel.\n */\nexport class PulseDisposedError extends PulseError {\n constructor(target = 'Pulse', opts?: ErrorOptions) {\n super(`${target} instance is disposed`, opts);\n }\n}\n\n/**\n * Thrown when the server sends a frame that cannot be parsed or violates\n * the wire protocol schema.\n */\nexport class PulseProtocolError extends PulseError {\n readonly raw: unknown;\n\n constructor(message: string, raw?: unknown, opts?: ErrorOptions) {\n super(message, opts);\n this.raw = raw;\n }\n}\n"],"mappings":";AAIA,IAAa,IAAb,MAAa,UAAmB,MAAM;CACpC,YAAY,GAAiB,GAAqB;EAGhD,AAFA,MAAM,GAAS,CAAI,GACnB,KAAK,OAAO,WAAW,MACvB,OAAO,eAAe,MAAM,WAAW,SAAS;CAClD;CAEA,OAAO,GAAG,GAAiC;EACzC,OAAO,aAAe;CACxB;AACF,GAMa,IAAb,cAA0C,EAAW;CACnD;CAEA,YAAY,GAAiB,GAAa,GAAqB;EAE7D,AADA,MAAM,GAAS,CAAI,GACnB,KAAK,MAAM;CACb;AACF,GAKa,IAAb,cAAuC,EAAW;CAChD;CAEA,YAAY,GAAe,GAAqB;EAE9C,AADA,MAAM,0BAA0B,EAAM,IAAI,CAAI,GAC9C,KAAK,QAAQ;CACf;AACF,GAMa,IAAb,cAA2C,EAAW;CACpD;CAEA,YAAY,GAAc,GAAqB;EAE7C,AADA,MAAM,+BAA+B,EAAK,IAAI,CAAI,GAClD,KAAK,OAAO;CACd;AACF,GAMa,IAAb,cAAqC,EAAW;CAC9C,YAAY,GAAqB;EAC/B,MAAM,WAAW,CAAI;CACvB;AACF,GAKa,IAAb,cAAwC,EAAW;CACjD,YAAY,IAAS,SAAS,GAAqB;EACjD,MAAM,GAAG,EAAO,wBAAwB,CAAI;CAC9C;AACF,GAMa,IAAb,cAAwC,EAAW;CACjD;CAEA,YAAY,GAAiB,GAAe,GAAqB;EAE/D,AADA,MAAM,GAAS,CAAI,GACnB,KAAK,MAAM;CACb;AACF"}
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./errors.cjs"),t=require("./pulse.cjs");exports.PulseAbortError=e.PulseAbortError,exports.PulseConnectionError=e.PulseConnectionError,exports.PulseDisposedError=e.PulseDisposedError,exports.PulseError=e.PulseError,exports.PulseProtocolError=e.PulseProtocolError,exports.PulseTimeoutError=e.PulseTimeoutError,exports.createPulse=t.createPulse;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./errors.cjs"),t=require("./pulse.cjs");exports.PulseAbortError=e.PulseAbortError,exports.PulseConnectionError=e.PulseConnectionError,exports.PulseDisposedError=e.PulseDisposedError,exports.PulseError=e.PulseError,exports.PulseProtocolError=e.PulseProtocolError,exports.PulseRoomTimeoutError=e.PulseRoomTimeoutError,exports.PulseTimeoutError=e.PulseTimeoutError,exports.createPulse=t.createPulse;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { PulseAbortError, PulseConnectionError, PulseDisposedError, PulseError, PulseProtocolError, PulseTimeoutError, } from './errors';
1
+ export { PulseAbortError, PulseConnectionError, PulseDisposedError, PulseError, PulseProtocolError, PulseRoomTimeoutError, PulseTimeoutError, } from './errors';
2
2
  export { createPulse } from './pulse';
3
- export type { ChannelDefinition, ChannelDefinitions, EventKey, HeartbeatOptions, MessageMap, OutgoingMessage, OutgoingTransform, PresenceChannel, PresenceDefinitions, Pulse, PulseChannel, PulseOptions, PulseStatus, ReconnectOptions, Unsubscribe, } from './types';
3
+ export type { ChannelDefinition, ChannelDefinitions, ClientEvents, EventKey, HeartbeatOptions, MessageMap, OutgoingMessage, OutgoingTransform, PresenceRoomScope, Pulse, PulseChannel, PulseOptions, PulseSchema, PulseStatus, ReconnectOptions, RoomDefinition, RoomDefinitions, RoomMap, RoomOptions, RoomScope, RoomScopeBase, ServerEvents, Unsubscribe, } from './types';
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,kBAAkB,EAClB,UAAU,EACV,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,YAAY,EACV,iBAAiB,EACjB,kBAAkB,EAClB,QAAQ,EACR,gBAAgB,EAChB,UAAU,EACV,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,mBAAmB,EACnB,KAAK,EACL,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,gBAAgB,EAChB,WAAW,GACZ,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,kBAAkB,EAClB,UAAU,EACV,kBAAkB,EAClB,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,YAAY,EACV,iBAAiB,EACjB,kBAAkB,EAClB,YAAY,EACZ,QAAQ,EACR,gBAAgB,EAChB,UAAU,EACV,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,KAAK,EACL,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,WAAW,EACX,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,OAAO,EACP,WAAW,EACX,SAAS,EACT,aAAa,EACb,YAAY,EACZ,WAAW,GACZ,MAAM,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import { PulseAbortError as e, PulseConnectionError as t, PulseDisposedError as n, PulseError as r, PulseProtocolError as i, PulseTimeoutError as a } from "./errors.js";
2
- import { createPulse as o } from "./pulse.js";
3
- export { e as PulseAbortError, t as PulseConnectionError, n as PulseDisposedError, r as PulseError, i as PulseProtocolError, a as PulseTimeoutError, o as createPulse };
1
+ import { PulseAbortError as e, PulseConnectionError as t, PulseDisposedError as n, PulseError as r, PulseProtocolError as i, PulseRoomTimeoutError as a, PulseTimeoutError as o } from "./errors.js";
2
+ import { createPulse as s } from "./pulse.js";
3
+ export { e as PulseAbortError, t as PulseConnectionError, n as PulseDisposedError, r as PulseError, i as PulseProtocolError, a as PulseRoomTimeoutError, o as PulseTimeoutError, s as createPulse };
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}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;
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,`room`)!==null}function o(e){return i(e,`code`)!==null&&i(e,`message`)!==null}function s(e){return!r(e)||i(e,`event`)===null||!(`payload`in e)?!1:e.channel===void 0||typeof e.channel==`string`}function c(e){return r(e)&&typeof e.ts==`number`}function l(e){return r(e)&&i(e,`id`)!==null&&i(e,`room`)!==null&&`state`in e}function u(e){return i(e,`id`)!==null&&i(e,`room`)!==null}function d(e){return r(e)&&i(e,`room`)!==null&&r(e.members)}function f(e){return i(e,`channel`)!==null}function p(t){let r=n(t);switch(r.type){case`error`:if(o(r))return{frame:r,kind:`known`};break;case`joined`:if(a(r))return{frame:r,kind:`known`};break;case`left`:if(a(r))return{frame:r,kind:`known`};break;case`message`:if(s(r))return{frame:r,kind:`known`};break;case`pong`:if(c(r))return{frame:r,kind:`known`};break;case`presence_join`:if(l(r))return{frame:r,kind:`known`};break;case`presence_leave`:if(u(r))return{frame:r,kind:`known`};break;case`presence_state`:if(d(r))return{frame:r,kind:`known`};break;case`subscribed`:if(f(r))return{frame:r,kind:`known`};break;case`unsubscribed`:if(f(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=p,exports.encode=t,exports.isErrorFrame=o,exports.isMessageFrame=s,exports.isPongFrame=c,exports.isPresenceJoinFrame=l,exports.isPresenceLeaveFrame=u,exports.isPresenceStateFrame=d;
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\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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"}
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\nfunction hasRoom(frame: unknown): frame is { room: string } {\n return getStringProp(frame, 'room') !== 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 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 hasChannel(frame: unknown): frame is { channel: string } {\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 (hasRoom(frame)) return { frame: frame as InJoinedFrame, kind: 'known' };\n\n break;\n case 'left':\n if (hasRoom(frame)) return { frame: frame as InLeftFrame, 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 (hasChannel(frame)) return { frame: frame as InSubscribedFrame, kind: 'known' };\n\n break;\n case 'unsubscribed':\n if (hasChannel(frame)) return { frame: frame as InUnsubscribedFrame, 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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"}
@@ -86,8 +86,6 @@ export type DecodedInFrame = {
86
86
  type: string;
87
87
  };
88
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
89
  export declare function isMessageFrame(frame: unknown): frame is InMessageFrame;
92
90
  export declare function isPongFrame(frame: unknown): frame is InPongFrame;
93
91
  export declare function isPresenceJoinFrame(frame: unknown): frame is InPresenceJoinFrame;
@@ -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":"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"}
package/dist/protocol.js CHANGED
@@ -23,94 +23,88 @@ function i(e, t) {
23
23
  return typeof n == "string" ? n : null;
24
24
  }
25
25
  function a(e) {
26
- return i(e, "code") !== null && i(e, "message") !== null;
26
+ return i(e, "room") !== null;
27
27
  }
28
28
  function o(e) {
29
- return i(e, "room") !== null;
29
+ return i(e, "code") !== null && i(e, "message") !== null;
30
30
  }
31
31
  function s(e) {
32
- return i(e, "room") !== null;
33
- }
34
- function c(e) {
35
32
  return !r(e) || i(e, "event") === null || !("payload" in e) ? !1 : e.channel === void 0 || typeof e.channel == "string";
36
33
  }
37
- function l(e) {
34
+ function c(e) {
38
35
  return r(e) && typeof e.ts == "number";
39
36
  }
40
- function u(e) {
37
+ function l(e) {
41
38
  return r(e) && i(e, "id") !== null && i(e, "room") !== null && "state" in e;
42
39
  }
43
- function d(e) {
40
+ function u(e) {
44
41
  return i(e, "id") !== null && i(e, "room") !== null;
45
42
  }
46
- function f(e) {
43
+ function d(e) {
47
44
  return r(e) && i(e, "room") !== null && r(e.members);
48
45
  }
49
- function p(e) {
50
- return i(e, "channel") !== null;
51
- }
52
- function m(e) {
46
+ function f(e) {
53
47
  return i(e, "channel") !== null;
54
48
  }
55
- function h(t) {
49
+ function p(t) {
56
50
  let r = n(t);
57
51
  switch (r.type) {
58
52
  case "error":
59
- if (a(r)) return {
53
+ if (o(r)) return {
60
54
  frame: r,
61
55
  kind: "known"
62
56
  };
63
57
  break;
64
58
  case "joined":
65
- if (o(r)) return {
59
+ if (a(r)) return {
66
60
  frame: r,
67
61
  kind: "known"
68
62
  };
69
63
  break;
70
64
  case "left":
71
- if (s(r)) return {
65
+ if (a(r)) return {
72
66
  frame: r,
73
67
  kind: "known"
74
68
  };
75
69
  break;
76
70
  case "message":
77
- if (c(r)) return {
71
+ if (s(r)) return {
78
72
  frame: r,
79
73
  kind: "known"
80
74
  };
81
75
  break;
82
76
  case "pong":
83
- if (l(r)) return {
77
+ if (c(r)) return {
84
78
  frame: r,
85
79
  kind: "known"
86
80
  };
87
81
  break;
88
82
  case "presence_join":
89
- if (u(r)) return {
83
+ if (l(r)) return {
90
84
  frame: r,
91
85
  kind: "known"
92
86
  };
93
87
  break;
94
88
  case "presence_leave":
95
- if (d(r)) return {
89
+ if (u(r)) return {
96
90
  frame: r,
97
91
  kind: "known"
98
92
  };
99
93
  break;
100
94
  case "presence_state":
101
- if (f(r)) return {
95
+ if (d(r)) return {
102
96
  frame: r,
103
97
  kind: "known"
104
98
  };
105
99
  break;
106
100
  case "subscribed":
107
- if (p(r)) return {
101
+ if (f(r)) return {
108
102
  frame: r,
109
103
  kind: "known"
110
104
  };
111
105
  break;
112
106
  case "unsubscribed":
113
- if (m(r)) return {
107
+ if (f(r)) return {
114
108
  frame: r,
115
109
  kind: "known"
116
110
  };
@@ -123,6 +117,6 @@ function h(t) {
123
117
  throw new e(`Malformed "${r.type}" frame`, r);
124
118
  }
125
119
  //#endregion
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 };
120
+ export { n as decode, p as decodeValidated, t as encode, o as isErrorFrame, s as isMessageFrame, c as isPongFrame, l as isPresenceJoinFrame, u as isPresenceLeaveFrame, d as isPresenceStateFrame };
127
121
 
128
122
  //# 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\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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"}
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\nfunction hasRoom(frame: unknown): frame is { room: string } {\n return getStringProp(frame, 'room') !== 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 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 hasChannel(frame: unknown): frame is { channel: string } {\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 (hasRoom(frame)) return { frame: frame as InJoinedFrame, kind: 'known' };\n\n break;\n case 'left':\n if (hasRoom(frame)) return { frame: frame as InLeftFrame, 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 (hasChannel(frame)) return { frame: frame as InSubscribedFrame, kind: 'known' };\n\n break;\n case 'unsubscribed':\n if (hasChannel(frame)) return { frame: frame as InUnsubscribedFrame, 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`});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}};function s(e,...t){return t.length===0?e:t.reduce(c,e)}function c(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 l(e){let t=new AbortController;return e.aborted?t.abort(e.reason):e.addEventListener(`abort`,()=>t.abort(e.reason),{once:!0}),t}function u(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 d(e,t=3e4){let n=Math.min(1e3*2**e,t);return Math.random()*n}var f=5;function p(e,t,r,i){let o=r===!0?{}:r||void 0,s=o?o.maxAttempts??f:0,c=o?.delay,l=typeof c==`function`?c:typeof c==`number`?()=>c:d,p=!1,m=!1,h=!1,g,_,v,y=new Set;function b(e){i.onStatus(e)}function x(e){for(let t of y)t.reject(e);y.clear()}function S(){for(let e of y)e.resolve();y.clear()}function C(e,t){v?.socket===e&&(v.reject(t),v=void 0)}function w(){if(_?.readyState===WebSocket.OPEN)return Promise.resolve();if(v)return v.promise;let r=new WebSocket(e,t);_=r;let a,o,s=new Promise((e,t)=>{a=e,o=t});return v={promise:s,reject:o,resolve:a,socket:r},r.onopen=()=>{if(_!==r||!m){r.close(1e3,`stale connection`);return}v?.socket===r&&(v.resolve(),v=void 0),b(`open`),i.onOpen(),S()},r.onmessage=e=>{_===r&&i.onMessage(e)},r.onerror=()=>{if(_!==r)return;let t=new n(`WebSocket error`,e);i.onError(t),C(r,t),x(t)},r.onclose=()=>{if(_!==r)return;_=void 0,i.onClose();let t=new n(`Connection closed`,e);if(C(r,t),!m||p){b(`closed`);return}h||T()},s}async function T(){if(h||p||!m)return;h=!0;let t=new AbortController;g=t,b(`reconnecting`);let r;for(let i=0;i<s&&m&&!p&&(await u(l(i),t.signal),!(t.signal.aborted||!m||p));i++)try{await w(),h=!1;return}catch(t){r=t instanceof n?t:new n(`Reconnect failed`,e)}!t.signal.aborted&&m&&!p&&(m=!1,b(`closed`),i.onError(r??new n(`Reconnect budget exhausted`,e)),x(r??new n(`Reconnect budget exhausted`,e))),g===t&&(g=void 0),h=!1}function E(){g?.abort(),g=void 0,h=!1}return{connect(){if(p)return Promise.reject(new a);if(m=!0,_?.readyState===WebSocket.OPEN)return Promise.resolve();let e=new Promise((e,t)=>{y.add({reject:t,resolve:e})});return!v&&!h&&(b(`connecting`),w().catch(()=>{})),e},disconnect(t=1e3,r=``){m=!1,E(),x(new n(`Connection closed`,e)),b(`closed`),_?.close(t,r)},dispose(){p||(p=!0,m=!1,E(),x(new a),_?.close(1e3,`disposed`),_=void 0,v=void 0,b(`closed`))},forceReconnect(e,t){m&&_?.readyState===WebSocket.OPEN&&_.close(e,t)},get open(){return _?.readyState===WebSocket.OPEN},send(t){if(_?.readyState!==WebSocket.OPEN)throw new n(`Connection is not open`,e);_.send(t)}}}function m(e,t,n,a){return new Promise((o,c)=>{let l=[t],u;if(n?.signal&&l.push(n.signal),n?.timeout!==void 0){let t=new AbortController;u=setTimeout(()=>{t.abort(new r(e))},n.timeout),l.push(t.signal)}let d=l.length===1?l[0]:s(l[0],...l.slice(1));if(d.aborted){clearTimeout(u),c(d.reason instanceof r?d.reason:new i);return}let f=()=>{},p=()=>{clearTimeout(u),f(),c(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 o=l(r),s=new Set,c=o.signal.aborted;return o.signal.addEventListener(`abort`,()=>{c=!0},{once:!0}),{get disposalSignal(){return o.signal},dispose(){if(!c){c=!0,o.abort();for(let e of s)e();s.clear(),i?.()}},get disposed(){return c},get name(){return e},on(t,r){if(c)throw new a(`Channel "${e}"`);let i=n(e,t,r);return s.add(i),()=>{i(),s.delete(i)}},once(t,r){if(c)throw new a(`Channel "${e}"`);let i=()=>{};return i=n(e,t,e=>{s.delete(i),i(),r(e)}),s.add(i),()=>{s.delete(i),i()}},send(n,r){if(c)throw new a(`Channel "${e}"`);t(e,n,r)},[Symbol.dispose](){this.dispose()},wait(t,r){return m(t,o.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,c=!1;function l(){o=setTimeout(()=>{t(g({ts:Date.now(),type:`ping`})),s=setTimeout(()=>{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 P(t,n,r,i,o,s){let c=l(i),u=c.signal.aborted;c.signal.addEventListener(`abort`,()=>{u=!0},{once:!0});let d=(0,e.signal)(new Map);o?.(()=>{d.value=new Map});let f=new Set,p=new Set,m=[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);d.value=n}),r.onJoin(e=>{if(e.room!==t)return;let n=new Map(d.value);n.set(e.id,e.state),d.value=n;for(let t of f)t(e.id,e.state)}),r.onLeave(e=>{if(e.room!==t)return;let n=new Map(d.value);n.delete(e.id),d.value=n;for(let t of p)t(e.id)})];return{get disposalSignal(){return c.signal},dispose(){if(!u){u=!0,c.abort();for(let e of m)e();m.length=0,f.clear(),p.clear(),s?.()}},get disposed(){return u},onJoin(e){if(u)throw new a(`Presence "${t}"`);return f.add(e),()=>f.delete(e)},onLeave(e){if(u)throw new a(`Presence "${t}"`);return p.add(e),()=>p.delete(e)},get room(){return t},get state(){return d},[Symbol.dispose](){this.dispose()},update(e){if(u)throw new a(`Presence "${t}"`);n(t,e)}}}var F=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)}}clear(){this.map.clear()}dispatch(e,t,n){for(let r of this.map.get(e)?.get(t)??[])r(n)}};function I(s,c={}){let l=new AbortController,u=(0,e.signal)(`closed`),d=(0,e.signal)(new Set),f=new F,_=new Map,v=new Map,y=new Set,b=new Map,x=new Map,S=new Map,C=!1;function w(e){c.onError?.(e)}function T(e){I.send(e)}function E(e){return y.has(e)||(v.get(e)??0)>0}function D(e,t){let n=new Set(d.value);t?n.add(e):n.delete(e),d.value=n}function O(){d.value=new Set;for(let e of x.values())for(let t of e)t()}function k(e,t=!0){let n=!1;for(let[t,r]of S){let i=r;for(;i;){i.operation===`join`&&y.delete(t);for(let t of i.callbacks)t.reject(e),n=!0;i=i.next}}S.clear(),n&&t&&w(e)}function j(){L.stop(),O(),k(new n(`Connection closed before room confirmation`,s))}function M(){for(let e of _.keys())T(g({channel:e,type:`subscribe`}));for(let e of new Set([...y,...v.keys()]))E(e)&&T(g({room:e,type:`join`}));for(let[e,t]of b)E(e)&&T(g({room:e,state:t,type:`presence`}))}let I=p(s,c.protocols,c.reconnect,{onClose(){j()},onError:w,onMessage(e){let t;try{t=A(e.data)}catch(t){w(t instanceof o?t:new o(`Failed to decode frame`,e.data));return}if(t.kind===`unknown`){w(new o(`Unknown frame type "${t.type}"`,e.data));return}try{z(t)}catch(e){w(new o(`Frame handler threw`,t.frame,{cause:e}))}},onOpen(){M(),L.start()},onStatus(e){u.value=e}}),L=N(c.heartbeat,e=>{I.open&&T(e)},()=>I.forceReconnect(4e3,`heartbeat timeout`));function R(e,r){let i=S.get(e);if(!i||i.operation!==r)return;for(let e of i.callbacks)e.resolve();let a=i.next;if(!a){S.delete(e);let i=r===`join`&&!E(e)?`leave`:r===`leave`&&E(e)?`join`:void 0;if(i){let r={callbacks:new Set,operation:i};S.set(e,r);try{T(g({room:e,type:i}))}catch(r){S.delete(e),i===`join`&&y.delete(e),w(r instanceof t?r:new n(`Failed to send room request`,s))}}return}S.set(e,a);try{T(g({room:e,type:a.operation}))}catch(r){S.delete(e),a.operation===`join`&&y.delete(e);for(let e of a.callbacks)e.reject(r instanceof t?r:new n(`Failed to send room request`,s))}}function z(e){let t=e.frame;switch(t.type){case`error`:w(new o(`Server error [${t.code}]: ${t.message}`,t));break;case`joined`:E(t.room)&&D(t.room,!0),R(t.room,`join`);break;case`left`:D(t.room,!1),R(t.room,`leave`);break;case`message`:f.dispatch(t.channel??null,t.event,t.payload);break;case`pong`:L.onPong();break;case`presence_join`:case`presence_leave`:case`presence_state`:f.dispatch(null,t.type,t)}}function B(e,o,c){if(C)return o===`join`&&y.delete(e),Promise.reject(new a);if(!I.open)return o===`join`&&y.delete(e),Promise.reject(new n(`Connection is not open`,s));let l=S.get(e);if(o===`join`&&d.value.has(e)&&!l||o===`leave`&&!d.value.has(e)&&!l)return Promise.resolve();let u=l;if(u){for(;u.next;)u=u.next;if(u.operation!==o){let e={callbacks:new Set,operation:o};u.next=e,u=e}}else u={callbacks:new Set,operation:o};return new Promise((a,d)=>{let f=c?.timeout===void 0?void 0:new AbortController,p=c?.signal&&f?AbortSignal.any([c.signal,f.signal]):c?.signal??f?.signal,m=c?.timeout===void 0?void 0:setTimeout(()=>f?.abort(),c.timeout),h={reject(e){clearTimeout(m),p?.removeEventListener(`abort`,_),d(e)},resolve(){clearTimeout(m),p?.removeEventListener(`abort`,_),a()}},_=()=>{u.callbacks.delete(h),u.callbacks.size===0&&o===`join`&&y.delete(e),h.reject(f?.signal.aborted?new r(o):new i)};if(p?.aborted){_();return}if(u.callbacks.add(h),p?.addEventListener(`abort`,_,{once:!0}),!l){S.set(e,u);try{T(g({room:e,type:o}))}catch(r){u.callbacks.delete(h),u.callbacks.size===0&&S.delete(e),h.reject(r instanceof t?r:new n(`Failed to send room request`,s))}}})}function V(e){let t=_.get(e)??0;_.set(e,t+1),t===0&&I.open&&T(g({channel:e,type:`subscribe`}))}function H(e){let t=_.get(e);if(t){if(t>1){_.set(e,t-1);return}_.delete(e),I.open&&T(g({channel:e,type:`unsubscribe`}))}}function U(e,t){let n=v.get(e)??0,r=x.get(e)??new Set;r.add(t),x.set(e,r),v.set(e,n+1),n===0&&I.open&&T(g({room:e,type:`join`}))}function W(e,t){x.get(e)?.delete(t),x.get(e)?.size===0&&x.delete(e);let n=v.get(e);if(n){if(n>1){v.set(e,n-1);return}v.delete(e),b.delete(e),!y.has(e)&&I.open&&T(g({room:e,type:`leave`}))}}return{channel(e){if(C)throw new a;return V(e),h(e,(e,t,n)=>{let r=c.transform?c.transform({channel:e,event:t,payload:n}):{channel:e,event:t,payload:n};r!==null&&T(g({...r,type:`message`}))},(e,t,n)=>f.add(e,t,n),l.signal,()=>H(e))},connect(){return C?Promise.reject(new a):I.connect()},disconnect(e=1e3,t=``){C||(j(),I.disconnect(e,t))},get disposalSignal(){return l.signal},dispose(){C||(C=!0,L.stop(),O(),k(new a,!1),I.dispose(),_.clear(),v.clear(),y.clear(),b.clear(),x.clear(),S.clear(),f.clear(),l.abort())},get disposed(){return C},join(e,t){return y.add(e),B(e,`join`,t)},leave(e,t){return y.delete(e),E(e)?Promise.resolve():B(e,`leave`,t)},on(e,t){if(C)throw new a;return f.add(null,e,t)},once(e,t){if(C)throw new a;let n=()=>{};return n=f.add(null,e,e=>{n(),t(e)}),n},presence(e){if(C)throw new a;let t,n=P(e,(e,t)=>{T(g({room:e,state:t,type:`presence`})),b.set(e,t)},{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))},l.signal,e=>{t=e},()=>i()),r=t;if(!r)throw Error(`Presence reset callback was not initialized`);let i=()=>W(e,r);return U(e,r),n},get rooms(){return d},send(e,t){if(C)throw new a;let n=c.transform?c.transform({event:e,payload:t}):{event:e,payload:t};n!==null&&T(g({...n,type:`message`}))},get status(){return u},[Symbol.dispose](){this.dispose()},wait(e,t){return m(e,l.signal,t,(e,t)=>f.add(null,e,t))}}}exports.PulseAbortError=i,exports.PulseConnectionError=n,exports.PulseDisposedError=a,exports.PulseError=t,exports.PulseProtocolError=o,exports.PulseTimeoutError=r,exports.createPulse=I;
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{room;constructor(e,t){super(`Timed out waiting for room "${e}"`,t),this.room=e}},a=class extends t{constructor(e){super(`Aborted`,e)}},o=class extends t{constructor(e=`Pulse`,t){super(`${e} instance is disposed`,t)}},s=class extends t{raw;constructor(e,t,n){super(e,n),this.raw=t}};function c(e,...t){return t.length===0?e:t.reduce(l,e)}function l(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 d(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 f(e,t=3e4){let n=Math.min(1e3*2**e,t);return Math.random()*n}var p=5;function m(e,t,r,i){let a=r===!0?{}:r||void 0,s=a?a.maxAttempts??p:0,c=a?.delay,l=typeof c==`function`?c:typeof c==`number`?()=>c:f,u=!1,m=!1,h=!1,g,_,v,y=new Set;function b(e){i.onStatus(e)}function x(e){for(let t of y)t.reject(e);y.clear()}function S(){for(let e of y)e.resolve();y.clear()}function C(e,t){v?.socket===e&&(v.reject(t),v=void 0)}function w(){if(_?.readyState===WebSocket.OPEN)return Promise.resolve();if(v)return v.promise;let r=new WebSocket(e,t);_=r;let a,o,s=new Promise((e,t)=>{a=e,o=t});return v={promise:s,reject:o,resolve:a,socket:r},r.onopen=()=>{if(_!==r||!m){r.close(1e3,`stale connection`);return}v?.socket===r&&(v.resolve(),v=void 0),b(`open`),i.onOpen(),S()},r.onmessage=e=>{_===r&&i.onMessage(e)},r.onerror=()=>{if(_!==r)return;let t=new n(`WebSocket error`,e);i.onError(t),C(r,t),x(t)},r.onclose=()=>{if(_!==r)return;_=void 0,i.onClose();let t=new n(`Connection closed`,e);if(C(r,t),!m||u){b(`closed`);return}h||T()},s}async function T(){if(h||u||!m)return;h=!0;let t=new AbortController;g=t,b(`reconnecting`);let r;for(let i=0;i<s&&m&&!u&&(await d(l(i),t.signal),!(t.signal.aborted||!m||u));i++)try{await w(),h=!1;return}catch(t){r=t instanceof n?t:new n(`Reconnect failed`,e)}!t.signal.aborted&&m&&!u&&(m=!1,b(`closed`),i.onError(r??new n(`Reconnect budget exhausted`,e)),x(r??new n(`Reconnect budget exhausted`,e))),g===t&&(g=void 0),h=!1}function E(){g?.abort(),g=void 0,h=!1}return{connect(){if(u)return Promise.reject(new o);if(m=!0,_?.readyState===WebSocket.OPEN)return Promise.resolve();let e=new Promise((e,t)=>{y.add({reject:t,resolve:e})});return!v&&!h&&(b(`connecting`),w().catch(()=>{})),e},disconnect(t=1e3,r=``){m=!1,E(),x(new n(`Connection closed`,e)),b(`closed`),_?.close(t,r)},dispose(){u||(u=!0,m=!1,E(),x(new o),_?.close(1e3,`disposed`),_=void 0,v=void 0,b(`closed`))},forceReconnect(e,t){m&&_?.readyState===WebSocket.OPEN&&_.close(e,t)},get open(){return _?.readyState===WebSocket.OPEN},send(t){if(_?.readyState!==WebSocket.OPEN)throw new n(`Connection is not open`,e);_.send(t)}}}function h(e,t,n,i){return new Promise((o,s)=>{let l=[t],u;if(n?.signal&&l.push(n.signal),n?.timeout!==void 0){let t=new AbortController;u=setTimeout(()=>{t.abort(new r(e))},n.timeout),l.push(t.signal)}let d=l.length===1?l[0]:c(l[0],...l.slice(1));if(d.aborted){clearTimeout(u),s(d.reason instanceof r?d.reason:new a);return}let f=()=>{},p=()=>{clearTimeout(u),f(),s(d.reason instanceof r?d.reason:new a)};d.addEventListener(`abort`,p,{once:!0}),f=i(e,e=>{clearTimeout(u),d.removeEventListener(`abort`,p),o(e)})})}function g(e,t,n,r,i){let a=u(r),s=new Set,c=a.signal.aborted;return a.signal.addEventListener(`abort`,()=>{c=!0},{once:!0}),{get disposalSignal(){return a.signal},dispose(){if(!c){c=!0,a.abort();for(let e of s)e();s.clear(),i?.()}},get disposed(){return c},get name(){return e},on(t,r){if(c)throw new o(`Channel "${e}"`);let i=n(e,t,r);return s.add(i),()=>{i(),s.delete(i)}},once(t,r){if(c)throw new o(`Channel "${e}"`);let i=()=>{};return i=n(e,t,e=>{s.delete(i),i(),r(e)}),s.add(i),()=>{s.delete(i),i()}},send(n,r){if(c)throw new o(`Channel "${e}"`);t(e,n,r)},[Symbol.dispose](){this.dispose()},wait(t,r){return h(t,a.signal,r,(t,r)=>n(e,t,r))}}}function _(e){return JSON.stringify(e)}function v(e){if(typeof e!=`string`)throw new s(`Expected string message data`,e);let t;try{t=JSON.parse(e)}catch{throw new s(`JSON parse error`,e)}if(typeof t!=`object`||!t||!(`type`in t)||typeof t.type!=`string`)throw new s(`Missing or invalid "type" field`,t);return t}function y(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function b(e,t){if(!y(e))return null;let n=e[t];return typeof n==`string`?n:null}function x(e){return b(e,`room`)!==null}function S(e){return b(e,`code`)!==null&&b(e,`message`)!==null}function C(e){return!y(e)||b(e,`event`)===null||!(`payload`in e)?!1:e.channel===void 0||typeof e.channel==`string`}function w(e){return y(e)&&typeof e.ts==`number`}function T(e){return y(e)&&b(e,`id`)!==null&&b(e,`room`)!==null&&`state`in e}function E(e){return b(e,`id`)!==null&&b(e,`room`)!==null}function D(e){return y(e)&&b(e,`room`)!==null&&y(e.members)}function O(e){return b(e,`channel`)!==null}function k(e){let t=v(e);switch(t.type){case`error`:if(S(t))return{frame:t,kind:`known`};break;case`joined`:if(x(t))return{frame:t,kind:`known`};break;case`left`:if(x(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(O(t))return{frame:t,kind:`known`};break;default:return{kind:`unknown`,type:t.type}}throw new s(`Malformed "${t.type}" frame`,t)}var A=3e4,j=5e3;function M(e,t,n){if(!e)return{onPong(){},start(){},stop(){}};let r=e===!0?{}:e,i=r.interval??A,a=r.timeout??j,o,s,c=!1;function l(){o=setTimeout(()=>{t(_({ts:Date.now(),type:`ping`})),s=setTimeout(()=>{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 N(r){let s=(0,e.signal)(new Set),l=new Map;function d(e){let t=l.get(e);return t||(t={confirmed:!1,joinSent:!1,leaveAfterJoin:!1,localPresence:void 0,refs:0,scopes:new Set},l.set(e,t)),t}function f(e,t){let n=new Set(s.value);t?n.add(e):n.delete(e),s.value=n}function p(e){r.send(_({room:e,type:`join`}))}function m(e){r.send(_({room:e,type:`leave`}))}function h(e,t){let i=l.get(e);if(!i)throw new o(`Room "${e}"`);if(!r.isOpen())throw new n(`Connection is not open`,e);r.send(_({room:e,state:t,type:`presence`})),i.localPresence=t}function g(e,t){let n=l.get(e);!n||!n.scopes.has(t)||(n.scopes.delete(t),n.refs--,n.refs===0&&(n.confirmed?(n.confirmed=!1,n.localPresence=void 0,f(e,!1),r.isOpen()&&m(e),l.delete(e)):n.joinSent?n.leaveAfterJoin=!0:l.delete(e)))}return{createScope(s,l,f){if(r.disposalSignal.aborted)return F(s,l);let m=d(s),_=u(r.disposalSignal),v,y,b=!1,x=new Promise((e,t)=>{v=()=>{b||(b=!0,clearTimeout(S),w.removeEventListener(`abort`,A),e())},y=e=>{b||(b=!0,clearTimeout(S),w.removeEventListener(`abort`,A),t(e))}});x.catch(()=>{});let S,C=[_.signal];if(f?.signal&&C.push(f.signal),f?.timeout!==void 0){let e=new AbortController;S=setTimeout(()=>e.abort(new i(s)),f.timeout),C.push(e.signal)}let w=C.length===1?C[0]:c(C[0],...C.slice(1)),T,E,D=new Set,O=new Set;l&&(T=(0,e.signal)(new Map),E={onJoin(e,t){let n=new Map(T.value);n.set(e,t),T.value=n;for(let n of D)n(e,t)},onLeave(e){let t=new Map(T.value);t.delete(e),T.value=t;for(let t of O)t(e)},onState(e){let t=new Map;for(let[n,r]of Object.entries(e))t.set(n,r);T.value=t},reset(){T.value=new Map}});let k={isSettled:()=>b,joined:x,name:s,presenceHandlers:E,rejectJoined:y,resolveJoined:v},A=()=>{clearTimeout(S),_.abort();let e=w.reason,n=e instanceof t?e:new a;k.rejectJoined(n),g(s,k)};if(m.refs++,m.scopes.add(k),w.aborted)return A(),P(k,_.signal,T,D,O,s,h);if(w.addEventListener(`abort`,A,{once:!0}),m.confirmed)k.resolveJoined();else if(!m.joinSent&&r.isOpen()){m.joinSent=!0;try{p(s)}catch(e){m.joinSent=!1,k.rejectJoined(e instanceof t?e:new n(`Failed to send join`,s))}}return P(k,_.signal,T,D,O,s,h,()=>{_.signal.aborted||(clearTimeout(S),w.removeEventListener(`abort`,A),_.abort(),b||k.rejectJoined(new o(`Room "${s}"`)),g(s,k))})},dispose(){s.value=new Set,l.clear()},handleJoined(e){let t=l.get(e);if(t){if(t.confirmed=!0,t.joinSent=!1,f(e,!0),t.leaveAfterJoin){t.leaveAfterJoin=!1,t.confirmed=!1,f(e,!1),r.isOpen()&&m(e),l.delete(e);return}for(let e of t.scopes)e.isSettled()||e.resolveJoined()}},handleLeft(e){let t=l.get(e);t&&(t.confirmed=!1,t.joinSent=!1,f(e,!1))},handlePresenceJoin(e,t,n){let r=l.get(e);if(r)for(let e of r.scopes)e.presenceHandlers?.onJoin(t,n)},handlePresenceLeave(e,t){let n=l.get(e);if(n)for(let e of n.scopes)e.presenceHandlers?.onLeave(t)},handlePresenceState(e,t){let n=l.get(e);if(n)for(let e of n.scopes)e.presenceHandlers?.onState(t)},rejectAll(e){for(let t of l.values())for(let n of t.scopes)n.isSettled()||n.rejectJoined(e)},reset(){s.value=new Set;for(let[e,t]of l){if(t.refs===0){l.delete(e);continue}t.confirmed=!1,t.joinSent=!1;for(let e of t.scopes)e.presenceHandlers?.reset()}},restore(){for(let[e,t]of l)if(t.refs!==0){t.confirmed=!1,t.joinSent=!0;try{p(e)}catch{t.joinSent=!1}if(t.localPresence!==void 0)try{r.send(_({room:e,state:t.localPresence,type:`presence`}))}catch{}}},rooms:s}}function P(e,t,n,r,i,a,s,c){let l=c??(()=>{});return n?{get disposalSignal(){return t},dispose:l,get disposed(){return t.aborted},get joined(){return e.joined},get name(){return e.name},onJoin(e){if(t.aborted)throw new o(`Room "${a}"`);return r.add(e),()=>r.delete(e)},onLeave(e){if(t.aborted)throw new o(`Room "${a}"`);return i.add(e),()=>i.delete(e)},get presence(){return n},updatePresence(e){s(a,e)},[Symbol.dispose]:l}:{get disposalSignal(){return t},dispose:l,get disposed(){return t.aborted},get joined(){return e.joined},get name(){return e.name},[Symbol.dispose]:l}}function F(t,n){let r=new AbortController;r.abort(new o);let i=Promise.reject(new o(`Room "${t}"`));return i.catch(()=>{}),n?{disposalSignal:r.signal,dispose(){},disposed:!0,joined:i,name:t,onJoin(){throw new o(`Room "${t}"`)},onLeave(){throw new o(`Room "${t}"`)},presence:(0,e.signal)(new Map),updatePresence(){throw new o(`Room "${t}"`)},[Symbol.dispose](){}}:{disposalSignal:r.signal,dispose(){},disposed:!0,joined:i,name:t,[Symbol.dispose](){}}}var I=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)}}clear(){this.map.clear()}dispatch(e,t,n){for(let r of this.map.get(e)?.get(t)??[])r(n)}};function L(r,i={}){let a=new AbortController,c=(0,e.signal)(`closed`),l=new I,u=new Map,d=!1,f=!0;function p(e){i.onError?.(e)}function v(e){C.send(e)}let y=N({disposalSignal:a.signal,isOpen:()=>C.open,send:v});function b(){for(let e of u.keys())v(_({channel:e,type:`subscribe`}));y.restore()}function x(){y.reset()}function S(){f||(f=!0,w.stop(),x(),y.rejectAll(new n(`Connection closed before room confirmation`,r)))}let C=m(r,i.protocols,i.reconnect,{onClose(){S()},onError:p,onMessage(e){let t;try{t=k(e.data)}catch(t){p(t instanceof s?t:new s(`Failed to decode frame`,e.data));return}if(t.kind===`unknown`){p(new s(`Unknown frame type "${t.type}"`,e.data));return}try{T(t)}catch(e){p(new s(`Frame handler threw`,t.frame,{cause:e}))}},onOpen(){f=!1;try{b(),w.start()}catch(e){p(e instanceof t?e:new n(`Session restore failed`,r))}},onStatus(e){c.value=e}}),w=M(i.heartbeat,e=>{C.open&&v(e)},()=>C.forceReconnect(4e3,`heartbeat timeout`));function T(e){let t=e.frame;switch(t.type){case`error`:p(new s(`Server error [${t.code}]: ${t.message}`,t));break;case`joined`:y.handleJoined(t.room);break;case`left`:y.handleLeft(t.room);break;case`message`:l.dispatch(t.channel??null,t.event,t.payload);break;case`pong`:w.onPong();break;case`presence_join`:y.handlePresenceJoin(t.room,t.id,t.state);break;case`presence_leave`:y.handlePresenceLeave(t.room,t.id);break;case`presence_state`:y.handlePresenceState(t.room,t.members)}}function E(e){let t=u.get(e)??0;u.set(e,t+1),t===0&&C.open&&v(_({channel:e,type:`subscribe`}))}function D(e){let t=u.get(e);if(t){if(t>1){u.set(e,t-1);return}u.delete(e),C.open&&v(_({channel:e,type:`unsubscribe`}))}}return{channel(e){if(d)throw new o;return E(e),g(e,(e,t,n)=>{let r=i.transform?i.transform({channel:e,event:t,payload:n}):{channel:e,event:t,payload:n};r!==null&&v(_({...r,type:`message`}))},(e,t,n)=>l.add(e,t,n),a.signal,()=>D(e))},connect(){return d?Promise.reject(new o):C.connect()},disconnect(e=1e3,t=``){d||(S(),C.disconnect(e,t))},get disposalSignal(){return a.signal},dispose(){d||(d=!0,w.stop(),f=!0,y.reset(),y.rejectAll(new o),C.dispose(),u.clear(),y.dispose(),l.clear(),a.abort())},get disposed(){return d},on(e,t){if(d)throw new o;return l.add(null,e,t)},once(e,t){if(d)throw new o;let n=()=>{};return n=l.add(null,e,e=>{n(),t(e)}),n},room(e,t){if(d)throw new o;return y.createScope(e,!0,t)},get rooms(){return y.rooms},send(e,t){if(d)throw new o;let n=i.transform?i.transform({event:e,payload:t}):{event:e,payload:t};n!==null&&v(_({...n,type:`message`}))},get status(){return c},[Symbol.dispose](){this.dispose()},wait(e,t){return h(e,a.signal,t,(e,t)=>l.add(null,e,t))}}}exports.PulseAbortError=a,exports.PulseConnectionError=n,exports.PulseDisposedError=o,exports.PulseError=t,exports.PulseProtocolError=s,exports.PulseRoomTimeoutError=i,exports.PulseTimeoutError=r,exports.createPulse=L;
2
2
  //# sourceMappingURL=pulse.cjs.map