@shadow-js/realtime 0.0.0-stage → 1.0.0

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 (128) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/LICENSE +21 -0
  3. package/README.md +114 -2
  4. package/dist/dev/adapter.d.ts +75 -0
  5. package/dist/dev/client/core.d.ts +24 -0
  6. package/dist/dev/client/core.js +845 -0
  7. package/dist/dev/client/core.js.map +1 -0
  8. package/dist/dev/client/index.d.ts +5 -0
  9. package/dist/dev/client/index.js +3 -0
  10. package/dist/dev/client/presence.js +23 -0
  11. package/dist/dev/client/presence.js.map +1 -0
  12. package/dist/dev/client/sse.js +76 -0
  13. package/dist/dev/client/sse.js.map +1 -0
  14. package/dist/dev/client/types.d.ts +256 -0
  15. package/dist/dev/client/util.js +30 -0
  16. package/dist/dev/client/util.js.map +1 -0
  17. package/dist/dev/client/ws.js +39 -0
  18. package/dist/dev/client/ws.js.map +1 -0
  19. package/dist/dev/cloudflare/adapter.d.ts +80 -0
  20. package/dist/dev/cloudflare/adapter.js +347 -0
  21. package/dist/dev/cloudflare/adapter.js.map +1 -0
  22. package/dist/dev/cloudflare/core.js +701 -0
  23. package/dist/dev/cloudflare/core.js.map +1 -0
  24. package/dist/dev/cloudflare/link.js +397 -0
  25. package/dist/dev/cloudflare/link.js.map +1 -0
  26. package/dist/dev/cloudflare/object.d.ts +82 -0
  27. package/dist/dev/cloudflare/object.js +214 -0
  28. package/dist/dev/cloudflare/object.js.map +1 -0
  29. package/dist/dev/cloudflare/types.d.ts +100 -0
  30. package/dist/dev/cloudflare/wire.js +284 -0
  31. package/dist/dev/cloudflare/wire.js.map +1 -0
  32. package/dist/dev/cloudflare.d.ts +4 -0
  33. package/dist/dev/cloudflare.js +3 -0
  34. package/dist/dev/errors.d.ts +25 -0
  35. package/dist/dev/errors.js +24 -0
  36. package/dist/dev/errors.js.map +1 -0
  37. package/dist/dev/hooks.d.ts +110 -0
  38. package/dist/dev/hooks.js +162 -0
  39. package/dist/dev/hooks.js.map +1 -0
  40. package/dist/dev/index.d.ts +4 -0
  41. package/dist/dev/index.js +3 -0
  42. package/dist/dev/memory.d.ts +33 -0
  43. package/dist/dev/memory.js +255 -0
  44. package/dist/dev/memory.js.map +1 -0
  45. package/dist/dev/protocol.d.ts +211 -0
  46. package/dist/dev/protocol.js +400 -0
  47. package/dist/dev/protocol.js.map +1 -0
  48. package/dist/dev/redis/client.d.ts +139 -0
  49. package/dist/dev/redis/client.js +146 -0
  50. package/dist/dev/redis/client.js.map +1 -0
  51. package/dist/dev/redis/scripts.js +275 -0
  52. package/dist/dev/redis/scripts.js.map +1 -0
  53. package/dist/dev/redis/topics.d.ts +8 -0
  54. package/dist/dev/redis/topics.js +377 -0
  55. package/dist/dev/redis/topics.js.map +1 -0
  56. package/dist/dev/redis.d.ts +68 -0
  57. package/dist/dev/redis.js +422 -0
  58. package/dist/dev/redis.js.map +1 -0
  59. package/dist/dev/server/http.d.ts +48 -0
  60. package/dist/dev/server/http.js +286 -0
  61. package/dist/dev/server/http.js.map +1 -0
  62. package/dist/dev/server/hub.d.ts +114 -0
  63. package/dist/dev/server/hub.js +827 -0
  64. package/dist/dev/server/hub.js.map +1 -0
  65. package/dist/dev/server/index.d.ts +47 -0
  66. package/dist/dev/server/index.js +41 -0
  67. package/dist/dev/server/index.js.map +1 -0
  68. package/dist/dev/server/limits.d.ts +54 -0
  69. package/dist/dev/server/limits.js +85 -0
  70. package/dist/dev/server/limits.js.map +1 -0
  71. package/dist/dev/server/rules.d.ts +43 -0
  72. package/dist/dev/server/rules.js +72 -0
  73. package/dist/dev/server/rules.js.map +1 -0
  74. package/dist/dev/testing.d.ts +115 -0
  75. package/dist/dev/testing.js +427 -0
  76. package/dist/dev/testing.js.map +1 -0
  77. package/dist/prod/client/core.js +2 -0
  78. package/dist/prod/client/core.js.map +1 -0
  79. package/dist/prod/client/index.js +1 -0
  80. package/dist/prod/client/presence.js +2 -0
  81. package/dist/prod/client/presence.js.map +1 -0
  82. package/dist/prod/client/sse.js +5 -0
  83. package/dist/prod/client/sse.js.map +1 -0
  84. package/dist/prod/client/util.js +2 -0
  85. package/dist/prod/client/util.js.map +1 -0
  86. package/dist/prod/client/ws.js +2 -0
  87. package/dist/prod/client/ws.js.map +1 -0
  88. package/dist/prod/cloudflare/adapter.js +3 -0
  89. package/dist/prod/cloudflare/adapter.js.map +1 -0
  90. package/dist/prod/cloudflare/core.js +2 -0
  91. package/dist/prod/cloudflare/core.js.map +1 -0
  92. package/dist/prod/cloudflare/link.js +2 -0
  93. package/dist/prod/cloudflare/link.js.map +1 -0
  94. package/dist/prod/cloudflare/object.js +2 -0
  95. package/dist/prod/cloudflare/object.js.map +1 -0
  96. package/dist/prod/cloudflare/wire.js +2 -0
  97. package/dist/prod/cloudflare/wire.js.map +1 -0
  98. package/dist/prod/cloudflare.js +1 -0
  99. package/dist/prod/errors.js +2 -0
  100. package/dist/prod/errors.js.map +1 -0
  101. package/dist/prod/hooks.js +2 -0
  102. package/dist/prod/hooks.js.map +1 -0
  103. package/dist/prod/index.js +1 -0
  104. package/dist/prod/memory.js +2 -0
  105. package/dist/prod/memory.js.map +1 -0
  106. package/dist/prod/protocol.js +2 -0
  107. package/dist/prod/protocol.js.map +1 -0
  108. package/dist/prod/redis/client.js +2 -0
  109. package/dist/prod/redis/client.js.map +1 -0
  110. package/dist/prod/redis/scripts.js +157 -0
  111. package/dist/prod/redis/scripts.js.map +1 -0
  112. package/dist/prod/redis/topics.js +2 -0
  113. package/dist/prod/redis/topics.js.map +1 -0
  114. package/dist/prod/redis.js +2 -0
  115. package/dist/prod/redis.js.map +1 -0
  116. package/dist/prod/server/http.js +2 -0
  117. package/dist/prod/server/http.js.map +1 -0
  118. package/dist/prod/server/hub.js +2 -0
  119. package/dist/prod/server/hub.js.map +1 -0
  120. package/dist/prod/server/index.js +2 -0
  121. package/dist/prod/server/index.js.map +1 -0
  122. package/dist/prod/server/limits.js +2 -0
  123. package/dist/prod/server/limits.js.map +1 -0
  124. package/dist/prod/server/rules.js +2 -0
  125. package/dist/prod/server/rules.js.map +1 -0
  126. package/dist/prod/testing.js +2 -0
  127. package/dist/prod/testing.js.map +1 -0
  128. package/package.json +105 -4
@@ -0,0 +1,80 @@
1
+ import { Adapter } from "../adapter.js";
2
+ import { ChannelNamespace } from "./types.js";
3
+ //#region src/cloudflare/adapter.d.ts
4
+ /**
5
+ * Options of {@link cloudflareAdapter}.
6
+ * @since 1.0.0
7
+ */
8
+ export interface CloudflareRealtimeOptions {
9
+ /** The Durable Object namespace binding of {@link RealtimeChannelObject} (`env.CHANNELS`). */
10
+ readonly namespace: ChannelNamespace;
11
+ /**
12
+ * Separates this adapter's channels from other adapters on the same namespace (the object's name is the prefix, a newline and
13
+ * the channel). Default: none, the object is named by the channel alone.
14
+ */
15
+ readonly prefix?: string;
16
+ /** A Cloudflare location hint for objects this adapter creates (for example `"weur"`). */
17
+ readonly locationHint?: string;
18
+ /** Milliseconds one request to an object may take. Default: 10,000. */
19
+ readonly requestTimeoutMs?: number;
20
+ /** Milliseconds `subscribe` waits for the subscribe socket before it rejects with `unavailable`. Default: 10,000. */
21
+ readonly connectTimeoutMs?: number;
22
+ /**
23
+ * How many times a request that is safe to repeat (history, presence reads and updates, a publish with an idempotency key) is
24
+ * repeated after a transport failure. Default: 2.
25
+ */
26
+ readonly retries?: number;
27
+ /** Backoff of the subscribe socket: full jitter from `minDelayMs` up to `maxDelayMs`. Defaults: 250 and 10,000. */
28
+ readonly reconnect?: {
29
+ readonly minDelayMs?: number;
30
+ readonly maxDelayMs?: number;
31
+ };
32
+ /** Milliseconds between keep-alive pings on a subscribe socket; 0 turns them off. Default: 30,000. */
33
+ readonly keepAliveMs?: number;
34
+ /**
35
+ * Milliseconds after a presence member's expiry that the object's alarm waits before it announces the leave itself. The hub's
36
+ * own `presenceReap` normally runs inside that window. Default: 2,000.
37
+ */
38
+ readonly presenceGraceMs?: number;
39
+ /**
40
+ * Forget a channel (numbering, history, keys) after this many milliseconds with no message, no presence and no subscriber.
41
+ * A client that resumes later sees `reset`. Default: 0, never.
42
+ */
43
+ readonly idleMs?: number;
44
+ /** The most events queued for one listener before newer ones are dropped (the hub then recovers from history). Default: 1,000. */
45
+ readonly maxQueued?: number;
46
+ /** Receives failures nobody else sees: a listener that threw. Default: `console.error`. */
47
+ readonly onError?: (error: unknown, context: {
48
+ readonly where: string;
49
+ readonly channel: string;
50
+ }) => void;
51
+ }
52
+ /**
53
+ * The adapter for Cloudflare Workers: each channel is one Durable Object ({@link RealtimeChannelObject}) that numbers,
54
+ * retains and fans out its messages, so a channel is ordered and gapless wherever the Workers that use it run.
55
+ *
56
+ * `publish`, `history` and the presence calls are one request to the channel's object each. `subscribe` and
57
+ * `presenceSubscribe` share one WebSocket per channel from this adapter to the object, which the adapter reopens with backoff
58
+ * and resumes with the last seq it saw. Call `close()` when the adapter is no longer needed; it closes every socket and
59
+ * cancels every timer.
60
+ *
61
+ * Within one Worker request, create the adapter where the hub lives. A Worker may touch a socket only in the request that
62
+ * created it, so one hub that serves many client connections belongs inside a Durable Object, which keeps one context for all
63
+ * of them; see the package's Cloudflare guide.
64
+ *
65
+ * @param options The namespace binding of the channel object and the tuning.
66
+ * @returns The adapter.
67
+ * @throws {TypeError} When an option is not valid.
68
+ * @example
69
+ * import { createHub } from "@shadow-js/realtime/server";
70
+ * import { cloudflareAdapter } from "@shadow-js/realtime/cloudflare";
71
+ *
72
+ * declare const env: { CHANNELS: Parameters<typeof cloudflareAdapter>[0]["namespace"] };
73
+ * const adapter = cloudflareAdapter({ namespace: env.CHANNELS });
74
+ * const hub = createHub({ adapter, channels: { "chat:{room}": { subscribe: true, publish: true } } });
75
+ * void hub.close();
76
+ * @since 1.0.0
77
+ */
78
+ export declare function cloudflareAdapter(options: CloudflareRealtimeOptions): Adapter;
79
+ //#endregion
80
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1,347 @@
1
+ import { RealtimeError } from "../errors.js";
2
+ import { isWholeNumber, parseMember, parseStored } from "./wire.js";
3
+ import { ChannelLink } from "./link.js";
4
+ //#region src/cloudflare/adapter.ts
5
+ const ORIGIN = "https://realtime.internal";
6
+ const PAGE = 512;
7
+ /** Codes the object answers that are the caller's to see; everything else is `unavailable`. */
8
+ const PASS = /* @__PURE__ */ new Set([
9
+ "too_large",
10
+ "bad_channel",
11
+ "bad_frame",
12
+ "limit",
13
+ "conflict"
14
+ ]);
15
+ function isRecord(value) {
16
+ return typeof value === "object" && value !== null && !Array.isArray(value);
17
+ }
18
+ function unavailable(message, cause) {
19
+ return new RealtimeError("unavailable", message, cause === void 0 ? void 0 : { cause });
20
+ }
21
+ function count(value, name, positive = false) {
22
+ if (!isWholeNumber(value) || positive && value < 1) throw new TypeError(`${name} must be a whole number`);
23
+ return value;
24
+ }
25
+ function option(value, fallback, name) {
26
+ return value === void 0 ? fallback : count(value, name);
27
+ }
28
+ const readPublish = (body) => isRecord(body) && isWholeNumber(body["seq"]) && body["seq"] >= 1 && typeof body["duplicate"] === "boolean" ? {
29
+ seq: body["seq"],
30
+ duplicate: body["duplicate"]
31
+ } : void 0;
32
+ const readHistory = (body) => {
33
+ if (!isRecord(body) || !Array.isArray(body["messages"])) return void 0;
34
+ const { head, oldest, more } = body;
35
+ if (!isWholeNumber(head) || !isWholeNumber(oldest) || typeof more !== "boolean") return void 0;
36
+ const messages = [];
37
+ for (const item of body["messages"]) {
38
+ const message = parseStored(item);
39
+ if (message === void 0) return void 0;
40
+ messages.push(message);
41
+ }
42
+ return {
43
+ messages,
44
+ head,
45
+ oldest,
46
+ more
47
+ };
48
+ };
49
+ const readMembers = (body) => {
50
+ if (!isRecord(body) || !Array.isArray(body["members"])) return void 0;
51
+ const members = [];
52
+ for (const item of body["members"]) {
53
+ const member = parseMember(item);
54
+ if (member === void 0) return void 0;
55
+ members.push(member);
56
+ }
57
+ return members;
58
+ };
59
+ const readRemoved = (body) => isRecord(body) && typeof body["removed"] === "boolean" ? body["removed"] : void 0;
60
+ const readMissing = (body) => {
61
+ if (!isRecord(body) || !Array.isArray(body["missing"])) return void 0;
62
+ return body["missing"].every((id) => typeof id === "string") ? body["missing"] : void 0;
63
+ };
64
+ const readEmpty = (body) => isRecord(body) ? true : void 0;
65
+ /**
66
+ * The adapter for Cloudflare Workers: each channel is one Durable Object ({@link RealtimeChannelObject}) that numbers,
67
+ * retains and fans out its messages, so a channel is ordered and gapless wherever the Workers that use it run.
68
+ *
69
+ * `publish`, `history` and the presence calls are one request to the channel's object each. `subscribe` and
70
+ * `presenceSubscribe` share one WebSocket per channel from this adapter to the object, which the adapter reopens with backoff
71
+ * and resumes with the last seq it saw. Call `close()` when the adapter is no longer needed; it closes every socket and
72
+ * cancels every timer.
73
+ *
74
+ * Within one Worker request, create the adapter where the hub lives. A Worker may touch a socket only in the request that
75
+ * created it, so one hub that serves many client connections belongs inside a Durable Object, which keeps one context for all
76
+ * of them; see the package's Cloudflare guide.
77
+ *
78
+ * @param options The namespace binding of the channel object and the tuning.
79
+ * @returns The adapter.
80
+ * @throws {TypeError} When an option is not valid.
81
+ * @example
82
+ * import { createHub } from "@shadow-js/realtime/server";
83
+ * import { cloudflareAdapter } from "@shadow-js/realtime/cloudflare";
84
+ *
85
+ * declare const env: { CHANNELS: Parameters<typeof cloudflareAdapter>[0]["namespace"] };
86
+ * const adapter = cloudflareAdapter({ namespace: env.CHANNELS });
87
+ * const hub = createHub({ adapter, channels: { "chat:{room}": { subscribe: true, publish: true } } });
88
+ * void hub.close();
89
+ * @since 1.0.0
90
+ */
91
+ function cloudflareAdapter(options) {
92
+ const { namespace } = options;
93
+ if (typeof namespace?.idFromName !== "function" || typeof namespace.get !== "function") throw new TypeError("cloudflareAdapter: namespace must be a Durable Object namespace binding");
94
+ const prefix = options.prefix;
95
+ if (prefix !== void 0 && (prefix.length === 0 || prefix.length > 256 || prefix.includes("\n"))) throw new TypeError("cloudflareAdapter: prefix must be 1..256 characters without a newline");
96
+ const requestTimeoutMs = option(options.requestTimeoutMs, 1e4, "requestTimeoutMs");
97
+ const connectTimeoutMs = option(options.connectTimeoutMs, 1e4, "connectTimeoutMs");
98
+ const retries = option(options.retries, 2, "retries");
99
+ const minDelayMs = option(options.reconnect?.minDelayMs, 250, "reconnect.minDelayMs");
100
+ const maxDelayMs = option(options.reconnect?.maxDelayMs, 1e4, "reconnect.maxDelayMs");
101
+ const keepAliveMs = option(options.keepAliveMs, 3e4, "keepAliveMs");
102
+ const graceMs = option(options.presenceGraceMs, 2e3, "presenceGraceMs");
103
+ const idleMs = option(options.idleMs, 0, "idleMs");
104
+ const maxQueued = count(options.maxQueued ?? 1e3, "maxQueued", true);
105
+ if (requestTimeoutMs === 0 || connectTimeoutMs === 0) throw new TypeError("cloudflareAdapter: timeouts must be above 0");
106
+ const report = options.onError ?? ((error, context) => console.error(`[realtime] ${context.where} ${context.channel}`, error));
107
+ let closed = false;
108
+ const links = /* @__PURE__ */ new Map();
109
+ const cancels = /* @__PURE__ */ new Set();
110
+ const inflight = /* @__PURE__ */ new Set();
111
+ const sleepers = /* @__PURE__ */ new Set();
112
+ function stubFor(channel) {
113
+ const name = prefix === void 0 ? channel : `${prefix}\n${channel}`;
114
+ const id = namespace.idFromName(name);
115
+ return options.locationHint === void 0 ? namespace.get(id) : namespace.get(id, { locationHint: options.locationHint });
116
+ }
117
+ function after(ms, task) {
118
+ const handle = setTimeout(() => {
119
+ cancels.delete(cancel);
120
+ task();
121
+ }, ms);
122
+ handle.unref?.();
123
+ const cancel = () => {
124
+ clearTimeout(handle);
125
+ cancels.delete(cancel);
126
+ };
127
+ cancels.add(cancel);
128
+ return cancel;
129
+ }
130
+ function every(ms, task) {
131
+ const handle = setInterval(task, ms);
132
+ handle.unref?.();
133
+ const cancel = () => {
134
+ clearInterval(handle);
135
+ cancels.delete(cancel);
136
+ };
137
+ cancels.add(cancel);
138
+ return cancel;
139
+ }
140
+ function delay(ms) {
141
+ return new Promise((resolve, reject) => {
142
+ const stop = (error) => {
143
+ cancel();
144
+ sleepers.delete(stop);
145
+ reject(error);
146
+ };
147
+ const cancel = after(ms, () => {
148
+ sleepers.delete(stop);
149
+ resolve();
150
+ });
151
+ sleepers.add(stop);
152
+ });
153
+ }
154
+ function assertOpen() {
155
+ if (closed) throw new RealtimeError("closed", "the adapter is closed");
156
+ }
157
+ /**
158
+ * One request to the channel's object. A transport failure, a 5xx or an unreadable reply is `unavailable`; with `safe` it is
159
+ * repeated first, with a short growing pause.
160
+ */
161
+ async function call(channel, op, body, read, safe) {
162
+ for (let attempt = 0;; attempt += 1) {
163
+ assertOpen();
164
+ const controller = new AbortController();
165
+ inflight.add(controller);
166
+ const timer = setTimeout(() => controller.abort(), requestTimeoutMs);
167
+ try {
168
+ const response = await stubFor(channel).fetch(`${ORIGIN}/v1/${op}`, {
169
+ method: "POST",
170
+ headers: { "content-type": "application/json" },
171
+ body: JSON.stringify({
172
+ channel,
173
+ ...body
174
+ }),
175
+ signal: controller.signal
176
+ });
177
+ const text = await response.text();
178
+ let parsed;
179
+ try {
180
+ parsed = JSON.parse(text);
181
+ } catch {
182
+ throw unavailable(`the channel object answered ${response.status} with a body that is not JSON`);
183
+ }
184
+ if (response.status === 200) {
185
+ const result = read(parsed);
186
+ if (result === void 0) throw unavailable("the channel object answered an unexpected shape");
187
+ return result;
188
+ }
189
+ const refusal = isRecord(parsed) && isRecord(parsed["error"]) ? parsed["error"] : void 0;
190
+ const code = refusal?.["code"];
191
+ if (typeof code === "string" && PASS.has(code)) throw new RealtimeError(code, String(refusal?.["message"] ?? code));
192
+ throw unavailable(`the channel object answered ${response.status}`);
193
+ } catch (error) {
194
+ if (closed) throw new RealtimeError("closed", "the adapter is closed", { cause: error });
195
+ if (error instanceof RealtimeError && error.code !== "unavailable") throw error;
196
+ if (safe && attempt < retries) {
197
+ await delay(Math.min(50 * 3 ** attempt, 1e3));
198
+ continue;
199
+ }
200
+ throw error instanceof RealtimeError ? error : unavailable("the channel object is unreachable", error);
201
+ } finally {
202
+ clearTimeout(timer);
203
+ inflight.delete(controller);
204
+ }
205
+ }
206
+ }
207
+ const host = {
208
+ reconnectMinMs: minDelayMs,
209
+ reconnectMaxMs: Math.max(minDelayMs, maxDelayMs),
210
+ keepAliveMs,
211
+ connectTimeoutMs,
212
+ maxQueued,
213
+ now: Date.now,
214
+ random: Math.random,
215
+ async open(channel, from) {
216
+ const query = `c=${encodeURIComponent(channel)}${from === void 0 ? "" : `&after=${from}`}`;
217
+ const response = await stubFor(channel).fetch(`${ORIGIN}/v1/ws?${query}`, { headers: { upgrade: "websocket" } });
218
+ const socket = response.webSocket;
219
+ if (response.status !== 101 || socket === void 0 || socket === null) throw unavailable(`the channel object refused the subscribe socket (${response.status})`);
220
+ return socket;
221
+ },
222
+ after,
223
+ every,
224
+ report: (error, where, channel) => report(error, {
225
+ where,
226
+ channel
227
+ }),
228
+ forget(link) {
229
+ if (links.get(link.channel) === link) links.delete(link.channel);
230
+ }
231
+ };
232
+ function linkFor(channel) {
233
+ let link = links.get(channel);
234
+ if (link === void 0) {
235
+ link = new ChannelLink(channel, host);
236
+ links.set(channel, link);
237
+ }
238
+ return link;
239
+ }
240
+ async function join(channel, add) {
241
+ assertOpen();
242
+ const link = linkFor(channel);
243
+ const sub = add(link);
244
+ try {
245
+ await link.whenReady(connectTimeoutMs);
246
+ } catch (error) {
247
+ link.remove(sub);
248
+ if (closed) throw new RealtimeError("closed", "the adapter is closed", { cause: error });
249
+ throw error instanceof RealtimeError ? error : unavailable("could not subscribe", error);
250
+ }
251
+ return async () => {
252
+ link.remove(sub);
253
+ };
254
+ }
255
+ const keepRetained = (entry) => ({
256
+ event: entry.event,
257
+ data: entry.data,
258
+ ...entry.publisher === void 0 ? {} : { publisher: entry.publisher },
259
+ ...entry.user === void 0 ? {} : { user: entry.user }
260
+ });
261
+ return {
262
+ publish(channel, entry, rules) {
263
+ return call(channel, "publish", {
264
+ entry: keepRetained(entry),
265
+ options: {
266
+ history: rules.history,
267
+ historyTtlMs: rules.historyTtlMs,
268
+ idempotencyTtlMs: rules.idempotencyTtlMs,
269
+ ...rules.key === void 0 ? {} : { key: rules.key }
270
+ },
271
+ ...idleMs > 0 ? { idleMs } : {}
272
+ }, readPublish, rules.key !== void 0);
273
+ },
274
+ subscribe(channel, listener) {
275
+ return join(channel, (link) => link.addMessages(listener));
276
+ },
277
+ async history(channel, after, limit) {
278
+ const read = (from, want) => call(channel, "history", {
279
+ after: from,
280
+ limit: want
281
+ }, readHistory, true);
282
+ let page = await read(after, limit <= 0 ? 0 : Math.min(limit, PAGE));
283
+ const { oldest } = page;
284
+ const messages = [...page.messages];
285
+ while (page.more && messages.length < limit) {
286
+ const tail = messages[messages.length - 1];
287
+ if (tail === void 0) break;
288
+ page = await read(tail.seq, Math.min(limit - messages.length, PAGE));
289
+ messages.push(...page.messages);
290
+ }
291
+ return {
292
+ messages,
293
+ head: page.head,
294
+ oldest
295
+ };
296
+ },
297
+ async presenceSet(channel, member, ttlMs) {
298
+ await call(channel, "presence-set", {
299
+ member,
300
+ ttlMs,
301
+ graceMs
302
+ }, readEmpty, true);
303
+ },
304
+ presenceRemove(channel, id) {
305
+ return call(channel, "presence-remove", { id }, readRemoved, false);
306
+ },
307
+ presenceList(channel) {
308
+ return call(channel, "presence-list", {}, readMembers, true);
309
+ },
310
+ async presenceTouch(entries, ttlMs) {
311
+ const byChannel = /* @__PURE__ */ new Map();
312
+ for (const entry of entries) {
313
+ const ids = byChannel.get(entry.channel);
314
+ if (ids === void 0) byChannel.set(entry.channel, [entry.id]);
315
+ else ids.push(entry.id);
316
+ }
317
+ return (await Promise.all([...byChannel].map(async ([channel, ids]) => {
318
+ return (await call(channel, "presence-touch", {
319
+ ids,
320
+ ttlMs
321
+ }, readMissing, true)).map((id) => ({
322
+ channel,
323
+ id
324
+ }));
325
+ }))).flat();
326
+ },
327
+ presenceReap(channel) {
328
+ return call(channel, "presence-reap", {}, readMembers, false);
329
+ },
330
+ presenceSubscribe(channel, listener) {
331
+ return join(channel, (link) => link.addPresence(listener));
332
+ },
333
+ async close() {
334
+ if (closed) return;
335
+ closed = true;
336
+ for (const controller of inflight) controller.abort();
337
+ for (const stop of [...sleepers]) stop(new RealtimeError("closed", "the adapter is closed"));
338
+ for (const link of [...links.values()]) link.dispose(new RealtimeError("closed", "the adapter is closed"));
339
+ links.clear();
340
+ for (const cancel of [...cancels]) cancel();
341
+ }
342
+ };
343
+ }
344
+ //#endregion
345
+ export { cloudflareAdapter };
346
+
347
+ //# sourceMappingURL=adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.js","names":[],"sources":["../../../src/cloudflare/adapter.ts"],"sourcesContent":["import type {\n Adapter,\n HistoryResult,\n PresenceRef,\n PublishOptions,\n PublishResult,\n Unsubscribe,\n} from \"../adapter\";\nimport { type ErrorCode, RealtimeError } from \"../errors\";\nimport type { Member, PresenceEvent, StoredEntry, StoredMessage } from \"../protocol\";\nimport { ChannelLink, type LinkHost } from \"./link\";\nimport type { ChannelNamespace, ChannelStub } from \"./types\";\nimport { isWholeNumber, parseMember, parseStored } from \"./wire\";\n\n/**\n * Options of {@link cloudflareAdapter}.\n * @since 1.0.0\n */\nexport interface CloudflareRealtimeOptions {\n /** The Durable Object namespace binding of {@link RealtimeChannelObject} (`env.CHANNELS`). */\n readonly namespace: ChannelNamespace;\n /**\n * Separates this adapter's channels from other adapters on the same namespace (the object's name is the prefix, a newline and\n * the channel). Default: none, the object is named by the channel alone.\n */\n readonly prefix?: string;\n /** A Cloudflare location hint for objects this adapter creates (for example `\"weur\"`). */\n readonly locationHint?: string;\n /** Milliseconds one request to an object may take. Default: 10,000. */\n readonly requestTimeoutMs?: number;\n /** Milliseconds `subscribe` waits for the subscribe socket before it rejects with `unavailable`. Default: 10,000. */\n readonly connectTimeoutMs?: number;\n /**\n * How many times a request that is safe to repeat (history, presence reads and updates, a publish with an idempotency key) is\n * repeated after a transport failure. Default: 2.\n */\n readonly retries?: number;\n /** Backoff of the subscribe socket: full jitter from `minDelayMs` up to `maxDelayMs`. Defaults: 250 and 10,000. */\n readonly reconnect?: { readonly minDelayMs?: number; readonly maxDelayMs?: number };\n /** Milliseconds between keep-alive pings on a subscribe socket; 0 turns them off. Default: 30,000. */\n readonly keepAliveMs?: number;\n /**\n * Milliseconds after a presence member's expiry that the object's alarm waits before it announces the leave itself. The hub's\n * own `presenceReap` normally runs inside that window. Default: 2,000.\n */\n readonly presenceGraceMs?: number;\n /**\n * Forget a channel (numbering, history, keys) after this many milliseconds with no message, no presence and no subscriber.\n * A client that resumes later sees `reset`. Default: 0, never.\n */\n readonly idleMs?: number;\n /** The most events queued for one listener before newer ones are dropped (the hub then recovers from history). Default: 1,000. */\n readonly maxQueued?: number;\n /** Receives failures nobody else sees: a listener that threw. Default: `console.error`. */\n readonly onError?: (\n error: unknown,\n context: { readonly where: string; readonly channel: string },\n ) => void;\n}\n\nconst ORIGIN = \"https://realtime.internal\";\nconst PAGE = 512;\n/** Codes the object answers that are the caller's to see; everything else is `unavailable`. */\nconst PASS: ReadonlySet<string> = new Set<ErrorCode>([\n \"too_large\",\n \"bad_channel\",\n \"bad_frame\",\n \"limit\",\n \"conflict\",\n]);\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction unavailable(message: string, cause?: unknown): RealtimeError {\n return new RealtimeError(\"unavailable\", message, cause === undefined ? undefined : { cause });\n}\n\nfunction count(value: unknown, name: string, positive = false): number {\n if (!isWholeNumber(value) || (positive && value < 1))\n throw new TypeError(`${name} must be a whole number`);\n return value;\n}\n\nfunction option(value: number | undefined, fallback: number, name: string): number {\n return value === undefined ? fallback : count(value, name);\n}\n\n/** Reads the object's reply. `parse` returns `undefined` for a shape it does not accept. */\ntype Reader<T> = (body: unknown) => T | undefined;\n\nconst readPublish: Reader<PublishResult> = (body) =>\n isRecord(body) &&\n isWholeNumber(body[\"seq\"]) &&\n body[\"seq\"] >= 1 &&\n typeof body[\"duplicate\"] === \"boolean\"\n ? { seq: body[\"seq\"], duplicate: body[\"duplicate\"] }\n : undefined;\n\ninterface Page extends HistoryResult {\n readonly more: boolean;\n}\n\nconst readHistory: Reader<Page> = (body) => {\n if (!isRecord(body) || !Array.isArray(body[\"messages\"])) return undefined;\n const { head, oldest, more } = body;\n if (!isWholeNumber(head) || !isWholeNumber(oldest) || typeof more !== \"boolean\") return undefined;\n const messages: StoredMessage[] = [];\n for (const item of body[\"messages\"]) {\n const message = parseStored(item);\n if (message === undefined) return undefined;\n messages.push(message);\n }\n return { messages, head, oldest, more };\n};\n\nconst readMembers: Reader<Member[]> = (body) => {\n if (!isRecord(body) || !Array.isArray(body[\"members\"])) return undefined;\n const members: Member[] = [];\n for (const item of body[\"members\"]) {\n const member = parseMember(item);\n if (member === undefined) return undefined;\n members.push(member);\n }\n return members;\n};\n\nconst readRemoved: Reader<boolean> = (body) =>\n isRecord(body) && typeof body[\"removed\"] === \"boolean\" ? body[\"removed\"] : undefined;\n\nconst readMissing: Reader<string[]> = (body) => {\n if (!isRecord(body) || !Array.isArray(body[\"missing\"])) return undefined;\n return body[\"missing\"].every((id) => typeof id === \"string\")\n ? (body[\"missing\"] as string[])\n : undefined;\n};\n\nconst readEmpty: Reader<true> = (body) => (isRecord(body) ? true : undefined);\n\n/**\n * The adapter for Cloudflare Workers: each channel is one Durable Object ({@link RealtimeChannelObject}) that numbers,\n * retains and fans out its messages, so a channel is ordered and gapless wherever the Workers that use it run.\n *\n * `publish`, `history` and the presence calls are one request to the channel's object each. `subscribe` and\n * `presenceSubscribe` share one WebSocket per channel from this adapter to the object, which the adapter reopens with backoff\n * and resumes with the last seq it saw. Call `close()` when the adapter is no longer needed; it closes every socket and\n * cancels every timer.\n *\n * Within one Worker request, create the adapter where the hub lives. A Worker may touch a socket only in the request that\n * created it, so one hub that serves many client connections belongs inside a Durable Object, which keeps one context for all\n * of them; see the package's Cloudflare guide.\n *\n * @param options The namespace binding of the channel object and the tuning.\n * @returns The adapter.\n * @throws {TypeError} When an option is not valid.\n * @example\n * import { createHub } from \"@shadow-js/realtime/server\";\n * import { cloudflareAdapter } from \"@shadow-js/realtime/cloudflare\";\n *\n * declare const env: { CHANNELS: Parameters<typeof cloudflareAdapter>[0][\"namespace\"] };\n * const adapter = cloudflareAdapter({ namespace: env.CHANNELS });\n * const hub = createHub({ adapter, channels: { \"chat:{room}\": { subscribe: true, publish: true } } });\n * void hub.close();\n * @since 1.0.0\n */\nexport function cloudflareAdapter(options: CloudflareRealtimeOptions): Adapter {\n const { namespace } = options;\n if (typeof namespace?.idFromName !== \"function\" || typeof namespace.get !== \"function\")\n throw new TypeError(\"cloudflareAdapter: namespace must be a Durable Object namespace binding\");\n const prefix = options.prefix;\n if (prefix !== undefined && (prefix.length === 0 || prefix.length > 256 || prefix.includes(\"\\n\")))\n throw new TypeError(\"cloudflareAdapter: prefix must be 1..256 characters without a newline\");\n const requestTimeoutMs = option(options.requestTimeoutMs, 10_000, \"requestTimeoutMs\");\n const connectTimeoutMs = option(options.connectTimeoutMs, 10_000, \"connectTimeoutMs\");\n const retries = option(options.retries, 2, \"retries\");\n const minDelayMs = option(options.reconnect?.minDelayMs, 250, \"reconnect.minDelayMs\");\n const maxDelayMs = option(options.reconnect?.maxDelayMs, 10_000, \"reconnect.maxDelayMs\");\n const keepAliveMs = option(options.keepAliveMs, 30_000, \"keepAliveMs\");\n const graceMs = option(options.presenceGraceMs, 2000, \"presenceGraceMs\");\n const idleMs = option(options.idleMs, 0, \"idleMs\");\n const maxQueued = count(options.maxQueued ?? 1000, \"maxQueued\", true);\n if (requestTimeoutMs === 0 || connectTimeoutMs === 0)\n throw new TypeError(\"cloudflareAdapter: timeouts must be above 0\");\n const report =\n options.onError ??\n ((error: unknown, context: { readonly where: string; readonly channel: string }): void =>\n console.error(`[realtime] ${context.where} ${context.channel}`, error));\n\n let closed = false;\n const links = new Map<string, ChannelLink>();\n const cancels = new Set<() => void>();\n const inflight = new Set<AbortController>();\n const sleepers = new Set<(error: unknown) => void>();\n\n function stubFor(channel: string): ChannelStub {\n const name = prefix === undefined ? channel : `${prefix}\\n${channel}`;\n const id = namespace.idFromName(name);\n return options.locationHint === undefined\n ? namespace.get(id)\n : namespace.get(id, { locationHint: options.locationHint });\n }\n\n function after(ms: number, task: () => void): () => void {\n const handle = setTimeout(() => {\n cancels.delete(cancel);\n task();\n }, ms);\n (handle as { unref?: () => void }).unref?.();\n const cancel = (): void => {\n clearTimeout(handle);\n cancels.delete(cancel);\n };\n cancels.add(cancel);\n return cancel;\n }\n\n function every(ms: number, task: () => void): () => void {\n const handle = setInterval(task, ms);\n (handle as { unref?: () => void }).unref?.();\n const cancel = (): void => {\n clearInterval(handle);\n cancels.delete(cancel);\n };\n cancels.add(cancel);\n return cancel;\n }\n\n function delay(ms: number): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const stop = (error: unknown): void => {\n cancel();\n sleepers.delete(stop);\n reject(error);\n };\n const cancel = after(ms, () => {\n sleepers.delete(stop);\n resolve();\n });\n sleepers.add(stop);\n });\n }\n\n function assertOpen(): void {\n if (closed) throw new RealtimeError(\"closed\", \"the adapter is closed\");\n }\n\n /**\n * One request to the channel's object. A transport failure, a 5xx or an unreadable reply is `unavailable`; with `safe` it is\n * repeated first, with a short growing pause.\n */\n async function call<T>(\n channel: string,\n op: string,\n body: Readonly<Record<string, unknown>>,\n read: Reader<T>,\n safe: boolean,\n ): Promise<T> {\n for (let attempt = 0; ; attempt += 1) {\n assertOpen();\n const controller = new AbortController();\n inflight.add(controller);\n const timer = setTimeout(() => controller.abort(), requestTimeoutMs);\n try {\n const response = await stubFor(channel).fetch(`${ORIGIN}/v1/${op}`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ channel, ...body }),\n signal: controller.signal,\n });\n const text = await response.text();\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n throw unavailable(\n `the channel object answered ${response.status} with a body that is not JSON`,\n );\n }\n if (response.status === 200) {\n const result = read(parsed);\n if (result === undefined)\n throw unavailable(\"the channel object answered an unexpected shape\");\n return result;\n }\n const refusal = isRecord(parsed) && isRecord(parsed[\"error\"]) ? parsed[\"error\"] : undefined;\n const code = refusal?.[\"code\"];\n if (typeof code === \"string\" && PASS.has(code))\n throw new RealtimeError(code as ErrorCode, String(refusal?.[\"message\"] ?? code));\n throw unavailable(`the channel object answered ${response.status}`);\n } catch (error) {\n if (closed) throw new RealtimeError(\"closed\", \"the adapter is closed\", { cause: error });\n if (error instanceof RealtimeError && error.code !== \"unavailable\") throw error;\n if (safe && attempt < retries) {\n await delay(Math.min(50 * 3 ** attempt, 1000));\n continue;\n }\n throw error instanceof RealtimeError\n ? error\n : unavailable(\"the channel object is unreachable\", error);\n } finally {\n clearTimeout(timer);\n inflight.delete(controller);\n }\n }\n }\n\n const host: LinkHost = {\n reconnectMinMs: minDelayMs,\n reconnectMaxMs: Math.max(minDelayMs, maxDelayMs),\n keepAliveMs,\n connectTimeoutMs,\n maxQueued,\n now: Date.now,\n random: Math.random,\n async open(channel, from) {\n const query = `c=${encodeURIComponent(channel)}${from === undefined ? \"\" : `&after=${from}`}`;\n const response = await stubFor(channel).fetch(`${ORIGIN}/v1/ws?${query}`, {\n headers: { upgrade: \"websocket\" },\n });\n const socket = response.webSocket;\n if (response.status !== 101 || socket === undefined || socket === null)\n throw unavailable(`the channel object refused the subscribe socket (${response.status})`);\n return socket;\n },\n after,\n every,\n report: (error, where, channel) => report(error, { where, channel }),\n forget(link) {\n if (links.get(link.channel) === link) links.delete(link.channel);\n },\n };\n\n function linkFor(channel: string): ChannelLink {\n let link = links.get(channel);\n if (link === undefined) {\n link = new ChannelLink(channel, host);\n links.set(channel, link);\n }\n return link;\n }\n\n async function join(\n channel: string,\n add: (link: ChannelLink) => ReturnType<ChannelLink[\"addMessages\"]>,\n ): Promise<Unsubscribe> {\n assertOpen();\n const link = linkFor(channel);\n const sub = add(link);\n try {\n await link.whenReady(connectTimeoutMs);\n } catch (error) {\n link.remove(sub);\n if (closed) throw new RealtimeError(\"closed\", \"the adapter is closed\", { cause: error });\n throw error instanceof RealtimeError ? error : unavailable(\"could not subscribe\", error);\n }\n return async () => {\n link.remove(sub);\n };\n }\n\n const keepRetained = (entry: StoredEntry): Record<string, unknown> => ({\n event: entry.event,\n data: entry.data,\n ...(entry.publisher === undefined ? {} : { publisher: entry.publisher }),\n ...(entry.user === undefined ? {} : { user: entry.user }),\n });\n\n return {\n publish(channel: string, entry: StoredEntry, rules: PublishOptions): Promise<PublishResult> {\n return call(\n channel,\n \"publish\",\n {\n entry: keepRetained(entry),\n options: {\n history: rules.history,\n historyTtlMs: rules.historyTtlMs,\n idempotencyTtlMs: rules.idempotencyTtlMs,\n ...(rules.key === undefined ? {} : { key: rules.key }),\n },\n ...(idleMs > 0 ? { idleMs } : {}),\n },\n readPublish,\n rules.key !== undefined,\n );\n },\n\n subscribe(channel: string, listener: (message: StoredMessage) => void): Promise<Unsubscribe> {\n return join(channel, (link) => link.addMessages(listener));\n },\n\n async history(channel: string, after: number, limit: number): Promise<HistoryResult> {\n const read = (from: number, want: number): Promise<Page> =>\n call(channel, \"history\", { after: from, limit: want }, readHistory, true);\n let page = await read(after, limit <= 0 ? 0 : Math.min(limit, PAGE));\n const { oldest } = page;\n const messages: StoredMessage[] = [...page.messages];\n // The object answers at most a page at a time; keep reading until `limit` or the end of the retained messages.\n while (page.more && messages.length < limit) {\n const tail = messages[messages.length - 1];\n if (tail === undefined) break;\n page = await read(tail.seq, Math.min(limit - messages.length, PAGE));\n messages.push(...page.messages);\n }\n return { messages, head: page.head, oldest };\n },\n\n async presenceSet(channel: string, member: Member, ttlMs: number): Promise<void> {\n await call(channel, \"presence-set\", { member, ttlMs, graceMs }, readEmpty, true);\n },\n\n presenceRemove(channel: string, id: string): Promise<boolean> {\n return call(channel, \"presence-remove\", { id }, readRemoved, false);\n },\n\n presenceList(channel: string): Promise<readonly Member[]> {\n return call(channel, \"presence-list\", {}, readMembers, true);\n },\n\n async presenceTouch(\n entries: readonly PresenceRef[],\n ttlMs: number,\n ): Promise<readonly PresenceRef[]> {\n const byChannel = new Map<string, string[]>();\n for (const entry of entries) {\n const ids = byChannel.get(entry.channel);\n if (ids === undefined) byChannel.set(entry.channel, [entry.id]);\n else ids.push(entry.id);\n }\n const results = await Promise.all(\n [...byChannel].map(async ([channel, ids]) => {\n const missing = await call(channel, \"presence-touch\", { ids, ttlMs }, readMissing, true);\n return missing.map((id): PresenceRef => ({ channel, id }));\n }),\n );\n return results.flat();\n },\n\n presenceReap(channel: string): Promise<readonly Member[]> {\n return call(channel, \"presence-reap\", {}, readMembers, false);\n },\n\n presenceSubscribe(\n channel: string,\n listener: (event: PresenceEvent) => void,\n ): Promise<Unsubscribe> {\n return join(channel, (link) => link.addPresence(listener));\n },\n\n async close(): Promise<void> {\n if (closed) return;\n closed = true;\n for (const controller of inflight) controller.abort();\n for (const stop of [...sleepers]) stop(new RealtimeError(\"closed\", \"the adapter is closed\"));\n for (const link of [...links.values()])\n link.dispose(new RealtimeError(\"closed\", \"the adapter is closed\"));\n links.clear();\n for (const cancel of [...cancels]) cancel();\n },\n };\n}\n"],"mappings":"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"}