@pluv/platform-cloudflare 0.0.0-experimental-20250527040415

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Shoubhit Dash
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,18 @@
1
+ ## `@pluv/platform-cloudflare`
2
+
3
+ > Enables [@pluv/io](https://www.npmjs.com/package/@pluv/io) to run on [Cloudflare Workers](https://workers.cloudflare.com/).
4
+
5
+ **👉 See full documentation on [pluv.io](https://pluv.io/docs/introduction). 👈**
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ # npm
11
+ npm install @pluv/platform-cloudflare
12
+
13
+ # yarn
14
+ yarn add @pluv/platform-cloudflare
15
+
16
+ # pnpm
17
+ pnpm add @pluv/platform-cloudflare
18
+ ```
@@ -0,0 +1,127 @@
1
+ import { AbstractWebSocket, WebSocketSession, WebSocketSerializedState, AbstractWebSocketConfig, AbstractEventMap, AbstractListener, AbstractPlatform, WebSocketRegistrationMode, AbstractPlatformConfig, ConvertWebSocketConfig, PluvServer, InferInitContextType, CreateIOParams, PluvIOAuthorize, PluvContext } from '@pluv/io';
2
+ import { IOAuthorize, JsonObject, InferIOAuthorizeUser, Json, MaybePromise, InferIOAuthorize, BaseUser, Maybe, Id } from '@pluv/types';
3
+ import { DurableObject } from 'cloudflare:workers';
4
+
5
+ type CloudflareWebSocketConfig = AbstractWebSocketConfig;
6
+ declare class CloudflareWebSocket<TAuthorize extends IOAuthorize<any, any> | null = null> extends AbstractWebSocket<WebSocket, TAuthorize> {
7
+ set presence(presence: JsonObject | null);
8
+ get readyState(): 0 | 1 | 2 | 3;
9
+ get session(): WebSocketSession<TAuthorize>;
10
+ get sessionId(): string;
11
+ get state(): WebSocketSerializedState;
12
+ set state(state: WebSocketSerializedState);
13
+ set user(user: InferIOAuthorizeUser<TAuthorize>);
14
+ constructor(webSocket: WebSocket, config: CloudflareWebSocketConfig);
15
+ addEventListener<TType extends keyof AbstractEventMap>(type: TType, handler: AbstractListener<TType>): void;
16
+ close(code?: number | undefined, reason?: string | undefined): void;
17
+ send(message: string | ArrayBuffer | ArrayBufferView): void;
18
+ terminate(code?: number): void;
19
+ }
20
+
21
+ type CloudflarePlatformRoomContext<TEnv extends Record<string, any>, TMeta extends Record<string, Json>> = {
22
+ env: TEnv;
23
+ state: DurableObjectState;
24
+ } & (keyof TMeta extends never ? {
25
+ meta?: undefined;
26
+ } : {
27
+ meta: TMeta;
28
+ });
29
+ type CloudflarePlatformConfig<TEnv extends Record<string, any> = {}, TMeta extends Record<string, Json> = {}> = AbstractPlatformConfig<CloudflarePlatformRoomContext<TEnv, TMeta>> & {
30
+ mode?: WebSocketRegistrationMode;
31
+ };
32
+ declare class CloudflarePlatform<TAuthorize extends IOAuthorize<any, any> | null = null, TEnv extends Record<string, any> = {}, TMeta extends Record<string, Json> = {}> extends AbstractPlatform<CloudflareWebSocket<TAuthorize>, {
33
+ env: TEnv;
34
+ request: Request;
35
+ }, CloudflarePlatformRoomContext<TEnv, TMeta>, {
36
+ authorize: {
37
+ secret: true;
38
+ };
39
+ handleMode: "io";
40
+ requireAuth: false;
41
+ registrationMode: WebSocketRegistrationMode;
42
+ listeners: {
43
+ onRoomDeleted: true;
44
+ onRoomMessage: true;
45
+ onStorageUpdated: true;
46
+ onUserConnected: true;
47
+ onUserDisconnected: true;
48
+ };
49
+ router: true;
50
+ }> {
51
+ readonly id: string;
52
+ readonly _config: {
53
+ authorize: {
54
+ secret: true;
55
+ };
56
+ handleMode: "io";
57
+ registrationMode: WebSocketRegistrationMode;
58
+ requireAuth: false;
59
+ listeners: {
60
+ onRoomDeleted: true;
61
+ onRoomMessage: true;
62
+ onStorageUpdated: true;
63
+ onUserConnected: true;
64
+ onUserDisconnected: true;
65
+ };
66
+ router: true;
67
+ };
68
+ readonly _name = "platformCloudflare";
69
+ constructor(config: CloudflarePlatformConfig<TEnv, TMeta>);
70
+ acceptWebSocket(webSocket: CloudflareWebSocket<TAuthorize>): Promise<void>;
71
+ convertWebSocket(webSocket: WebSocket, config: ConvertWebSocketConfig): CloudflareWebSocket<TAuthorize>;
72
+ getLastPing(webSocket: CloudflareWebSocket<TAuthorize>): number | null;
73
+ getSerializedState(webSocket: WebSocket): WebSocketSerializedState | null;
74
+ getSessionId(webSocket: WebSocket): string | null;
75
+ getWebSockets(): readonly WebSocket[];
76
+ initialize(config: AbstractPlatformConfig<CloudflarePlatformRoomContext<TEnv, TMeta>>): this;
77
+ parseData(data: string | ArrayBuffer): Record<string, any>;
78
+ randomUUID(): string;
79
+ setSerializedState(webSocket: CloudflareWebSocket<TAuthorize>, state: WebSocketSerializedState): WebSocketSerializedState;
80
+ private _getDetachedState;
81
+ }
82
+
83
+ type AuthorizeFunctionContext<TPluvServer extends PluvServer<any, any, any, any>> = {
84
+ room: string;
85
+ } & InferInitContextType<TPluvServer extends PluvServer<infer IPlatform, any, any, any> ? IPlatform : never>;
86
+ type AuthorizeFunction<TPluvServer extends PluvServer<any, any, any, any>> = (ctx: AuthorizeFunctionContext<TPluvServer>) => MaybePromise<Maybe<InferIOAuthorizeUser<InferIOAuthorize<TPluvServer>>>>;
87
+ type CreatePluvHandlerConfig<TPluvServer extends PluvServer<any, any, any, any>, TEnv extends Record<string, any>> = {
88
+ binding: string;
89
+ endpoint?: string;
90
+ modify?: (request: Request, response: Response, env: TEnv) => MaybePromise<Response>;
91
+ io: TPluvServer;
92
+ } & (InferIOAuthorizeUser<InferIOAuthorize<TPluvServer>> extends BaseUser ? {
93
+ authorize: AuthorizeFunction<TPluvServer>;
94
+ } : {
95
+ authorize?: undefined;
96
+ });
97
+ type PluvHandlerFetch<TEnv extends Record<string, any> = {}> = (request: Request, env: TEnv) => Promise<Response | null>;
98
+ interface CreatePluvHandlerResult<TEnv extends Record<string, any> = {}> {
99
+ DurableObject: {
100
+ new (state: DurableObjectState, env: TEnv): DurableObject<TEnv>;
101
+ };
102
+ fetch: PluvHandlerFetch<TEnv>;
103
+ handler: ExportedHandler<TEnv>;
104
+ }
105
+ type InferCloudflarePluvHandlerEnv<TPluvServer extends PluvServer<CloudflarePlatform<any, any>, any, any, any>> = TPluvServer extends PluvServer<CloudflarePlatform<any, infer IEnv>, any, any, any> ? IEnv : {};
106
+ /**
107
+ * @deprecated Instructions will be provided on https://pluv.io on how to host this yourself instead.
108
+ * @date April 27, 2025
109
+ */
110
+ declare const createPluvHandler: <TPluvServer extends PluvServer<CloudflarePlatform<any, any>, any, any, any>>(config: CreatePluvHandlerConfig<TPluvServer, Id<InferCloudflarePluvHandlerEnv<TPluvServer>>>) => CreatePluvHandlerResult<Id<InferCloudflarePluvHandlerEnv<TPluvServer>>>;
111
+
112
+ declare const identity: <T extends unknown>(x: T) => T;
113
+
114
+ type InferCallback<TEnv extends Record<string, any> = {}, TMeta extends Record<string, Json> = {}> = (i: typeof identity) => {
115
+ env?: (io: TEnv) => TEnv;
116
+ meta?: (io: TMeta) => TMeta;
117
+ };
118
+ declare const infer: <TEnv extends Record<string, any> = {}, TMeta extends Record<string, Json> = {}>(callback: InferCallback<TEnv, TMeta>) => InferCallback<TEnv, TMeta>;
119
+
120
+ type PlatformCloudflareCreateIOParams<TEnv extends Record<string, any> = {}, TMeta extends Record<string, Json> = {}, TContext extends Record<string, any> = {}, TUser extends BaseUser | null = null> = Id<CloudflarePlatformConfig<TEnv, TMeta> & Omit<CreateIOParams<CloudflarePlatform<IOAuthorize<TUser, TContext>, TEnv, TMeta>, TContext, TUser>, "authorize" | "context" | "platform"> & {
121
+ authorize?: PluvIOAuthorize<CloudflarePlatform<IOAuthorize<TUser, TContext>, TEnv, TMeta>, TUser, InferInitContextType<CloudflarePlatform<IOAuthorize<TUser, TContext>, TEnv, TMeta>>>;
122
+ context?: PluvContext<CloudflarePlatform<IOAuthorize<TUser, any>, TEnv, TMeta>, TContext>;
123
+ types?: InferCallback<TEnv, TMeta>;
124
+ }>;
125
+ declare const platformCloudflare: <TEnv extends Record<string, any> = {}, TMeta extends Record<string, Json> = {}, TContext extends Record<string, any> = {}, TUser extends BaseUser | null = null>(config?: PlatformCloudflareCreateIOParams<TEnv, TMeta, TContext, TUser>) => CreateIOParams<CloudflarePlatform<IOAuthorize<TUser, TContext>, TEnv, TMeta>, TContext, TUser>;
126
+
127
+ export { type AuthorizeFunction, type AuthorizeFunctionContext, CloudflarePlatform, type CloudflarePlatformConfig, type CreatePluvHandlerConfig, type CreatePluvHandlerResult, type InferCallback, type PlatformCloudflareCreateIOParams, type PluvHandlerFetch, createPluvHandler, infer, platformCloudflare };
package/dist/index.mjs ADDED
@@ -0,0 +1,412 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defProps = Object.defineProperties;
3
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
7
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
+ var __spreadValues = (a, b) => {
9
+ for (var prop in b || (b = {}))
10
+ if (__hasOwnProp.call(b, prop))
11
+ __defNormalProp(a, prop, b[prop]);
12
+ if (__getOwnPropSymbols)
13
+ for (var prop of __getOwnPropSymbols(b)) {
14
+ if (__propIsEnum.call(b, prop))
15
+ __defNormalProp(a, prop, b[prop]);
16
+ }
17
+ return a;
18
+ };
19
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
+ var __async = (__this, __arguments, generator) => {
21
+ return new Promise((resolve, reject) => {
22
+ var fulfilled = (value) => {
23
+ try {
24
+ step(generator.next(value));
25
+ } catch (e) {
26
+ reject(e);
27
+ }
28
+ };
29
+ var rejected = (value) => {
30
+ try {
31
+ step(generator.throw(value));
32
+ } catch (e) {
33
+ reject(e);
34
+ }
35
+ };
36
+ var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
37
+ step((generator = generator.apply(__this, __arguments)).next());
38
+ });
39
+ };
40
+
41
+ // src/createPluvHandler.ts
42
+ import { DurableObject as BaseDurableObject } from "cloudflare:workers";
43
+ import { match } from "path-to-regexp";
44
+
45
+ // src/constants.ts
46
+ var DEFAULT_REGISTRATION_MODE = "detached";
47
+ var GARBAGE_COLLECT_INTERVAL_MS = 6e4;
48
+
49
+ // src/createPluvHandler.ts
50
+ var createPluvHandler = (config) => {
51
+ const { authorize, binding, endpoint = "/api/pluv", modify, io } = config;
52
+ const DurableObject = class extends BaseDurableObject {
53
+ constructor(state, env) {
54
+ super(state, env);
55
+ this._room = io.createRoom(state.id.toString(), { env, state });
56
+ }
57
+ webSocketClose(ws, code, reason, wasClean) {
58
+ return __async(this, null, function* () {
59
+ if (io._defs.platform._config.registrationMode !== "detached") return;
60
+ const onCloseHandler = this._room.onClose(ws);
61
+ yield onCloseHandler({ code, reason });
62
+ });
63
+ }
64
+ webSocketError(ws, error) {
65
+ return __async(this, null, function* () {
66
+ if (io._defs.platform._config.registrationMode !== "detached") return;
67
+ const onErrorHandler = this._room.onError(ws);
68
+ const eventError = error instanceof Error ? error : new Error("Internal Error");
69
+ yield onErrorHandler({ error: eventError, message: eventError.message });
70
+ });
71
+ }
72
+ webSocketMessage(ws, message) {
73
+ return __async(this, null, function* () {
74
+ if (io._defs.platform._config.registrationMode !== "detached") return;
75
+ const onMessageHandler = this._room.onMessage(ws);
76
+ yield onMessageHandler({ data: message });
77
+ });
78
+ }
79
+ fetch(request) {
80
+ return __async(this, null, function* () {
81
+ const isWSRequest = request.headers.get("Upgrade") === "websocket";
82
+ if (!isWSRequest) return new Response("Expected websocket", { status: 400 });
83
+ const { 0: client, 1: server } = new WebSocketPair();
84
+ const token = new URL(request.url).searchParams.get("token");
85
+ const alarm = yield this.ctx.storage.getAlarm();
86
+ if (alarm !== null)
87
+ yield this.ctx.storage.setAlarm(Date.now() + GARBAGE_COLLECT_INTERVAL_MS);
88
+ yield this._room.register(server, { env: this.env, request, token });
89
+ return new Response(null, { status: 101, webSocket: client });
90
+ });
91
+ }
92
+ alarm(alarmInfo) {
93
+ return __async(this, null, function* () {
94
+ yield this._room.garbageCollect();
95
+ yield this.ctx.storage.setAlarm(Date.now() + GARBAGE_COLLECT_INTERVAL_MS);
96
+ });
97
+ }
98
+ };
99
+ const getDurableObjectNamespace = (env) => {
100
+ const namespace = env[binding];
101
+ if (!namespace) {
102
+ throw new Error(`Could not find DurableObject binding: ${binding}`);
103
+ }
104
+ return namespace;
105
+ };
106
+ const authHandler = (request, env) => __async(null, null, function* () {
107
+ if (!authorize) return null;
108
+ const { pathname, searchParams } = new URL(request.url);
109
+ const matcher = match(`${endpoint}/authorize`);
110
+ const matched = matcher(pathname);
111
+ if (!matched) return null;
112
+ const room = searchParams.get("room");
113
+ if (!room) {
114
+ return new Response("Not found", {
115
+ headers: { "Content-Type": "text/plain" },
116
+ status: 404
117
+ });
118
+ }
119
+ try {
120
+ const user = yield authorize({
121
+ env,
122
+ request,
123
+ room
124
+ });
125
+ if (!user) throw new Error();
126
+ const namespace = getDurableObjectNamespace(env);
127
+ const durableObjectId = namespace.idFromName(room);
128
+ const token = yield io.createToken({
129
+ env,
130
+ room: durableObjectId.toString(),
131
+ user,
132
+ request
133
+ });
134
+ return new Response(token, {
135
+ headers: { "Content-Type": "text/plain" },
136
+ status: 200
137
+ });
138
+ } catch (err) {
139
+ return new Response(err instanceof Error ? err.message : "Unauthorized", {
140
+ headers: { "Content-Type": "text/plain" },
141
+ status: 403
142
+ });
143
+ }
144
+ });
145
+ const roomHandler = (request, env) => __async(null, null, function* () {
146
+ const { pathname } = new URL(request.url);
147
+ const matcher = match(`${endpoint}/room/:roomId`);
148
+ const matched = matcher(pathname);
149
+ if (!matched) return null;
150
+ const { roomId } = matched.params;
151
+ if (!roomId) {
152
+ return new Response("Not found", {
153
+ headers: { "Content-Type": "text/plain" },
154
+ status: 404
155
+ });
156
+ }
157
+ const namespace = getDurableObjectNamespace(env);
158
+ const durableObjectId = namespace.idFromName(roomId);
159
+ const room = namespace.get(durableObjectId);
160
+ return room.fetch(request);
161
+ });
162
+ const handlerFetch = (request, env) => __async(null, null, function* () {
163
+ return [authHandler, roomHandler].reduce((promise, current) => __async(null, null, function* () {
164
+ return yield promise.then((value) => __async(null, null, function* () {
165
+ return value != null ? value : yield current(request, env);
166
+ }));
167
+ }), Promise.resolve(null));
168
+ });
169
+ const handler = {
170
+ fetch: (request, env) => __async(null, null, function* () {
171
+ var _a, _b;
172
+ const response = (_a = yield handlerFetch(request, env)) != null ? _a : new Response("Not Found", {
173
+ headers: { "Content-Type": "text/plain" },
174
+ status: 404
175
+ });
176
+ return (_b = modify == null ? void 0 : modify(request, response, env)) != null ? _b : response;
177
+ })
178
+ };
179
+ return { fetch: handlerFetch, DurableObject, handler };
180
+ };
181
+
182
+ // src/infer.ts
183
+ var infer = (callback) => callback;
184
+
185
+ // src/CloudflarePlatform.ts
186
+ import { AbstractPlatform } from "@pluv/io";
187
+ import { PersistenceCloudflareTransactionalStorage } from "@pluv/persistence-cloudflare-transactional-storage";
188
+
189
+ // src/CloudflareWebSocket.ts
190
+ import { AbstractWebSocket } from "@pluv/io";
191
+ var CloudflareWebSocket = class extends AbstractWebSocket {
192
+ set presence(presence) {
193
+ const deserialized = this.webSocket.deserializeAttachment();
194
+ const state = deserialized.state;
195
+ this.webSocket.serializeAttachment(__spreadProps(__spreadValues({}, this.webSocket.deserializeAttachment()), {
196
+ state: __spreadProps(__spreadValues({}, state), { presence })
197
+ }));
198
+ }
199
+ get readyState() {
200
+ return this.webSocket.readyState;
201
+ }
202
+ get session() {
203
+ var _a;
204
+ const deserialized = this.webSocket.deserializeAttachment();
205
+ const sessionId = deserialized.sessionId;
206
+ const state = this.state;
207
+ const user = (_a = deserialized.user) != null ? _a : null;
208
+ return __spreadProps(__spreadValues({}, state), {
209
+ id: sessionId,
210
+ user,
211
+ webSocket: this
212
+ });
213
+ }
214
+ get sessionId() {
215
+ var _a, _b;
216
+ const deserialized = (_a = this.webSocket.deserializeAttachment()) != null ? _a : {};
217
+ const sessionId = (_b = deserialized.sessionId) != null ? _b : `p_${crypto.randomUUID()}`;
218
+ if (typeof deserialized.sessionId !== "string") {
219
+ this.webSocket.serializeAttachment(__spreadProps(__spreadValues({}, deserialized), { sessionId }));
220
+ }
221
+ return sessionId;
222
+ }
223
+ get state() {
224
+ var _a;
225
+ const deserialized = this.webSocket.deserializeAttachment();
226
+ const state = (_a = deserialized == null ? void 0 : deserialized.state) != null ? _a : null;
227
+ if (!state) throw new Error("Could not get websocket state");
228
+ const currentPing = state.timers.ping;
229
+ const lastPing = this._platform.getLastPing(this);
230
+ if (!lastPing) return state;
231
+ if (currentPing >= lastPing) return state;
232
+ const newState = __spreadProps(__spreadValues({}, state), {
233
+ timers: __spreadProps(__spreadValues({}, state.timers), { ping: lastPing })
234
+ });
235
+ return this._platform.setSerializedState(this, newState);
236
+ }
237
+ set state(state) {
238
+ const deserialized = this.webSocket.deserializeAttachment();
239
+ this.webSocket.serializeAttachment(__spreadProps(__spreadValues({}, deserialized), { state }));
240
+ }
241
+ set user(user) {
242
+ const deserialized = this.webSocket.deserializeAttachment();
243
+ this.webSocket.serializeAttachment(__spreadProps(__spreadValues({}, deserialized), { user }));
244
+ }
245
+ constructor(webSocket, config) {
246
+ const { room } = config;
247
+ super(webSocket, config);
248
+ const state = {
249
+ presence: null,
250
+ quit: false,
251
+ room,
252
+ timers: {
253
+ ping: (/* @__PURE__ */ new Date()).getTime(),
254
+ presence: null
255
+ }
256
+ };
257
+ webSocket.serializeAttachment(__spreadValues({
258
+ state
259
+ }, webSocket.deserializeAttachment()));
260
+ }
261
+ addEventListener(type, handler) {
262
+ this.webSocket.addEventListener(type, handler);
263
+ }
264
+ close(code, reason) {
265
+ const canClose = [this.CONNECTING, this.OPEN].some(
266
+ (readyState) => readyState === this.readyState
267
+ );
268
+ if (!canClose) return;
269
+ this.webSocket.close(code, reason);
270
+ }
271
+ send(message) {
272
+ if (this.readyState !== this.OPEN) return;
273
+ this.webSocket.send(message);
274
+ }
275
+ terminate(code = 1011) {
276
+ return this.webSocket.close(code, "Terminated");
277
+ }
278
+ };
279
+
280
+ // src/CloudflarePlatform.ts
281
+ var CloudflarePlatform = class _CloudflarePlatform extends AbstractPlatform {
282
+ constructor(config) {
283
+ var _a, _b;
284
+ super(__spreadValues(__spreadValues({}, config), config.roomContext && config.mode === "detached" ? {
285
+ persistence: (_a = config.persistence) != null ? _a : new PersistenceCloudflareTransactionalStorage({ mode: "sqlite" })
286
+ } : {}));
287
+ this.id = crypto.randomUUID();
288
+ this._name = "platformCloudflare";
289
+ this._config = {
290
+ authorize: {
291
+ secret: true
292
+ },
293
+ handleMode: "io",
294
+ registrationMode: (_b = config.mode) != null ? _b : DEFAULT_REGISTRATION_MODE,
295
+ requireAuth: false,
296
+ listeners: {
297
+ onRoomDeleted: true,
298
+ onRoomMessage: true,
299
+ onStorageUpdated: true,
300
+ onUserConnected: true,
301
+ onUserDisconnected: true
302
+ },
303
+ router: true
304
+ };
305
+ const detachedState = this._getDetachedState();
306
+ if (!detachedState) return;
307
+ detachedState.setWebSocketAutoResponse(
308
+ new WebSocketRequestResponsePair(
309
+ '{"type":"$ping","data":{}}',
310
+ JSON.stringify({ type: "$pong", data: {} })
311
+ )
312
+ );
313
+ }
314
+ acceptWebSocket(webSocket) {
315
+ return __async(this, null, function* () {
316
+ const detachedState = this._getDetachedState();
317
+ if (!detachedState) {
318
+ webSocket.webSocket.accept();
319
+ return;
320
+ }
321
+ detachedState.acceptWebSocket(webSocket.webSocket);
322
+ });
323
+ }
324
+ convertWebSocket(webSocket, config) {
325
+ const { room } = config;
326
+ return new CloudflareWebSocket(webSocket, {
327
+ persistence: this.persistence,
328
+ platform: this,
329
+ room
330
+ });
331
+ }
332
+ getLastPing(webSocket) {
333
+ var _a;
334
+ const detachedState = this._getDetachedState();
335
+ if (!detachedState) return null;
336
+ const timestamp = detachedState.getWebSocketAutoResponseTimestamp(webSocket.webSocket);
337
+ return (_a = timestamp == null ? void 0 : timestamp.getTime()) != null ? _a : null;
338
+ }
339
+ getSerializedState(webSocket) {
340
+ var _a;
341
+ const deserialized = webSocket.deserializeAttachment();
342
+ return (_a = deserialized == null ? void 0 : deserialized.state) != null ? _a : null;
343
+ }
344
+ getSessionId(webSocket) {
345
+ var _a;
346
+ const deserialized = (_a = webSocket.deserializeAttachment()) != null ? _a : {};
347
+ const sessionId = deserialized.sessionId;
348
+ if (typeof sessionId !== "string") return null;
349
+ return sessionId;
350
+ }
351
+ getWebSockets() {
352
+ var _a;
353
+ const detachedState = this._getDetachedState();
354
+ if (!detachedState) return [];
355
+ const webSockets = (_a = detachedState.getWebSockets()) != null ? _a : [];
356
+ return webSockets;
357
+ }
358
+ initialize(config) {
359
+ var _a;
360
+ const ctx = (_a = config.roomContext) != null ? _a : __spreadValues({}, this._roomContext);
361
+ if (!ctx.env || !ctx.state) throw new Error("Could not derive platform roomContext");
362
+ const roomContext = {
363
+ env: ctx.env,
364
+ meta: ctx.meta,
365
+ state: ctx.state
366
+ };
367
+ return new _CloudflarePlatform({
368
+ roomContext,
369
+ mode: this._config.registrationMode,
370
+ persistence: this.persistence.initialize(roomContext),
371
+ pubSub: this.pubSub
372
+ })._initialize();
373
+ }
374
+ parseData(data) {
375
+ if (typeof data === "string") return JSON.parse(data);
376
+ const decoder = new TextDecoder("utf8");
377
+ return JSON.parse(decoder.decode(data));
378
+ }
379
+ randomUUID() {
380
+ return crypto.randomUUID();
381
+ }
382
+ setSerializedState(webSocket, state) {
383
+ var _a;
384
+ const deserialized = (_a = webSocket.webSocket.deserializeAttachment()) != null ? _a : {};
385
+ webSocket.webSocket.serializeAttachment(__spreadProps(__spreadValues({}, deserialized), { state }));
386
+ return state;
387
+ }
388
+ _getDetachedState() {
389
+ var _a, _b;
390
+ if (this._config.registrationMode !== "detached") return null;
391
+ const detachedState = (_b = (_a = this._roomContext) == null ? void 0 : _a.state) != null ? _b : null;
392
+ return detachedState;
393
+ }
394
+ };
395
+
396
+ // src/platformCloudflare.ts
397
+ var platformCloudflare = (config = {}) => {
398
+ const { authorize, context, crdt, debug, limits } = config;
399
+ return {
400
+ authorize,
401
+ context,
402
+ crdt,
403
+ debug,
404
+ limits,
405
+ platform: () => new CloudflarePlatform(config)
406
+ };
407
+ };
408
+ export {
409
+ createPluvHandler,
410
+ infer,
411
+ platformCloudflare
412
+ };
@@ -0,0 +1,4 @@
1
+ import { config } from "eslint-config-pluv/base";
2
+
3
+ /** @type {import("eslint").Linter.Config} */
4
+ export default config;
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@pluv/platform-cloudflare",
3
+ "version": "0.0.0-experimental-20250527040415",
4
+ "description": "@pluv/io adapter for cloudflare workers",
5
+ "author": "leedavidcs",
6
+ "license": "MIT",
7
+ "homepage": "https://github.com/pluv-io/pluv",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/pluv-io/pluv.git",
11
+ "directory": "packages/platform-cloudflare"
12
+ },
13
+ "module": "./dist/index.mjs",
14
+ "types": "./dist/index.d.mts",
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "dependencies": {
19
+ "path-to-regexp": "^8.2.0",
20
+ "@pluv/io": "^0.0.0-experimental-20250527040415",
21
+ "@pluv/persistence-cloudflare-transactional-storage": "^0.0.0-experimental-20250527040415",
22
+ "@pluv/types": "^0.0.0-experimental-20250527040415"
23
+ },
24
+ "devDependencies": {
25
+ "@cloudflare/workers-types": "^4.20250521.0",
26
+ "eslint": "^9.27.0",
27
+ "tsup": "^8.5.0",
28
+ "typescript": "^5.8.3",
29
+ "@pluv/tsconfig": "^0.0.0-experimental-20250527040415",
30
+ "eslint-config-pluv": "^0.0.0-experimental-20250527040415"
31
+ },
32
+ "scripts": {
33
+ "build": "tsup src/index.ts",
34
+ "dev": "tsup src/index.ts --format esm,cjs --watch --dts",
35
+ "lint": "eslint \"src/**/*.ts*\" --fix --max-warnings 0",
36
+ "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
37
+ }
38
+ }