@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.
@@ -0,0 +1,209 @@
1
+ import type {
2
+ AbstractPlatformConfig,
3
+ ConvertWebSocketConfig,
4
+ WebSocketRegistrationMode,
5
+ WebSocketSerializedState,
6
+ } from "@pluv/io";
7
+ import { AbstractPlatform } from "@pluv/io";
8
+ import { PersistenceCloudflareTransactionalStorage } from "@pluv/persistence-cloudflare-transactional-storage";
9
+ import type { IOAuthorize, Json } from "@pluv/types";
10
+ import { CloudflareWebSocket } from "./CloudflareWebSocket";
11
+ import { DEFAULT_REGISTRATION_MODE } from "./constants";
12
+
13
+ export type CloudflarePlatformRoomContext<
14
+ TEnv extends Record<string, any>,
15
+ TMeta extends Record<string, Json>,
16
+ > = {
17
+ env: TEnv;
18
+ state: DurableObjectState;
19
+ } & (keyof TMeta extends never ? { meta?: undefined } : { meta: TMeta });
20
+
21
+ export type CloudflarePlatformConfig<
22
+ TEnv extends Record<string, any> = {},
23
+ TMeta extends Record<string, Json> = {},
24
+ > = AbstractPlatformConfig<CloudflarePlatformRoomContext<TEnv, TMeta>> & {
25
+ mode?: WebSocketRegistrationMode;
26
+ };
27
+
28
+ export class CloudflarePlatform<
29
+ TAuthorize extends IOAuthorize<any, any> | null = null,
30
+ TEnv extends Record<string, any> = {},
31
+ TMeta extends Record<string, Json> = {},
32
+ > extends AbstractPlatform<
33
+ CloudflareWebSocket<TAuthorize>,
34
+ { env: TEnv; request: Request },
35
+ CloudflarePlatformRoomContext<TEnv, TMeta>,
36
+ {
37
+ authorize: {
38
+ secret: true;
39
+ };
40
+ handleMode: "io";
41
+ requireAuth: false;
42
+ registrationMode: WebSocketRegistrationMode;
43
+ listeners: {
44
+ onRoomDeleted: true;
45
+ onRoomMessage: true;
46
+ onStorageUpdated: true;
47
+ onUserConnected: true;
48
+ onUserDisconnected: true;
49
+ };
50
+ router: true;
51
+ }
52
+ > {
53
+ public readonly id = crypto.randomUUID();
54
+ public readonly _config;
55
+ public readonly _name = "platformCloudflare";
56
+
57
+ constructor(config: CloudflarePlatformConfig<TEnv, TMeta>) {
58
+ super({
59
+ ...config,
60
+ ...(config.roomContext && config.mode === "detached"
61
+ ? {
62
+ persistence:
63
+ config.persistence ??
64
+ new PersistenceCloudflareTransactionalStorage({ mode: "sqlite" }),
65
+ }
66
+ : {}),
67
+ });
68
+
69
+ this._config = {
70
+ authorize: {
71
+ secret: true as const,
72
+ },
73
+ handleMode: "io" as const,
74
+ registrationMode: config.mode ?? DEFAULT_REGISTRATION_MODE,
75
+ requireAuth: false as const,
76
+ listeners: {
77
+ onRoomDeleted: true as const,
78
+ onRoomMessage: true as const,
79
+ onStorageUpdated: true as const,
80
+ onUserConnected: true as const,
81
+ onUserDisconnected: true as const,
82
+ },
83
+ router: true as const,
84
+ };
85
+
86
+ const detachedState = this._getDetachedState();
87
+
88
+ if (!detachedState) return;
89
+
90
+ detachedState.setWebSocketAutoResponse(
91
+ new WebSocketRequestResponsePair(
92
+ '{"type":"$ping","data":{}}',
93
+ JSON.stringify({ type: "$pong", data: {} }),
94
+ ),
95
+ );
96
+ }
97
+
98
+ public async acceptWebSocket(webSocket: CloudflareWebSocket<TAuthorize>): Promise<void> {
99
+ const detachedState = this._getDetachedState();
100
+
101
+ if (!detachedState) {
102
+ webSocket.webSocket.accept();
103
+
104
+ return;
105
+ }
106
+
107
+ detachedState.acceptWebSocket(webSocket.webSocket);
108
+ }
109
+
110
+ public convertWebSocket(
111
+ webSocket: WebSocket,
112
+ config: ConvertWebSocketConfig,
113
+ ): CloudflareWebSocket<TAuthorize> {
114
+ const { room } = config;
115
+
116
+ return new CloudflareWebSocket<TAuthorize>(webSocket, {
117
+ persistence: this.persistence,
118
+ platform: this,
119
+ room,
120
+ });
121
+ }
122
+
123
+ public getLastPing(webSocket: CloudflareWebSocket<TAuthorize>): number | null {
124
+ const detachedState = this._getDetachedState();
125
+
126
+ if (!detachedState) return null;
127
+
128
+ const timestamp = detachedState.getWebSocketAutoResponseTimestamp(webSocket.webSocket);
129
+
130
+ return timestamp?.getTime() ?? null;
131
+ }
132
+
133
+ public getSerializedState(webSocket: WebSocket): WebSocketSerializedState | null {
134
+ const deserialized = webSocket.deserializeAttachment();
135
+
136
+ return deserialized?.state ?? null;
137
+ }
138
+
139
+ public getSessionId(webSocket: WebSocket): string | null {
140
+ const deserialized = webSocket.deserializeAttachment() ?? {};
141
+ const sessionId = deserialized.sessionId;
142
+
143
+ if (typeof sessionId !== "string") return null;
144
+
145
+ return sessionId;
146
+ }
147
+
148
+ public getWebSockets(): readonly WebSocket[] {
149
+ const detachedState = this._getDetachedState();
150
+
151
+ if (!detachedState) return [];
152
+
153
+ const webSockets = detachedState.getWebSockets() ?? [];
154
+
155
+ return webSockets;
156
+ }
157
+
158
+ public initialize(
159
+ config: AbstractPlatformConfig<CloudflarePlatformRoomContext<TEnv, TMeta>>,
160
+ ): this {
161
+ const ctx = config.roomContext ?? { ...this._roomContext };
162
+
163
+ if (!ctx.env || !ctx.state) throw new Error("Could not derive platform roomContext");
164
+
165
+ const roomContext = {
166
+ env: ctx.env,
167
+ meta: ctx.meta,
168
+ state: ctx.state,
169
+ } as CloudflarePlatformRoomContext<TEnv, TMeta>;
170
+
171
+ return new CloudflarePlatform<TAuthorize, TEnv, TMeta>({
172
+ roomContext,
173
+ mode: this._config.registrationMode,
174
+ persistence: this.persistence.initialize(roomContext),
175
+ pubSub: this.pubSub,
176
+ })._initialize() as this;
177
+ }
178
+
179
+ public parseData(data: string | ArrayBuffer): Record<string, any> {
180
+ if (typeof data === "string") return JSON.parse(data);
181
+
182
+ const decoder = new TextDecoder("utf8");
183
+
184
+ return JSON.parse(decoder.decode(data));
185
+ }
186
+
187
+ public randomUUID(): string {
188
+ return crypto.randomUUID();
189
+ }
190
+
191
+ public setSerializedState(
192
+ webSocket: CloudflareWebSocket<TAuthorize>,
193
+ state: WebSocketSerializedState,
194
+ ): WebSocketSerializedState {
195
+ const deserialized = webSocket.webSocket.deserializeAttachment() ?? {};
196
+
197
+ webSocket.webSocket.serializeAttachment({ ...deserialized, state });
198
+
199
+ return state;
200
+ }
201
+
202
+ private _getDetachedState(): DurableObjectState | null {
203
+ if (this._config.registrationMode !== "detached") return null;
204
+
205
+ const detachedState = this._roomContext?.state ?? null;
206
+
207
+ return detachedState;
208
+ }
209
+ }
@@ -0,0 +1,141 @@
1
+ import type {
2
+ AbstractEventMap,
3
+ AbstractListener,
4
+ AbstractWebSocketConfig,
5
+ WebSocketSerializedState,
6
+ WebSocketSession,
7
+ } from "@pluv/io";
8
+ import { AbstractWebSocket } from "@pluv/io";
9
+ import type { InferIOAuthorizeUser, IOAuthorize, JsonObject } from "@pluv/types";
10
+
11
+ export interface CloudflareWebSocketEventMap {
12
+ close: CloseEvent;
13
+ message: MessageEvent;
14
+ open: Event;
15
+ error: ErrorEvent;
16
+ }
17
+
18
+ export type CloudflareWebSocketConfig = AbstractWebSocketConfig;
19
+
20
+ export class CloudflareWebSocket<
21
+ TAuthorize extends IOAuthorize<any, any> | null = null,
22
+ > extends AbstractWebSocket<WebSocket, TAuthorize> {
23
+ public set presence(presence: JsonObject | null) {
24
+ const deserialized = this.webSocket.deserializeAttachment();
25
+ const state = deserialized.state;
26
+
27
+ this.webSocket.serializeAttachment({
28
+ ...this.webSocket.deserializeAttachment(),
29
+ state: { ...state, presence },
30
+ });
31
+ }
32
+
33
+ public get readyState(): 0 | 1 | 2 | 3 {
34
+ return this.webSocket.readyState as 0 | 1 | 2 | 3;
35
+ }
36
+
37
+ public get session(): WebSocketSession<TAuthorize> {
38
+ const deserialized = this.webSocket.deserializeAttachment();
39
+ const sessionId = deserialized.sessionId as string;
40
+ const state = this.state;
41
+ const user = (deserialized.user ?? null) as InferIOAuthorizeUser<TAuthorize>;
42
+
43
+ return {
44
+ ...state,
45
+ id: sessionId,
46
+ user,
47
+ webSocket: this,
48
+ };
49
+ }
50
+
51
+ public get sessionId(): string {
52
+ const deserialized = this.webSocket.deserializeAttachment() ?? {};
53
+ const sessionId = deserialized.sessionId ?? `p_${crypto.randomUUID()}`;
54
+
55
+ if (typeof deserialized.sessionId !== "string") {
56
+ this.webSocket.serializeAttachment({ ...deserialized, sessionId });
57
+ }
58
+
59
+ return sessionId;
60
+ }
61
+
62
+ public get state(): WebSocketSerializedState {
63
+ const deserialized = this.webSocket.deserializeAttachment();
64
+ const state = deserialized?.state ?? null;
65
+
66
+ if (!state) throw new Error("Could not get websocket state");
67
+
68
+ const currentPing = state.timers.ping;
69
+ const lastPing = this._platform.getLastPing(this);
70
+
71
+ if (!lastPing) return state;
72
+ if (currentPing >= lastPing) return state;
73
+
74
+ const newState: WebSocketSerializedState = {
75
+ ...state,
76
+ timers: { ...state.timers, ping: lastPing },
77
+ };
78
+
79
+ return this._platform.setSerializedState(this, newState);
80
+ }
81
+
82
+ public set state(state: WebSocketSerializedState) {
83
+ const deserialized = this.webSocket.deserializeAttachment();
84
+
85
+ this.webSocket.serializeAttachment({ ...deserialized, state });
86
+ }
87
+
88
+ public set user(user: InferIOAuthorizeUser<TAuthorize>) {
89
+ const deserialized = this.webSocket.deserializeAttachment();
90
+
91
+ this.webSocket.serializeAttachment({ ...deserialized, user });
92
+ }
93
+
94
+ constructor(webSocket: WebSocket, config: CloudflareWebSocketConfig) {
95
+ const { room } = config;
96
+
97
+ super(webSocket, config);
98
+
99
+ const state: WebSocketSerializedState = {
100
+ presence: null,
101
+ quit: false,
102
+ room,
103
+ timers: {
104
+ ping: new Date().getTime(),
105
+ presence: null,
106
+ },
107
+ };
108
+
109
+ webSocket.serializeAttachment({
110
+ state,
111
+ ...webSocket.deserializeAttachment(),
112
+ });
113
+ }
114
+
115
+ public addEventListener<TType extends keyof AbstractEventMap>(
116
+ type: TType,
117
+ handler: AbstractListener<TType>,
118
+ ) {
119
+ this.webSocket.addEventListener(type, handler as any);
120
+ }
121
+
122
+ public close(code?: number | undefined, reason?: string | undefined): void {
123
+ const canClose = [this.CONNECTING, this.OPEN].some(
124
+ (readyState) => readyState === this.readyState,
125
+ );
126
+
127
+ if (!canClose) return;
128
+
129
+ this.webSocket.close(code, reason);
130
+ }
131
+
132
+ public send(message: string | ArrayBuffer | ArrayBufferView): void {
133
+ if (this.readyState !== this.OPEN) return;
134
+
135
+ this.webSocket.send(message);
136
+ }
137
+
138
+ public terminate(code: number = 1011): void {
139
+ return this.webSocket.close(code, "Terminated");
140
+ }
141
+ }
@@ -0,0 +1,4 @@
1
+ import type { WebSocketRegistrationMode } from "@pluv/io";
2
+
3
+ export const DEFAULT_REGISTRATION_MODE: WebSocketRegistrationMode = "detached";
4
+ export const GARBAGE_COLLECT_INTERVAL_MS = 60_000;
@@ -0,0 +1,244 @@
1
+ import type { InferInitContextType, IORoom, PluvServer } from "@pluv/io";
2
+ import type {
3
+ BaseUser,
4
+ Id,
5
+ InferIOAuthorize,
6
+ InferIOAuthorizeUser,
7
+ Maybe,
8
+ MaybePromise,
9
+ } from "@pluv/types";
10
+ import { DurableObject as BaseDurableObject } from "cloudflare:workers";
11
+ import { match } from "path-to-regexp";
12
+ import { CloudflarePlatform } from "./CloudflarePlatform";
13
+ import { GARBAGE_COLLECT_INTERVAL_MS } from "./constants";
14
+
15
+ export type AuthorizeFunctionContext<TPluvServer extends PluvServer<any, any, any, any>> = {
16
+ room: string;
17
+ } & InferInitContextType<
18
+ TPluvServer extends PluvServer<infer IPlatform, any, any, any> ? IPlatform : never
19
+ >;
20
+ export type AuthorizeFunction<TPluvServer extends PluvServer<any, any, any, any>> = (
21
+ ctx: AuthorizeFunctionContext<TPluvServer>,
22
+ ) => MaybePromise<Maybe<InferIOAuthorizeUser<InferIOAuthorize<TPluvServer>>>>;
23
+
24
+ export type CreatePluvHandlerConfig<
25
+ TPluvServer extends PluvServer<any, any, any, any>,
26
+ TEnv extends Record<string, any>,
27
+ > = {
28
+ binding: string;
29
+ endpoint?: string;
30
+ modify?: (request: Request, response: Response, env: TEnv) => MaybePromise<Response>;
31
+ io: TPluvServer;
32
+ } & (InferIOAuthorizeUser<InferIOAuthorize<TPluvServer>> extends BaseUser
33
+ ? { authorize: AuthorizeFunction<TPluvServer> }
34
+ : { authorize?: undefined });
35
+
36
+ export type PluvHandlerFetch<TEnv extends Record<string, any> = {}> = (
37
+ request: Request,
38
+ env: TEnv,
39
+ ) => Promise<Response | null>;
40
+
41
+ export interface CreatePluvHandlerResult<TEnv extends Record<string, any> = {}> {
42
+ DurableObject: {
43
+ new (state: DurableObjectState, env: TEnv): BaseDurableObject<TEnv>;
44
+ };
45
+ fetch: PluvHandlerFetch<TEnv>;
46
+ handler: ExportedHandler<TEnv>;
47
+ }
48
+
49
+ type InferCloudflarePluvHandlerEnv<
50
+ TPluvServer extends PluvServer<CloudflarePlatform<any, any>, any, any, any>,
51
+ > = TPluvServer extends PluvServer<CloudflarePlatform<any, infer IEnv>, any, any, any> ? IEnv : {};
52
+
53
+ /**
54
+ * @deprecated Instructions will be provided on https://pluv.io on how to host this yourself instead.
55
+ * @date April 27, 2025
56
+ */
57
+ export const createPluvHandler = <
58
+ TPluvServer extends PluvServer<CloudflarePlatform<any, any>, any, any, any>,
59
+ >(
60
+ config: CreatePluvHandlerConfig<TPluvServer, Id<InferCloudflarePluvHandlerEnv<TPluvServer>>>,
61
+ ): CreatePluvHandlerResult<Id<InferCloudflarePluvHandlerEnv<TPluvServer>>> => {
62
+ const { authorize, binding, endpoint = "/api/pluv", modify, io } = config;
63
+
64
+ const DurableObject = class extends BaseDurableObject<
65
+ Id<InferCloudflarePluvHandlerEnv<TPluvServer>>
66
+ > {
67
+ private _room: IORoom<CloudflarePlatform<any, any>>;
68
+
69
+ constructor(
70
+ state: DurableObjectState,
71
+ env: Id<InferCloudflarePluvHandlerEnv<TPluvServer>>,
72
+ ) {
73
+ super(state, env);
74
+
75
+ this._room = io.createRoom(state.id.toString(), { env, state });
76
+ }
77
+
78
+ public async webSocketClose(
79
+ ws: WebSocket,
80
+ code: number,
81
+ reason: string,
82
+ wasClean: boolean,
83
+ ): Promise<void> {
84
+ if (io._defs.platform._config.registrationMode !== "detached") return;
85
+
86
+ const onCloseHandler = this._room.onClose(ws);
87
+
88
+ await onCloseHandler({ code, reason });
89
+ }
90
+
91
+ public async webSocketError(ws: WebSocket, error: unknown): Promise<void> {
92
+ if (io._defs.platform._config.registrationMode !== "detached") return;
93
+
94
+ const onErrorHandler = this._room.onError(ws);
95
+ const eventError = error instanceof Error ? error : new Error("Internal Error");
96
+
97
+ await onErrorHandler({ error: eventError, message: eventError.message });
98
+ }
99
+
100
+ public async webSocketMessage(ws: WebSocket, message: string | ArrayBuffer): Promise<void> {
101
+ if (io._defs.platform._config.registrationMode !== "detached") return;
102
+
103
+ const onMessageHandler = this._room.onMessage(ws);
104
+
105
+ await onMessageHandler({ data: message });
106
+ }
107
+
108
+ async fetch(request: Request<any, CfProperties<any>>): Promise<Response> {
109
+ const isWSRequest = request.headers.get("Upgrade") === "websocket";
110
+
111
+ if (!isWSRequest) return new Response("Expected websocket", { status: 400 });
112
+
113
+ const { 0: client, 1: server } = new WebSocketPair();
114
+ const token = new URL(request.url).searchParams.get("token");
115
+
116
+ const alarm = await this.ctx.storage.getAlarm();
117
+ if (alarm !== null)
118
+ await this.ctx.storage.setAlarm(Date.now() + GARBAGE_COLLECT_INTERVAL_MS);
119
+
120
+ await this._room.register(server, { env: this.env, request, token });
121
+
122
+ return new Response(null, { status: 101, webSocket: client });
123
+ }
124
+
125
+ public async alarm(alarmInfo?: AlarmInvocationInfo): Promise<void> {
126
+ await this._room.garbageCollect();
127
+ await this.ctx.storage.setAlarm(Date.now() + GARBAGE_COLLECT_INTERVAL_MS);
128
+ }
129
+ };
130
+
131
+ const getDurableObjectNamespace = (
132
+ env: Id<InferCloudflarePluvHandlerEnv<TPluvServer>>,
133
+ ): DurableObjectNamespace => {
134
+ const namespace = env[binding as keyof typeof env] as DurableObjectNamespace;
135
+
136
+ if (!namespace) {
137
+ throw new Error(`Could not find DurableObject binding: ${binding}`);
138
+ }
139
+
140
+ return namespace;
141
+ };
142
+
143
+ const authHandler: PluvHandlerFetch<Id<InferCloudflarePluvHandlerEnv<TPluvServer>>> = async (
144
+ request,
145
+ env,
146
+ ) => {
147
+ if (!authorize) return null;
148
+
149
+ const { pathname, searchParams } = new URL(request.url);
150
+ const matcher = match<{}>(`${endpoint}/authorize`);
151
+ const matched = matcher(pathname);
152
+
153
+ if (!matched) return null;
154
+
155
+ const room = searchParams.get("room");
156
+
157
+ if (!room) {
158
+ return new Response("Not found", {
159
+ headers: { "Content-Type": "text/plain" },
160
+ status: 404,
161
+ });
162
+ }
163
+
164
+ try {
165
+ const user = await authorize({
166
+ env,
167
+ request,
168
+ room,
169
+ } as AuthorizeFunctionContext<TPluvServer>);
170
+
171
+ if (!user) throw new Error();
172
+
173
+ const namespace = getDurableObjectNamespace(env);
174
+ const durableObjectId = namespace.idFromName(room);
175
+
176
+ const token = await io.createToken({
177
+ env,
178
+ room: durableObjectId.toString(),
179
+ user: user as any,
180
+ request,
181
+ });
182
+
183
+ return new Response(token, {
184
+ headers: { "Content-Type": "text/plain" },
185
+ status: 200,
186
+ });
187
+ } catch (err) {
188
+ return new Response(err instanceof Error ? err.message : "Unauthorized", {
189
+ headers: { "Content-Type": "text/plain" },
190
+ status: 403,
191
+ });
192
+ }
193
+ };
194
+
195
+ const roomHandler: PluvHandlerFetch<Id<InferCloudflarePluvHandlerEnv<TPluvServer>>> = async (
196
+ request,
197
+ env,
198
+ ) => {
199
+ const { pathname } = new URL(request.url);
200
+ const matcher = match<{ roomId: string }>(`${endpoint}/room/:roomId`);
201
+ const matched = matcher(pathname);
202
+
203
+ if (!matched) return null;
204
+
205
+ const { roomId } = matched.params;
206
+
207
+ if (!roomId) {
208
+ return new Response("Not found", {
209
+ headers: { "Content-Type": "text/plain" },
210
+ status: 404,
211
+ });
212
+ }
213
+
214
+ const namespace = getDurableObjectNamespace(env);
215
+ const durableObjectId = namespace.idFromName(roomId);
216
+ const room = namespace.get(durableObjectId);
217
+
218
+ return room.fetch(request);
219
+ };
220
+
221
+ const handlerFetch: PluvHandlerFetch<Id<InferCloudflarePluvHandlerEnv<TPluvServer>>> = async (
222
+ request,
223
+ env,
224
+ ) => {
225
+ return [authHandler, roomHandler].reduce(async (promise, current) => {
226
+ return await promise.then(async (value) => value ?? (await current(request, env)));
227
+ }, Promise.resolve<Response | null>(null));
228
+ };
229
+
230
+ const handler: ExportedHandler<Id<InferCloudflarePluvHandlerEnv<TPluvServer>>> = {
231
+ fetch: async (request, env) => {
232
+ const response =
233
+ (await handlerFetch(request, env)) ??
234
+ new Response("Not Found", {
235
+ headers: { "Content-Type": "text/plain" },
236
+ status: 404,
237
+ });
238
+
239
+ return modify?.(request, response, env) ?? response;
240
+ },
241
+ };
242
+
243
+ return { fetch: handlerFetch, DurableObject, handler };
244
+ };
package/src/index.ts ADDED
@@ -0,0 +1,13 @@
1
+ export type { CloudflarePlatform, CloudflarePlatformConfig } from "./CloudflarePlatform";
2
+ export { createPluvHandler } from "./createPluvHandler";
3
+ export type {
4
+ AuthorizeFunction,
5
+ AuthorizeFunctionContext,
6
+ CreatePluvHandlerConfig,
7
+ CreatePluvHandlerResult,
8
+ PluvHandlerFetch,
9
+ } from "./createPluvHandler";
10
+ export { infer } from "./infer";
11
+ export type { InferCallback } from "./infer";
12
+ export { platformCloudflare } from "./platformCloudflare";
13
+ export type { PlatformCloudflareCreateIOParams } from "./platformCloudflare";
package/src/infer.ts ADDED
@@ -0,0 +1,17 @@
1
+ import type { Json } from "@pluv/types";
2
+ import type { identity } from "./utils";
3
+
4
+ export type InferCallback<
5
+ TEnv extends Record<string, any> = {},
6
+ TMeta extends Record<string, Json> = {},
7
+ > = (i: typeof identity) => {
8
+ env?: (io: TEnv) => TEnv;
9
+ meta?: (io: TMeta) => TMeta;
10
+ };
11
+
12
+ export const infer = <
13
+ TEnv extends Record<string, any> = {},
14
+ TMeta extends Record<string, Json> = {},
15
+ >(
16
+ callback: InferCallback<TEnv, TMeta>,
17
+ ) => callback;
@@ -0,0 +1,57 @@
1
+ import type { CreateIOParams, InferInitContextType, PluvContext, PluvIOAuthorize } from "@pluv/io";
2
+ import type { BaseUser, Id, IOAuthorize, Json } from "@pluv/types";
3
+ import type { CloudflarePlatformConfig } from "./CloudflarePlatform";
4
+ import { CloudflarePlatform } from "./CloudflarePlatform";
5
+ import type { InferCallback } from "./infer";
6
+
7
+ export type PlatformCloudflareCreateIOParams<
8
+ TEnv extends Record<string, any> = {},
9
+ TMeta extends Record<string, Json> = {},
10
+ TContext extends Record<string, any> = {},
11
+ TUser extends BaseUser | null = null,
12
+ > = Id<
13
+ CloudflarePlatformConfig<TEnv, TMeta> &
14
+ Omit<
15
+ CreateIOParams<
16
+ CloudflarePlatform<IOAuthorize<TUser, TContext>, TEnv, TMeta>,
17
+ TContext,
18
+ TUser
19
+ >,
20
+ "authorize" | "context" | "platform"
21
+ > & {
22
+ authorize?: PluvIOAuthorize<
23
+ CloudflarePlatform<IOAuthorize<TUser, TContext>, TEnv, TMeta>,
24
+ TUser,
25
+ InferInitContextType<CloudflarePlatform<IOAuthorize<TUser, TContext>, TEnv, TMeta>>
26
+ >;
27
+ context?: PluvContext<
28
+ CloudflarePlatform<IOAuthorize<TUser, any>, TEnv, TMeta>,
29
+ TContext
30
+ >;
31
+ types?: InferCallback<TEnv, TMeta>;
32
+ }
33
+ >;
34
+
35
+ export const platformCloudflare = <
36
+ TEnv extends Record<string, any> = {},
37
+ TMeta extends Record<string, Json> = {},
38
+ TContext extends Record<string, any> = {},
39
+ TUser extends BaseUser | null = null,
40
+ >(
41
+ config: PlatformCloudflareCreateIOParams<TEnv, TMeta, TContext, TUser> = {},
42
+ ): CreateIOParams<
43
+ CloudflarePlatform<IOAuthorize<TUser, TContext>, TEnv, TMeta>,
44
+ TContext,
45
+ TUser
46
+ > => {
47
+ const { authorize, context, crdt, debug, limits } = config;
48
+
49
+ return {
50
+ authorize,
51
+ context,
52
+ crdt,
53
+ debug,
54
+ limits,
55
+ platform: () => new CloudflarePlatform<IOAuthorize<TUser, TContext>, TEnv, TMeta>(config),
56
+ };
57
+ };
@@ -0,0 +1 @@
1
+ export const identity = <T extends unknown>(x: T): T => x;
@@ -0,0 +1 @@
1
+ export { identity } from "./identity";