@vielzeug/pulse 1.0.3
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 +68 -0
- package/dist/_dev.cjs +2 -0
- package/dist/_dev.cjs.map +1 -0
- package/dist/_dev.d.ts +2 -0
- package/dist/_dev.d.ts.map +1 -0
- package/dist/_dev.js +9 -0
- package/dist/_dev.js.map +1 -0
- package/dist/_utils.cjs +2 -0
- package/dist/_utils.cjs.map +1 -0
- package/dist/_utils.d.ts +2 -0
- package/dist/_utils.d.ts.map +1 -0
- package/dist/_utils.js +36 -0
- package/dist/_utils.js.map +1 -0
- package/dist/_wait.cjs +2 -0
- package/dist/_wait.cjs.map +1 -0
- package/dist/_wait.d.ts +2 -0
- package/dist/_wait.d.ts.map +1 -0
- package/dist/_wait.js +29 -0
- package/dist/_wait.js.map +1 -0
- package/dist/channel.cjs +2 -0
- package/dist/channel.cjs.map +1 -0
- package/dist/channel.d.ts +2 -0
- package/dist/channel.d.ts.map +1 -0
- package/dist/channel.js +58 -0
- package/dist/channel.js.map +1 -0
- package/dist/errors.cjs +2 -0
- package/dist/errors.cjs.map +1 -0
- package/dist/errors.d.ts +45 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +36 -0
- package/dist/errors.js.map +1 -0
- package/dist/heartbeat.cjs +2 -0
- package/dist/heartbeat.cjs.map +1 -0
- package/dist/heartbeat.d.ts +9 -0
- package/dist/heartbeat.d.ts.map +1 -0
- package/dist/heartbeat.js +37 -0
- package/dist/heartbeat.js.map +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/presence.cjs +2 -0
- package/dist/presence.cjs.map +1 -0
- package/dist/presence.d.ts +8 -0
- package/dist/presence.d.ts.map +1 -0
- package/dist/presence.js +71 -0
- package/dist/presence.js.map +1 -0
- package/dist/protocol.cjs +2 -0
- package/dist/protocol.cjs.map +1 -0
- package/dist/protocol.d.ts +81 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +20 -0
- package/dist/protocol.js.map +1 -0
- package/dist/pulse.cjs +2 -0
- package/dist/pulse.cjs.map +1 -0
- package/dist/pulse.d.ts +16 -0
- package/dist/pulse.d.ts.map +1 -0
- package/dist/pulse.iife.js +2 -0
- package/dist/pulse.iife.js.map +1 -0
- package/dist/pulse.js +2 -0
- package/dist/pulse.js.map +1 -0
- package/dist/reconnect.cjs +2 -0
- package/dist/reconnect.cjs.map +1 -0
- package/dist/reconnect.d.ts +10 -0
- package/dist/reconnect.d.ts.map +1 -0
- package/dist/reconnect.js +29 -0
- package/dist/reconnect.js.map +1 -0
- package/dist/types.d.ts +243 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# @vielzeug/pulse
|
|
2
|
+
|
|
3
|
+
Typed WebSocket client with channel multiplexing, room management, reactive presence, auto-reconnect, and heartbeat — built on `@vielzeug/ripple` signals.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pnpm add @vielzeug/pulse @vielzeug/ripple
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { createPulse } from '@vielzeug/pulse';
|
|
15
|
+
import { effect } from '@vielzeug/ripple';
|
|
16
|
+
|
|
17
|
+
type ServerEvents = {
|
|
18
|
+
'chat:message': { user: string; text: string };
|
|
19
|
+
'user:joined': { userId: string };
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
type ClientEvents = {
|
|
23
|
+
'chat:send': { text: string };
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const pulse = createPulse<ServerEvents, ClientEvents>('wss://api.example.com/ws', {
|
|
27
|
+
reconnect: { maxAttempts: 5 },
|
|
28
|
+
heartbeat: true,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Reactive status via ripple signal
|
|
32
|
+
effect(() => console.log('status:', pulse.status.value));
|
|
33
|
+
|
|
34
|
+
// Typed server events
|
|
35
|
+
pulse.on('chat:message', ({ user, text }) => console.log(`${user}: ${text}`));
|
|
36
|
+
|
|
37
|
+
// Typed client messages
|
|
38
|
+
pulse.send('chat:send', { text: 'Hello!' });
|
|
39
|
+
|
|
40
|
+
// Isolated channel namespace
|
|
41
|
+
const notif = pulse.channel<{ alert: { level: string; msg: string } }>('notifications');
|
|
42
|
+
notif.on('alert', ({ level, msg }) => console.log(`[${level}] ${msg}`));
|
|
43
|
+
|
|
44
|
+
// Reactive presence tracking
|
|
45
|
+
const lobby = pulse.presence<{ name: string; status: string }>('lobby');
|
|
46
|
+
effect(() => console.log('online:', [...lobby.state.value.keys()]));
|
|
47
|
+
lobby.update({ name: 'Alice', status: 'active' });
|
|
48
|
+
|
|
49
|
+
// Clean disposal
|
|
50
|
+
using _ = pulse;
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Features
|
|
54
|
+
|
|
55
|
+
- **Typed event maps** — `TServer` and `TClient` generics enforce payload types at compile time
|
|
56
|
+
- **`on()` / `once()` / `wait()`** — persistent, one-shot, and async-await subscriptions
|
|
57
|
+
- **`channel()`** — isolated namespaces multiplexed over the shared connection
|
|
58
|
+
- **`join()` / `leave()`** — room membership with server-confirmation promises
|
|
59
|
+
- **`presence()`** — reactive `Signal<Map<memberId, T>>` with `onJoin`/`onLeave` callbacks
|
|
60
|
+
- **Middleware** — intercept outgoing `send()` calls; omit `next()` to suppress
|
|
61
|
+
- **Auto-reconnect** — exponential backoff, configurable `maxAttempts` and `delay`
|
|
62
|
+
- **Heartbeat** — ping/pong keep-alive with dead-connection detection
|
|
63
|
+
- **Reactive signals** — `pulse.status` and `pulse.rooms` are ripple `Reactive`s
|
|
64
|
+
- **`dispose()` + `[Symbol.dispose]`** — deterministic teardown via `using` declarations
|
|
65
|
+
|
|
66
|
+
## Documentation
|
|
67
|
+
|
|
68
|
+
Full docs at [vielzeug.dev/pulse](https://vielzeug.dev/pulse/).
|
package/dist/_dev.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_dev.cjs","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["const isDev = !(globalThis as { __PULSE_PROD__?: boolean }).__PULSE_PROD__;\n\n/** @internal */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/pulse] ${msg}`);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() / error() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n"],"mappings":"AAAA,IAAM,EAAQ,CAAE,WAA4C,eAG5D,SAAgB,EAAK,EAAmB,CAClC,GAAO,QAAQ,KAAK,qBAAqB,GAAK,CACpD"}
|
package/dist/_dev.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_dev.d.ts","sourceRoot":"","sources":["../src/_dev.ts"],"names":[],"mappings":""}
|
package/dist/_dev.js
ADDED
package/dist/_dev.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_dev.js","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["const isDev = !(globalThis as { __PULSE_PROD__?: boolean }).__PULSE_PROD__;\n\n/** @internal */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/pulse] ${msg}`);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() / error() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n"],"mappings":";AAAA,IAAM,IAAQ,CAAE,WAA4C;AAG5D,SAAgB,EAAK,GAAmB;CACtC,AAAI,KAAO,QAAQ,KAAK,qBAAqB,GAAK;AACpD"}
|
package/dist/_utils.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function e(e,...n){return n.length===0?e:n.reduce(t,e)}function t(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 n(e){let t=new AbortController;return e.aborted?t.abort(e.reason):e.addEventListener(`abort`,()=>t.abort(e.reason),{once:!0}),t}function r(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 i(e,t=3e4){let n=Math.min(1e3*2**e,t);return Math.random()*n}exports.combineSignals=e,exports.defaultReconnectDelay=i,exports.deriveAbortController=n,exports.sleep=r;
|
|
2
|
+
//# sourceMappingURL=_utils.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_utils.cjs","names":[],"sources":["../src/_utils.ts"],"sourcesContent":["/**\n * Returns a signal that aborts as soon as any of the provided signals abort.\n * With a single argument, returns it directly (no allocation).\n * Cleanup listeners are registered to prevent leaks.\n *\n * @internal\n */\nexport function combineSignals(first: AbortSignal, ...rest: AbortSignal[]): AbortSignal {\n if (rest.length === 0) return first;\n\n return rest.reduce(mergeTwo, first);\n}\n\nfunction mergeTwo(a: AbortSignal, b: AbortSignal): AbortSignal {\n if (a.aborted) return a;\n\n if (b.aborted) return b;\n\n const ctrl = new AbortController();\n const onA = (): void => ctrl.abort(a.reason);\n const onB = (): void => ctrl.abort(b.reason);\n\n a.addEventListener('abort', onA, { once: true });\n b.addEventListener('abort', onB, { once: true });\n ctrl.signal.addEventListener(\n 'abort',\n () => {\n a.removeEventListener('abort', onA);\n b.removeEventListener('abort', onB);\n },\n { once: true },\n );\n\n return ctrl.signal;\n}\n\n/**\n * Create a child `AbortController` that aborts as soon as `parent` does.\n * Handles the case where `parent` is already aborted at call time — a plain\n * `addEventListener('abort', ...)` would miss that, since the event already fired.\n *\n * @internal\n */\nexport function deriveAbortController(parent: AbortSignal): AbortController {\n const ctrl = new AbortController();\n\n if (parent.aborted) {\n ctrl.abort(parent.reason);\n } else {\n parent.addEventListener('abort', () => ctrl.abort(parent.reason), { once: true });\n }\n\n return ctrl;\n}\n\n/**\n * Sleep for `ms` milliseconds, aborting early when the signal fires.\n * Resolves (does not reject) on abort — callers check `signal.aborted`.\n *\n * @internal\n */\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve) => {\n if (signal?.aborted) {\n resolve();\n\n return;\n }\n\n const id = setTimeout(resolve, ms);\n\n signal?.addEventListener(\n 'abort',\n () => {\n clearTimeout(id);\n resolve();\n },\n { once: true },\n );\n });\n}\n\n/**\n * Full-jitter exponential backoff in ms, capped at `maxMs`.\n * `attempt` is zero-based.\n *\n * @internal\n */\nexport function defaultReconnectDelay(attempt: number, maxMs = 30_000): number {\n const base = Math.min(1000 * Math.pow(2, attempt), maxMs);\n\n return Math.random() * base;\n}\n"],"mappings":"AAOA,SAAgB,EAAe,EAAoB,GAAG,EAAkC,CAGtF,OAFI,EAAK,SAAW,EAAU,EAEvB,EAAK,OAAO,EAAU,CAAK,CACpC,CAEA,SAAS,EAAS,EAAgB,EAA6B,CAC7D,GAAI,EAAE,QAAS,OAAO,EAEtB,GAAI,EAAE,QAAS,OAAO,EAEtB,IAAM,EAAO,IAAI,gBACX,MAAkB,EAAK,MAAM,EAAE,MAAM,EACrC,MAAkB,EAAK,MAAM,EAAE,MAAM,EAa3C,OAXA,EAAE,iBAAiB,QAAS,EAAK,CAAE,KAAM,EAAK,CAAC,EAC/C,EAAE,iBAAiB,QAAS,EAAK,CAAE,KAAM,EAAK,CAAC,EAC/C,EAAK,OAAO,iBACV,YACM,CACJ,EAAE,oBAAoB,QAAS,CAAG,EAClC,EAAE,oBAAoB,QAAS,CAAG,CACpC,EACA,CAAE,KAAM,EAAK,CACf,EAEO,EAAK,MACd,CASA,SAAgB,EAAsB,EAAsC,CAC1E,IAAM,EAAO,IAAI,gBAQjB,OANI,EAAO,QACT,EAAK,MAAM,EAAO,MAAM,EAExB,EAAO,iBAAiB,YAAe,EAAK,MAAM,EAAO,MAAM,EAAG,CAAE,KAAM,EAAK,CAAC,EAG3E,CACT,CAQA,SAAgB,EAAM,EAAY,EAAqC,CACrE,OAAO,IAAI,QAAS,GAAY,CAC9B,GAAI,GAAQ,QAAS,CACnB,EAAQ,EAER,MACF,CAEA,IAAM,EAAK,WAAW,EAAS,CAAE,EAEjC,GAAQ,iBACN,YACM,CACJ,aAAa,CAAE,EACf,EAAQ,CACV,EACA,CAAE,KAAM,EAAK,CACf,CACF,CAAC,CACH,CAQA,SAAgB,EAAsB,EAAiB,EAAQ,IAAgB,CAC7E,IAAM,EAAO,KAAK,IAAI,IAAgB,GAAG,EAAU,CAAK,EAExD,OAAO,KAAK,OAAO,EAAI,CACzB"}
|
package/dist/_utils.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_utils.d.ts","sourceRoot":"","sources":["../src/_utils.ts"],"names":[],"mappings":""}
|
package/dist/_utils.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
//#region src/_utils.ts
|
|
2
|
+
function e(e, ...n) {
|
|
3
|
+
return n.length === 0 ? e : n.reduce(t, e);
|
|
4
|
+
}
|
|
5
|
+
function t(e, t) {
|
|
6
|
+
if (e.aborted) return e;
|
|
7
|
+
if (t.aborted) return t;
|
|
8
|
+
let n = new AbortController(), r = () => n.abort(e.reason), i = () => n.abort(t.reason);
|
|
9
|
+
return e.addEventListener("abort", r, { once: !0 }), t.addEventListener("abort", i, { once: !0 }), n.signal.addEventListener("abort", () => {
|
|
10
|
+
e.removeEventListener("abort", r), t.removeEventListener("abort", i);
|
|
11
|
+
}, { once: !0 }), n.signal;
|
|
12
|
+
}
|
|
13
|
+
function n(e) {
|
|
14
|
+
let t = new AbortController();
|
|
15
|
+
return e.aborted ? t.abort(e.reason) : e.addEventListener("abort", () => t.abort(e.reason), { once: !0 }), t;
|
|
16
|
+
}
|
|
17
|
+
function r(e, t) {
|
|
18
|
+
return new Promise((n) => {
|
|
19
|
+
if (t?.aborted) {
|
|
20
|
+
n();
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
let r = setTimeout(n, e);
|
|
24
|
+
t?.addEventListener("abort", () => {
|
|
25
|
+
clearTimeout(r), n();
|
|
26
|
+
}, { once: !0 });
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function i(e, t = 3e4) {
|
|
30
|
+
let n = Math.min(1e3 * 2 ** e, t);
|
|
31
|
+
return Math.random() * n;
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
export { e as combineSignals, i as defaultReconnectDelay, n as deriveAbortController, r as sleep };
|
|
35
|
+
|
|
36
|
+
//# sourceMappingURL=_utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_utils.js","names":[],"sources":["../src/_utils.ts"],"sourcesContent":["/**\n * Returns a signal that aborts as soon as any of the provided signals abort.\n * With a single argument, returns it directly (no allocation).\n * Cleanup listeners are registered to prevent leaks.\n *\n * @internal\n */\nexport function combineSignals(first: AbortSignal, ...rest: AbortSignal[]): AbortSignal {\n if (rest.length === 0) return first;\n\n return rest.reduce(mergeTwo, first);\n}\n\nfunction mergeTwo(a: AbortSignal, b: AbortSignal): AbortSignal {\n if (a.aborted) return a;\n\n if (b.aborted) return b;\n\n const ctrl = new AbortController();\n const onA = (): void => ctrl.abort(a.reason);\n const onB = (): void => ctrl.abort(b.reason);\n\n a.addEventListener('abort', onA, { once: true });\n b.addEventListener('abort', onB, { once: true });\n ctrl.signal.addEventListener(\n 'abort',\n () => {\n a.removeEventListener('abort', onA);\n b.removeEventListener('abort', onB);\n },\n { once: true },\n );\n\n return ctrl.signal;\n}\n\n/**\n * Create a child `AbortController` that aborts as soon as `parent` does.\n * Handles the case where `parent` is already aborted at call time — a plain\n * `addEventListener('abort', ...)` would miss that, since the event already fired.\n *\n * @internal\n */\nexport function deriveAbortController(parent: AbortSignal): AbortController {\n const ctrl = new AbortController();\n\n if (parent.aborted) {\n ctrl.abort(parent.reason);\n } else {\n parent.addEventListener('abort', () => ctrl.abort(parent.reason), { once: true });\n }\n\n return ctrl;\n}\n\n/**\n * Sleep for `ms` milliseconds, aborting early when the signal fires.\n * Resolves (does not reject) on abort — callers check `signal.aborted`.\n *\n * @internal\n */\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve) => {\n if (signal?.aborted) {\n resolve();\n\n return;\n }\n\n const id = setTimeout(resolve, ms);\n\n signal?.addEventListener(\n 'abort',\n () => {\n clearTimeout(id);\n resolve();\n },\n { once: true },\n );\n });\n}\n\n/**\n * Full-jitter exponential backoff in ms, capped at `maxMs`.\n * `attempt` is zero-based.\n *\n * @internal\n */\nexport function defaultReconnectDelay(attempt: number, maxMs = 30_000): number {\n const base = Math.min(1000 * Math.pow(2, attempt), maxMs);\n\n return Math.random() * base;\n}\n"],"mappings":";AAOA,SAAgB,EAAe,GAAoB,GAAG,GAAkC;CAGtF,OAFI,EAAK,WAAW,IAAU,IAEvB,EAAK,OAAO,GAAU,CAAK;AACpC;AAEA,SAAS,EAAS,GAAgB,GAA6B;CAC7D,IAAI,EAAE,SAAS,OAAO;CAEtB,IAAI,EAAE,SAAS,OAAO;CAEtB,IAAM,IAAO,IAAI,gBAAgB,GAC3B,UAAkB,EAAK,MAAM,EAAE,MAAM,GACrC,UAAkB,EAAK,MAAM,EAAE,MAAM;CAa3C,OAXA,EAAE,iBAAiB,SAAS,GAAK,EAAE,MAAM,GAAK,CAAC,GAC/C,EAAE,iBAAiB,SAAS,GAAK,EAAE,MAAM,GAAK,CAAC,GAC/C,EAAK,OAAO,iBACV,eACM;EAEJ,AADA,EAAE,oBAAoB,SAAS,CAAG,GAClC,EAAE,oBAAoB,SAAS,CAAG;CACpC,GACA,EAAE,MAAM,GAAK,CACf,GAEO,EAAK;AACd;AASA,SAAgB,EAAsB,GAAsC;CAC1E,IAAM,IAAO,IAAI,gBAAgB;CAQjC,OANI,EAAO,UACT,EAAK,MAAM,EAAO,MAAM,IAExB,EAAO,iBAAiB,eAAe,EAAK,MAAM,EAAO,MAAM,GAAG,EAAE,MAAM,GAAK,CAAC,GAG3E;AACT;AAQA,SAAgB,EAAM,GAAY,GAAqC;CACrE,OAAO,IAAI,SAAS,MAAY;EAC9B,IAAI,GAAQ,SAAS;GACnB,EAAQ;GAER;EACF;EAEA,IAAM,IAAK,WAAW,GAAS,CAAE;EAEjC,GAAQ,iBACN,eACM;GAEJ,AADA,aAAa,CAAE,GACf,EAAQ;EACV,GACA,EAAE,MAAM,GAAK,CACf;CACF,CAAC;AACH;AAQA,SAAgB,EAAsB,GAAiB,IAAQ,KAAgB;CAC7E,IAAM,IAAO,KAAK,IAAI,MAAgB,KAAG,GAAU,CAAK;CAExD,OAAO,KAAK,OAAO,IAAI;AACzB"}
|
package/dist/_wait.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("./errors.cjs"),t=require("./_utils.cjs");function n(n,r,i,a){return new Promise((o,s)=>{let c=[r],l;if(i?.signal&&c.push(i.signal),i?.timeout!==void 0){let t=new AbortController;l=setTimeout(()=>{t.abort(new e.PulseTimeoutError(n))},i.timeout),c.push(t.signal)}let u=c.length===1?c[0]:t.combineSignals(c[0],...c.slice(1));if(u.aborted){clearTimeout(l),s(u.reason instanceof e.PulseTimeoutError?u.reason:new e.PulseAbortError);return}let d=()=>{},f=()=>{clearTimeout(l),d(),s(u.reason instanceof e.PulseTimeoutError?u.reason:new e.PulseAbortError)};u.addEventListener(`abort`,f,{once:!0}),d=a(n,e=>{clearTimeout(l),u.removeEventListener(`abort`,f),o(e)})})}exports.createWaitPromise=n;
|
|
2
|
+
//# sourceMappingURL=_wait.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_wait.cjs","names":[],"sources":["../src/_wait.ts"],"sourcesContent":["import type { Unsubscribe } from './types';\n\nimport { combineSignals } from './_utils';\nimport { PulseAbortError, PulseTimeoutError } from './errors';\n\n/**\n * Shared implementation for `pulse.wait()` and `channel.wait()`.\n * Resolves on the next event dispatched via `subscribe`, or rejects when\n * the combined signal aborts or the optional timeout elapses.\n *\n * @internal\n */\nexport function createWaitPromise<T>(\n event: string,\n baseSignal: AbortSignal,\n opts: { signal?: AbortSignal; timeout?: number } | undefined,\n subscribe: (event: string, handler: (payload: unknown) => void) => Unsubscribe,\n): Promise<T> {\n return new Promise((resolve, reject) => {\n const signals: AbortSignal[] = [baseSignal];\n\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n if (opts?.signal) signals.push(opts.signal);\n\n if (opts?.timeout !== undefined) {\n const timeoutCtrl = new AbortController();\n\n timeoutId = setTimeout(() => {\n timeoutCtrl.abort(new PulseTimeoutError(event));\n }, opts.timeout);\n signals.push(timeoutCtrl.signal);\n }\n\n const combined = signals.length === 1 ? signals[0]! : combineSignals(signals[0]!, ...signals.slice(1));\n\n if (combined.aborted) {\n clearTimeout(timeoutId);\n reject(combined.reason instanceof PulseTimeoutError ? combined.reason : new PulseAbortError());\n\n return;\n }\n\n let unsub: Unsubscribe = () => {};\n\n const onAbort = (): void => {\n clearTimeout(timeoutId);\n unsub();\n reject(combined.reason instanceof PulseTimeoutError ? combined.reason : new PulseAbortError());\n };\n\n combined.addEventListener('abort', onAbort, { once: true });\n\n unsub = subscribe(event, (payload) => {\n clearTimeout(timeoutId);\n combined.removeEventListener('abort', onAbort);\n resolve(payload as T);\n });\n });\n}\n"],"mappings":"0DAYA,SAAgB,EACd,EACA,EACA,EACA,EACY,CACZ,OAAO,IAAI,SAAS,EAAS,IAAW,CACtC,IAAM,EAAyB,CAAC,CAAU,EAEtC,EAIJ,GAFI,GAAM,QAAQ,EAAQ,KAAK,EAAK,MAAM,EAEtC,GAAM,UAAY,IAAA,GAAW,CAC/B,IAAM,EAAc,IAAI,gBAExB,EAAY,eAAiB,CAC3B,EAAY,MAAM,IAAI,EAAA,kBAAkB,CAAK,CAAC,CAChD,EAAG,EAAK,OAAO,EACf,EAAQ,KAAK,EAAY,MAAM,CACjC,CAEA,IAAM,EAAW,EAAQ,SAAW,EAAI,EAAQ,GAAM,EAAA,eAAe,EAAQ,GAAK,GAAG,EAAQ,MAAM,CAAC,CAAC,EAErG,GAAI,EAAS,QAAS,CACpB,aAAa,CAAS,EACtB,EAAO,EAAS,kBAAkB,EAAA,kBAAoB,EAAS,OAAS,IAAI,EAAA,eAAiB,EAE7F,MACF,CAEA,IAAI,MAA2B,CAAC,EAE1B,MAAsB,CAC1B,aAAa,CAAS,EACtB,EAAM,EACN,EAAO,EAAS,kBAAkB,EAAA,kBAAoB,EAAS,OAAS,IAAI,EAAA,eAAiB,CAC/F,EAEA,EAAS,iBAAiB,QAAS,EAAS,CAAE,KAAM,EAAK,CAAC,EAE1D,EAAQ,EAAU,EAAQ,GAAY,CACpC,aAAa,CAAS,EACtB,EAAS,oBAAoB,QAAS,CAAO,EAC7C,EAAQ,CAAY,CACtB,CAAC,CACH,CAAC,CACH"}
|
package/dist/_wait.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_wait.d.ts","sourceRoot":"","sources":["../src/_wait.ts"],"names":[],"mappings":""}
|
package/dist/_wait.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { PulseAbortError as e, PulseTimeoutError as t } from "./errors.js";
|
|
2
|
+
import { combineSignals as n } from "./_utils.js";
|
|
3
|
+
//#region src/_wait.ts
|
|
4
|
+
function r(r, i, a, o) {
|
|
5
|
+
return new Promise((s, c) => {
|
|
6
|
+
let l = [i], u;
|
|
7
|
+
if (a?.signal && l.push(a.signal), a?.timeout !== void 0) {
|
|
8
|
+
let e = new AbortController();
|
|
9
|
+
u = setTimeout(() => {
|
|
10
|
+
e.abort(new t(r));
|
|
11
|
+
}, a.timeout), l.push(e.signal);
|
|
12
|
+
}
|
|
13
|
+
let d = l.length === 1 ? l[0] : n(l[0], ...l.slice(1));
|
|
14
|
+
if (d.aborted) {
|
|
15
|
+
clearTimeout(u), c(d.reason instanceof t ? d.reason : new e());
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
let f = () => {}, p = () => {
|
|
19
|
+
clearTimeout(u), f(), c(d.reason instanceof t ? d.reason : new e());
|
|
20
|
+
};
|
|
21
|
+
d.addEventListener("abort", p, { once: !0 }), f = o(r, (e) => {
|
|
22
|
+
clearTimeout(u), d.removeEventListener("abort", p), s(e);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { r as createWaitPromise };
|
|
28
|
+
|
|
29
|
+
//# sourceMappingURL=_wait.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_wait.js","names":[],"sources":["../src/_wait.ts"],"sourcesContent":["import type { Unsubscribe } from './types';\n\nimport { combineSignals } from './_utils';\nimport { PulseAbortError, PulseTimeoutError } from './errors';\n\n/**\n * Shared implementation for `pulse.wait()` and `channel.wait()`.\n * Resolves on the next event dispatched via `subscribe`, or rejects when\n * the combined signal aborts or the optional timeout elapses.\n *\n * @internal\n */\nexport function createWaitPromise<T>(\n event: string,\n baseSignal: AbortSignal,\n opts: { signal?: AbortSignal; timeout?: number } | undefined,\n subscribe: (event: string, handler: (payload: unknown) => void) => Unsubscribe,\n): Promise<T> {\n return new Promise((resolve, reject) => {\n const signals: AbortSignal[] = [baseSignal];\n\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n if (opts?.signal) signals.push(opts.signal);\n\n if (opts?.timeout !== undefined) {\n const timeoutCtrl = new AbortController();\n\n timeoutId = setTimeout(() => {\n timeoutCtrl.abort(new PulseTimeoutError(event));\n }, opts.timeout);\n signals.push(timeoutCtrl.signal);\n }\n\n const combined = signals.length === 1 ? signals[0]! : combineSignals(signals[0]!, ...signals.slice(1));\n\n if (combined.aborted) {\n clearTimeout(timeoutId);\n reject(combined.reason instanceof PulseTimeoutError ? combined.reason : new PulseAbortError());\n\n return;\n }\n\n let unsub: Unsubscribe = () => {};\n\n const onAbort = (): void => {\n clearTimeout(timeoutId);\n unsub();\n reject(combined.reason instanceof PulseTimeoutError ? combined.reason : new PulseAbortError());\n };\n\n combined.addEventListener('abort', onAbort, { once: true });\n\n unsub = subscribe(event, (payload) => {\n clearTimeout(timeoutId);\n combined.removeEventListener('abort', onAbort);\n resolve(payload as T);\n });\n });\n}\n"],"mappings":";;;AAYA,SAAgB,EACd,GACA,GACA,GACA,GACY;CACZ,OAAO,IAAI,SAAS,GAAS,MAAW;EACtC,IAAM,IAAyB,CAAC,CAAU,GAEtC;EAIJ,IAFI,GAAM,UAAQ,EAAQ,KAAK,EAAK,MAAM,GAEtC,GAAM,YAAY,KAAA,GAAW;GAC/B,IAAM,IAAc,IAAI,gBAAgB;GAKxC,AAHA,IAAY,iBAAiB;IAC3B,EAAY,MAAM,IAAI,EAAkB,CAAK,CAAC;GAChD,GAAG,EAAK,OAAO,GACf,EAAQ,KAAK,EAAY,MAAM;EACjC;EAEA,IAAM,IAAW,EAAQ,WAAW,IAAI,EAAQ,KAAM,EAAe,EAAQ,IAAK,GAAG,EAAQ,MAAM,CAAC,CAAC;EAErG,IAAI,EAAS,SAAS;GAEpB,AADA,aAAa,CAAS,GACtB,EAAO,EAAS,kBAAkB,IAAoB,EAAS,SAAS,IAAI,EAAgB,CAAC;GAE7F;EACF;EAEA,IAAI,UAA2B,CAAC,GAE1B,UAAsB;GAG1B,AAFA,aAAa,CAAS,GACtB,EAAM,GACN,EAAO,EAAS,kBAAkB,IAAoB,EAAS,SAAS,IAAI,EAAgB,CAAC;EAC/F;EAIA,AAFA,EAAS,iBAAiB,SAAS,GAAS,EAAE,MAAM,GAAK,CAAC,GAE1D,IAAQ,EAAU,IAAQ,MAAY;GAGpC,AAFA,aAAa,CAAS,GACtB,EAAS,oBAAoB,SAAS,CAAO,GAC7C,EAAQ,CAAY;EACtB,CAAC;CACH,CAAC;AACH"}
|
package/dist/channel.cjs
ADDED
|
@@ -0,0 +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;
|
|
2
|
+
//# sourceMappingURL=channel.cjs.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":""}
|
package/dist/channel.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { warn as e } from "./_dev.js";
|
|
2
|
+
import { deriveAbortController as t } from "./_utils.js";
|
|
3
|
+
import { createWaitPromise as n } from "./_wait.js";
|
|
4
|
+
//#region src/channel.ts
|
|
5
|
+
function r(r, i, a, o, s) {
|
|
6
|
+
let c = t(o), l = /* @__PURE__ */ new Set(), u = c.signal.aborted;
|
|
7
|
+
return {
|
|
8
|
+
get disposalSignal() {
|
|
9
|
+
return c.signal;
|
|
10
|
+
},
|
|
11
|
+
dispose() {
|
|
12
|
+
if (!u) {
|
|
13
|
+
u = !0, c.abort();
|
|
14
|
+
for (let e of l) e();
|
|
15
|
+
l.clear(), s?.();
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
get disposed() {
|
|
19
|
+
return u;
|
|
20
|
+
},
|
|
21
|
+
get name() {
|
|
22
|
+
return r;
|
|
23
|
+
},
|
|
24
|
+
on(t, n) {
|
|
25
|
+
if (u) return e(`on('${String(t)}') called on a disposed channel '${r}' — listener ignored`), () => {};
|
|
26
|
+
let i = a(r, t, n);
|
|
27
|
+
return l.add(i), () => {
|
|
28
|
+
i(), l.delete(i);
|
|
29
|
+
};
|
|
30
|
+
},
|
|
31
|
+
once(t, n) {
|
|
32
|
+
if (u) return e(`once('${String(t)}') called on a disposed channel '${r}' — listener ignored`), () => {};
|
|
33
|
+
let i = () => {};
|
|
34
|
+
return i = a(r, t, (e) => {
|
|
35
|
+
l.delete(i), i(), n(e);
|
|
36
|
+
}), l.add(i), () => {
|
|
37
|
+
l.delete(i), i();
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
send(t, n) {
|
|
41
|
+
if (u) {
|
|
42
|
+
e(`send('${String(t)}') called on a disposed channel '${r}' — message dropped`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
i(r, t, n);
|
|
46
|
+
},
|
|
47
|
+
[Symbol.dispose]() {
|
|
48
|
+
this.dispose();
|
|
49
|
+
},
|
|
50
|
+
wait(e, t) {
|
|
51
|
+
return n(e, c.signal, t, (e, t) => a(r, e, t));
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
export { r as createChannel };
|
|
57
|
+
|
|
58
|
+
//# sourceMappingURL=channel.js.map
|
|
@@ -0,0 +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"}
|
package/dist/errors.cjs
ADDED
|
@@ -0,0 +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;
|
|
2
|
+
//# sourceMappingURL=errors.cjs.map
|
|
@@ -0,0 +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,IAAI,OAAO,KACvB,OAAO,eAAe,KAAM,IAAI,OAAO,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"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base class for all pulse errors.
|
|
3
|
+
* Use `instanceof PulseError` to catch any pulse-originated error in one branch.
|
|
4
|
+
*/
|
|
5
|
+
export declare class PulseError extends Error {
|
|
6
|
+
constructor(message: string, opts?: ErrorOptions);
|
|
7
|
+
static is(err: unknown): err is PulseError;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Thrown when a WebSocket connection cannot be established or is lost
|
|
11
|
+
* without triggering auto-reconnect (budget exhausted, or reconnect disabled).
|
|
12
|
+
*/
|
|
13
|
+
export declare class PulseConnectionError extends PulseError {
|
|
14
|
+
readonly url: string;
|
|
15
|
+
constructor(message: string, url: string, opts?: ErrorOptions);
|
|
16
|
+
}
|
|
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.
|
|
20
|
+
*/
|
|
21
|
+
export declare class PulseTimeoutError extends PulseError {
|
|
22
|
+
readonly event: string;
|
|
23
|
+
constructor(event: string, opts?: ErrorOptions);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Thrown when `wait()` is aborted via an AbortSignal before the event fires.
|
|
27
|
+
*/
|
|
28
|
+
export declare class PulseAbortError extends PulseError {
|
|
29
|
+
constructor(opts?: ErrorOptions);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Thrown when a method is called on a disposed Pulse instance or channel.
|
|
33
|
+
*/
|
|
34
|
+
export declare class PulseDisposedError extends PulseError {
|
|
35
|
+
constructor(target?: string, opts?: ErrorOptions);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Thrown when the server sends a frame that cannot be parsed or violates
|
|
39
|
+
* the wire protocol schema.
|
|
40
|
+
*/
|
|
41
|
+
export declare class PulseProtocolError extends PulseError {
|
|
42
|
+
readonly raw: unknown;
|
|
43
|
+
constructor(message: string, raw?: unknown, opts?: ErrorOptions);
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +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"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
//#region src/errors.ts
|
|
2
|
+
var e = class e extends Error {
|
|
3
|
+
constructor(e, t) {
|
|
4
|
+
super(e, t), this.name = new.target.name, Object.setPrototypeOf(this, new.target.prototype);
|
|
5
|
+
}
|
|
6
|
+
static is(t) {
|
|
7
|
+
return t instanceof e;
|
|
8
|
+
}
|
|
9
|
+
}, t = class extends e {
|
|
10
|
+
url;
|
|
11
|
+
constructor(e, t, n) {
|
|
12
|
+
super(e, n), this.url = t;
|
|
13
|
+
}
|
|
14
|
+
}, n = class extends e {
|
|
15
|
+
event;
|
|
16
|
+
constructor(e, t) {
|
|
17
|
+
super(`Timed out waiting for "${e}"`, t), this.event = e;
|
|
18
|
+
}
|
|
19
|
+
}, r = class extends e {
|
|
20
|
+
constructor(e) {
|
|
21
|
+
super("Aborted", e);
|
|
22
|
+
}
|
|
23
|
+
}, i = class extends e {
|
|
24
|
+
constructor(e = "Pulse", t) {
|
|
25
|
+
super(`${e} instance is disposed`, t);
|
|
26
|
+
}
|
|
27
|
+
}, a = class extends e {
|
|
28
|
+
raw;
|
|
29
|
+
constructor(e, t, n) {
|
|
30
|
+
super(e, n), this.raw = t;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
//#endregion
|
|
34
|
+
export { r as PulseAbortError, t as PulseConnectionError, i as PulseDisposedError, e as PulseError, a as PulseProtocolError, n as PulseTimeoutError };
|
|
35
|
+
|
|
36
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +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,IAAI,OAAO,MACvB,OAAO,eAAe,MAAM,IAAI,OAAO,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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("./_dev.cjs"),t=require("./protocol.cjs");var n=3e4,r=5e3;function i(i,a,o){if(!i)return{onPong(){},start(){},stop(){}};let s=i===!0?{}:i,c=s.interval??n,l=s.timeout??r,u,d,f=!1;function p(){u=setTimeout(()=>{a(t.encode({ts:Date.now(),type:`ping`})),d=setTimeout(()=>{e.warn(`Heartbeat timed out after ${l}ms — treating connection as dead`),o()},l)},c)}return{onPong(){clearTimeout(d),d=void 0,f&&p()},start(){f=!0,clearTimeout(u),clearTimeout(d),d=void 0,p()},stop(){f=!1,clearTimeout(u),clearTimeout(d),u=void 0,d=void 0}}}exports.createHeartbeat=i;
|
|
2
|
+
//# sourceMappingURL=heartbeat.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heartbeat.cjs","names":[],"sources":["../src/heartbeat.ts"],"sourcesContent":["import type { HeartbeatOptions } from './types';\n\nimport { warn } from './_dev';\nimport { encode } from './protocol';\n\nconst DEFAULT_INTERVAL = 30_000;\nconst DEFAULT_TIMEOUT = 5_000;\n\nexport type HeartbeatHandle = {\n /** Called when a `pong` frame is received. Resets the timeout timer. */\n onPong(): void;\n /** Start the heartbeat loop. */\n start(): void;\n /** Stop the heartbeat loop and cancel any pending timers. */\n stop(): void;\n};\n\n/**\n * Create a heartbeat manager that sends periodic pings and expects pong replies.\n * Calls `onDead` when a pong is not received within the timeout window.\n * @internal\n */\nexport function createHeartbeat(\n opts: boolean | HeartbeatOptions | undefined,\n send: (frame: string) => void,\n onDead: () => void,\n): HeartbeatHandle {\n if (!opts) {\n return { onPong() {}, start() {}, stop() {} };\n }\n\n const options: HeartbeatOptions = opts === true ? {} : opts;\n const interval = options.interval ?? DEFAULT_INTERVAL;\n const timeout = options.timeout ?? DEFAULT_TIMEOUT;\n\n let pingTimer: ReturnType<typeof setTimeout> | undefined;\n let pongTimer: ReturnType<typeof setTimeout> | undefined;\n let running = false;\n\n function schedulePing(): void {\n pingTimer = setTimeout(() => {\n const ts = Date.now();\n\n send(encode({ ts, type: 'ping' }));\n\n pongTimer = setTimeout(() => {\n warn(`Heartbeat timed out after ${timeout}ms — treating connection as dead`);\n onDead();\n }, timeout);\n }, interval);\n }\n\n return {\n onPong() {\n clearTimeout(pongTimer);\n pongTimer = undefined;\n\n if (running) schedulePing();\n },\n\n start() {\n running = true;\n clearTimeout(pingTimer);\n clearTimeout(pongTimer);\n pongTimer = undefined;\n schedulePing();\n },\n\n stop() {\n running = false;\n clearTimeout(pingTimer);\n clearTimeout(pongTimer);\n pingTimer = undefined;\n pongTimer = undefined;\n },\n };\n}\n"],"mappings":"0DAKA,IAAM,EAAmB,IACnB,EAAkB,IAgBxB,SAAgB,EACd,EACA,EACA,EACiB,CACjB,GAAI,CAAC,EACH,MAAO,CAAE,QAAS,CAAC,EAAG,OAAQ,CAAC,EAAG,MAAO,CAAC,CAAE,EAG9C,IAAM,EAA4B,IAAS,GAAO,CAAC,EAAI,EACjD,EAAW,EAAQ,UAAY,EAC/B,EAAU,EAAQ,SAAW,EAE/B,EACA,EACA,EAAU,GAEd,SAAS,GAAqB,CAC5B,EAAY,eAAiB,CAG3B,EAAK,EAAA,OAAO,CAAE,GAFH,KAAK,IAEF,EAAI,KAAM,MAAO,CAAC,CAAC,EAEjC,EAAY,eAAiB,CAC3B,EAAA,KAAK,6BAA6B,EAAQ,iCAAiC,EAC3E,EAAO,CACT,EAAG,CAAO,CACZ,EAAG,CAAQ,CACb,CAEA,MAAO,CACL,QAAS,CACP,aAAa,CAAS,EACtB,EAAY,IAAA,GAER,GAAS,EAAa,CAC5B,EAEA,OAAQ,CACN,EAAU,GACV,aAAa,CAAS,EACtB,aAAa,CAAS,EACtB,EAAY,IAAA,GACZ,EAAa,CACf,EAEA,MAAO,CACL,EAAU,GACV,aAAa,CAAS,EACtB,aAAa,CAAS,EACtB,EAAY,IAAA,GACZ,EAAY,IAAA,EACd,CACF,CACF"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type HeartbeatHandle = {
|
|
2
|
+
/** Called when a `pong` frame is received. Resets the timeout timer. */
|
|
3
|
+
onPong(): void;
|
|
4
|
+
/** Start the heartbeat loop. */
|
|
5
|
+
start(): void;
|
|
6
|
+
/** Stop the heartbeat loop and cancel any pending timers. */
|
|
7
|
+
stop(): void;
|
|
8
|
+
};
|
|
9
|
+
//# sourceMappingURL=heartbeat.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heartbeat.d.ts","sourceRoot":"","sources":["../src/heartbeat.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,eAAe,GAAG;IAC5B,wEAAwE;IACxE,MAAM,IAAI,IAAI,CAAC;IACf,gCAAgC;IAChC,KAAK,IAAI,IAAI,CAAC;IACd,6DAA6D;IAC7D,IAAI,IAAI,IAAI,CAAC;CACd,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { warn as e } from "./_dev.js";
|
|
2
|
+
import { encode as t } from "./protocol.js";
|
|
3
|
+
//#region src/heartbeat.ts
|
|
4
|
+
var n = 3e4, r = 5e3;
|
|
5
|
+
function i(i, a, o) {
|
|
6
|
+
if (!i) return {
|
|
7
|
+
onPong() {},
|
|
8
|
+
start() {},
|
|
9
|
+
stop() {}
|
|
10
|
+
};
|
|
11
|
+
let s = i === !0 ? {} : i, c = s.interval ?? n, l = s.timeout ?? r, u, d, f = !1;
|
|
12
|
+
function p() {
|
|
13
|
+
u = setTimeout(() => {
|
|
14
|
+
a(t({
|
|
15
|
+
ts: Date.now(),
|
|
16
|
+
type: "ping"
|
|
17
|
+
})), d = setTimeout(() => {
|
|
18
|
+
e(`Heartbeat timed out after ${l}ms — treating connection as dead`), o();
|
|
19
|
+
}, l);
|
|
20
|
+
}, c);
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
onPong() {
|
|
24
|
+
clearTimeout(d), d = void 0, f && p();
|
|
25
|
+
},
|
|
26
|
+
start() {
|
|
27
|
+
f = !0, clearTimeout(u), clearTimeout(d), d = void 0, p();
|
|
28
|
+
},
|
|
29
|
+
stop() {
|
|
30
|
+
f = !1, clearTimeout(u), clearTimeout(d), u = void 0, d = void 0;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
export { i as createHeartbeat };
|
|
36
|
+
|
|
37
|
+
//# sourceMappingURL=heartbeat.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heartbeat.js","names":[],"sources":["../src/heartbeat.ts"],"sourcesContent":["import type { HeartbeatOptions } from './types';\n\nimport { warn } from './_dev';\nimport { encode } from './protocol';\n\nconst DEFAULT_INTERVAL = 30_000;\nconst DEFAULT_TIMEOUT = 5_000;\n\nexport type HeartbeatHandle = {\n /** Called when a `pong` frame is received. Resets the timeout timer. */\n onPong(): void;\n /** Start the heartbeat loop. */\n start(): void;\n /** Stop the heartbeat loop and cancel any pending timers. */\n stop(): void;\n};\n\n/**\n * Create a heartbeat manager that sends periodic pings and expects pong replies.\n * Calls `onDead` when a pong is not received within the timeout window.\n * @internal\n */\nexport function createHeartbeat(\n opts: boolean | HeartbeatOptions | undefined,\n send: (frame: string) => void,\n onDead: () => void,\n): HeartbeatHandle {\n if (!opts) {\n return { onPong() {}, start() {}, stop() {} };\n }\n\n const options: HeartbeatOptions = opts === true ? {} : opts;\n const interval = options.interval ?? DEFAULT_INTERVAL;\n const timeout = options.timeout ?? DEFAULT_TIMEOUT;\n\n let pingTimer: ReturnType<typeof setTimeout> | undefined;\n let pongTimer: ReturnType<typeof setTimeout> | undefined;\n let running = false;\n\n function schedulePing(): void {\n pingTimer = setTimeout(() => {\n const ts = Date.now();\n\n send(encode({ ts, type: 'ping' }));\n\n pongTimer = setTimeout(() => {\n warn(`Heartbeat timed out after ${timeout}ms — treating connection as dead`);\n onDead();\n }, timeout);\n }, interval);\n }\n\n return {\n onPong() {\n clearTimeout(pongTimer);\n pongTimer = undefined;\n\n if (running) schedulePing();\n },\n\n start() {\n running = true;\n clearTimeout(pingTimer);\n clearTimeout(pongTimer);\n pongTimer = undefined;\n schedulePing();\n },\n\n stop() {\n running = false;\n clearTimeout(pingTimer);\n clearTimeout(pongTimer);\n pingTimer = undefined;\n pongTimer = undefined;\n },\n };\n}\n"],"mappings":";;;AAKA,IAAM,IAAmB,KACnB,IAAkB;AAgBxB,SAAgB,EACd,GACA,GACA,GACiB;CACjB,IAAI,CAAC,GACH,OAAO;EAAE,SAAS,CAAC;EAAG,QAAQ,CAAC;EAAG,OAAO,CAAC;CAAE;CAG9C,IAAM,IAA4B,MAAS,KAAO,CAAC,IAAI,GACjD,IAAW,EAAQ,YAAY,GAC/B,IAAU,EAAQ,WAAW,GAE/B,GACA,GACA,IAAU;CAEd,SAAS,IAAqB;EAC5B,IAAY,iBAAiB;GAK3B,AAFA,EAAK,EAAO;IAAE,IAFH,KAAK,IAEF;IAAI,MAAM;GAAO,CAAC,CAAC,GAEjC,IAAY,iBAAiB;IAE3B,AADA,EAAK,6BAA6B,EAAQ,iCAAiC,GAC3E,EAAO;GACT,GAAG,CAAO;EACZ,GAAG,CAAQ;CACb;CAEA,OAAO;EACL,SAAS;GAIP,AAHA,aAAa,CAAS,GACtB,IAAY,KAAA,GAER,KAAS,EAAa;EAC5B;EAEA,QAAQ;GAKN,AAJA,IAAU,IACV,aAAa,CAAS,GACtB,aAAa,CAAS,GACtB,IAAY,KAAA,GACZ,EAAa;EACf;EAEA,OAAO;GAKL,AAJA,IAAU,IACV,aAAa,CAAS,GACtB,aAAa,CAAS,GACtB,IAAY,KAAA,GACZ,IAAY,KAAA;EACd;CACF;AACF"}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +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;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type { BufferOptions, EventKey, HeartbeatOptions, MessageMap, Middleware, PresenceChannel, Pulse, PulseChannel, PulseOptions, PulseStatus, ReadonlyMap, ReconnectOptions, Unsubscribe, } from './types';
|
|
2
|
+
export { PulseAbortError, PulseConnectionError, PulseDisposedError, PulseError, PulseProtocolError, PulseTimeoutError, } from './errors';
|
|
3
|
+
export { createPulse } from './pulse';
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,aAAa,EACb,QAAQ,EACR,gBAAgB,EAChB,UAAU,EACV,UAAU,EACV,eAAe,EACf,KAAK,EACL,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,WAAW,EACX,gBAAgB,EAChB,WAAW,GACZ,MAAM,SAAS,CAAC;AAEjB,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,kBAAkB,EAClB,UAAU,EACV,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,UAAU,CAAC;AAElB,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +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 };
|
|
@@ -0,0 +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;
|
|
2
|
+
//# sourceMappingURL=presence.cjs.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Unsubscribe } from './types';
|
|
2
|
+
import { type InPresenceJoinFrame, type InPresenceLeaveFrame, type InPresenceStateFrame } from './protocol';
|
|
3
|
+
export type PresenceEvents = {
|
|
4
|
+
onJoin(handler: (frame: InPresenceJoinFrame) => void): Unsubscribe;
|
|
5
|
+
onLeave(handler: (frame: InPresenceLeaveFrame) => void): Unsubscribe;
|
|
6
|
+
onState(handler: (frame: InPresenceStateFrame) => void): Unsubscribe;
|
|
7
|
+
};
|
|
8
|
+
//# sourceMappingURL=presence.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presence.d.ts","sourceRoot":"","sources":["../src/presence.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAmB,WAAW,EAAE,MAAM,SAAS,CAAC;AAI5D,OAAO,EAAE,KAAK,mBAAmB,EAAE,KAAK,oBAAoB,EAAE,KAAK,oBAAoB,EAAU,MAAM,YAAY,CAAC;AAIpH,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,GAAG,WAAW,CAAC;IACnE,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,GAAG,WAAW,CAAC;IACrE,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,GAAG,WAAW,CAAC;CACtE,CAAC"}
|