@shadow-js/realtime 0.0.0-stage → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/LICENSE +21 -0
  3. package/README.md +114 -2
  4. package/dist/dev/adapter.d.ts +73 -0
  5. package/dist/dev/client/core.d.ts +22 -0
  6. package/dist/dev/client/core.js +844 -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 +22 -0
  11. package/dist/dev/client/presence.js.map +1 -0
  12. package/dist/dev/client/sse.js +75 -0
  13. package/dist/dev/client/sse.js.map +1 -0
  14. package/dist/dev/client/types.d.ts +254 -0
  15. package/dist/dev/client/util.js +29 -0
  16. package/dist/dev/client/util.js.map +1 -0
  17. package/dist/dev/client/ws.js +38 -0
  18. package/dist/dev/client/ws.js.map +1 -0
  19. package/dist/dev/cloudflare/adapter.d.ts +78 -0
  20. package/dist/dev/cloudflare/adapter.js +346 -0
  21. package/dist/dev/cloudflare/adapter.js.map +1 -0
  22. package/dist/dev/cloudflare/core.js +700 -0
  23. package/dist/dev/cloudflare/core.js.map +1 -0
  24. package/dist/dev/cloudflare/link.js +396 -0
  25. package/dist/dev/cloudflare/link.js.map +1 -0
  26. package/dist/dev/cloudflare/object.d.ts +80 -0
  27. package/dist/dev/cloudflare/object.js +213 -0
  28. package/dist/dev/cloudflare/object.js.map +1 -0
  29. package/dist/dev/cloudflare/types.d.ts +98 -0
  30. package/dist/dev/cloudflare/wire.js +283 -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 +23 -0
  35. package/dist/dev/errors.js +23 -0
  36. package/dist/dev/errors.js.map +1 -0
  37. package/dist/dev/hooks.d.ts +108 -0
  38. package/dist/dev/hooks.js +161 -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 +31 -0
  43. package/dist/dev/memory.js +254 -0
  44. package/dist/dev/memory.js.map +1 -0
  45. package/dist/dev/protocol.d.ts +209 -0
  46. package/dist/dev/protocol.js +399 -0
  47. package/dist/dev/protocol.js.map +1 -0
  48. package/dist/dev/redis/client.d.ts +137 -0
  49. package/dist/dev/redis/client.js +145 -0
  50. package/dist/dev/redis/client.js.map +1 -0
  51. package/dist/dev/redis/scripts.js +274 -0
  52. package/dist/dev/redis/scripts.js.map +1 -0
  53. package/dist/dev/redis/topics.d.ts +6 -0
  54. package/dist/dev/redis/topics.js +376 -0
  55. package/dist/dev/redis/topics.js.map +1 -0
  56. package/dist/dev/redis.d.ts +66 -0
  57. package/dist/dev/redis.js +421 -0
  58. package/dist/dev/redis.js.map +1 -0
  59. package/dist/dev/server/http.d.ts +46 -0
  60. package/dist/dev/server/http.js +285 -0
  61. package/dist/dev/server/http.js.map +1 -0
  62. package/dist/dev/server/hub.d.ts +112 -0
  63. package/dist/dev/server/hub.js +826 -0
  64. package/dist/dev/server/hub.js.map +1 -0
  65. package/dist/dev/server/index.d.ts +45 -0
  66. package/dist/dev/server/index.js +40 -0
  67. package/dist/dev/server/index.js.map +1 -0
  68. package/dist/dev/server/limits.d.ts +52 -0
  69. package/dist/dev/server/limits.js +84 -0
  70. package/dist/dev/server/limits.js.map +1 -0
  71. package/dist/dev/server/rules.d.ts +41 -0
  72. package/dist/dev/server/rules.js +71 -0
  73. package/dist/dev/server/rules.js.map +1 -0
  74. package/dist/dev/testing.d.ts +113 -0
  75. package/dist/dev/testing.js +426 -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,213 @@
1
+ import { RealtimeError } from "../errors.js";
2
+ import { isChannelName } from "../protocol.js";
3
+ import { isOperation, parseRequest } from "./wire.js";
4
+ import { ChannelCore } from "./core.js";
5
+ // src/cloudflare/object.ts
6
+ function workersPlatform() {
7
+ return {
8
+ createPair() {
9
+ const Pair = Reflect.get(globalThis, "WebSocketPair");
10
+ if (typeof Pair !== "function") throw new RealtimeError("unavailable", "WebSocketPair is missing: this is not a Worker");
11
+ const pair = Reflect.construct(Pair, []);
12
+ const client = Reflect.get(Object(pair), "0");
13
+ const server = Reflect.get(Object(pair), "1");
14
+ if (typeof server !== "object" || server === null || typeof Reflect.get(server, "send") !== "function" || typeof Reflect.get(server, "close") !== "function") throw new TypeError("WebSocketPair returned an unexpected socket");
15
+ return {
16
+ client,
17
+ server
18
+ };
19
+ },
20
+ upgrade(client) {
21
+ return new Response(null, {
22
+ status: 101,
23
+ webSocket: client
24
+ });
25
+ },
26
+ autoResponse() {
27
+ const Pair = Reflect.get(globalThis, "WebSocketRequestResponsePair");
28
+ return typeof Pair === "function" ? Reflect.construct(Pair, ["ping", "pong"]) : void 0;
29
+ }
30
+ };
31
+ }
32
+ const JSON_HEADERS = { "content-type": "application/json" };
33
+ function reply(body, status = 200) {
34
+ return new Response(JSON.stringify(body), {
35
+ status,
36
+ headers: JSON_HEADERS
37
+ });
38
+ }
39
+ function refusal(code, message, status) {
40
+ return reply({ error: {
41
+ code,
42
+ message
43
+ } }, status);
44
+ }
45
+ const STATUS = {
46
+ bad_frame: 400,
47
+ bad_channel: 400,
48
+ too_large: 413,
49
+ limit: 429,
50
+ conflict: 409
51
+ };
52
+ /** A close code `close()` may send: the reserved 1005, 1006 and 1015 never go on the wire. */
53
+ function replyCode(code) {
54
+ return code >= 1e3 && code < 5e3 && code !== 1005 && code !== 1006 && code !== 1015 ? code : 1e3;
55
+ }
56
+ function readAfter(url) {
57
+ const value = url.searchParams.get("after");
58
+ if (value === null) return void 0;
59
+ if (!/^\d{1,16}$/.test(value)) return "bad";
60
+ return Number(value);
61
+ }
62
+ /**
63
+ * The Durable Object that owns one channel: the single writer of its sequence numbers, its retained history, its idempotency
64
+ * keys and its presence set, and the fan-out to the Workers that subscribed. Re-export it from your Worker and bind it as a
65
+ * SQLite-backed Durable Object; `cloudflareAdapter` talks to it.
66
+ *
67
+ * It keeps everything in Durable Object storage, so it can be evicted and restart at any time, and it uses the WebSocket
68
+ * Hibernation API, so a channel nobody publishes to costs nothing while its subscribers stay connected. An alarm reaps expired
69
+ * presence members and expired retention.
70
+ *
71
+ * @example
72
+ * // worker.ts
73
+ * export { RealtimeChannelObject } from "@shadow-js/realtime/cloudflare";
74
+ *
75
+ * // wrangler.toml
76
+ * // [[durable_objects.bindings]]
77
+ * // name = "CHANNELS"
78
+ * // class_name = "RealtimeChannelObject"
79
+ * // [[migrations]]
80
+ * // tag = "v1"
81
+ * // new_sqlite_classes = ["RealtimeChannelObject"]
82
+ * @since 1.0.0
83
+ */
84
+ var RealtimeChannelObject = class {
85
+ #state;
86
+ #core;
87
+ #platform;
88
+ /**
89
+ * @param state The Durable Object state the runtime passes.
90
+ * @param env The Worker's bindings (unused).
91
+ * @param platform The platform seams (the runtime passes none).
92
+ */
93
+ constructor(state, env, platform) {
94
+ this.#state = state;
95
+ this.#platform = platform ?? workersPlatform();
96
+ this.#core = new ChannelCore({ storage: state.storage });
97
+ this.#core.adopt(state.getWebSockets());
98
+ const pair = this.#platform.autoResponse();
99
+ if (pair !== void 0) state.setWebSocketAutoResponse?.(pair);
100
+ }
101
+ /**
102
+ * Handles a request from `cloudflareAdapter`: `POST /v1/<operation>` with a JSON body, or `GET /v1/ws` to subscribe.
103
+ *
104
+ * @param request The request.
105
+ * @returns The response.
106
+ */
107
+ async fetch(request) {
108
+ const url = new URL(request.url);
109
+ if (url.pathname === `/v1/ws`) return this.#subscribe(request, url);
110
+ const operation = /^\/v(\d+)\/([a-z-]+)$/.exec(url.pathname);
111
+ if (operation?.[1] !== String(1) || !isOperation(operation[2] ?? "")) return refusal("bad_frame", "unknown operation", 404);
112
+ const op = operation[2] ?? "";
113
+ if (!isOperation(op)) return refusal("bad_frame", "unknown operation", 404);
114
+ if (request.method !== "POST") return refusal("bad_frame", "use POST", 405);
115
+ if (Number(request.headers.get("content-length") ?? 0) > 262144) return refusal("too_large", "the body is too large", 413);
116
+ const text = await request.text();
117
+ if (text.length > 262144) return refusal("too_large", "the body is too large", 413);
118
+ let body;
119
+ try {
120
+ body = JSON.parse(text);
121
+ } catch {
122
+ return refusal("bad_frame", "the body is not JSON", 400);
123
+ }
124
+ const parsed = parseRequest(op, body);
125
+ if (!parsed.ok) return refusal(parsed.code, parsed.message, STATUS[parsed.code] ?? 400);
126
+ const call = parsed.request;
127
+ try {
128
+ switch (call.op) {
129
+ case "publish": return reply(await this.#core.publish(call.channel, call.entry, call.options, call.idleMs));
130
+ case "history": return reply(await this.#core.history(call.channel, call.after, call.limit));
131
+ case "presence-set":
132
+ await this.#core.presenceSet(call.channel, call.member, call.ttlMs, call.graceMs);
133
+ return reply({});
134
+ case "presence-remove": return reply({ removed: await this.#core.presenceRemove(call.channel, call.id) });
135
+ case "presence-list": return reply({ members: await this.#core.presenceList(call.channel) });
136
+ case "presence-touch": return reply({ missing: await this.#core.presenceTouch(call.channel, call.ids, call.ttlMs) });
137
+ case "presence-reap": return reply({ members: await this.#core.presenceReap(call.channel) });
138
+ }
139
+ } catch (error) {
140
+ if (error instanceof RealtimeError) return refusal(error.code, error.message, STATUS[error.code] ?? 500);
141
+ console.error("[realtime] channel object", error);
142
+ return refusal("unavailable", "the channel object failed", 500);
143
+ }
144
+ }
145
+ async #subscribe(request, url) {
146
+ if (request.headers.get("upgrade")?.toLowerCase() !== "websocket") return refusal("bad_frame", "expected a WebSocket upgrade", 426);
147
+ const channel = url.searchParams.get("c");
148
+ if (!isChannelName(channel)) return refusal("bad_channel", "bad channel name", 400);
149
+ const after = readAfter(url);
150
+ if (after === "bad") return refusal("bad_frame", "bad after", 400);
151
+ const { client, server } = this.#platform.createPair();
152
+ this.#state.acceptWebSocket(server);
153
+ try {
154
+ await this.#core.attach(server, channel, after);
155
+ } catch (error) {
156
+ try {
157
+ server.close(1011, "attach failed");
158
+ } catch {}
159
+ if (error instanceof RealtimeError) return refusal(error.code, error.message, STATUS[error.code] ?? 500);
160
+ console.error("[realtime] channel object", error);
161
+ return refusal("unavailable", "the channel object failed", 500);
162
+ }
163
+ return this.#platform.upgrade(client);
164
+ }
165
+ /**
166
+ * Runtime hook: a message on a subscribe socket. The only message is the keep-alive `ping`, which the runtime normally answers
167
+ * itself without waking the object.
168
+ *
169
+ * @param socket The socket.
170
+ * @param message The message.
171
+ */
172
+ webSocketMessage(socket, message) {
173
+ if (message === "ping") try {
174
+ socket.send("pong");
175
+ } catch {
176
+ this.#core.detach(socket);
177
+ }
178
+ }
179
+ /**
180
+ * Runtime hook: a subscribe socket closed.
181
+ *
182
+ * @param socket The socket.
183
+ * @param code The close code the peer sent.
184
+ * @param reason The reason the peer sent.
185
+ */
186
+ webSocketClose(socket, code, reason) {
187
+ this.#core.detach(socket);
188
+ try {
189
+ socket.close(replyCode(code), reason.slice(0, 100));
190
+ } catch {}
191
+ }
192
+ /**
193
+ * Runtime hook: a subscribe socket failed.
194
+ *
195
+ * @param socket The socket.
196
+ */
197
+ webSocketError(socket) {
198
+ this.#core.detach(socket);
199
+ try {
200
+ socket.close(1011, "error");
201
+ } catch {}
202
+ }
203
+ /**
204
+ * Runtime hook: the alarm. Reaps expired presence members (one `leave` each), expired idempotency keys and expired retained
205
+ * messages, and sets the next alarm.
206
+ */
207
+ async alarm() {
208
+ await this.#core.alarm();
209
+ }
210
+ };
211
+ export { RealtimeChannelObject };
212
+
213
+ //# sourceMappingURL=object.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"object.js","names":[],"sources":["../../../src/cloudflare/object.ts"],"sourcesContent":["import { RealtimeError } from \"../errors\";\nimport { isChannelName } from \"../protocol\";\nimport { ChannelCore } from \"./core\";\nimport type { ChannelObjectState, ChannelSocket } from \"./types\";\nimport { isOperation, MAX_BODY_BYTES, parseRequest, WIRE_VERSION } from \"./wire\";\n\n/**\n * The pieces of the platform the object creates: a WebSocket pair, the upgrade response and the auto-response pair. The default\n * reads the Workers globals; the unit tests pass their own.\n * @internal\n */\nexport interface ObjectPlatform {\n createPair(): { readonly client: unknown; readonly server: ChannelSocket };\n upgrade(client: unknown): Response;\n autoResponse(): object | undefined;\n}\n\nfunction workersPlatform(): ObjectPlatform {\n return {\n createPair() {\n const Pair: unknown = Reflect.get(globalThis, \"WebSocketPair\");\n if (typeof Pair !== \"function\")\n throw new RealtimeError(\"unavailable\", \"WebSocketPair is missing: this is not a Worker\");\n const pair: unknown = Reflect.construct(Pair, []);\n const client: unknown = Reflect.get(Object(pair), \"0\");\n const server: unknown = Reflect.get(Object(pair), \"1\");\n if (\n typeof server !== \"object\" ||\n server === null ||\n typeof Reflect.get(server, \"send\") !== \"function\" ||\n typeof Reflect.get(server, \"close\") !== \"function\"\n )\n throw new TypeError(\"WebSocketPair returned an unexpected socket\");\n return { client, server: server as ChannelSocket };\n },\n upgrade(client) {\n // `webSocket` is a Workers extension of `ResponseInit`; the standard type does not have it.\n return new Response(null, { status: 101, webSocket: client } as unknown as ResponseInit);\n },\n autoResponse() {\n const Pair: unknown = Reflect.get(globalThis, \"WebSocketRequestResponsePair\");\n return typeof Pair === \"function\"\n ? (Reflect.construct(Pair, [\"ping\", \"pong\"]) as object)\n : undefined;\n },\n };\n}\n\nconst JSON_HEADERS = { \"content-type\": \"application/json\" } as const;\n\nfunction reply(body: unknown, status = 200): Response {\n return new Response(JSON.stringify(body), { status, headers: JSON_HEADERS });\n}\n\nfunction refusal(code: string, message: string, status: number): Response {\n return reply({ error: { code, message } }, status);\n}\n\nconst STATUS: Readonly<Record<string, number>> = {\n bad_frame: 400,\n bad_channel: 400,\n too_large: 413,\n limit: 429,\n conflict: 409,\n};\n\n/** A close code `close()` may send: the reserved 1005, 1006 and 1015 never go on the wire. */\nfunction replyCode(code: number): number {\n return code >= 1000 && code < 5000 && code !== 1005 && code !== 1006 && code !== 1015\n ? code\n : 1000;\n}\n\nfunction readAfter(url: URL): number | undefined | \"bad\" {\n const value = url.searchParams.get(\"after\");\n if (value === null) return undefined;\n if (!/^\\d{1,16}$/.test(value)) return \"bad\";\n return Number(value);\n}\n\n/**\n * The Durable Object that owns one channel: the single writer of its sequence numbers, its retained history, its idempotency\n * keys and its presence set, and the fan-out to the Workers that subscribed. Re-export it from your Worker and bind it as a\n * SQLite-backed Durable Object; `cloudflareAdapter` talks to it.\n *\n * It keeps everything in Durable Object storage, so it can be evicted and restart at any time, and it uses the WebSocket\n * Hibernation API, so a channel nobody publishes to costs nothing while its subscribers stay connected. An alarm reaps expired\n * presence members and expired retention.\n *\n * @example\n * // worker.ts\n * export { RealtimeChannelObject } from \"@shadow-js/realtime/cloudflare\";\n *\n * // wrangler.toml\n * // [[durable_objects.bindings]]\n * // name = \"CHANNELS\"\n * // class_name = \"RealtimeChannelObject\"\n * // [[migrations]]\n * // tag = \"v1\"\n * // new_sqlite_classes = [\"RealtimeChannelObject\"]\n * @since 1.0.0\n */\nexport class RealtimeChannelObject {\n readonly #state: ChannelObjectState;\n readonly #core: ChannelCore;\n readonly #platform: ObjectPlatform;\n\n /**\n * @param state The Durable Object state the runtime passes.\n * @param env The Worker's bindings (unused).\n * @param platform The platform seams (the runtime passes none).\n */\n constructor(state: ChannelObjectState, env?: unknown, platform?: ObjectPlatform) {\n void env;\n this.#state = state;\n this.#platform = platform ?? workersPlatform();\n this.#core = new ChannelCore({ storage: state.storage });\n this.#core.adopt(state.getWebSockets());\n const pair = this.#platform.autoResponse();\n if (pair !== undefined) state.setWebSocketAutoResponse?.(pair);\n }\n\n /**\n * Handles a request from `cloudflareAdapter`: `POST /v1/<operation>` with a JSON body, or `GET /v1/ws` to subscribe.\n *\n * @param request The request.\n * @returns The response.\n */\n async fetch(request: Request): Promise<Response> {\n const url = new URL(request.url);\n if (url.pathname === `/v${WIRE_VERSION}/ws`) return this.#subscribe(request, url);\n const operation = /^\\/v(\\d+)\\/([a-z-]+)$/.exec(url.pathname);\n if (operation?.[1] !== String(WIRE_VERSION) || !isOperation(operation[2] ?? \"\"))\n return refusal(\"bad_frame\", \"unknown operation\", 404);\n const op = operation[2] ?? \"\";\n if (!isOperation(op)) return refusal(\"bad_frame\", \"unknown operation\", 404);\n if (request.method !== \"POST\") return refusal(\"bad_frame\", \"use POST\", 405);\n const declared = Number(request.headers.get(\"content-length\") ?? 0);\n if (declared > MAX_BODY_BYTES) return refusal(\"too_large\", \"the body is too large\", 413);\n const text = await request.text();\n if (text.length > MAX_BODY_BYTES) return refusal(\"too_large\", \"the body is too large\", 413);\n let body: unknown;\n try {\n body = JSON.parse(text);\n } catch {\n return refusal(\"bad_frame\", \"the body is not JSON\", 400);\n }\n const parsed = parseRequest(op, body);\n if (!parsed.ok) return refusal(parsed.code, parsed.message, STATUS[parsed.code] ?? 400);\n const call = parsed.request;\n try {\n switch (call.op) {\n case \"publish\":\n return reply(\n await this.#core.publish(call.channel, call.entry, call.options, call.idleMs),\n );\n case \"history\":\n return reply(await this.#core.history(call.channel, call.after, call.limit));\n case \"presence-set\":\n await this.#core.presenceSet(call.channel, call.member, call.ttlMs, call.graceMs);\n return reply({});\n case \"presence-remove\":\n return reply({ removed: await this.#core.presenceRemove(call.channel, call.id) });\n case \"presence-list\":\n return reply({ members: await this.#core.presenceList(call.channel) });\n case \"presence-touch\":\n return reply({\n missing: await this.#core.presenceTouch(call.channel, call.ids, call.ttlMs),\n });\n case \"presence-reap\":\n return reply({ members: await this.#core.presenceReap(call.channel) });\n }\n } catch (error) {\n if (error instanceof RealtimeError)\n return refusal(error.code, error.message, STATUS[error.code] ?? 500);\n console.error(\"[realtime] channel object\", error);\n return refusal(\"unavailable\", \"the channel object failed\", 500);\n }\n }\n\n async #subscribe(request: Request, url: URL): Promise<Response> {\n if (request.headers.get(\"upgrade\")?.toLowerCase() !== \"websocket\")\n return refusal(\"bad_frame\", \"expected a WebSocket upgrade\", 426);\n const channel = url.searchParams.get(\"c\");\n if (!isChannelName(channel)) return refusal(\"bad_channel\", \"bad channel name\", 400);\n const after = readAfter(url);\n if (after === \"bad\") return refusal(\"bad_frame\", \"bad after\", 400);\n const { client, server } = this.#platform.createPair();\n this.#state.acceptWebSocket(server);\n try {\n await this.#core.attach(server, channel, after);\n } catch (error) {\n try {\n server.close(1011, \"attach failed\");\n } catch {\n // Already closed.\n }\n if (error instanceof RealtimeError)\n return refusal(error.code, error.message, STATUS[error.code] ?? 500);\n console.error(\"[realtime] channel object\", error);\n return refusal(\"unavailable\", \"the channel object failed\", 500);\n }\n return this.#platform.upgrade(client);\n }\n\n /**\n * Runtime hook: a message on a subscribe socket. The only message is the keep-alive `ping`, which the runtime normally answers\n * itself without waking the object.\n *\n * @param socket The socket.\n * @param message The message.\n */\n webSocketMessage(socket: ChannelSocket, message: string | ArrayBuffer): void {\n if (message === \"ping\") {\n try {\n socket.send(\"pong\");\n } catch {\n this.#core.detach(socket);\n }\n }\n }\n\n /**\n * Runtime hook: a subscribe socket closed.\n *\n * @param socket The socket.\n * @param code The close code the peer sent.\n * @param reason The reason the peer sent.\n */\n webSocketClose(socket: ChannelSocket, code: number, reason: string): void {\n this.#core.detach(socket);\n try {\n socket.close(replyCode(code), reason.slice(0, 100));\n } catch {\n // Already closed.\n }\n }\n\n /**\n * Runtime hook: a subscribe socket failed.\n *\n * @param socket The socket.\n */\n webSocketError(socket: ChannelSocket): void {\n this.#core.detach(socket);\n try {\n socket.close(1011, \"error\");\n } catch {\n // Already closed.\n }\n }\n\n /**\n * Runtime hook: the alarm. Reaps expired presence members (one `leave` each), expired idempotency keys and expired retained\n * messages, and sets the next alarm.\n */\n async alarm(): Promise<void> {\n await this.#core.alarm();\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * The few shapes of the Cloudflare Workers runtime the Durable Object adapter touches. They are declared here, structurally, so
3
+ * the package has no dependency on `@cloudflare/workers-types`: the real `DurableObjectNamespace`, `DurableObjectState` and
4
+ * `DurableObjectStorage` satisfy them, and so do the in-memory doubles the unit tests use.
5
+ * @packageDocumentation
6
+ */
7
+ /** Options of {@link ChannelStorage.list}. @since 1.0.0 */
8
+ export interface StorageListOptions {
9
+ /** Only keys with this prefix. */
10
+ readonly prefix?: string;
11
+ /** The first key to return (inclusive). */
12
+ readonly start?: string;
13
+ /** The key to stop at (exclusive). */
14
+ readonly end?: string;
15
+ /** The most entries to return. */
16
+ readonly limit?: number;
17
+ /** Return in descending key order. */
18
+ readonly reverse?: boolean;
19
+ }
20
+ /** The writes available inside {@link ChannelStorage.transaction}. @since 1.0.0 */
21
+ export interface StorageTransaction {
22
+ put(entries: Readonly<Record<string, unknown>>): Promise<void>;
23
+ delete(keys: readonly string[]): Promise<number>;
24
+ }
25
+ /**
26
+ * The part of a Durable Object's storage the channel object uses. Keys sort as strings, so numbers inside keys are zero padded.
27
+ * @since 1.0.0
28
+ */
29
+ export interface ChannelStorage {
30
+ get<T>(key: string): Promise<T | undefined>;
31
+ get<T>(keys: readonly string[]): Promise<Map<string, T>>;
32
+ put(entries: Readonly<Record<string, unknown>>): Promise<void>;
33
+ delete(keys: readonly string[]): Promise<number>;
34
+ list<T>(options?: StorageListOptions): Promise<Map<string, T>>;
35
+ transaction<T>(closure: (transaction: StorageTransaction) => Promise<T>): Promise<T>;
36
+ getAlarm(): Promise<number | null>;
37
+ setAlarm(scheduledTime: number): Promise<void>;
38
+ deleteAlarm(): Promise<void>;
39
+ deleteAll(): Promise<void>;
40
+ }
41
+ /** A WebSocket as the channel object and the adapter use it. @since 1.0.0 */
42
+ export interface ChannelSocket {
43
+ send(data: string): void;
44
+ close(code?: number, reason?: string): void;
45
+ }
46
+ /** What the object needs from `DurableObjectState`. @since 1.0.0 */
47
+ export interface ChannelObjectState {
48
+ readonly storage: ChannelStorage;
49
+ /** Accepts a server-side socket with the Hibernation API: the runtime may evict the object while the socket stays open. */
50
+ acceptWebSocket(socket: ChannelSocket, tags?: string[]): void;
51
+ /** The sockets accepted with `acceptWebSocket` that are still open. */
52
+ getWebSockets(tag?: string): ChannelSocket[];
53
+ /** Answers a fixed request text without waking the object. */
54
+ setWebSocketAutoResponse?(pair?: object): void;
55
+ }
56
+ /** A response of a Durable Object stub, with the WebSocket of an accepted upgrade. @since 1.0.0 */
57
+ export interface ChannelResponse {
58
+ readonly status: number;
59
+ readonly ok: boolean;
60
+ readonly webSocket?: ClientSocket | null;
61
+ text(): Promise<string>;
62
+ }
63
+ /** The client end of a WebSocket pair, before `accept()`. @since 1.0.0 */
64
+ export interface ClientSocket {
65
+ accept(): void;
66
+ send(data: string): void;
67
+ close(code?: number, reason?: string): void;
68
+ addEventListener(type: "message", listener: (event: {
69
+ readonly data: unknown;
70
+ }) => void): void;
71
+ addEventListener(type: "close", listener: (event: {
72
+ readonly code?: number;
73
+ }) => void): void;
74
+ addEventListener(type: "error", listener: (event: unknown) => void): void;
75
+ }
76
+ /** What a request to a stub carries. @since 1.0.0 */
77
+ export interface ChannelRequestInit {
78
+ readonly method?: string;
79
+ readonly headers?: Readonly<Record<string, string>>;
80
+ readonly body?: string;
81
+ readonly signal?: AbortSignal;
82
+ }
83
+ /** A Durable Object stub. @since 1.0.0 */
84
+ export interface ChannelStub {
85
+ fetch(url: string, init?: ChannelRequestInit): Promise<ChannelResponse>;
86
+ }
87
+ /**
88
+ * The namespace binding of the channel object (`env.CHANNELS` for a binding named `CHANNELS`): the real
89
+ * `DurableObjectNamespace` fits.
90
+ * @since 1.0.0
91
+ */
92
+ export interface ChannelNamespace<Id = unknown> {
93
+ idFromName(name: string): Id;
94
+ get(id: Id, options?: {
95
+ readonly locationHint?: string;
96
+ }): ChannelStub;
97
+ }
98
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,283 @@
1
+ import { byteLength, isChannelName, isEventName, isIdempotencyKey } from "../protocol.js";
2
+ // src/cloudflare/wire.ts
3
+ /** The most bytes of one message (data, event and names) the object stores in one value; the platform's smallest value limit is 128 KiB. @internal */
4
+ const MAX_ENTRY_BYTES = 122880;
5
+ /** The most bytes of one member's state. @internal */
6
+ const MAX_STATE_BYTES = 16384;
7
+ /** The most members one channel object keeps. @internal */
8
+ const MAX_MEMBERS = 1e4;
9
+ /** The most messages one channel retains. @internal */
10
+ const MAX_HISTORY = 1e4;
11
+ /** The longest expiry of a presence member or an idempotency key. @internal */
12
+ const MAX_TTL_MS = 864e5;
13
+ /** The most bytes of a request body the object reads. @internal */
14
+ const MAX_BODY_BYTES = 262144;
15
+ const OPERATIONS = /* @__PURE__ */ new Set([
16
+ "publish",
17
+ "history",
18
+ "presence-set",
19
+ "presence-remove",
20
+ "presence-list",
21
+ "presence-touch",
22
+ "presence-reap"
23
+ ]);
24
+ /** Whether `value` names an operation. @internal */
25
+ function isOperation(value) {
26
+ return OPERATIONS.has(value);
27
+ }
28
+ function isRecord(value) {
29
+ return typeof value === "object" && value !== null && !Array.isArray(value);
30
+ }
31
+ function isCount(value, max = Number.MAX_SAFE_INTEGER) {
32
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= max;
33
+ }
34
+ function fail(message, code = "bad_frame") {
35
+ return {
36
+ ok: false,
37
+ code,
38
+ message
39
+ };
40
+ }
41
+ /**
42
+ * Whether `value` is a well formed member: an id, an optional user, a version of 1 or more and a JSON state of bounded size.
43
+ * @internal
44
+ */
45
+ function parseMember(value) {
46
+ if (!isRecord(value)) return void 0;
47
+ const { id, u, s, v } = value;
48
+ if (typeof id !== "string" || id.length === 0 || id.length > 256) return void 0;
49
+ if (u !== void 0 && (typeof u !== "string" || u.length > 256)) return void 0;
50
+ if (typeof v !== "number" || !Number.isInteger(v) || v < 1) return void 0;
51
+ const state = s === void 0 ? null : s;
52
+ const text = JSON.stringify(state);
53
+ if (text === void 0 || byteLength(text) > 16384) return void 0;
54
+ return u === void 0 ? {
55
+ id,
56
+ s: state,
57
+ v
58
+ } : {
59
+ id,
60
+ u,
61
+ s: state,
62
+ v
63
+ };
64
+ }
65
+ function parseEntry(value) {
66
+ if (!isRecord(value)) return void 0;
67
+ const { event, data, publisher, user } = value;
68
+ if (!isEventName(event) || typeof data !== "string") return void 0;
69
+ if (publisher !== void 0 && (typeof publisher !== "string" || publisher.length > 256)) return void 0;
70
+ if (user !== void 0 && (typeof user !== "string" || user.length > 256)) return void 0;
71
+ return {
72
+ event,
73
+ data,
74
+ ...publisher === void 0 ? {} : { publisher },
75
+ ...user === void 0 ? {} : { user }
76
+ };
77
+ }
78
+ function parseOptions(value) {
79
+ if (!isRecord(value)) return void 0;
80
+ const { history, historyTtlMs, key, idempotencyTtlMs } = value;
81
+ if (!isCount(history, 1e4)) return void 0;
82
+ if (!isCount(historyTtlMs, 316224e5)) return void 0;
83
+ if (!isCount(idempotencyTtlMs, 864e5)) return void 0;
84
+ if (key !== void 0 && !isIdempotencyKey(key)) return void 0;
85
+ return key === void 0 ? {
86
+ history,
87
+ historyTtlMs,
88
+ idempotencyTtlMs
89
+ } : {
90
+ history,
91
+ historyTtlMs,
92
+ key,
93
+ idempotencyTtlMs
94
+ };
95
+ }
96
+ /**
97
+ * Parses the JSON body of a request to operation `op`.
98
+ *
99
+ * @param op The operation from the path.
100
+ * @param body The parsed JSON body.
101
+ * @returns The request, or the refusal to answer.
102
+ * @internal
103
+ */
104
+ function parseRequest(op, body) {
105
+ if (!isRecord(body)) return fail("the body must be an object");
106
+ const { channel } = body;
107
+ if (!isChannelName(channel)) return fail("bad channel name", "bad_channel");
108
+ switch (op) {
109
+ case "publish": {
110
+ const entry = parseEntry(body["entry"]);
111
+ const options = parseOptions(body["options"]);
112
+ const idleMs = body["idleMs"] ?? 0;
113
+ if (entry === void 0) return fail("bad entry");
114
+ if (options === void 0) return fail("bad options");
115
+ if (!isCount(idleMs, 31536e7)) return fail("bad idleMs");
116
+ if (byteLength(entry.data) + byteLength(entry.event) > 122880) return fail("the message is too large for one stored value", "too_large");
117
+ return {
118
+ ok: true,
119
+ request: {
120
+ op,
121
+ channel,
122
+ entry,
123
+ options,
124
+ idleMs
125
+ }
126
+ };
127
+ }
128
+ case "history": {
129
+ const { after, limit } = body;
130
+ if (!isCount(after) || !isCount(limit, 1e5)) return fail("bad after or limit");
131
+ return {
132
+ ok: true,
133
+ request: {
134
+ op,
135
+ channel,
136
+ after,
137
+ limit
138
+ }
139
+ };
140
+ }
141
+ case "presence-set": {
142
+ const member = parseMember(body["member"]);
143
+ const { ttlMs } = body;
144
+ const graceMs = body["graceMs"] ?? 0;
145
+ if (member === void 0) return fail("bad member");
146
+ if (!isCount(ttlMs, 864e5) || ttlMs === 0) return fail("bad ttlMs");
147
+ if (!isCount(graceMs, 36e5)) return fail("bad graceMs");
148
+ return {
149
+ ok: true,
150
+ request: {
151
+ op,
152
+ channel,
153
+ member,
154
+ ttlMs,
155
+ graceMs
156
+ }
157
+ };
158
+ }
159
+ case "presence-remove": {
160
+ const { id } = body;
161
+ if (typeof id !== "string" || id.length === 0 || id.length > 256) return fail("bad id");
162
+ return {
163
+ ok: true,
164
+ request: {
165
+ op,
166
+ channel,
167
+ id
168
+ }
169
+ };
170
+ }
171
+ case "presence-touch": {
172
+ const { ids, ttlMs } = body;
173
+ if (!isCount(ttlMs, 864e5) || ttlMs === 0) return fail("bad ttlMs");
174
+ if (!Array.isArray(ids) || ids.length > 1e4 || !ids.every((id) => typeof id === "string" && id.length > 0 && id.length <= 256)) return fail("bad ids");
175
+ return {
176
+ ok: true,
177
+ request: {
178
+ op,
179
+ channel,
180
+ ids,
181
+ ttlMs
182
+ }
183
+ };
184
+ }
185
+ case "presence-list":
186
+ case "presence-reap": return {
187
+ ok: true,
188
+ request: {
189
+ op,
190
+ channel
191
+ }
192
+ };
193
+ }
194
+ }
195
+ /** Decodes a frame from the object; `undefined` for anything that is not a known, well formed frame. @internal */
196
+ function decodeLinkFrame(text) {
197
+ let value;
198
+ try {
199
+ value = JSON.parse(text);
200
+ } catch {
201
+ return;
202
+ }
203
+ if (!isRecord(value)) return void 0;
204
+ switch (value["t"]) {
205
+ case "ready": {
206
+ const { head, oldest } = value;
207
+ return isCount(head) && isCount(oldest) ? {
208
+ t: "ready",
209
+ head,
210
+ oldest
211
+ } : void 0;
212
+ }
213
+ case "snap": {
214
+ const list = value["members"];
215
+ if (!Array.isArray(list)) return void 0;
216
+ const members = [];
217
+ for (const item of list) {
218
+ const member = parseMember(item);
219
+ if (member === void 0) return void 0;
220
+ members.push(member);
221
+ }
222
+ return {
223
+ t: "snap",
224
+ members
225
+ };
226
+ }
227
+ case "msg": {
228
+ const message = parseStored(value["m"]);
229
+ return message === void 0 ? void 0 : {
230
+ t: "msg",
231
+ m: message
232
+ };
233
+ }
234
+ case "pres": {
235
+ const member = parseMember(value["member"]);
236
+ const op = value["op"];
237
+ if (member === void 0 || op !== "join" && op !== "set" && op !== "leave") return void 0;
238
+ return {
239
+ t: "pres",
240
+ op,
241
+ member
242
+ };
243
+ }
244
+ case "more": return { t: "more" };
245
+ case "bye": return {
246
+ t: "bye",
247
+ why: typeof value["why"] === "string" ? value["why"] : ""
248
+ };
249
+ default: return;
250
+ }
251
+ }
252
+ /** Reads a stored message; `undefined` when it is malformed. @internal */
253
+ function parseStored(value) {
254
+ if (!isRecord(value)) return void 0;
255
+ const { seq, event, data, at, publisher, user } = value;
256
+ if (!isCount(seq) || seq < 1 || typeof event !== "string" || typeof data !== "string") return void 0;
257
+ if (!isCount(at)) return void 0;
258
+ if (publisher !== void 0 && typeof publisher !== "string") return void 0;
259
+ if (user !== void 0 && typeof user !== "string") return void 0;
260
+ return {
261
+ seq,
262
+ event,
263
+ data,
264
+ at,
265
+ ...publisher === void 0 ? {} : { publisher },
266
+ ...user === void 0 ? {} : { user }
267
+ };
268
+ }
269
+ /** Whether `value` is a count (a non-negative safe integer). @internal */
270
+ function isWholeNumber(value) {
271
+ return isCount(value);
272
+ }
273
+ /** The key of retained message `seq`: zero padded so keys sort by seq. @internal */
274
+ function historyKey(seq) {
275
+ return `h:${String(seq).padStart(16, "0")}`;
276
+ }
277
+ /** The seq a history key stands for. @internal */
278
+ function seqOfKey(key) {
279
+ return Number(key.slice(2));
280
+ }
281
+ export { MAX_BODY_BYTES, MAX_ENTRY_BYTES, MAX_HISTORY, MAX_MEMBERS, MAX_STATE_BYTES, MAX_TTL_MS, decodeLinkFrame, historyKey, isOperation, isWholeNumber, parseMember, parseRequest, parseStored, seqOfKey };
282
+
283
+ //# sourceMappingURL=wire.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wire.js","names":[],"sources":["../../../src/cloudflare/wire.ts"],"sourcesContent":["/**\n * The private protocol between the adapter (in a Worker) and the channel object (a Durable Object): JSON requests to\n * `/v1/<op>` and the frames of the subscribe WebSocket. Both sides validate what they read; neither trusts the other's shape.\n * @packageDocumentation\n */\nimport type { PublishOptions } from \"../adapter\";\nimport type { ErrorCode } from \"../errors\";\nimport {\n byteLength,\n isChannelName,\n isEventName,\n isIdempotencyKey,\n type Json,\n type Member,\n type PresenceEvent,\n type StoredEntry,\n type StoredMessage,\n} from \"../protocol\";\n\n/** The version in the request path. @internal */\nexport const WIRE_VERSION: number = 1;\n\n/** The most bytes of one message (data, event and names) the object stores in one value; the platform's smallest value limit is 128 KiB. @internal */\nexport const MAX_ENTRY_BYTES: number = 120 * 1024;\n/** The most bytes of one member's state. @internal */\nexport const MAX_STATE_BYTES: number = 16 * 1024;\n/** The most members one channel object keeps. @internal */\nexport const MAX_MEMBERS: number = 10_000;\n/** The most messages one channel retains. @internal */\nexport const MAX_HISTORY: number = 10_000;\n/** The longest expiry of a presence member or an idempotency key. @internal */\nexport const MAX_TTL_MS: number = 86_400_000;\n/** The most bytes of a request body the object reads. @internal */\nexport const MAX_BODY_BYTES: number = 256 * 1024;\n\n/** The operations of the request protocol. @internal */\nexport type Operation =\n | \"publish\"\n | \"history\"\n | \"presence-set\"\n | \"presence-remove\"\n | \"presence-list\"\n | \"presence-touch\"\n | \"presence-reap\";\n\n/** A parsed request. @internal */\nexport type ParsedRequest =\n | {\n readonly op: \"publish\";\n readonly channel: string;\n readonly entry: StoredEntry;\n readonly options: PublishOptions;\n readonly idleMs: number;\n }\n | {\n readonly op: \"history\";\n readonly channel: string;\n readonly after: number;\n readonly limit: number;\n }\n | {\n readonly op: \"presence-set\";\n readonly channel: string;\n readonly member: Member;\n readonly ttlMs: number;\n readonly graceMs: number;\n }\n | { readonly op: \"presence-remove\"; readonly channel: string; readonly id: string }\n | { readonly op: \"presence-list\"; readonly channel: string }\n | {\n readonly op: \"presence-touch\";\n readonly channel: string;\n readonly ids: readonly string[];\n readonly ttlMs: number;\n }\n | { readonly op: \"presence-reap\"; readonly channel: string };\n\n/** What parsing a request yields. @internal */\nexport type Parsed =\n | { readonly ok: true; readonly request: ParsedRequest }\n | { readonly ok: false; readonly code: ErrorCode; readonly message: string };\n\n/** A page of history as the object returns it. @internal */\nexport interface HistoryPage {\n readonly messages: readonly StoredMessage[];\n readonly head: number;\n readonly oldest: number;\n /** True when retained messages after the last one returned remain. */\n readonly more: boolean;\n}\n\n/** Frames the object sends on a subscribe socket. @internal */\nexport type LinkFrame =\n | { readonly t: \"ready\"; readonly head: number; readonly oldest: number }\n | { readonly t: \"snap\"; readonly members: readonly Member[] }\n | { readonly t: \"msg\"; readonly m: StoredMessage }\n | {\n readonly t: \"pres\";\n readonly op: PresenceEvent[\"op\"];\n readonly member: Member;\n }\n | { readonly t: \"more\" }\n | { readonly t: \"bye\"; readonly why: string };\n\nconst OPERATIONS: ReadonlySet<string> = new Set<Operation>([\n \"publish\",\n \"history\",\n \"presence-set\",\n \"presence-remove\",\n \"presence-list\",\n \"presence-touch\",\n \"presence-reap\",\n]);\n\n/** Whether `value` names an operation. @internal */\nexport function isOperation(value: string): value is Operation {\n return OPERATIONS.has(value);\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 isCount(value: unknown, max = Number.MAX_SAFE_INTEGER): value is number {\n return typeof value === \"number\" && Number.isInteger(value) && value >= 0 && value <= max;\n}\n\nfunction fail(message: string, code: ErrorCode = \"bad_frame\"): Parsed {\n return { ok: false, code, message };\n}\n\n/**\n * Whether `value` is a well formed member: an id, an optional user, a version of 1 or more and a JSON state of bounded size.\n * @internal\n */\nexport function parseMember(value: unknown): Member | undefined {\n if (!isRecord(value)) return undefined;\n const { id, u, s, v } = value;\n if (typeof id !== \"string\" || id.length === 0 || id.length > 256) return undefined;\n if (u !== undefined && (typeof u !== \"string\" || u.length > 256)) return undefined;\n if (typeof v !== \"number\" || !Number.isInteger(v) || v < 1) return undefined;\n const state: Json = s === undefined ? null : (s as Json);\n const text = JSON.stringify(state);\n if (text === undefined || byteLength(text) > MAX_STATE_BYTES) return undefined;\n return u === undefined ? { id, s: state, v } : { id, u, s: state, v };\n}\n\nfunction parseEntry(value: unknown): StoredEntry | undefined {\n if (!isRecord(value)) return undefined;\n const { event, data, publisher, user } = value;\n if (!isEventName(event) || typeof data !== \"string\") return undefined;\n if (publisher !== undefined && (typeof publisher !== \"string\" || publisher.length > 256))\n return undefined;\n if (user !== undefined && (typeof user !== \"string\" || user.length > 256)) return undefined;\n return {\n event,\n data,\n ...(publisher === undefined ? {} : { publisher }),\n ...(user === undefined ? {} : { user }),\n };\n}\n\nfunction parseOptions(value: unknown): PublishOptions | undefined {\n if (!isRecord(value)) return undefined;\n const { history, historyTtlMs, key, idempotencyTtlMs } = value;\n if (!isCount(history, MAX_HISTORY)) return undefined;\n if (!isCount(historyTtlMs, MAX_TTL_MS * 366)) return undefined;\n if (!isCount(idempotencyTtlMs, MAX_TTL_MS)) return undefined;\n if (key !== undefined && !isIdempotencyKey(key)) return undefined;\n return key === undefined\n ? { history, historyTtlMs, idempotencyTtlMs }\n : { history, historyTtlMs, key, idempotencyTtlMs };\n}\n\n/**\n * Parses the JSON body of a request to operation `op`.\n *\n * @param op The operation from the path.\n * @param body The parsed JSON body.\n * @returns The request, or the refusal to answer.\n * @internal\n */\nexport function parseRequest(op: Operation, body: unknown): Parsed {\n if (!isRecord(body)) return fail(\"the body must be an object\");\n const { channel } = body;\n if (!isChannelName(channel)) return fail(\"bad channel name\", \"bad_channel\");\n switch (op) {\n case \"publish\": {\n const entry = parseEntry(body[\"entry\"]);\n const options = parseOptions(body[\"options\"]);\n const idleMs = body[\"idleMs\"] ?? 0;\n if (entry === undefined) return fail(\"bad entry\");\n if (options === undefined) return fail(\"bad options\");\n if (!isCount(idleMs, MAX_TTL_MS * 3650)) return fail(\"bad idleMs\");\n if (byteLength(entry.data) + byteLength(entry.event) > MAX_ENTRY_BYTES)\n return fail(\"the message is too large for one stored value\", \"too_large\");\n return { ok: true, request: { op, channel, entry, options, idleMs } };\n }\n case \"history\": {\n const { after, limit } = body;\n if (!isCount(after) || !isCount(limit, 100_000)) return fail(\"bad after or limit\");\n return { ok: true, request: { op, channel, after, limit } };\n }\n case \"presence-set\": {\n const member = parseMember(body[\"member\"]);\n const { ttlMs } = body;\n const graceMs = body[\"graceMs\"] ?? 0;\n if (member === undefined) return fail(\"bad member\");\n if (!isCount(ttlMs, MAX_TTL_MS) || ttlMs === 0) return fail(\"bad ttlMs\");\n if (!isCount(graceMs, 3_600_000)) return fail(\"bad graceMs\");\n return { ok: true, request: { op, channel, member, ttlMs, graceMs } };\n }\n case \"presence-remove\": {\n const { id } = body;\n if (typeof id !== \"string\" || id.length === 0 || id.length > 256) return fail(\"bad id\");\n return { ok: true, request: { op, channel, id } };\n }\n case \"presence-touch\": {\n const { ids, ttlMs } = body;\n if (!isCount(ttlMs, MAX_TTL_MS) || ttlMs === 0) return fail(\"bad ttlMs\");\n if (\n !Array.isArray(ids) ||\n ids.length > MAX_MEMBERS ||\n !ids.every((id) => typeof id === \"string\" && id.length > 0 && id.length <= 256)\n )\n return fail(\"bad ids\");\n return { ok: true, request: { op, channel, ids: ids as string[], ttlMs } };\n }\n case \"presence-list\":\n case \"presence-reap\":\n return { ok: true, request: { op, channel } };\n }\n}\n\n/** Decodes a frame from the object; `undefined` for anything that is not a known, well formed frame. @internal */\nexport function decodeLinkFrame(text: string): LinkFrame | undefined {\n let value: unknown;\n try {\n value = JSON.parse(text);\n } catch {\n return undefined;\n }\n if (!isRecord(value)) return undefined;\n switch (value[\"t\"]) {\n case \"ready\": {\n const { head, oldest } = value;\n return isCount(head) && isCount(oldest) ? { t: \"ready\", head, oldest } : undefined;\n }\n case \"snap\": {\n const list = value[\"members\"];\n if (!Array.isArray(list)) return undefined;\n const members: Member[] = [];\n for (const item of list) {\n const member = parseMember(item);\n if (member === undefined) return undefined;\n members.push(member);\n }\n return { t: \"snap\", members };\n }\n case \"msg\": {\n const message = parseStored(value[\"m\"]);\n return message === undefined ? undefined : { t: \"msg\", m: message };\n }\n case \"pres\": {\n const member = parseMember(value[\"member\"]);\n const op = value[\"op\"];\n if (member === undefined || (op !== \"join\" && op !== \"set\" && op !== \"leave\"))\n return undefined;\n return { t: \"pres\", op, member };\n }\n case \"more\":\n return { t: \"more\" };\n case \"bye\":\n return { t: \"bye\", why: typeof value[\"why\"] === \"string\" ? value[\"why\"] : \"\" };\n default:\n return undefined;\n }\n}\n\n/** Reads a stored message; `undefined` when it is malformed. @internal */\nexport function parseStored(value: unknown): StoredMessage | undefined {\n if (!isRecord(value)) return undefined;\n const { seq, event, data, at, publisher, user } = value;\n if (!isCount(seq) || seq < 1 || typeof event !== \"string\" || typeof data !== \"string\")\n return undefined;\n if (!isCount(at)) return undefined;\n if (publisher !== undefined && typeof publisher !== \"string\") return undefined;\n if (user !== undefined && typeof user !== \"string\") return undefined;\n return {\n seq,\n event,\n data,\n at,\n ...(publisher === undefined ? {} : { publisher }),\n ...(user === undefined ? {} : { user }),\n };\n}\n\n/** Whether `value` is a count (a non-negative safe integer). @internal */\nexport function isWholeNumber(value: unknown): value is number {\n return isCount(value);\n}\n\n/** The key of retained message `seq`: zero padded so keys sort by seq. @internal */\nexport function historyKey(seq: number): string {\n return `h:${String(seq).padStart(16, \"0\")}`;\n}\n\n/** The seq a history key stands for. @internal */\nexport function seqOfKey(key: string): number {\n return Number(key.slice(2));\n}\n"],"mappings":";;;AAuBA,MAAa,kBAA0B;;AAEvC,MAAa,kBAA0B;;AAEvC,MAAa,cAAsB;;AAEnC,MAAa,cAAsB;;AAEnC,MAAa,aAAqB;;AAElC,MAAa,iBAAyB;AAuEtC,MAAM,6BAAkC,IAAI,IAAe;CACzD;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;AAGD,SAAgB,YAAY,OAAmC;CAC7D,OAAO,WAAW,IAAI,KAAK;AAC7B;AAEA,SAAS,SAAS,OAA4D;CAC5E,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,SAAS,QAAQ,OAAgB,MAAM,OAAO,kBAAmC;CAC/E,OAAO,OAAO,UAAU,YAAY,OAAO,UAAU,KAAK,KAAK,SAAS,KAAK,SAAS;AACxF;AAEA,SAAS,KAAK,SAAiB,OAAkB,aAAqB;CACpE,OAAO;EAAE,IAAI;EAAO;EAAM;CAAQ;AACpC;;;;;AAMA,SAAgB,YAAY,OAAoC;CAC9D,IAAI,CAAC,SAAS,KAAK,GAAG,OAAO,KAAA;CAC7B,MAAM,EAAE,IAAI,GAAG,GAAG,MAAM;CACxB,IAAI,OAAO,OAAO,YAAY,GAAG,WAAW,KAAK,GAAG,SAAS,KAAK,OAAO,KAAA;CACzE,IAAI,MAAM,KAAA,MAAc,OAAO,MAAM,YAAY,EAAE,SAAS,MAAM,OAAO,KAAA;CACzE,IAAI,OAAO,MAAM,YAAY,CAAC,OAAO,UAAU,CAAC,KAAK,IAAI,GAAG,OAAO,KAAA;CACnE,MAAM,QAAc,MAAM,KAAA,IAAY,OAAQ;CAC9C,MAAM,OAAO,KAAK,UAAU,KAAK;CACjC,IAAI,SAAS,KAAA,KAAa,WAAW,IAAI,IAAA,OAAqB,OAAO,KAAA;CACrE,OAAO,MAAM,KAAA,IAAY;EAAE;EAAI,GAAG;EAAO;CAAE,IAAI;EAAE;EAAI;EAAG,GAAG;EAAO;CAAE;AACtE;AAEA,SAAS,WAAW,OAAyC;CAC3D,IAAI,CAAC,SAAS,KAAK,GAAG,OAAO,KAAA;CAC7B,MAAM,EAAE,OAAO,MAAM,WAAW,SAAS;CACzC,IAAI,CAAC,YAAY,KAAK,KAAK,OAAO,SAAS,UAAU,OAAO,KAAA;CAC5D,IAAI,cAAc,KAAA,MAAc,OAAO,cAAc,YAAY,UAAU,SAAS,MAClF,OAAO,KAAA;CACT,IAAI,SAAS,KAAA,MAAc,OAAO,SAAS,YAAY,KAAK,SAAS,MAAM,OAAO,KAAA;CAClF,OAAO;EACL;EACA;EACA,GAAI,cAAc,KAAA,IAAY,CAAC,IAAI,EAAE,UAAU;EAC/C,GAAI,SAAS,KAAA,IAAY,CAAC,IAAI,EAAE,KAAK;CACvC;AACF;AAEA,SAAS,aAAa,OAA4C;CAChE,IAAI,CAAC,SAAS,KAAK,GAAG,OAAO,KAAA;CAC7B,MAAM,EAAE,SAAS,cAAc,KAAK,qBAAqB;CACzD,IAAI,CAAC,QAAQ,SAAA,GAAoB,GAAG,OAAO,KAAA;CAC3C,IAAI,CAAC,QAAQ,cAAA,QAA8B,GAAG,OAAO,KAAA;CACrD,IAAI,CAAC,QAAQ,kBAAA,KAA4B,GAAG,OAAO,KAAA;CACnD,IAAI,QAAQ,KAAA,KAAa,CAAC,iBAAiB,GAAG,GAAG,OAAO,KAAA;CACxD,OAAO,QAAQ,KAAA,IACX;EAAE;EAAS;EAAc;CAAiB,IAC1C;EAAE;EAAS;EAAc;EAAK;CAAiB;AACrD;;;;;;;;;AAUA,SAAgB,aAAa,IAAe,MAAuB;CACjE,IAAI,CAAC,SAAS,IAAI,GAAG,OAAO,KAAK,4BAA4B;CAC7D,MAAM,EAAE,YAAY;CACpB,IAAI,CAAC,cAAc,OAAO,GAAG,OAAO,KAAK,oBAAoB,aAAa;CAC1E,QAAQ,IAAR;EACE,KAAK,WAAW;GACd,MAAM,QAAQ,WAAW,KAAK,QAAQ;GACtC,MAAM,UAAU,aAAa,KAAK,UAAU;GAC5C,MAAM,SAAS,KAAK,aAAa;GACjC,IAAI,UAAU,KAAA,GAAW,OAAO,KAAK,WAAW;GAChD,IAAI,YAAY,KAAA,GAAW,OAAO,KAAK,aAAa;GACpD,IAAI,CAAC,QAAQ,QAAA,OAAyB,GAAG,OAAO,KAAK,YAAY;GACjE,IAAI,WAAW,MAAM,IAAI,IAAI,WAAW,MAAM,KAAK,IAAA,QACjD,OAAO,KAAK,iDAAiD,WAAW;GAC1E,OAAO;IAAE,IAAI;IAAM,SAAS;KAAE;KAAI;KAAS;KAAO;KAAS;IAAO;GAAE;EACtE;EACA,KAAK,WAAW;GACd,MAAM,EAAE,OAAO,UAAU;GACzB,IAAI,CAAC,QAAQ,KAAK,KAAK,CAAC,QAAQ,OAAO,GAAO,GAAG,OAAO,KAAK,oBAAoB;GACjF,OAAO;IAAE,IAAI;IAAM,SAAS;KAAE;KAAI;KAAS;KAAO;IAAM;GAAE;EAC5D;EACA,KAAK,gBAAgB;GACnB,MAAM,SAAS,YAAY,KAAK,SAAS;GACzC,MAAM,EAAE,UAAU;GAClB,MAAM,UAAU,KAAK,cAAc;GACnC,IAAI,WAAW,KAAA,GAAW,OAAO,KAAK,YAAY;GAClD,IAAI,CAAC,QAAQ,OAAA,KAAiB,KAAK,UAAU,GAAG,OAAO,KAAK,WAAW;GACvE,IAAI,CAAC,QAAQ,SAAS,IAAS,GAAG,OAAO,KAAK,aAAa;GAC3D,OAAO;IAAE,IAAI;IAAM,SAAS;KAAE;KAAI;KAAS;KAAQ;KAAO;IAAQ;GAAE;EACtE;EACA,KAAK,mBAAmB;GACtB,MAAM,EAAE,OAAO;GACf,IAAI,OAAO,OAAO,YAAY,GAAG,WAAW,KAAK,GAAG,SAAS,KAAK,OAAO,KAAK,QAAQ;GACtF,OAAO;IAAE,IAAI;IAAM,SAAS;KAAE;KAAI;KAAS;IAAG;GAAE;EAClD;EACA,KAAK,kBAAkB;GACrB,MAAM,EAAE,KAAK,UAAU;GACvB,IAAI,CAAC,QAAQ,OAAA,KAAiB,KAAK,UAAU,GAAG,OAAO,KAAK,WAAW;GACvE,IACE,CAAC,MAAM,QAAQ,GAAG,KAClB,IAAI,SAAA,OACJ,CAAC,IAAI,OAAO,OAAO,OAAO,OAAO,YAAY,GAAG,SAAS,KAAK,GAAG,UAAU,GAAG,GAE9E,OAAO,KAAK,SAAS;GACvB,OAAO;IAAE,IAAI;IAAM,SAAS;KAAE;KAAI;KAAc;KAAiB;IAAM;GAAE;EAC3E;EACA,KAAK;EACL,KAAK,iBACH,OAAO;GAAE,IAAI;GAAM,SAAS;IAAE;IAAI;GAAQ;EAAE;CAChD;AACF;;AAGA,SAAgB,gBAAgB,MAAqC;CACnE,IAAI;CACJ,IAAI;EACF,QAAQ,KAAK,MAAM,IAAI;CACzB,QAAQ;EACN;CACF;CACA,IAAI,CAAC,SAAS,KAAK,GAAG,OAAO,KAAA;CAC7B,QAAQ,MAAM,MAAd;EACE,KAAK,SAAS;GACZ,MAAM,EAAE,MAAM,WAAW;GACzB,OAAO,QAAQ,IAAI,KAAK,QAAQ,MAAM,IAAI;IAAE,GAAG;IAAS;IAAM;GAAO,IAAI,KAAA;EAC3E;EACA,KAAK,QAAQ;GACX,MAAM,OAAO,MAAM;GACnB,IAAI,CAAC,MAAM,QAAQ,IAAI,GAAG,OAAO,KAAA;GACjC,MAAM,UAAoB,CAAC;GAC3B,KAAK,MAAM,QAAQ,MAAM;IACvB,MAAM,SAAS,YAAY,IAAI;IAC/B,IAAI,WAAW,KAAA,GAAW,OAAO,KAAA;IACjC,QAAQ,KAAK,MAAM;GACrB;GACA,OAAO;IAAE,GAAG;IAAQ;GAAQ;EAC9B;EACA,KAAK,OAAO;GACV,MAAM,UAAU,YAAY,MAAM,IAAI;GACtC,OAAO,YAAY,KAAA,IAAY,KAAA,IAAY;IAAE,GAAG;IAAO,GAAG;GAAQ;EACpE;EACA,KAAK,QAAQ;GACX,MAAM,SAAS,YAAY,MAAM,SAAS;GAC1C,MAAM,KAAK,MAAM;GACjB,IAAI,WAAW,KAAA,KAAc,OAAO,UAAU,OAAO,SAAS,OAAO,SACnE,OAAO,KAAA;GACT,OAAO;IAAE,GAAG;IAAQ;IAAI;GAAO;EACjC;EACA,KAAK,QACH,OAAO,EAAE,GAAG,OAAO;EACrB,KAAK,OACH,OAAO;GAAE,GAAG;GAAO,KAAK,OAAO,MAAM,WAAW,WAAW,MAAM,SAAS;EAAG;EAC/E,SACE;CACJ;AACF;;AAGA,SAAgB,YAAY,OAA2C;CACrE,IAAI,CAAC,SAAS,KAAK,GAAG,OAAO,KAAA;CAC7B,MAAM,EAAE,KAAK,OAAO,MAAM,IAAI,WAAW,SAAS;CAClD,IAAI,CAAC,QAAQ,GAAG,KAAK,MAAM,KAAK,OAAO,UAAU,YAAY,OAAO,SAAS,UAC3E,OAAO,KAAA;CACT,IAAI,CAAC,QAAQ,EAAE,GAAG,OAAO,KAAA;CACzB,IAAI,cAAc,KAAA,KAAa,OAAO,cAAc,UAAU,OAAO,KAAA;CACrE,IAAI,SAAS,KAAA,KAAa,OAAO,SAAS,UAAU,OAAO,KAAA;CAC3D,OAAO;EACL;EACA;EACA;EACA;EACA,GAAI,cAAc,KAAA,IAAY,CAAC,IAAI,EAAE,UAAU;EAC/C,GAAI,SAAS,KAAA,IAAY,CAAC,IAAI,EAAE,KAAK;CACvC;AACF;;AAGA,SAAgB,cAAc,OAAiC;CAC7D,OAAO,QAAQ,KAAK;AACtB;;AAGA,SAAgB,WAAW,KAAqB;CAC9C,OAAO,KAAK,OAAO,GAAG,CAAC,CAAC,SAAS,IAAI,GAAG;AAC1C;;AAGA,SAAgB,SAAS,KAAqB;CAC5C,OAAO,OAAO,IAAI,MAAM,CAAC,CAAC;AAC5B"}
@@ -0,0 +1,4 @@
1
+ import { ChannelNamespace, ChannelObjectState, ChannelRequestInit, ChannelResponse, ChannelSocket, ChannelStorage, ChannelStub, ClientSocket, StorageListOptions, StorageTransaction } from "./cloudflare/types.js";
2
+ import { CloudflareRealtimeOptions, cloudflareAdapter } from "./cloudflare/adapter.js";
3
+ import { RealtimeChannelObject } from "./cloudflare/object.js";
4
+ export { type ChannelNamespace, type ChannelObjectState, type ChannelRequestInit, type ChannelResponse, type ChannelSocket, type ChannelStorage, type ChannelStub, type ClientSocket, type CloudflareRealtimeOptions, RealtimeChannelObject, type StorageListOptions, type StorageTransaction, cloudflareAdapter };