crossws 0.4.5 → 0.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/adapters/vercel.d.ts +2 -0
  2. package/dist/THIRD-PARTY-LICENSES.md +1 -1
  3. package/dist/_chunks/_request.mjs +2 -2
  4. package/dist/_chunks/_types.d.mts +8 -8
  5. package/dist/_chunks/adapter.d.mts +458 -4
  6. package/dist/_chunks/adapter.mjs +90 -13
  7. package/dist/_chunks/bun.d.mts +5 -3
  8. package/dist/_chunks/bunny.d.mts +2 -2
  9. package/dist/_chunks/cloudflare.d.mts +3 -3
  10. package/dist/_chunks/deno.d.mts +2 -2
  11. package/dist/_chunks/error.mjs +1 -1
  12. package/dist/_chunks/libs/ws.mjs +53 -4
  13. package/dist/_chunks/node.d.mts +2 -2
  14. package/dist/_chunks/node.mjs +17 -11
  15. package/dist/_chunks/peer.mjs +38 -26
  16. package/dist/_chunks/sse.d.mts +2 -2
  17. package/dist/_chunks/web.d.mts +1 -1
  18. package/dist/adapters/bun.d.mts +1 -1
  19. package/dist/adapters/bun.mjs +18 -9
  20. package/dist/adapters/bunny.d.mts +1 -1
  21. package/dist/adapters/bunny.mjs +8 -6
  22. package/dist/adapters/cloudflare.d.mts +1 -1
  23. package/dist/adapters/cloudflare.mjs +16 -13
  24. package/dist/adapters/deno.d.mts +1 -1
  25. package/dist/adapters/deno.mjs +22 -9
  26. package/dist/adapters/node.d.mts +2 -2
  27. package/dist/adapters/node.mjs +1 -1
  28. package/dist/adapters/sse.d.mts +1 -1
  29. package/dist/adapters/sse.mjs +7 -5
  30. package/dist/adapters/uws.d.mts +5 -4
  31. package/dist/adapters/uws.mjs +17 -10
  32. package/dist/adapters/vercel.d.mts +25 -0
  33. package/dist/adapters/vercel.mjs +48 -0
  34. package/dist/index.d.mts +30 -7
  35. package/dist/index.mjs +78 -35
  36. package/dist/server/bun.d.mts +1 -1
  37. package/dist/server/bunny.d.mts +1 -1
  38. package/dist/server/cloudflare.d.mts +1 -1
  39. package/dist/server/default.d.mts +1 -1
  40. package/dist/server/deno.d.mts +1 -1
  41. package/dist/server/node.d.mts +1 -1
  42. package/dist/server/node.mjs +1 -1
  43. package/dist/sync.d.mts +2 -0
  44. package/dist/sync.mjs +200 -0
  45. package/dist/websocket/node.mjs +1 -1
  46. package/dist/websocket/sse.d.mts +1 -1
  47. package/package.json +23 -35
  48. package/sync.d.ts +2 -0
  49. package/dist/_chunks/rolldown-runtime.mjs +0 -24
package/dist/sync.mjs ADDED
@@ -0,0 +1,200 @@
1
+ function broadcastChannel(opts) {
2
+ return ({ id }) => {
3
+ const channel = new BroadcastChannel(opts.channel);
4
+ return {
5
+ subscribe(deliver) {
6
+ channel.addEventListener("message", (event) => {
7
+ const envelope = event.data;
8
+ const msg = envelope?.msg;
9
+ if (!envelope || envelope.id === id || typeof msg?.topic !== "string" || typeof msg.namespace !== "string" || !(typeof msg.data === "string" || msg.data instanceof Uint8Array)) return;
10
+ deliver(msg);
11
+ });
12
+ },
13
+ publish(msg) {
14
+ channel.postMessage({
15
+ id,
16
+ msg
17
+ });
18
+ },
19
+ close() {
20
+ channel.close();
21
+ }
22
+ };
23
+ };
24
+ }
25
+ function encodeEnvelope(id, msg) {
26
+ const binary = msg.data instanceof Uint8Array;
27
+ return JSON.stringify({
28
+ id,
29
+ msg: {
30
+ namespace: msg.namespace,
31
+ topic: msg.topic,
32
+ binary,
33
+ data: binary ? toBase64(msg.data) : msg.data
34
+ }
35
+ });
36
+ }
37
+ function decodeEnvelope(raw) {
38
+ try {
39
+ const parsed = JSON.parse(raw);
40
+ if (!parsed || typeof parsed.id !== "string" || !parsed.msg || typeof parsed.msg.topic !== "string" || typeof parsed.msg.namespace !== "string" || typeof parsed.msg.data !== "string") return;
41
+ return {
42
+ id: parsed.id,
43
+ msg: {
44
+ namespace: parsed.msg.namespace,
45
+ topic: parsed.msg.topic,
46
+ data: parsed.msg.binary ? fromBase64(parsed.msg.data) : parsed.msg.data
47
+ }
48
+ };
49
+ } catch {
50
+ return;
51
+ }
52
+ }
53
+ function toBase64(data) {
54
+ let binary = "";
55
+ for (const byte of data) binary += String.fromCharCode(byte);
56
+ return btoa(binary);
57
+ }
58
+ function fromBase64(data) {
59
+ const binary = atob(data);
60
+ const out = new Uint8Array(binary.length);
61
+ for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);
62
+ return out;
63
+ }
64
+ function redis(opts) {
65
+ const channel = opts.channel;
66
+ const isNodeRedis = opts.connector === void 0 ? typeof opts.client.pSubscribe === "function" : opts.connector === "node-redis";
67
+ return ({ id }) => {
68
+ const subscriber = opts.client.duplicate();
69
+ let started = false;
70
+ let onMessage;
71
+ return {
72
+ async subscribe(deliver) {
73
+ started = true;
74
+ const handle = (raw) => {
75
+ const envelope = decodeEnvelope(raw);
76
+ if (!envelope || envelope.id === id) return;
77
+ deliver(envelope.msg);
78
+ };
79
+ if (isNodeRedis) {
80
+ await subscriber.connect?.();
81
+ await subscriber.subscribe(channel, (raw) => handle(raw));
82
+ } else {
83
+ onMessage = (ch, raw) => {
84
+ if (ch === channel) handle(raw);
85
+ };
86
+ subscriber.on?.("message", onMessage);
87
+ await subscriber.subscribe(channel);
88
+ }
89
+ },
90
+ async publish(msg) {
91
+ await opts.client.publish(channel, encodeEnvelope(id, msg));
92
+ },
93
+ async close() {
94
+ if (!started) return;
95
+ if (onMessage) {
96
+ subscriber.off?.("message", onMessage);
97
+ onMessage = void 0;
98
+ }
99
+ try {
100
+ await subscriber.quit?.();
101
+ } catch (error) {
102
+ console.error("[crossws] sync redis close failed:", error);
103
+ }
104
+ }
105
+ };
106
+ };
107
+ }
108
+ function pgsql(opts) {
109
+ const channel = opts.channel;
110
+ if (new TextEncoder().encode(channel).length > 63) throw new Error(`[crossws] pgsql sync channel name must be at most 63 bytes (got ${channel.length} chars): ${channel}`);
111
+ const isPostgresJs = opts.connector === void 0 ? typeof opts.client.listen === "function" : opts.connector === "postgres.js";
112
+ if (!isPostgresJs && "idleCount" in opts.client && "totalCount" in opts.client && typeof opts.client.query === "function") throw new Error("[crossws] pgsql sync requires a dedicated `Client`, not a `Pool` (pool connections rotate, so LISTEN/NOTIFY can't deliver). Pass `new Client()` (node-postgres) or a `postgres()` instance (postgres.js).");
113
+ const quotedChannel = `"${channel.replace(/"/g, "\"\"")}"`;
114
+ return ({ id }) => {
115
+ let unlisten;
116
+ let onNotification;
117
+ return {
118
+ async subscribe(deliver) {
119
+ const handle = (raw) => {
120
+ const envelope = decodeEnvelope(raw);
121
+ if (!envelope || envelope.id === id) return;
122
+ deliver(envelope.msg);
123
+ };
124
+ if (isPostgresJs) unlisten = (await opts.client.listen(channel, (payload) => handle(payload)))?.unlisten;
125
+ else {
126
+ onNotification = (msg) => {
127
+ if (msg.channel === channel && msg.payload !== void 0) handle(msg.payload);
128
+ };
129
+ opts.client.on("notification", onNotification);
130
+ await opts.client.query(`LISTEN ${quotedChannel}`);
131
+ }
132
+ },
133
+ async publish(msg) {
134
+ const payload = encodeEnvelope(id, msg);
135
+ if (isPostgresJs) await opts.client.notify(channel, payload);
136
+ else await opts.client.query("SELECT pg_notify($1, $2)", [channel, payload]);
137
+ },
138
+ async close() {
139
+ if (isPostgresJs) await unlisten?.();
140
+ else if (onNotification) {
141
+ opts.client.removeListener?.("notification", onNotification);
142
+ onNotification = void 0;
143
+ await opts.client.query?.(`UNLISTEN ${quotedChannel}`);
144
+ }
145
+ }
146
+ };
147
+ };
148
+ }
149
+ const CLUSTER_MESSAGE = "crossws:sync";
150
+ function isClusterEnvelope(message) {
151
+ return typeof message === "object" && message !== null && typeof message[CLUSTER_MESSAGE] === "string" && typeof message.env === "string";
152
+ }
153
+ let relayInstalled = false;
154
+ async function setupPrimaryCluster() {
155
+ const cluster = (await import("node:cluster")).default;
156
+ if (!cluster.isPrimary || relayInstalled) return;
157
+ relayInstalled = true;
158
+ cluster.on("message", (_worker, message) => {
159
+ if (!isClusterEnvelope(message)) return;
160
+ for (const id in cluster.workers) cluster.workers[id]?.send(message);
161
+ });
162
+ }
163
+ function cluster(opts) {
164
+ const channel = opts.channel;
165
+ return ({ id }) => {
166
+ const proc = globalThis.process;
167
+ let onMessage;
168
+ return {
169
+ subscribe(deliver) {
170
+ if (typeof proc?.send !== "function") throw new Error("[crossws] cluster sync must run in a worker forked by node:cluster (process.send is unavailable). Call setupPrimaryCluster() in the primary process and start your server in the workers.");
171
+ onMessage = (message) => {
172
+ if (!isClusterEnvelope(message) || message[CLUSTER_MESSAGE] !== channel) return;
173
+ const envelope = decodeEnvelope(message.env);
174
+ if (!envelope || envelope.id === id) return;
175
+ deliver(envelope.msg);
176
+ };
177
+ proc.on("message", onMessage);
178
+ },
179
+ publish(msg) {
180
+ proc?.send?.({
181
+ [CLUSTER_MESSAGE]: channel,
182
+ env: encodeEnvelope(id, msg)
183
+ });
184
+ },
185
+ close() {
186
+ if (onMessage) {
187
+ proc?.off?.("message", onMessage);
188
+ onMessage = void 0;
189
+ }
190
+ }
191
+ };
192
+ };
193
+ }
194
+ const syncDrivers = [
195
+ "broadcastChannel",
196
+ "redis",
197
+ "pgsql",
198
+ "cluster"
199
+ ];
200
+ export { broadcastChannel, cluster, decodeEnvelope, encodeEnvelope, pgsql, redis, setupPrimaryCluster, syncDrivers };
@@ -1,3 +1,3 @@
1
- import { t as import_websocket } from "../_chunks/libs/ws.mjs";
1
+ import { import_websocket } from "../_chunks/libs/ws.mjs";
2
2
  const Websocket = globalThis.WebSocket || import_websocket.default;
3
3
  export { Websocket as default };
@@ -1,4 +1,4 @@
1
- import { a as WebSocket, i as MessageEvent, n as Event, r as EventTarget, t as CloseEvent } from "../_chunks/web.mjs";
1
+ import { CloseEvent, Event, EventTarget, MessageEvent, WebSocket } from "../_chunks/web.mjs";
2
2
  type Ctor<T> = {
3
3
  prototype: T;
4
4
  new (): T;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "crossws",
3
- "version": "0.4.5",
3
+ "version": "0.4.7",
4
4
  "description": "Cross-platform WebSocket Servers for Node.js, Deno, Bun and Cloudflare Workers",
5
5
  "homepage": "https://crossws.h3.dev",
6
6
  "license": "MIT",
@@ -19,12 +19,14 @@
19
19
  "types": "./dist/index.d.mts",
20
20
  "exports": {
21
21
  ".": "./dist/index.mjs",
22
+ "./sync": "./dist/sync.mjs",
22
23
  "./adapters/bun": "./dist/adapters/bun.mjs",
23
24
  "./adapters/bunny": "./dist/adapters/bunny.mjs",
24
25
  "./adapters/deno": "./dist/adapters/deno.mjs",
25
26
  "./adapters/cloudflare": "./dist/adapters/cloudflare.mjs",
26
27
  "./adapters/sse": "./dist/adapters/sse.mjs",
27
28
  "./adapters/node": "./dist/adapters/node.mjs",
29
+ "./adapters/vercel": "./dist/adapters/vercel.mjs",
28
30
  "./adapters/uws": "./dist/adapters/uws.mjs",
29
31
  "./server/bun": "./dist/server/bun.mjs",
30
32
  "./server/bunny": "./dist/server/bunny.mjs",
@@ -67,14 +69,14 @@
67
69
  "typecheck": "tsgo --noEmit --skipLibCheck"
68
70
  },
69
71
  "devDependencies": {
70
- "@cloudflare/workers-types": "^4.20260410.1",
71
- "@types/bun": "^1.3.12",
72
- "@types/deno": "^2.5.0",
73
- "@types/node": "^25.6.0",
74
- "@types/web": "^0.0.345",
72
+ "@cloudflare/workers-types": "^4.20260629.1",
73
+ "@types/bun": "^1.3.14",
74
+ "@types/deno": "^2.7.0",
75
+ "@types/node": "^26.0.1",
76
+ "@types/web": "^0.0.351",
75
77
  "@types/ws": "^8.18.1",
76
- "@typescript/native-preview": "^7.0.0-dev.20260410.1",
77
- "@vitest/coverage-v8": "^4.1.4",
78
+ "@typescript/native-preview": "7.0.0-dev.20260629.1",
79
+ "@vitest/coverage-v8": "^4.1.9",
78
80
  "automd": "^0.4.3",
79
81
  "changelogen": "^0.6.2",
80
82
  "consola": "^3.4.2",
@@ -82,20 +84,20 @@
82
84
  "eventsource": "^4.1.0",
83
85
  "execa": "^9.6.1",
84
86
  "get-port-please": "^3.2.0",
85
- "h3": "^2.0.1-rc.20",
86
- "jiti": "^2.6.1",
87
- "listhen": "^1.9.1",
88
- "obuild": "^0.4.33",
89
- "oxfmt": "^0.44.0",
90
- "oxlint": "^1.59.0",
91
- "srvx": "^0.11.15",
92
- "typescript": "^6.0.2",
87
+ "h3": "2.0.1-rc.22",
88
+ "jiti": "^2.7.0",
89
+ "listhen": "^1.10.0",
90
+ "obuild": "^0.4.37",
91
+ "oxfmt": "^0.56.0",
92
+ "oxlint": "^1.71.0",
93
+ "srvx": "^0.11.17",
94
+ "typescript": "^6.0.3",
93
95
  "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.57.0",
94
96
  "unbuild": "^3.6.1",
95
- "undici": "^8.0.2",
96
- "vitest": "^4.1.4",
97
- "wrangler": "^4.81.1",
98
- "ws": "^8.20.0"
97
+ "undici": "^8.5.0",
98
+ "vitest": "^4.1.9",
99
+ "wrangler": "^4.105.0",
100
+ "ws": "^8.21.0"
99
101
  },
100
102
  "peerDependencies": {
101
103
  "srvx": ">=0.11.5"
@@ -108,19 +110,5 @@
108
110
  "resolutions": {
109
111
  "crossws": "workspace:*"
110
112
  },
111
- "packageManager": "pnpm@10.33.0",
112
- "pnpm": {
113
- "ignoredBuiltDependencies": [
114
- "@parcel/watcher",
115
- "esbuild",
116
- "sharp",
117
- "workerd"
118
- ],
119
- "onlyBuiltDependencies": [
120
- "@parcel/watcher",
121
- "esbuild",
122
- "sharp",
123
- "workerd"
124
- ]
125
- }
113
+ "packageManager": "pnpm@11.7.0"
126
114
  }
package/sync.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./dist/sync.mjs";
2
+ export { default } from "./dist/sync.mjs";
@@ -1,24 +0,0 @@
1
- import { createRequire } from "node:module";
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __commonJSMin = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
9
- var __copyProps = (to, from, except, desc) => {
10
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
- key = keys[i];
12
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
13
- get: ((k) => from[k]).bind(null, key),
14
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
- });
16
- }
17
- return to;
18
- };
19
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
20
- value: mod,
21
- enumerable: true
22
- }) : target, mod));
23
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
24
- export { __require as n, __toESM as r, __commonJSMin as t };