verani 0.8.2 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/typed.mjs CHANGED
@@ -1 +1 @@
1
- import{g as defineRoom,t as createActorHandler}from"./actor-runtime-mfMYeKoB.mjs";import"./encode-BhJqnsto.mjs";import{a as getValidationErrorHandler,c as withValidation,d as payload,l as defineContract,o as isValidatedContract,r as getClientValidator,s as validateData,t as createValidatedHandler,u as isContract}from"./validation-NB7a2Sf1.mjs";function createTypedSocketEmit(e){let l=((l,u)=>{e.emit(l,u)});return l.to=l=>{let u=e.to(l);return{emit:(e,l)=>u.emit(e,l)}},l}function createTypedActorEmit(e){let l=((l,u)=>e.emit.emit(l,u));return l.to=l=>{let u=e.emit.to(l);return{emit:(e,l)=>u.emit(e,l)}},l}function wrapContext(e){return{actor:Object.assign({},e.actor,{emit:createTypedActorEmit(e.actor)}),ws:e.ws,meta:e.meta,emit:createTypedSocketEmit(e.emit)}}function wrapMessageContext(e){return{...wrapContext(e),frame:e.frame}}function createTypedRoom(l,d){let f=defineRoom({name:d.name,websocketPath:d.websocketPath??`/ws`,extractMeta:d.extractMeta,onConnect:d.onConnect?e=>d.onConnect(wrapContext(e)):void 0,onDisconnect:d.onDisconnect?e=>d.onDisconnect(wrapContext(e)):void 0,onError:d.onError?(e,l)=>d.onError(e,wrapContext(l)):void 0,onHibernationRestore:d.onHibernationRestore}),p=isValidatedContract(l),h=p?getValidationErrorHandler(l):void 0;return{on(e,u){f.on(e,(d,f)=>{let m=wrapMessageContext(d);if(p){let d=getClientValidator(l,e);if(d){let l=validateData(d,f,e,`client`,h);return l===void 0?void 0:u(m,l)}}return u(m,f)})},off(e,l){f.off(e)},get definition(){return f},contract:l}}export{createActorHandler,createTypedRoom,createValidatedHandler,defineContract,getClientValidator,getValidationErrorHandler,isContract,isValidatedContract,payload,validateData,withValidation};
1
+ import{n as defineRoom,t as createActorHandler}from"./actor-runtime-83AF97II.mjs";import"./encode-BhJqnsto.mjs";import{a as getValidationErrorHandler,c as withValidation,d as payload,l as defineContract,o as isValidatedContract,r as getClientValidator,s as validateData,t as createValidatedHandler,u as isContract}from"./validation-NB7a2Sf1.mjs";function createTypedSocketEmit(e){let l=((l,u)=>{e.emit(l,u)});return l.to=l=>{let u=e.to(l);return{emit:(e,l)=>u.emit(e,l)}},l}function createTypedActorEmit(e){let l=((l,u)=>e.emit.emit(l,u));return l.to=l=>{let u=e.emit.to(l);return{emit:(e,l)=>u.emit(e,l)}},l}function wrapContext(e){return{actor:Object.assign({},e.actor,{emit:createTypedActorEmit(e.actor)}),ws:e.ws,meta:e.meta,emit:createTypedSocketEmit(e.emit)}}function wrapMessageContext(e){return{...wrapContext(e),frame:e.frame}}function createTypedRoom(l,d){let f=defineRoom({name:d.name,websocketPath:d.websocketPath??`/ws`,extractMeta:d.extractMeta,onConnect:d.onConnect?e=>d.onConnect(wrapContext(e)):void 0,onDisconnect:d.onDisconnect?e=>d.onDisconnect(wrapContext(e)):void 0,onError:d.onError?(e,l)=>d.onError(e,wrapContext(l)):void 0,onHibernationRestore:d.onHibernationRestore}),p=isValidatedContract(l),h=p?getValidationErrorHandler(l):void 0;return{on(e,u){f.on(e,(d,f)=>{let m=wrapMessageContext(d);if(p){let d=getClientValidator(l,e);if(d){let l=validateData(d,f,e,`client`,h);return l===void 0?void 0:u(m,l)}}return u(m,f)})},off(e,l){f.off(e)},get definition(){return f},contract:l}}export{createActorHandler,createTypedRoom,createValidatedHandler,defineContract,getClientValidator,getValidationErrorHandler,isContract,isValidatedContract,payload,validateData,withValidation};
package/dist/verani.cjs CHANGED
@@ -1 +1 @@
1
- const require_actor_runtime=require(`./actor-runtime-Fr0n1XGB.cjs`),require_encode=require(`./encode-BYFW_6TI.cjs`),require_types=require(`./types-DUO6RVw1.cjs`);exports.PROTOCOL_VERSION=require_types.t,exports.PersistError=require_actor_runtime.n,exports.PersistNotReadyError=require_actor_runtime.r,exports.clearPersistedState=require_actor_runtime.i,exports.createActorHandler=require_actor_runtime.t,exports.decodeClientMessage=require_encode.i,exports.decodeFrame=require_encode.a,exports.decodeServerMessage=require_encode.o,exports.defineRoom=require_actor_runtime.g,exports.deletePersistedKey=require_actor_runtime.a,exports.encodeClientMessage=require_encode.t,exports.encodeFrame=require_encode.n,exports.encodeServerMessage=require_encode.r,exports.getPersistedKeys=require_actor_runtime.o,exports.getPersistedState=require_actor_runtime.s,exports.initializePersistedState=require_actor_runtime.c,exports.isStateReady=require_actor_runtime.l,exports.persistKey=require_actor_runtime.u,exports.restoreSessions=require_actor_runtime.m,exports.safeDeserialize=require_actor_runtime.d,exports.safeSerialize=require_actor_runtime.f,exports.setPeristErrorHandler=require_actor_runtime.p,exports.storeAttachment=require_actor_runtime.h;
1
+ const require_actor_runtime=require(`./actor-runtime-CCoZUVrQ.cjs`),require_encode=require(`./encode-BYFW_6TI.cjs`),require_types=require(`./types-DUO6RVw1.cjs`);let __cloudflare_actors=require(`@cloudflare/actors`);const WS=Symbol(`WS`),META=Symbol(`META`),ROOMS=Symbol(`ROOMS`);function createConnectionHandler(i){let a=i.name||`VeraniConnectionDO`,u=i.websocketPath||`/ws`;class d extends __cloudflare_actors.Actor{constructor(...a){super(...a),this[WS]=null,this[META]=null,this[ROOMS]=new Map,this[require_actor_runtime.o]=!1,this[require_actor_runtime.r]=i.state?{...i.state}:{},this.handlers=new Map}get connectionState(){return this[require_actor_runtime.r]}isStateReady(){return require_actor_runtime.f(this)}static configuration(e){return{sockets:{upgradePath:u}}}getRoomDO(){let e=this.env;return e.ROOM_DO||e.RoomDO||e.VERANI_ROOM}getConnectionDO(){let e=this.env;return e.CONNECTION_DO||e.ConnectionDO||e.VERANI_CONNECTION}createEmit(){let e=this;return{emit(i,a){e.sendToWebSocket(i,a)},to(i){return i.startsWith(`room:`)?e.createRoomEmitBuilder(i.slice(5)):e.createUserEmitBuilder(i)},toRoom(i){return e.createRoomEmitBuilder(i)},toUser(i){return e.createUserEmitBuilder(i)}}}createRoomEmitBuilder(e){let i=this;return{async emit(a,o){let s=i.getRoomDO();if(!s)return 0;try{let l=s.get(e),u={exceptUserId:i[META]?.userId};return await l.broadcast(a,o,u)}catch{return 0}}}}createUserEmitBuilder(e){let i=this;return{async emit(a,o){let s=i.getConnectionDO();if(!s)return 0;try{return await s.get(e).deliverMessage(a,o)?1:0}catch{return 0}}}}sendToWebSocket(i,a){let o=this[WS];if(!o||o.readyState!==WebSocket.OPEN)return!1;try{let s={type:`event`,channel:`default`,data:{type:i,...a}};return o.send(require_actor_runtime._(s)),!0}catch{return!1}}createContext(){return{actor:this,ws:this[WS],meta:this[META],emit:this.createEmit(),state:this.connectionState}}async onInit(){i.state&&(i.onPersistError&&require_actor_runtime.g(this,i.onPersistError),this[require_actor_runtime.r]=await require_actor_runtime.d(this,i.state,i.persistedKeys,i.persistOptions));let a=await this.ctx.storage.get(`_connection_rooms`);if(a&&Array.isArray(a)&&(this[ROOMS]=new Map(a.map(e=>[e.roomName,e.metadata]))),i.handlers)for(let[e,a]of i.handlers.entries())this.handlers.set(e,a);let o=this.ctx.getWebSockets();if(o.length>0){let e=o[0];if(e.readyState===WebSocket.OPEN){this[WS]=e;let a=e.deserializeAttachment();a&&(this[META]=a,await this.rejoinRoomsAfterHibernation(),i.onHibernationRestore&&await i.onHibernationRestore(this))}}}async shouldUpgradeWebSocket(e){return!0}async onWebSocketConnect(a,o){this[WS]&&this[WS].readyState===WebSocket.OPEN&&this[WS].close(1e3,`New connection established`);let l;if(l=i.extractMeta?await i.extractMeta(o):{userId:`anonymous`,clientId:crypto.randomUUID(),channels:[`default`]},require_actor_runtime.y(a,l),this[WS]=a,this[META]=l,i.onConnect)try{await i.onConnect(this.createContext())}catch(e){i.onError&&await i.onError(e,this.createContext()),a.close(1011,`Connection handler error`);return}}async onWebSocketMessage(e,a){if(this[META])try{let e=typeof a==`string`?JSON.parse(a):a,o=e.data?.type||e.type,s=this.handlers.get(o);if(s){await s(this.createContext(),e.data);return}i.onMessage&&await i.onMessage(this.createContext(),e)}catch(e){i.onError&&await i.onError(e,this.createContext())}}async onWebSocketDisconnect(e){if(this[META]&&i.onDisconnect)try{await i.onDisconnect(this.createContext())}catch{}for(let e of this[ROOMS].keys())try{await this.leaveRoomInternal(e)}catch{}this[WS]=null}async fetch(e){let i=new URL(e.url),a=e.headers.get(`Upgrade`);return i.pathname===u&&a===`websocket`&&await this.shouldUpgradeWebSocket(e)?this.onWebSocketUpgrade(e):this.onRequest(e)}async deliverMessage(e,i){return this.sendToWebSocket(e,i)}async deliverSystemEvent(e,i){this.sendToWebSocket(`system:${e}`,i)}async getUserId(){return this[META]?.userId??null}async isConnected(){return this[WS]!==null&&this[WS].readyState===WebSocket.OPEN}async joinRoom(e,i){if(!this[META])throw Error(`Cannot join room: not connected`);let a=this.getRoomDO();if(!a)throw Error(`RoomDO binding not found`);await a.get(e).join(this[META].userId,i),this[ROOMS].set(e,i),await this.persistRooms()}async persistRooms(){let e=Array.from(this[ROOMS].entries()).map(([e,i])=>({roomName:e,metadata:i}));await this.ctx.storage.put(`_connection_rooms`,e)}async rejoinRoomsAfterHibernation(){if(this[ROOMS].size===0)return;let e=this.getRoomDO();if(!e)return;let i=this[META]?.userId;if(!i)return;let a=[];for(let[o,s]of this[ROOMS].entries())try{await e.get(o).join(i,s)}catch{a.push(o)}if(a.length>0){for(let e of a)this[ROOMS].delete(e);await this.persistRooms()}}async leaveRoomInternal(e){if(!this[META])return;let i=this.getRoomDO();i&&await i.get(e).leave(this[META].userId)}async leaveRoom(e){if(!this[META])throw Error(`Cannot leave room: not connected`);await this.leaveRoomInternal(e),this[ROOMS].delete(e),await this.persistRooms()}async getRooms(){return Array.from(this[ROOMS].keys())}getStorage(){return this.ctx.storage}on(e,i){this.handlers.set(e,i)}off(e){this.handlers.delete(e)}}return Object.defineProperty(d,`name`,{value:a,writable:!1,configurable:!0}),d}function defineConnection(e){let i=new Map;return{...e,handlers:i,on(e,a){i.set(e,a)},off(e){i.delete(e)}}}const MEMBERS=Symbol(`MEMBERS`),ROOM_NAME=Symbol(`ROOM_NAME`);function createRoomHandler(e={}){let i=e.name||`VeraniRoomDO`;class a extends __cloudflare_actors.Actor{constructor(...e){super(...e),this[MEMBERS]=new Map,this[ROOM_NAME]=``,this.roomState={}}getConnectionDO(){let e=this.env;return e.CONNECTION_DO||e.ConnectionDO||e.VERANI_CONNECTION}async onInit(){await this.restoreMembersFromStorage(),await this.restoreRoomStateFromStorage(),e.onInit&&await e.onInit(this.roomState)}async restoreMembersFromStorage(){let e=await this.ctx.storage.list({prefix:`_room_member:`});this[MEMBERS].clear();for(let[i,a]of e.entries()){let e=i.replace(`_room_member:`,``);this[MEMBERS].set(e,a)}}async restoreRoomStateFromStorage(){let e=await this.ctx.storage.list({prefix:`_room_state:`});this.roomState={};for(let[i,a]of e.entries()){let e=i.replace(`_room_state:`,``);this.roomState[e]=a}}async join(i,a={}){let o={userId:i,joinedAt:Date.now(),metadata:a};this[MEMBERS].set(i,o),await this.ctx.storage.put(`_room_member:${i}`,o),e.onJoin&&await e.onJoin(this.roomState,i,a)}async leave(i){this[MEMBERS].get(i)&&(this[MEMBERS].delete(i),await this.ctx.storage.delete(`_room_member:${i}`),e.onLeave&&await e.onLeave(this.roomState,i))}async broadcast(e,i,a){let o=this.getConnectionDO();if(!o)return 0;let s=0,c=[];for(let[l,u]of this[MEMBERS].entries())if(!(a?.userIds&&!a.userIds.includes(l))&&a?.exceptUserId!==l)try{await o.get(l).deliverMessage(e,i),s++}catch(e){c.push(e),(e.message?.includes(`not found`)||e.message?.includes(`no connection`))&&await this.leave(l)}return s}async getMembers(){return Array.from(this[MEMBERS].values())}async getMemberCount(){return this[MEMBERS].size}async hasMember(e){return this[MEMBERS].has(e)}async updateMemberMetadata(e,i){let a=this[MEMBERS].get(e);a&&(a.metadata={...a.metadata,...i},this[MEMBERS].set(e,a),await this.ctx.storage.put(`_room_member:${e}`,a))}async getRoomState(){return{...this.roomState}}async setRoomState(e,i){this.roomState[e]=i,await this.ctx.storage.put(`_room_state:${e}`,i)}}return Object.defineProperty(a,`name`,{value:i,writable:!1,configurable:!0}),a}exports.PROTOCOL_VERSION=require_types.t,exports.PersistError=require_actor_runtime.i,exports.PersistNotReadyError=require_actor_runtime.a,exports.clearPersistedState=require_actor_runtime.s,exports.createActorHandler=require_actor_runtime.t,exports.createConnectionHandler=createConnectionHandler,exports.createRoomHandler=createRoomHandler,exports.decodeClientMessage=require_encode.i,exports.decodeFrame=require_encode.a,exports.decodeServerMessage=require_encode.o,exports.defineConnection=defineConnection,exports.defineRoom=require_actor_runtime.n,exports.deletePersistedKey=require_actor_runtime.c,exports.encodeClientMessage=require_encode.t,exports.encodeFrame=require_encode.n,exports.encodeServerMessage=require_encode.r,exports.getPersistedKeys=require_actor_runtime.l,exports.getPersistedState=require_actor_runtime.u,exports.initializePersistedState=require_actor_runtime.d,exports.isStateReady=require_actor_runtime.f,exports.persistKey=require_actor_runtime.p,exports.restoreSessions=require_actor_runtime.v,exports.safeDeserialize=require_actor_runtime.m,exports.safeSerialize=require_actor_runtime.h,exports.setPeristErrorHandler=require_actor_runtime.g,exports.storeAttachment=require_actor_runtime.y;
package/dist/verani.d.cts CHANGED
@@ -1,7 +1,218 @@
1
1
  import { a as ServerMessage, i as PROTOCOL_VERSION, n as ConnectionMeta, o as VeraniMessage, r as MessageFrame, t as ClientMessage } from "./types-DOI6EHQJ.cjs";
2
2
  import { a as encodeFrame, i as encodeClientMessage, n as decodeFrame, o as encodeServerMessage, r as decodeServerMessage, t as decodeClientMessage } from "./decode--RaAAv0U.cjs";
3
- import { C as safeSerialize, S as safeDeserialize, _ as getPersistedKeys, a as EventHandler, b as isStateReady, c as RoomDefinition, d as PersistError, f as PersistNotReadyError, g as deletePersistedKey, h as clearPersistedState, i as BroadcastOptions, l as RpcBroadcastOptions, m as SafePersistOptions, n as createActorHandler, o as MessageContext, p as PersistableActor, r as ActorStub, s as RoomContext, t as ActorHandlerClass, u as VeraniActor, v as getPersistedState, w as setPeristErrorHandler, x as persistKey, y as initializePersistedState } from "./actor-runtime-1TpQk-wg.cjs";
3
+ import { A as safeSerialize, C as deletePersistedKey, D as isStateReady, E as initializePersistedState, O as persistKey, S as clearPersistedState, T as getPersistedState, _ as VeraniEnv, a as BroadcastOptions, b as PersistableActor, c as ConnectionEmit, d as RoomContext, f as RoomCoordinatorDefinition, g as VeraniActor, h as RpcBroadcastOptions, i as AsyncEmitBuilder, j as setPeristErrorHandler, k as safeDeserialize, l as EventHandler, m as RoomMember, n as createActorHandler, o as ConnectionActor, p as RoomDefinition, r as ActorStub, s as ConnectionActorStub, t as ActorHandlerClass, u as MessageContext, v as PersistError, w as getPersistedKeys, x as SafePersistOptions, y as PersistNotReadyError } from "./actor-runtime-gFDsFVsU.cjs";
4
+ import { Actor, ActorConfiguration } from "@cloudflare/actors";
4
5
 
6
+ //#region src/actor/connection-actor.d.ts
7
+ /**
8
+ * Definition for creating a ConnectionDO (per-user connection handler)
9
+ */
10
+ interface ConnectionDefinition<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown, TState extends Record<string, unknown> = Record<string, unknown>> {
11
+ /** Optional name for debugging */
12
+ name?: string;
13
+ /** WebSocket upgrade path (default: "/ws") */
14
+ websocketPath?: string;
15
+ /**
16
+ * Extract metadata from the connection request
17
+ */
18
+ extractMeta?(req: Request): TMeta | Promise<TMeta>;
19
+ /**
20
+ * Called when a WebSocket connection is established
21
+ */
22
+ onConnect?(ctx: ConnectionContext<TMeta, E, TState>): void | Promise<void>;
23
+ /**
24
+ * Called when the WebSocket connection is closed
25
+ */
26
+ onDisconnect?(ctx: ConnectionContext<TMeta, E, TState>): void | Promise<void>;
27
+ /**
28
+ * Called when a message is received from the WebSocket
29
+ */
30
+ onMessage?(ctx: ConnectionContext<TMeta, E, TState>, frame: any): void | Promise<void>;
31
+ /**
32
+ * Called when an error occurs
33
+ */
34
+ onError?(error: Error, ctx: ConnectionContext<TMeta, E, TState>): void | Promise<void>;
35
+ /**
36
+ * Called after waking from hibernation
37
+ */
38
+ onHibernationRestore?(actor: any): void | Promise<void>;
39
+ /**
40
+ * Event handlers map (socket.io-like)
41
+ */
42
+ handlers?: Map<string, (ctx: ConnectionContext<TMeta, E, TState>, data: any) => void | Promise<void>>;
43
+ /**
44
+ * Initial state for this connection
45
+ */
46
+ state?: TState;
47
+ /**
48
+ * Keys to persist to storage
49
+ */
50
+ persistedKeys?: (string & keyof TState)[];
51
+ /**
52
+ * Persistence options
53
+ */
54
+ persistOptions?: {
55
+ shallow?: boolean;
56
+ throwOnError?: boolean;
57
+ };
58
+ /**
59
+ * Called when persistence fails
60
+ */
61
+ onPersistError?(key: string, error: Error): void;
62
+ }
63
+ /**
64
+ * Context provided to connection lifecycle hooks
65
+ */
66
+ interface ConnectionContext<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown, TState extends Record<string, unknown> = Record<string, unknown>> {
67
+ /** The connection actor instance */
68
+ actor: any;
69
+ /** The WebSocket connection (may be null after disconnect) */
70
+ ws: WebSocket | null;
71
+ /** Connection metadata */
72
+ meta: TMeta;
73
+ /** Socket.io-like emit API */
74
+ emit: ConnectionEmit<TMeta, E>;
75
+ /** Persisted state */
76
+ state: TState;
77
+ }
78
+ /**
79
+ * Return type for createConnectionHandler
80
+ */
81
+ type ConnectionHandlerClass<E = unknown> = {
82
+ new (state: any, env: E): Actor<E>;
83
+ get(userId: string): ConnectionActorStub;
84
+ configuration(request?: Request): ActorConfiguration;
85
+ };
86
+ /**
87
+ * Creates a Connection handler (per-user Durable Object)
88
+ *
89
+ * Each ConnectionDO owns exactly ONE WebSocket connection for a single user.
90
+ * Messages are received via RPC from RoomDOs and delivered to the WebSocket.
91
+ *
92
+ * @param definition - Connection definition with lifecycle hooks
93
+ * @returns ConnectionDO class for Cloudflare Workers
94
+ *
95
+ * @example
96
+ * ```typescript
97
+ * const UserConnection = createConnectionHandler({
98
+ * name: "user-connection",
99
+ * extractMeta: (req) => ({
100
+ * userId: extractUserIdFromToken(req),
101
+ * clientId: crypto.randomUUID(),
102
+ * channels: ["default"]
103
+ * }),
104
+ * onConnect: (ctx) => {
105
+ * console.log(`User ${ctx.meta.userId} connected`);
106
+ * ctx.actor.joinRoom("presence");
107
+ * },
108
+ * onDisconnect: (ctx) => {
109
+ * console.log(`User ${ctx.meta.userId} disconnected`);
110
+ * }
111
+ * });
112
+ *
113
+ * // In Worker:
114
+ * const userId = extractUserId(request);
115
+ * const stub = UserConnection.get(userId);
116
+ * return stub.fetch(request);
117
+ * ```
118
+ */
119
+ declare function createConnectionHandler<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown, TState extends Record<string, unknown> = Record<string, unknown>>(definition: ConnectionDefinition<TMeta, E, TState>): ConnectionHandlerClass<E>;
120
+ /**
121
+ * Helper to define a connection with socket.io-like event handlers
122
+ */
123
+ interface ConnectionDefinitionWithHandlers<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown, TState extends Record<string, unknown> = Record<string, unknown>> extends ConnectionDefinition<TMeta, E, TState> {
124
+ on(event: string, handler: (ctx: ConnectionContext<TMeta, E, TState>, data: any) => void | Promise<void>): void;
125
+ off(event: string): void;
126
+ }
127
+ /**
128
+ * Define a connection with socket.io-like convenience methods
129
+ */
130
+ declare function defineConnection<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown, TState extends Record<string, unknown> = Record<string, unknown>>(def: ConnectionDefinition<TMeta, E, TState>): ConnectionDefinitionWithHandlers<TMeta, E, TState>;
131
+ //#endregion
132
+ //#region src/actor/room-actor.d.ts
133
+ /**
134
+ * RoomDO - Coordination Durable Object for managing room membership and message fanout.
135
+ *
136
+ * This DO does NOT hold any WebSocket connections. Instead, it:
137
+ * - Manages a set of member userIds
138
+ * - Routes broadcast messages to member ConnectionDOs via RPC
139
+ * - Persists membership across hibernation
140
+ *
141
+ * One RoomDO instance per room/channel (e.g., RoomDO.get("chat"), RoomDO.get("presence"))
142
+ */
143
+ interface RoomActorStub {
144
+ /**
145
+ * Add a user to this room
146
+ */
147
+ join(userId: string, metadata?: Record<string, unknown>): Promise<void>;
148
+ /**
149
+ * Remove a user from this room
150
+ */
151
+ leave(userId: string): Promise<void>;
152
+ /**
153
+ * Broadcast a message to all members in the room
154
+ * This triggers RPC calls to each member's ConnectionDO
155
+ */
156
+ broadcast(event: string, data?: any, opts?: BroadcastOptions): Promise<number>;
157
+ /**
158
+ * Get all current members of the room
159
+ */
160
+ getMembers(): Promise<RoomMember[]>;
161
+ /**
162
+ * Get the count of members in the room
163
+ */
164
+ getMemberCount(): Promise<number>;
165
+ /**
166
+ * Check if a user is a member of this room
167
+ */
168
+ hasMember(userId: string): Promise<boolean>;
169
+ /**
170
+ * Update metadata for a member
171
+ */
172
+ updateMemberMetadata(userId: string, metadata: Record<string, unknown>): Promise<void>;
173
+ /**
174
+ * Get room state (for coordination purposes)
175
+ */
176
+ getRoomState(): Promise<Record<string, unknown>>;
177
+ /**
178
+ * Set room state (for coordination purposes)
179
+ */
180
+ setRoomState(key: string, value: unknown): Promise<void>;
181
+ }
182
+ /**
183
+ * Return type for createRoomHandler - represents a RoomDO class constructor
184
+ */
185
+ type RoomHandlerClass<E = unknown> = {
186
+ new (state: any, env: E): Actor<E>;
187
+ get(roomName: string): RoomActorStub;
188
+ };
189
+ /**
190
+ * Creates a Room coordination handler (Durable Object)
191
+ *
192
+ * This creates a DO class that manages room membership and coordinates
193
+ * message delivery between ConnectionDOs via RPC.
194
+ *
195
+ * @param definition - Room coordinator definition with optional hooks
196
+ * @returns RoomDO class for Cloudflare Workers
197
+ *
198
+ * @example
199
+ * ```typescript
200
+ * const ChatRoom = createRoomHandler({
201
+ * name: "chat-room",
202
+ * onJoin: async (roomState, userId, metadata) => {
203
+ * console.log(`${userId} joined the chat`);
204
+ * },
205
+ * onLeave: async (roomState, userId) => {
206
+ * console.log(`${userId} left the chat`);
207
+ * }
208
+ * });
209
+ *
210
+ * // In wrangler.toml, bind this as a separate DO
211
+ * // Then use: ChatRoom.get("general").join(userId)
212
+ * ```
213
+ */
214
+ declare function createRoomHandler<E = unknown>(definition?: RoomCoordinatorDefinition<E>): RoomHandlerClass<E>;
215
+ //#endregion
5
216
  //#region src/actor/router.d.ts
6
217
  /**
7
218
  * Extended room definition with socket.io-like convenience methods
@@ -64,4 +275,4 @@ declare function storeAttachment(ws: WebSocket, meta: ConnectionMeta): void;
64
275
  */
65
276
  declare function restoreSessions(actor: any): void;
66
277
  //#endregion
67
- export { type ActorHandlerClass, type ActorStub, type BroadcastOptions, type ClientMessage, type ConnectionMeta, type MessageContext, type MessageFrame, PROTOCOL_VERSION, PersistError, PersistNotReadyError, type PersistableActor, type RoomContext, type RoomDefinition, type RpcBroadcastOptions, type SafePersistOptions, type ServerMessage, type VeraniActor, type VeraniMessage, clearPersistedState, createActorHandler, decodeClientMessage, decodeFrame, decodeServerMessage, defineRoom, deletePersistedKey, encodeClientMessage, encodeFrame, encodeServerMessage, getPersistedKeys, getPersistedState, initializePersistedState, isStateReady, persistKey, restoreSessions, safeDeserialize, safeSerialize, setPeristErrorHandler, storeAttachment };
278
+ export { type ActorHandlerClass, type ActorStub, type AsyncEmitBuilder, type BroadcastOptions, type ClientMessage, type ConnectionActor, type ConnectionActorStub, type ConnectionContext, type ConnectionDefinition, type ConnectionDefinitionWithHandlers, type ConnectionEmit, type ConnectionHandlerClass, type ConnectionMeta, type MessageContext, type MessageFrame, PROTOCOL_VERSION, PersistError, PersistNotReadyError, type PersistableActor, type RoomActorStub, type RoomContext, type RoomCoordinatorDefinition, type RoomDefinition, type RoomHandlerClass, type RoomMember, type RpcBroadcastOptions, type SafePersistOptions, type ServerMessage, type VeraniActor, type VeraniEnv, type VeraniMessage, clearPersistedState, createActorHandler, createConnectionHandler, createRoomHandler, decodeClientMessage, decodeFrame, decodeServerMessage, defineConnection, defineRoom, deletePersistedKey, encodeClientMessage, encodeFrame, encodeServerMessage, getPersistedKeys, getPersistedState, initializePersistedState, isStateReady, persistKey, restoreSessions, safeDeserialize, safeSerialize, setPeristErrorHandler, storeAttachment };
package/dist/verani.d.mts CHANGED
@@ -1,7 +1,218 @@
1
1
  import { a as ServerMessage, i as PROTOCOL_VERSION, n as ConnectionMeta, o as VeraniMessage, r as MessageFrame, t as ClientMessage } from "./types-_41fL9Dn.mjs";
2
2
  import { a as encodeFrame, i as encodeClientMessage, n as decodeFrame, o as encodeServerMessage, r as decodeServerMessage, t as decodeClientMessage } from "./decode-BUzd77kA.mjs";
3
- import { C as safeSerialize, S as safeDeserialize, _ as getPersistedKeys, a as EventHandler, b as isStateReady, c as RoomDefinition, d as PersistError, f as PersistNotReadyError, g as deletePersistedKey, h as clearPersistedState, i as BroadcastOptions, l as RpcBroadcastOptions, m as SafePersistOptions, n as createActorHandler, o as MessageContext, p as PersistableActor, r as ActorStub, s as RoomContext, t as ActorHandlerClass, u as VeraniActor, v as getPersistedState, w as setPeristErrorHandler, x as persistKey, y as initializePersistedState } from "./actor-runtime-BOmyu8Vl.mjs";
3
+ import { A as safeSerialize, C as deletePersistedKey, D as isStateReady, E as initializePersistedState, O as persistKey, S as clearPersistedState, T as getPersistedState, _ as VeraniEnv, a as BroadcastOptions, b as PersistableActor, c as ConnectionEmit, d as RoomContext, f as RoomCoordinatorDefinition, g as VeraniActor, h as RpcBroadcastOptions, i as AsyncEmitBuilder, j as setPeristErrorHandler, k as safeDeserialize, l as EventHandler, m as RoomMember, n as createActorHandler, o as ConnectionActor, p as RoomDefinition, r as ActorStub, s as ConnectionActorStub, t as ActorHandlerClass, u as MessageContext, v as PersistError, w as getPersistedKeys, x as SafePersistOptions, y as PersistNotReadyError } from "./actor-runtime-BqF6_7z_.mjs";
4
+ import { Actor, ActorConfiguration } from "@cloudflare/actors";
4
5
 
6
+ //#region src/actor/connection-actor.d.ts
7
+ /**
8
+ * Definition for creating a ConnectionDO (per-user connection handler)
9
+ */
10
+ interface ConnectionDefinition<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown, TState extends Record<string, unknown> = Record<string, unknown>> {
11
+ /** Optional name for debugging */
12
+ name?: string;
13
+ /** WebSocket upgrade path (default: "/ws") */
14
+ websocketPath?: string;
15
+ /**
16
+ * Extract metadata from the connection request
17
+ */
18
+ extractMeta?(req: Request): TMeta | Promise<TMeta>;
19
+ /**
20
+ * Called when a WebSocket connection is established
21
+ */
22
+ onConnect?(ctx: ConnectionContext<TMeta, E, TState>): void | Promise<void>;
23
+ /**
24
+ * Called when the WebSocket connection is closed
25
+ */
26
+ onDisconnect?(ctx: ConnectionContext<TMeta, E, TState>): void | Promise<void>;
27
+ /**
28
+ * Called when a message is received from the WebSocket
29
+ */
30
+ onMessage?(ctx: ConnectionContext<TMeta, E, TState>, frame: any): void | Promise<void>;
31
+ /**
32
+ * Called when an error occurs
33
+ */
34
+ onError?(error: Error, ctx: ConnectionContext<TMeta, E, TState>): void | Promise<void>;
35
+ /**
36
+ * Called after waking from hibernation
37
+ */
38
+ onHibernationRestore?(actor: any): void | Promise<void>;
39
+ /**
40
+ * Event handlers map (socket.io-like)
41
+ */
42
+ handlers?: Map<string, (ctx: ConnectionContext<TMeta, E, TState>, data: any) => void | Promise<void>>;
43
+ /**
44
+ * Initial state for this connection
45
+ */
46
+ state?: TState;
47
+ /**
48
+ * Keys to persist to storage
49
+ */
50
+ persistedKeys?: (string & keyof TState)[];
51
+ /**
52
+ * Persistence options
53
+ */
54
+ persistOptions?: {
55
+ shallow?: boolean;
56
+ throwOnError?: boolean;
57
+ };
58
+ /**
59
+ * Called when persistence fails
60
+ */
61
+ onPersistError?(key: string, error: Error): void;
62
+ }
63
+ /**
64
+ * Context provided to connection lifecycle hooks
65
+ */
66
+ interface ConnectionContext<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown, TState extends Record<string, unknown> = Record<string, unknown>> {
67
+ /** The connection actor instance */
68
+ actor: any;
69
+ /** The WebSocket connection (may be null after disconnect) */
70
+ ws: WebSocket | null;
71
+ /** Connection metadata */
72
+ meta: TMeta;
73
+ /** Socket.io-like emit API */
74
+ emit: ConnectionEmit<TMeta, E>;
75
+ /** Persisted state */
76
+ state: TState;
77
+ }
78
+ /**
79
+ * Return type for createConnectionHandler
80
+ */
81
+ type ConnectionHandlerClass<E = unknown> = {
82
+ new (state: any, env: E): Actor<E>;
83
+ get(userId: string): ConnectionActorStub;
84
+ configuration(request?: Request): ActorConfiguration;
85
+ };
86
+ /**
87
+ * Creates a Connection handler (per-user Durable Object)
88
+ *
89
+ * Each ConnectionDO owns exactly ONE WebSocket connection for a single user.
90
+ * Messages are received via RPC from RoomDOs and delivered to the WebSocket.
91
+ *
92
+ * @param definition - Connection definition with lifecycle hooks
93
+ * @returns ConnectionDO class for Cloudflare Workers
94
+ *
95
+ * @example
96
+ * ```typescript
97
+ * const UserConnection = createConnectionHandler({
98
+ * name: "user-connection",
99
+ * extractMeta: (req) => ({
100
+ * userId: extractUserIdFromToken(req),
101
+ * clientId: crypto.randomUUID(),
102
+ * channels: ["default"]
103
+ * }),
104
+ * onConnect: (ctx) => {
105
+ * console.log(`User ${ctx.meta.userId} connected`);
106
+ * ctx.actor.joinRoom("presence");
107
+ * },
108
+ * onDisconnect: (ctx) => {
109
+ * console.log(`User ${ctx.meta.userId} disconnected`);
110
+ * }
111
+ * });
112
+ *
113
+ * // In Worker:
114
+ * const userId = extractUserId(request);
115
+ * const stub = UserConnection.get(userId);
116
+ * return stub.fetch(request);
117
+ * ```
118
+ */
119
+ declare function createConnectionHandler<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown, TState extends Record<string, unknown> = Record<string, unknown>>(definition: ConnectionDefinition<TMeta, E, TState>): ConnectionHandlerClass<E>;
120
+ /**
121
+ * Helper to define a connection with socket.io-like event handlers
122
+ */
123
+ interface ConnectionDefinitionWithHandlers<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown, TState extends Record<string, unknown> = Record<string, unknown>> extends ConnectionDefinition<TMeta, E, TState> {
124
+ on(event: string, handler: (ctx: ConnectionContext<TMeta, E, TState>, data: any) => void | Promise<void>): void;
125
+ off(event: string): void;
126
+ }
127
+ /**
128
+ * Define a connection with socket.io-like convenience methods
129
+ */
130
+ declare function defineConnection<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown, TState extends Record<string, unknown> = Record<string, unknown>>(def: ConnectionDefinition<TMeta, E, TState>): ConnectionDefinitionWithHandlers<TMeta, E, TState>;
131
+ //#endregion
132
+ //#region src/actor/room-actor.d.ts
133
+ /**
134
+ * RoomDO - Coordination Durable Object for managing room membership and message fanout.
135
+ *
136
+ * This DO does NOT hold any WebSocket connections. Instead, it:
137
+ * - Manages a set of member userIds
138
+ * - Routes broadcast messages to member ConnectionDOs via RPC
139
+ * - Persists membership across hibernation
140
+ *
141
+ * One RoomDO instance per room/channel (e.g., RoomDO.get("chat"), RoomDO.get("presence"))
142
+ */
143
+ interface RoomActorStub {
144
+ /**
145
+ * Add a user to this room
146
+ */
147
+ join(userId: string, metadata?: Record<string, unknown>): Promise<void>;
148
+ /**
149
+ * Remove a user from this room
150
+ */
151
+ leave(userId: string): Promise<void>;
152
+ /**
153
+ * Broadcast a message to all members in the room
154
+ * This triggers RPC calls to each member's ConnectionDO
155
+ */
156
+ broadcast(event: string, data?: any, opts?: BroadcastOptions): Promise<number>;
157
+ /**
158
+ * Get all current members of the room
159
+ */
160
+ getMembers(): Promise<RoomMember[]>;
161
+ /**
162
+ * Get the count of members in the room
163
+ */
164
+ getMemberCount(): Promise<number>;
165
+ /**
166
+ * Check if a user is a member of this room
167
+ */
168
+ hasMember(userId: string): Promise<boolean>;
169
+ /**
170
+ * Update metadata for a member
171
+ */
172
+ updateMemberMetadata(userId: string, metadata: Record<string, unknown>): Promise<void>;
173
+ /**
174
+ * Get room state (for coordination purposes)
175
+ */
176
+ getRoomState(): Promise<Record<string, unknown>>;
177
+ /**
178
+ * Set room state (for coordination purposes)
179
+ */
180
+ setRoomState(key: string, value: unknown): Promise<void>;
181
+ }
182
+ /**
183
+ * Return type for createRoomHandler - represents a RoomDO class constructor
184
+ */
185
+ type RoomHandlerClass<E = unknown> = {
186
+ new (state: any, env: E): Actor<E>;
187
+ get(roomName: string): RoomActorStub;
188
+ };
189
+ /**
190
+ * Creates a Room coordination handler (Durable Object)
191
+ *
192
+ * This creates a DO class that manages room membership and coordinates
193
+ * message delivery between ConnectionDOs via RPC.
194
+ *
195
+ * @param definition - Room coordinator definition with optional hooks
196
+ * @returns RoomDO class for Cloudflare Workers
197
+ *
198
+ * @example
199
+ * ```typescript
200
+ * const ChatRoom = createRoomHandler({
201
+ * name: "chat-room",
202
+ * onJoin: async (roomState, userId, metadata) => {
203
+ * console.log(`${userId} joined the chat`);
204
+ * },
205
+ * onLeave: async (roomState, userId) => {
206
+ * console.log(`${userId} left the chat`);
207
+ * }
208
+ * });
209
+ *
210
+ * // In wrangler.toml, bind this as a separate DO
211
+ * // Then use: ChatRoom.get("general").join(userId)
212
+ * ```
213
+ */
214
+ declare function createRoomHandler<E = unknown>(definition?: RoomCoordinatorDefinition<E>): RoomHandlerClass<E>;
215
+ //#endregion
5
216
  //#region src/actor/router.d.ts
6
217
  /**
7
218
  * Extended room definition with socket.io-like convenience methods
@@ -64,4 +275,4 @@ declare function storeAttachment(ws: WebSocket, meta: ConnectionMeta): void;
64
275
  */
65
276
  declare function restoreSessions(actor: any): void;
66
277
  //#endregion
67
- export { type ActorHandlerClass, type ActorStub, type BroadcastOptions, type ClientMessage, type ConnectionMeta, type MessageContext, type MessageFrame, PROTOCOL_VERSION, PersistError, PersistNotReadyError, type PersistableActor, type RoomContext, type RoomDefinition, type RpcBroadcastOptions, type SafePersistOptions, type ServerMessage, type VeraniActor, type VeraniMessage, clearPersistedState, createActorHandler, decodeClientMessage, decodeFrame, decodeServerMessage, defineRoom, deletePersistedKey, encodeClientMessage, encodeFrame, encodeServerMessage, getPersistedKeys, getPersistedState, initializePersistedState, isStateReady, persistKey, restoreSessions, safeDeserialize, safeSerialize, setPeristErrorHandler, storeAttachment };
278
+ export { type ActorHandlerClass, type ActorStub, type AsyncEmitBuilder, type BroadcastOptions, type ClientMessage, type ConnectionActor, type ConnectionActorStub, type ConnectionContext, type ConnectionDefinition, type ConnectionDefinitionWithHandlers, type ConnectionEmit, type ConnectionHandlerClass, type ConnectionMeta, type MessageContext, type MessageFrame, PROTOCOL_VERSION, PersistError, PersistNotReadyError, type PersistableActor, type RoomActorStub, type RoomContext, type RoomCoordinatorDefinition, type RoomDefinition, type RoomHandlerClass, type RoomMember, type RpcBroadcastOptions, type SafePersistOptions, type ServerMessage, type VeraniActor, type VeraniEnv, type VeraniMessage, clearPersistedState, createActorHandler, createConnectionHandler, createRoomHandler, decodeClientMessage, decodeFrame, decodeServerMessage, defineConnection, defineRoom, deletePersistedKey, encodeClientMessage, encodeFrame, encodeServerMessage, getPersistedKeys, getPersistedState, initializePersistedState, isStateReady, persistKey, restoreSessions, safeDeserialize, safeSerialize, setPeristErrorHandler, storeAttachment };
package/dist/verani.mjs CHANGED
@@ -1 +1 @@
1
- import{a as deletePersistedKey,c as initializePersistedState,d as safeDeserialize,f as safeSerialize,g as defineRoom,h as storeAttachment,i as clearPersistedState,l as isStateReady,m as restoreSessions,n as PersistError,o as getPersistedKeys,p as setPeristErrorHandler,r as PersistNotReadyError,s as getPersistedState,t as createActorHandler,u as persistKey}from"./actor-runtime-mfMYeKoB.mjs";import{a as decodeFrame,i as decodeClientMessage,n as encodeFrame,o as decodeServerMessage,r as encodeServerMessage,t as encodeClientMessage}from"./encode-BhJqnsto.mjs";import{t as PROTOCOL_VERSION}from"./types-rZsftNPE.mjs";export{PROTOCOL_VERSION,PersistError,PersistNotReadyError,clearPersistedState,createActorHandler,decodeClientMessage,decodeFrame,decodeServerMessage,defineRoom,deletePersistedKey,encodeClientMessage,encodeFrame,encodeServerMessage,getPersistedKeys,getPersistedState,initializePersistedState,isStateReady,persistKey,restoreSessions,safeDeserialize,safeSerialize,setPeristErrorHandler,storeAttachment};
1
+ import{_ as encodeFrame$1,a as PersistNotReadyError,c as deletePersistedKey,d as initializePersistedState,f as isStateReady,g as setPeristErrorHandler,h as safeSerialize,i as PersistError,l as getPersistedKeys,m as safeDeserialize,n as defineRoom,o as STATE_READY,p as persistKey,r as PERSISTED_STATE,s as clearPersistedState,t as createActorHandler,u as getPersistedState,v as restoreSessions,y as storeAttachment}from"./actor-runtime-83AF97II.mjs";import{a as decodeFrame,i as decodeClientMessage,n as encodeFrame,o as decodeServerMessage,r as encodeServerMessage,t as encodeClientMessage}from"./encode-BhJqnsto.mjs";import{t as PROTOCOL_VERSION}from"./types-rZsftNPE.mjs";import{Actor}from"@cloudflare/actors";const WS=Symbol(`WS`),META=Symbol(`META`),ROOMS=Symbol(`ROOMS`);function createConnectionHandler(O){let k=O.name||`VeraniConnectionDO`,N=O.websocketPath||`/ws`;class P extends Actor{constructor(...u){super(...u),this[WS]=null,this[META]=null,this[ROOMS]=new Map,this[STATE_READY]=!1,this[PERSISTED_STATE]=O.state?{...O.state}:{},this.handlers=new Map}get connectionState(){return this[PERSISTED_STATE]}isStateReady(){return isStateReady(this)}static configuration(u){return{sockets:{upgradePath:N}}}getRoomDO(){let u=this.env;return u.ROOM_DO||u.RoomDO||u.VERANI_ROOM}getConnectionDO(){let u=this.env;return u.CONNECTION_DO||u.ConnectionDO||u.VERANI_CONNECTION}createEmit(){let u=this;return{emit(O,k){u.sendToWebSocket(O,k)},to(O){return O.startsWith(`room:`)?u.createRoomEmitBuilder(O.slice(5)):u.createUserEmitBuilder(O)},toRoom(O){return u.createRoomEmitBuilder(O)},toUser(O){return u.createUserEmitBuilder(O)}}}createRoomEmitBuilder(u){let O=this;return{async emit(k,A){let j=O.getRoomDO();if(!j)return 0;try{let M=j.get(u),N={exceptUserId:O[META]?.userId};return await M.broadcast(k,A,N)}catch{return 0}}}}createUserEmitBuilder(u){let O=this;return{async emit(k,A){let j=O.getConnectionDO();if(!j)return 0;try{return await j.get(u).deliverMessage(k,A)?1:0}catch{return 0}}}}sendToWebSocket(O,k){let A=this[WS];if(!A||A.readyState!==WebSocket.OPEN)return!1;try{let j={type:`event`,channel:`default`,data:{type:O,...k}};return A.send(encodeFrame$1(j)),!0}catch{return!1}}createContext(){return{actor:this,ws:this[WS],meta:this[META],emit:this.createEmit(),state:this.connectionState}}async onInit(){O.state&&(O.onPersistError&&setPeristErrorHandler(this,O.onPersistError),this[PERSISTED_STATE]=await initializePersistedState(this,O.state,O.persistedKeys,O.persistOptions));let u=await this.ctx.storage.get(`_connection_rooms`);if(u&&Array.isArray(u)&&(this[ROOMS]=new Map(u.map(u=>[u.roomName,u.metadata]))),O.handlers)for(let[u,k]of O.handlers.entries())this.handlers.set(u,k);let k=this.ctx.getWebSockets();if(k.length>0){let u=k[0];if(u.readyState===WebSocket.OPEN){this[WS]=u;let k=u.deserializeAttachment();k&&(this[META]=k,await this.rejoinRoomsAfterHibernation(),O.onHibernationRestore&&await O.onHibernationRestore(this))}}}async shouldUpgradeWebSocket(u){return!0}async onWebSocketConnect(u,k){this[WS]&&this[WS].readyState===WebSocket.OPEN&&this[WS].close(1e3,`New connection established`);let A;if(A=O.extractMeta?await O.extractMeta(k):{userId:`anonymous`,clientId:crypto.randomUUID(),channels:[`default`]},storeAttachment(u,A),this[WS]=u,this[META]=A,O.onConnect)try{await O.onConnect(this.createContext())}catch(k){O.onError&&await O.onError(k,this.createContext()),u.close(1011,`Connection handler error`);return}}async onWebSocketMessage(u,k){if(this[META])try{let u=typeof k==`string`?JSON.parse(k):k,A=u.data?.type||u.type,j=this.handlers.get(A);if(j){await j(this.createContext(),u.data);return}O.onMessage&&await O.onMessage(this.createContext(),u)}catch(u){O.onError&&await O.onError(u,this.createContext())}}async onWebSocketDisconnect(u){if(this[META]&&O.onDisconnect)try{await O.onDisconnect(this.createContext())}catch{}for(let u of this[ROOMS].keys())try{await this.leaveRoomInternal(u)}catch{}this[WS]=null}async fetch(u){let O=new URL(u.url),k=u.headers.get(`Upgrade`);return O.pathname===N&&k===`websocket`&&await this.shouldUpgradeWebSocket(u)?this.onWebSocketUpgrade(u):this.onRequest(u)}async deliverMessage(u,O){return this.sendToWebSocket(u,O)}async deliverSystemEvent(u,O){this.sendToWebSocket(`system:${u}`,O)}async getUserId(){return this[META]?.userId??null}async isConnected(){return this[WS]!==null&&this[WS].readyState===WebSocket.OPEN}async joinRoom(u,O){if(!this[META])throw Error(`Cannot join room: not connected`);let k=this.getRoomDO();if(!k)throw Error(`RoomDO binding not found`);await k.get(u).join(this[META].userId,O),this[ROOMS].set(u,O),await this.persistRooms()}async persistRooms(){let u=Array.from(this[ROOMS].entries()).map(([u,O])=>({roomName:u,metadata:O}));await this.ctx.storage.put(`_connection_rooms`,u)}async rejoinRoomsAfterHibernation(){if(this[ROOMS].size===0)return;let u=this.getRoomDO();if(!u)return;let O=this[META]?.userId;if(!O)return;let k=[];for(let[A,j]of this[ROOMS].entries())try{await u.get(A).join(O,j)}catch{k.push(A)}if(k.length>0){for(let u of k)this[ROOMS].delete(u);await this.persistRooms()}}async leaveRoomInternal(u){if(!this[META])return;let O=this.getRoomDO();O&&await O.get(u).leave(this[META].userId)}async leaveRoom(u){if(!this[META])throw Error(`Cannot leave room: not connected`);await this.leaveRoomInternal(u),this[ROOMS].delete(u),await this.persistRooms()}async getRooms(){return Array.from(this[ROOMS].keys())}getStorage(){return this.ctx.storage}on(u,O){this.handlers.set(u,O)}off(u){this.handlers.delete(u)}}return Object.defineProperty(P,`name`,{value:k,writable:!1,configurable:!0}),P}function defineConnection(u){let O=new Map;return{...u,handlers:O,on(u,k){O.set(u,k)},off(u){O.delete(u)}}}const MEMBERS=Symbol(`MEMBERS`),ROOM_NAME=Symbol(`ROOM_NAME`);function createRoomHandler(u={}){let O=u.name||`VeraniRoomDO`;class k extends Actor{constructor(...u){super(...u),this[MEMBERS]=new Map,this[ROOM_NAME]=``,this.roomState={}}getConnectionDO(){let u=this.env;return u.CONNECTION_DO||u.ConnectionDO||u.VERANI_CONNECTION}async onInit(){await this.restoreMembersFromStorage(),await this.restoreRoomStateFromStorage(),u.onInit&&await u.onInit(this.roomState)}async restoreMembersFromStorage(){let u=await this.ctx.storage.list({prefix:`_room_member:`});this[MEMBERS].clear();for(let[O,k]of u.entries()){let u=O.replace(`_room_member:`,``);this[MEMBERS].set(u,k)}}async restoreRoomStateFromStorage(){let u=await this.ctx.storage.list({prefix:`_room_state:`});this.roomState={};for(let[O,k]of u.entries()){let u=O.replace(`_room_state:`,``);this.roomState[u]=k}}async join(O,k={}){let A={userId:O,joinedAt:Date.now(),metadata:k};this[MEMBERS].set(O,A),await this.ctx.storage.put(`_room_member:${O}`,A),u.onJoin&&await u.onJoin(this.roomState,O,k)}async leave(O){this[MEMBERS].get(O)&&(this[MEMBERS].delete(O),await this.ctx.storage.delete(`_room_member:${O}`),u.onLeave&&await u.onLeave(this.roomState,O))}async broadcast(u,O,k){let A=this.getConnectionDO();if(!A)return 0;let j=0,M=[];for(let[N,P]of this[MEMBERS].entries())if(!(k?.userIds&&!k.userIds.includes(N))&&k?.exceptUserId!==N)try{await A.get(N).deliverMessage(u,O),j++}catch(u){M.push(u),(u.message?.includes(`not found`)||u.message?.includes(`no connection`))&&await this.leave(N)}return j}async getMembers(){return Array.from(this[MEMBERS].values())}async getMemberCount(){return this[MEMBERS].size}async hasMember(u){return this[MEMBERS].has(u)}async updateMemberMetadata(u,O){let k=this[MEMBERS].get(u);k&&(k.metadata={...k.metadata,...O},this[MEMBERS].set(u,k),await this.ctx.storage.put(`_room_member:${u}`,k))}async getRoomState(){return{...this.roomState}}async setRoomState(u,O){this.roomState[u]=O,await this.ctx.storage.put(`_room_state:${u}`,O)}}return Object.defineProperty(k,`name`,{value:O,writable:!1,configurable:!0}),k}export{PROTOCOL_VERSION,PersistError,PersistNotReadyError,clearPersistedState,createActorHandler,createConnectionHandler,createRoomHandler,decodeClientMessage,decodeFrame,decodeServerMessage,defineConnection,defineRoom,deletePersistedKey,encodeClientMessage,encodeFrame,encodeServerMessage,getPersistedKeys,getPersistedState,initializePersistedState,isStateReady,persistKey,restoreSessions,safeDeserialize,safeSerialize,setPeristErrorHandler,storeAttachment};
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "verani",
3
- "version": "0.8.2",
3
+ "version": "0.9.0",
4
4
  "description": "A simple, focused realtime SDK for Cloudflare Actors with Socket.io-like semantics",
5
5
  "license": "ISC",
6
+ "workspaces": ["site"],
6
7
  "keywords": [
7
8
  "cloudflare",
8
9
  "actors",
@@ -1 +0,0 @@
1
- const require_encode=require(`./encode-BYFW_6TI.cjs`);let __cloudflare_actors=require(`@cloudflare/actors`);var RoomEventEmitterImpl=class{constructor(){this.handlers=new Map}on(u,I){this.handlers.has(u)||this.handlers.set(u,new Set),this.handlers.get(u).add(I)}off(u,I){let L=this.handlers.get(u);L&&(I?(L.delete(I),L.size===0&&this.handlers.delete(u)):this.handlers.delete(u))}async emit(u,I,L){let R=this.handlers.get(u);if(R&&R.size>0){let u=[];for(let z of R)try{let R=z(I,L);R instanceof Promise&&u.push(R)}catch{}await Promise.all(u)}let z=this.handlers.get(`*`);if(z&&z.size>0){let u=[];for(let R of z)try{let z=R(I,L);z instanceof Promise&&u.push(z)}catch{}await Promise.all(u)}}hasHandlers(u){return this.handlers.has(u)&&this.handlers.get(u).size>0||this.handlers.has(`*`)&&this.handlers.get(`*`).size>0}getEventNames(){return Array.from(this.handlers.keys())}rebuildHandlers(u){this.handlers.clear();for(let[I,L]of u.entries())this.handlers.set(I,new Set(L))}};function createRoomEventEmitter(){return new RoomEventEmitterImpl}function defaultExtractMeta(u){let I=crypto.randomUUID(),L=crypto.randomUUID(),R=new URL(u.url).searchParams.get(`channels`);return{userId:I,clientId:L,channels:R?R.split(`,`).map(u=>u.trim()).filter(Boolean):[`default`]}}function defineRoom(u){let I=u.eventEmitter||createRoomEventEmitter(),L=u._staticHandlers||new Map;return{name:u.name,websocketPath:u.websocketPath,extractMeta:u.extractMeta||(u=>defaultExtractMeta(u)),onConnect:u.onConnect,onDisconnect:u.onDisconnect,onMessage:u.onMessage,onError:u.onError,onHibernationRestore:u.onHibernationRestore,eventEmitter:I,_staticHandlers:L,state:u.state,persistedKeys:u.persistedKeys,persistOptions:u.persistOptions,onPersistError:u.onPersistError,on(u,R){I.on(u,R),L.has(u)||L.set(u,new Set),L.get(u).add(R)},off(u,R){I.off(u,R);let z=L.get(u);z&&(R?(z.delete(R),z.size===0&&L.delete(u)):L.delete(u))}}}function cleanupStaleSessions(u){let I=0,L=[];for(let[I,R]of u.entries())I.readyState!==WebSocket.OPEN&&L.push(I);for(let R of L)u.delete(R),I++;return I}function decodeFrame(I){return require_encode.a(I)??{type:`invalid`}}function encodeFrame(I){return require_encode.n(I)}function broadcast(u,I,L,R){let z=0,B=encodeFrame({type:`event`,channel:I,data:L}),V=[];for(let{ws:L,meta:H}of u.values())if(H.channels.includes(I)&&!(R?.except&&L===R.except)&&!(R?.userIds&&!R.userIds.includes(H.userId))&&!(R?.clientIds&&!R.clientIds.includes(H.clientId))){if(L.readyState!==WebSocket.OPEN){V.push(L);continue}try{L.send(B),z++}catch{V.push(L)}}for(let I of V)u.delete(I);return V.length,z}function sendToUser(u,I,L,R){let z=0,B=encodeFrame({type:`event`,channel:L,data:R}),V=[];for(let{ws:R,meta:H}of u.values())if(H.userId===I&&H.channels.includes(L)){if(R.readyState!==WebSocket.OPEN){V.push(R);continue}try{R.send(B),z++}catch{V.push(R)}}for(let I of V)u.delete(I);return V.length,z}function getSessionCount(u){return u.size}function getConnectedUserIds(u){let I=new Set;for(let{meta:L}of u.values())I.add(L.userId);return Array.from(I)}function getUserSessions(u,I){let L=[];for(let{ws:R,meta:z}of u.values())z.userId===I&&L.push(R);return L}function getStorage(u){return u.storage}function sanitizeToClassName(u){return u.replace(/^\/+/,``).split(/[-_\/\s]+/).map(u=>u.replace(/[^a-zA-Z0-9]/g,``)).filter(u=>u.length>0).map(u=>u.charAt(0).toUpperCase()+u.slice(1).toLowerCase()).join(``)||`VeraniActor`}function createConfiguration(u){return function(I){return{sockets:{upgradePath:u.websocketPath}}}}function isValidConnectionMeta(u){return!(!u||typeof u!=`object`||typeof u.userId!=`string`||!u.userId||typeof u.clientId!=`string`||!u.clientId||!Array.isArray(u.channels)||!u.channels.every(u=>typeof u==`string`))}function storeAttachment(u,I){u.serializeAttachment(I)}function restoreSessions(u){let I=0,L=0;for(let R of u.ctx.getWebSockets()){if(R.readyState!==WebSocket.OPEN){L++;continue}let z=R.deserializeAttachment();if(!z){L++;continue}if(!isValidConnectionMeta(z)){L++;continue}u.sessions.set(R,{ws:R,meta:z}),I++}}async function onInit(u,I){if(I.eventEmitter&&I._staticHandlers)try{I.eventEmitter.rebuildHandlers(I._staticHandlers)}catch{}try{restoreSessions(u)}catch{}if(I.onHibernationRestore&&u.sessions.size>0)try{await I.onHibernationRestore(u)}catch{}else I.onHibernationRestore&&u.sessions.size}function createUserEmitBuilder(u,I,L){return{emit(R,z){return sendToUser(I,u,L,{type:R,...z})}}}function createChannelEmitBuilder(u,I,L){return{emit(R,z){return broadcast(I,u,{type:R,...z},L)}}}function createSocketEmit(u){let I=u.meta.channels[0]||`default`;return{emit(L,R){if(u.ws.readyState===WebSocket.OPEN)try{let z={type:`event`,channel:I,data:{type:L,...R}};u.ws.send(encodeFrame(z))}catch{}},to(L){return u.meta.channels.includes(L)?createChannelEmitBuilder(L,u.actor.sessions,{except:u.ws}):createUserEmitBuilder(L,u.actor.sessions,I)}}}function createActorEmit(u){return{emit(I,L){let R={type:I,...L};return broadcast(u.sessions,`default`,R)},to(I){return createChannelEmitBuilder(I,u.sessions)}}}async function onWebSocketConnect(u,I,L,R){let z;try{z=I.extractMeta?await I.extractMeta(R):{userId:`anonymous`,clientId:crypto.randomUUID(),channels:[`default`]},storeAttachment(L,z);let B={actor:u,ws:L,meta:z,frame:{type:`connect`}};if(I.onConnect){let R={actor:u,ws:L,meta:z,emit:createSocketEmit(B)};await I.onConnect(R)}u.sessions.set(L,{ws:L,meta:z})}catch(R){if(I.onError&&z)try{let B={actor:u,ws:L,meta:z,frame:{type:`error`}};await I.onError(R,{actor:u,ws:L,meta:z,emit:createSocketEmit(B)})}catch{}L.close(1011,`Internal server error`)}}async function onWebSocketMessage(u,I,L,R){let z;try{let B=decodeFrame(R);if(B&&B.type===`ping`){if(L.readyState===WebSocket.OPEN)try{L.send(encodeFrame({type:`pong`}))}catch{}return}if(!B||B.type===`invalid`||(z=u.sessions.get(L),!z))return;let V={actor:u,ws:L,meta:z.meta,frame:B,emit:createSocketEmit({actor:u,ws:L,meta:z.meta,frame:B})},W=I.eventEmitter;W&&W.hasHandlers&&W.hasHandlers(B.type)?await W.emit(B.type,V,B.data||{}):I.onMessage&&await I.onMessage(V,B)}catch(R){if(I.onError&&z)try{await I.onError(R,{actor:u,ws:L,meta:z.meta,emit:createSocketEmit({actor:u,ws:L,meta:z.meta,frame:{type:`error`}})})}catch{}}}async function onWebSocketDisconnect(u,I,L){try{let R=u.sessions.get(L);if(u.sessions.delete(L),R&&I.onDisconnect){let z={actor:u,ws:L,meta:R.meta,frame:{type:`disconnect`}},B={actor:u,ws:L,meta:R.meta,emit:createSocketEmit(z)};await I.onDisconnect(B)}}catch{}}function createFetch(u,I){return async function(L){let R=new URL(L.url),z=L.headers.get(`Upgrade`);return R.pathname===u.websocketPath&&z===`websocket`&&await I.shouldUpgradeWebSocket(L)?I.onWebSocketUpgrade(L):I.onRequest(L)}}const PERSIST_ERROR_HANDLER=Symbol(`PERSIST_ERROR_HANDLER`),PERSISTED_STATE=Symbol(`PERSISTED_STATE`),STATE_READY=Symbol(`STATE_READY`);var PersistNotReadyError=class extends Error{constructor(u){super(`Cannot access persisted state key "${u}" before storage is initialized. Wait for onInit to complete or check actor.isStateReady().`),this.name=`PersistNotReadyError`}},PersistError=class extends Error{constructor(u,I){super(`Failed to persist key "${u}": ${I.message}`),this.name=`PersistError`,this.originalCause=I}};function safeSerialize(u){let I=new WeakSet;return JSON.stringify(u,(u,L)=>{if(L instanceof Date)return{__type:`Date`,value:L.toISOString()};if(L instanceof RegExp)return{__type:`RegExp`,source:L.source,flags:L.flags};if(L instanceof Map)return{__type:`Map`,entries:Array.from(L.entries())};if(L instanceof Set)return{__type:`Set`,values:Array.from(L.values())};if(L instanceof Error)return{__type:`Error`,name:L.name,message:L.message};if(typeof L==`object`&&L){if(I.has(L))return;I.add(L)}if(!(typeof L==`function`||typeof L==`symbol`))return L})}function safeDeserialize(u){return JSON.parse(u,(u,I)=>{if(I&&typeof I==`object`&&I.__type)switch(I.__type){case`Date`:return new Date(I.value);case`RegExp`:return new RegExp(I.source,I.flags);case`Map`:return new Map(I.entries);case`Set`:return new Set(I.values);case`Error`:{let u=Error(I.message);return u.name=I.name,u}}return I})}function createShallowProxy(u,I,L){return new Proxy(u,{set(u,L,R){let z=Reflect.set(u,L,R);return z&&typeof L==`string`&&I(L,R),z},deleteProperty(u,I){let R=Reflect.deleteProperty(u,I);return R&&typeof I==`string`&&L(I),R}})}async function initializePersistedState(u,I,L=[],R={}){let{shallow:z=!0,throwOnError:B=!0}=R,V=u.ctx.storage,H=L.length>0?L.map(String):Object.keys(I),U={...I};for(let u of H)try{let I=await V.get(`_verani_persist:${u}`);if(I!==void 0)try{U[u]=safeDeserialize(I)}catch(I){if(B)throw new PersistError(u,I)}}catch(I){if(B&&!(I instanceof PersistError))throw new PersistError(u,I)}let W=async(I,L)=>{if(H.includes(I))try{let u=safeSerialize(L);await V.put(`_verani_persist:${I}`,u)}catch(L){let R=u[PERSIST_ERROR_HANDLER];if(R&&R(I,L),B)throw new PersistError(I,L)}},G=async I=>{if(H.includes(I))try{await V.delete(`_verani_persist:${I}`)}catch(L){let R=u[PERSIST_ERROR_HANDLER];if(R&&R(I,L),B)throw new PersistError(I,L)}},K;return K=z?createShallowProxy(U,(u,I)=>{W(String(u),I)},u=>{G(String(u))}):createDeepProxy(U,(u,I)=>{W(u,I)},u=>{G(u)},H),u[STATE_READY]=!0,u[PERSISTED_STATE]=K,K}function createDeepProxy(u,I,L,R,z){return new Proxy(u,{get(u,B){let V=Reflect.get(u,B);if(V&&typeof V==`object`&&!Array.isArray(V)){let u=z??String(B);if(R.includes(u)||z!==void 0)return createDeepProxy(V,I,L,R,u)}return V},set(u,L,B){let V=Reflect.set(u,L,B);if(V){let u=z??String(L);R.includes(u)&&(z?I(z,void 0):I(String(L),B))}return V},deleteProperty(u,B){let V=Reflect.deleteProperty(u,B);if(V){let u=z??String(B);R.includes(u)&&(z?I(z,void 0):L(String(B)))}return V}})}function isStateReady(u){return u[STATE_READY]===!0}function getPersistedState(u){if(!isStateReady(u))throw new PersistNotReadyError(`state`);return u[PERSISTED_STATE]}function setPeristErrorHandler(u,I){u[PERSIST_ERROR_HANDLER]=I}async function persistKey(u,I,L){let R=u.ctx.storage,z=safeSerialize(L);await R.put(`_verani_persist:${I}`,z)}async function deletePersistedKey(u,I){await u.ctx.storage.delete(`_verani_persist:${I}`)}async function getPersistedKeys(u){let I=await u.ctx.storage.list({prefix:`_verani_persist:`});return Array.from(I.keys()).map(u=>u.replace(`_verani_persist:`,``))}async function clearPersistedState(u){let I=u.ctx.storage,L=await I.list({prefix:`_verani_persist:`}),R=Array.from(L.keys());await I.delete(R)}function createActorHandler(u){let L=sanitizeToClassName(u.name||u.websocketPath||`VeraniActor`),R=u;class z extends __cloudflare_actors.Actor{constructor(...I){super(...I),this.sessions=new Map,this.emit=createActorEmit(this),this[STATE_READY]=!1,this[PERSISTED_STATE]=u.state?{...u.state}:{},this.fetch=createFetch(R,this)}get roomState(){return this[PERSISTED_STATE]}isStateReady(){return isStateReady(this)}static{this.configuration=createConfiguration(R)}async shouldUpgradeWebSocket(u){return!0}async onInit(){if(u.state){u.onPersistError&&setPeristErrorHandler(this,u.onPersistError);let I=u.persistedKeys;this[PERSISTED_STATE]=await initializePersistedState(this,u.state,I,u.persistOptions)}await onInit(this,R)}async onWebSocketConnect(u,I){await onWebSocketConnect(this,R,u,I)}async onWebSocketMessage(u,I){await onWebSocketMessage(this,R,u,I)}async onWebSocketDisconnect(u){await onWebSocketDisconnect(this,R,u)}cleanupStaleSessions(){return cleanupStaleSessions(this.sessions)}broadcast(u,I,L){return broadcast(this.sessions,u,I,L)}getSessionCount(){return getSessionCount(this.sessions)}getConnectedUserIds(){return getConnectedUserIds(this.sessions)}getUserSessions(u){return getUserSessions(this.sessions,u)}sendToUser(u,I,L){return sendToUser(this.sessions,u,I,L)}emitToChannel(u,I,L){let R={type:I,...L};return broadcast(this.sessions,u,R)}emitToUser(u,I,L){let R=encodeFrame({type:`event`,channel:`default`,data:{type:I,...L}}),z=0,B=[];for(let{ws:I,meta:L}of this.sessions.values())if(L.userId===u){if(I.readyState!==WebSocket.OPEN){B.push(I);continue}try{I.send(R),z++}catch{B.push(I)}}for(let u of B)this.sessions.delete(u);return z}getStorage(){return getStorage(this.ctx)}}return Object.defineProperty(z,`name`,{value:L,writable:!1,configurable:!0}),z}Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return deletePersistedKey}}),Object.defineProperty(exports,`c`,{enumerable:!0,get:function(){return initializePersistedState}}),Object.defineProperty(exports,`d`,{enumerable:!0,get:function(){return safeDeserialize}}),Object.defineProperty(exports,`f`,{enumerable:!0,get:function(){return safeSerialize}}),Object.defineProperty(exports,`g`,{enumerable:!0,get:function(){return defineRoom}}),Object.defineProperty(exports,`h`,{enumerable:!0,get:function(){return storeAttachment}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return clearPersistedState}}),Object.defineProperty(exports,`l`,{enumerable:!0,get:function(){return isStateReady}}),Object.defineProperty(exports,`m`,{enumerable:!0,get:function(){return restoreSessions}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return PersistError}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return getPersistedKeys}}),Object.defineProperty(exports,`p`,{enumerable:!0,get:function(){return setPeristErrorHandler}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return PersistNotReadyError}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return getPersistedState}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return createActorHandler}}),Object.defineProperty(exports,`u`,{enumerable:!0,get:function(){return persistKey}});
@@ -1 +0,0 @@
1
- import{a as decodeFrame$1,n as encodeFrame$1}from"./encode-BhJqnsto.mjs";import{Actor}from"@cloudflare/actors";var RoomEventEmitterImpl=class{constructor(){this.handlers=new Map}on(p,L){this.handlers.has(p)||this.handlers.set(p,new Set),this.handlers.get(p).add(L)}off(p,L){let R=this.handlers.get(p);R&&(L?(R.delete(L),R.size===0&&this.handlers.delete(p)):this.handlers.delete(p))}async emit(p,L,R){let z=this.handlers.get(p);if(z&&z.size>0){let p=[];for(let B of z)try{let z=B(L,R);z instanceof Promise&&p.push(z)}catch{}await Promise.all(p)}let B=this.handlers.get(`*`);if(B&&B.size>0){let p=[];for(let z of B)try{let B=z(L,R);B instanceof Promise&&p.push(B)}catch{}await Promise.all(p)}}hasHandlers(p){return this.handlers.has(p)&&this.handlers.get(p).size>0||this.handlers.has(`*`)&&this.handlers.get(`*`).size>0}getEventNames(){return Array.from(this.handlers.keys())}rebuildHandlers(p){this.handlers.clear();for(let[L,R]of p.entries())this.handlers.set(L,new Set(R))}};function createRoomEventEmitter(){return new RoomEventEmitterImpl}function defaultExtractMeta(p){let L=crypto.randomUUID(),R=crypto.randomUUID(),z=new URL(p.url).searchParams.get(`channels`);return{userId:L,clientId:R,channels:z?z.split(`,`).map(p=>p.trim()).filter(Boolean):[`default`]}}function defineRoom(p){let L=p.eventEmitter||createRoomEventEmitter(),R=p._staticHandlers||new Map;return{name:p.name,websocketPath:p.websocketPath,extractMeta:p.extractMeta||(p=>defaultExtractMeta(p)),onConnect:p.onConnect,onDisconnect:p.onDisconnect,onMessage:p.onMessage,onError:p.onError,onHibernationRestore:p.onHibernationRestore,eventEmitter:L,_staticHandlers:R,state:p.state,persistedKeys:p.persistedKeys,persistOptions:p.persistOptions,onPersistError:p.onPersistError,on(p,z){L.on(p,z),R.has(p)||R.set(p,new Set),R.get(p).add(z)},off(p,z){L.off(p,z);let B=R.get(p);B&&(z?(B.delete(z),B.size===0&&R.delete(p)):R.delete(p))}}}function cleanupStaleSessions(p){let L=0,R=[];for(let[L,z]of p.entries())L.readyState!==WebSocket.OPEN&&R.push(L);for(let z of R)p.delete(z),L++;return L}function decodeFrame(L){return decodeFrame$1(L)??{type:`invalid`}}function encodeFrame(p){return encodeFrame$1(p)}function broadcast(p,L,R,z){let B=0,V=encodeFrame({type:`event`,channel:L,data:R}),H=[];for(let{ws:R,meta:U}of p.values())if(U.channels.includes(L)&&!(z?.except&&R===z.except)&&!(z?.userIds&&!z.userIds.includes(U.userId))&&!(z?.clientIds&&!z.clientIds.includes(U.clientId))){if(R.readyState!==WebSocket.OPEN){H.push(R);continue}try{R.send(V),B++}catch{H.push(R)}}for(let L of H)p.delete(L);return H.length,B}function sendToUser(p,L,R,z){let B=0,V=encodeFrame({type:`event`,channel:R,data:z}),H=[];for(let{ws:z,meta:U}of p.values())if(U.userId===L&&U.channels.includes(R)){if(z.readyState!==WebSocket.OPEN){H.push(z);continue}try{z.send(V),B++}catch{H.push(z)}}for(let L of H)p.delete(L);return H.length,B}function getSessionCount(p){return p.size}function getConnectedUserIds(p){let L=new Set;for(let{meta:R}of p.values())L.add(R.userId);return Array.from(L)}function getUserSessions(p,L){let R=[];for(let{ws:z,meta:B}of p.values())B.userId===L&&R.push(z);return R}function getStorage(p){return p.storage}function sanitizeToClassName(p){return p.replace(/^\/+/,``).split(/[-_\/\s]+/).map(p=>p.replace(/[^a-zA-Z0-9]/g,``)).filter(p=>p.length>0).map(p=>p.charAt(0).toUpperCase()+p.slice(1).toLowerCase()).join(``)||`VeraniActor`}function createConfiguration(p){return function(L){return{sockets:{upgradePath:p.websocketPath}}}}function isValidConnectionMeta(p){return!(!p||typeof p!=`object`||typeof p.userId!=`string`||!p.userId||typeof p.clientId!=`string`||!p.clientId||!Array.isArray(p.channels)||!p.channels.every(p=>typeof p==`string`))}function storeAttachment(p,L){p.serializeAttachment(L)}function restoreSessions(p){let L=0,R=0;for(let z of p.ctx.getWebSockets()){if(z.readyState!==WebSocket.OPEN){R++;continue}let B=z.deserializeAttachment();if(!B){R++;continue}if(!isValidConnectionMeta(B)){R++;continue}p.sessions.set(z,{ws:z,meta:B}),L++}}async function onInit(p,L){if(L.eventEmitter&&L._staticHandlers)try{L.eventEmitter.rebuildHandlers(L._staticHandlers)}catch{}try{restoreSessions(p)}catch{}if(L.onHibernationRestore&&p.sessions.size>0)try{await L.onHibernationRestore(p)}catch{}else L.onHibernationRestore&&p.sessions.size}function createUserEmitBuilder(p,L,R){return{emit(z,B){return sendToUser(L,p,R,{type:z,...B})}}}function createChannelEmitBuilder(p,L,R){return{emit(z,B){return broadcast(L,p,{type:z,...B},R)}}}function createSocketEmit(p){let L=p.meta.channels[0]||`default`;return{emit(R,z){if(p.ws.readyState===WebSocket.OPEN)try{let B={type:`event`,channel:L,data:{type:R,...z}};p.ws.send(encodeFrame(B))}catch{}},to(R){return p.meta.channels.includes(R)?createChannelEmitBuilder(R,p.actor.sessions,{except:p.ws}):createUserEmitBuilder(R,p.actor.sessions,L)}}}function createActorEmit(p){return{emit(L,R){let z={type:L,...R};return broadcast(p.sessions,`default`,z)},to(L){return createChannelEmitBuilder(L,p.sessions)}}}async function onWebSocketConnect(p,L,R,z){let B;try{B=L.extractMeta?await L.extractMeta(z):{userId:`anonymous`,clientId:crypto.randomUUID(),channels:[`default`]},storeAttachment(R,B);let V={actor:p,ws:R,meta:B,frame:{type:`connect`}};if(L.onConnect){let z={actor:p,ws:R,meta:B,emit:createSocketEmit(V)};await L.onConnect(z)}p.sessions.set(R,{ws:R,meta:B})}catch(z){if(L.onError&&B)try{let V={actor:p,ws:R,meta:B,frame:{type:`error`}};await L.onError(z,{actor:p,ws:R,meta:B,emit:createSocketEmit(V)})}catch{}R.close(1011,`Internal server error`)}}async function onWebSocketMessage(p,L,R,z){let B;try{let V=decodeFrame(z);if(V&&V.type===`ping`){if(R.readyState===WebSocket.OPEN)try{R.send(encodeFrame({type:`pong`}))}catch{}return}if(!V||V.type===`invalid`||(B=p.sessions.get(R),!B))return;let H={actor:p,ws:R,meta:B.meta,frame:V,emit:createSocketEmit({actor:p,ws:R,meta:B.meta,frame:V})},U=L.eventEmitter;U&&U.hasHandlers&&U.hasHandlers(V.type)?await U.emit(V.type,H,V.data||{}):L.onMessage&&await L.onMessage(H,V)}catch(z){if(L.onError&&B)try{await L.onError(z,{actor:p,ws:R,meta:B.meta,emit:createSocketEmit({actor:p,ws:R,meta:B.meta,frame:{type:`error`}})})}catch{}}}async function onWebSocketDisconnect(p,L,R){try{let z=p.sessions.get(R);if(p.sessions.delete(R),z&&L.onDisconnect){let B={actor:p,ws:R,meta:z.meta,frame:{type:`disconnect`}},V={actor:p,ws:R,meta:z.meta,emit:createSocketEmit(B)};await L.onDisconnect(V)}}catch{}}function createFetch(p,L){return async function(R){let z=new URL(R.url),B=R.headers.get(`Upgrade`);return z.pathname===p.websocketPath&&B===`websocket`&&await L.shouldUpgradeWebSocket(R)?L.onWebSocketUpgrade(R):L.onRequest(R)}}const PERSIST_ERROR_HANDLER=Symbol(`PERSIST_ERROR_HANDLER`),PERSISTED_STATE=Symbol(`PERSISTED_STATE`),STATE_READY=Symbol(`STATE_READY`);var PersistNotReadyError=class extends Error{constructor(p){super(`Cannot access persisted state key "${p}" before storage is initialized. Wait for onInit to complete or check actor.isStateReady().`),this.name=`PersistNotReadyError`}},PersistError=class extends Error{constructor(p,L){super(`Failed to persist key "${p}": ${L.message}`),this.name=`PersistError`,this.originalCause=L}};function safeSerialize(p){let L=new WeakSet;return JSON.stringify(p,(p,R)=>{if(R instanceof Date)return{__type:`Date`,value:R.toISOString()};if(R instanceof RegExp)return{__type:`RegExp`,source:R.source,flags:R.flags};if(R instanceof Map)return{__type:`Map`,entries:Array.from(R.entries())};if(R instanceof Set)return{__type:`Set`,values:Array.from(R.values())};if(R instanceof Error)return{__type:`Error`,name:R.name,message:R.message};if(typeof R==`object`&&R){if(L.has(R))return;L.add(R)}if(!(typeof R==`function`||typeof R==`symbol`))return R})}function safeDeserialize(p){return JSON.parse(p,(p,L)=>{if(L&&typeof L==`object`&&L.__type)switch(L.__type){case`Date`:return new Date(L.value);case`RegExp`:return new RegExp(L.source,L.flags);case`Map`:return new Map(L.entries);case`Set`:return new Set(L.values);case`Error`:{let p=Error(L.message);return p.name=L.name,p}}return L})}function createShallowProxy(p,L,R){return new Proxy(p,{set(p,R,z){let B=Reflect.set(p,R,z);return B&&typeof R==`string`&&L(R,z),B},deleteProperty(p,L){let z=Reflect.deleteProperty(p,L);return z&&typeof L==`string`&&R(L),z}})}async function initializePersistedState(p,L,R=[],z={}){let{shallow:B=!0,throwOnError:V=!0}=z,H=p.ctx.storage,U=R.length>0?R.map(String):Object.keys(L),W={...L};for(let p of U)try{let L=await H.get(`_verani_persist:${p}`);if(L!==void 0)try{W[p]=safeDeserialize(L)}catch(L){if(V)throw new PersistError(p,L)}}catch(L){if(V&&!(L instanceof PersistError))throw new PersistError(p,L)}let G=async(L,R)=>{if(U.includes(L))try{let p=safeSerialize(R);await H.put(`_verani_persist:${L}`,p)}catch(R){let z=p[PERSIST_ERROR_HANDLER];if(z&&z(L,R),V)throw new PersistError(L,R)}},K=async L=>{if(U.includes(L))try{await H.delete(`_verani_persist:${L}`)}catch(R){let z=p[PERSIST_ERROR_HANDLER];if(z&&z(L,R),V)throw new PersistError(L,R)}},q;return q=B?createShallowProxy(W,(p,L)=>{G(String(p),L)},p=>{K(String(p))}):createDeepProxy(W,(p,L)=>{G(p,L)},p=>{K(p)},U),p[STATE_READY]=!0,p[PERSISTED_STATE]=q,q}function createDeepProxy(p,L,R,z,B){return new Proxy(p,{get(p,V){let H=Reflect.get(p,V);if(H&&typeof H==`object`&&!Array.isArray(H)){let p=B??String(V);if(z.includes(p)||B!==void 0)return createDeepProxy(H,L,R,z,p)}return H},set(p,R,V){let H=Reflect.set(p,R,V);if(H){let p=B??String(R);z.includes(p)&&(B?L(B,void 0):L(String(R),V))}return H},deleteProperty(p,V){let H=Reflect.deleteProperty(p,V);if(H){let p=B??String(V);z.includes(p)&&(B?L(B,void 0):R(String(V)))}return H}})}function isStateReady(p){return p[STATE_READY]===!0}function getPersistedState(p){if(!isStateReady(p))throw new PersistNotReadyError(`state`);return p[PERSISTED_STATE]}function setPeristErrorHandler(p,L){p[PERSIST_ERROR_HANDLER]=L}async function persistKey(p,L,R){let z=p.ctx.storage,B=safeSerialize(R);await z.put(`_verani_persist:${L}`,B)}async function deletePersistedKey(p,L){await p.ctx.storage.delete(`_verani_persist:${L}`)}async function getPersistedKeys(p){let L=await p.ctx.storage.list({prefix:`_verani_persist:`});return Array.from(L.keys()).map(p=>p.replace(`_verani_persist:`,``))}async function clearPersistedState(p){let L=p.ctx.storage,R=await L.list({prefix:`_verani_persist:`}),z=Array.from(R.keys());await L.delete(z)}function createActorHandler(p){let L=sanitizeToClassName(p.name||p.websocketPath||`VeraniActor`),z=p;class B extends Actor{constructor(...L){super(...L),this.sessions=new Map,this.emit=createActorEmit(this),this[STATE_READY]=!1,this[PERSISTED_STATE]=p.state?{...p.state}:{},this.fetch=createFetch(z,this)}get roomState(){return this[PERSISTED_STATE]}isStateReady(){return isStateReady(this)}static{this.configuration=createConfiguration(z)}async shouldUpgradeWebSocket(p){return!0}async onInit(){if(p.state){p.onPersistError&&setPeristErrorHandler(this,p.onPersistError);let L=p.persistedKeys;this[PERSISTED_STATE]=await initializePersistedState(this,p.state,L,p.persistOptions)}await onInit(this,z)}async onWebSocketConnect(p,L){await onWebSocketConnect(this,z,p,L)}async onWebSocketMessage(p,L){await onWebSocketMessage(this,z,p,L)}async onWebSocketDisconnect(p){await onWebSocketDisconnect(this,z,p)}cleanupStaleSessions(){return cleanupStaleSessions(this.sessions)}broadcast(p,L,R){return broadcast(this.sessions,p,L,R)}getSessionCount(){return getSessionCount(this.sessions)}getConnectedUserIds(){return getConnectedUserIds(this.sessions)}getUserSessions(p){return getUserSessions(this.sessions,p)}sendToUser(p,L,R){return sendToUser(this.sessions,p,L,R)}emitToChannel(p,L,R){let z={type:L,...R};return broadcast(this.sessions,p,z)}emitToUser(p,L,R){let z=encodeFrame({type:`event`,channel:`default`,data:{type:L,...R}}),B=0,V=[];for(let{ws:L,meta:R}of this.sessions.values())if(R.userId===p){if(L.readyState!==WebSocket.OPEN){V.push(L);continue}try{L.send(z),B++}catch{V.push(L)}}for(let p of V)this.sessions.delete(p);return B}getStorage(){return getStorage(this.ctx)}}return Object.defineProperty(B,`name`,{value:L,writable:!1,configurable:!0}),B}export{deletePersistedKey as a,initializePersistedState as c,safeDeserialize as d,safeSerialize as f,defineRoom as g,storeAttachment as h,clearPersistedState as i,isStateReady as l,restoreSessions as m,PersistError as n,getPersistedKeys as o,setPeristErrorHandler as p,PersistNotReadyError as r,getPersistedState as s,createActorHandler as t,persistKey as u};
@@ -1 +0,0 @@
1
- import{o as decodeServerMessage$1,t as encodeClientMessage$1}from"./encode-BhJqnsto.mjs";function encodeClientMessage(e){return encodeClientMessage$1(e)}function decodeServerMessage(u){return decodeServerMessage$1(u)}const DEFAULT_RECONNECTION_CONFIG={enabled:!0,maxAttempts:10,initialDelay:1e3,maxDelay:3e4,backoffMultiplier:1.5};var ConnectionManager=class{constructor(e=DEFAULT_RECONNECTION_CONFIG,u){this.config=e,this.onStateChange=u,this.state=`disconnected`,this.reconnectAttempts=0,this.currentDelay=e.initialDelay}getState(){return this.state}isValidStateTransition(e,u){return{disconnected:[`connecting`,`reconnecting`],connecting:[`connected`,`disconnected`,`error`,`reconnecting`],connected:[`disconnected`,`reconnecting`],reconnecting:[`connecting`,`disconnected`,`error`],error:[`reconnecting`,`disconnected`,`connecting`]}[e]?.includes(u)??!1}setState(e){this.state!==e&&(this.isValidStateTransition(this.state,e),this.state=e,this.onStateChange?.(e))}resetReconnection(){this.reconnectAttempts=0,this.currentDelay=this.config.initialDelay,this.clearReconnectTimer()}scheduleReconnect(e){return this.config.enabled?this.config.maxAttempts>0&&this.reconnectAttempts>=this.config.maxAttempts?(this.setState(`error`),!1):(this.clearReconnectTimer(),this.setState(`reconnecting`),this.reconnectAttempts++,this.reconnectTimer=setTimeout(()=>{e(),this.currentDelay=Math.min(this.currentDelay*this.config.backoffMultiplier,this.config.maxDelay)},this.currentDelay),!0):!1}cancelReconnect(){this.clearReconnectTimer(),this.state===`reconnecting`&&this.setState(`disconnected`)}clearReconnectTimer(){this.reconnectTimer!==void 0&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=void 0)}getReconnectAttempts(){return this.reconnectAttempts}getNextDelay(){return this.currentDelay}destroy(){this.clearReconnectTimer()}};function resolveClientOptions(e){return{reconnection:{enabled:e.reconnection?.enabled??DEFAULT_RECONNECTION_CONFIG.enabled,maxAttempts:e.reconnection?.maxAttempts??DEFAULT_RECONNECTION_CONFIG.maxAttempts,initialDelay:e.reconnection?.initialDelay??DEFAULT_RECONNECTION_CONFIG.initialDelay,maxDelay:e.reconnection?.maxDelay??DEFAULT_RECONNECTION_CONFIG.maxDelay,backoffMultiplier:e.reconnection?.backoffMultiplier??DEFAULT_RECONNECTION_CONFIG.backoffMultiplier},maxQueueSize:e.maxQueueSize??100,connectionTimeout:e.connectionTimeout??1e4,pingInterval:e.pingInterval??5e3,pongTimeout:e.pongTimeout??5e3}}var MessageQueue=class{constructor(e){this.maxQueueSize=e,this.queue=[]}queueMessage(e){this.queue.length>=this.maxQueueSize&&this.queue.shift(),this.queue.push(e)}flushMessageQueue(e){if(!(!e||e.readyState!==WebSocket.OPEN))for(;this.queue.length>0;){let u=this.queue.shift();try{e.send(encodeClientMessage(u))}catch{}}}clear(){this.queue=[]}getLength(){return this.queue.length}};function isBrowserEnvironment(){return typeof globalThis<`u`&&`document`in globalThis&&globalThis.document!==void 0&&globalThis.document.hidden!==void 0&&typeof globalThis.document.addEventListener==`function`}function getDocument(){return isBrowserEnvironment()&&`document`in globalThis?globalThis.document:null}function isPageVisible(){let e=getDocument();return e?!e.hidden:!0}function onVisibilityChange(e){let u=getDocument();if(!u)return null;let d=()=>{e(isPageVisible())};return u.addEventListener(`visibilitychange`,d),()=>{u.removeEventListener(`visibilitychange`,d)}}var KeepaliveManager=class{constructor(e,u,d){this.options=e,this.getWebSocket=u,this.onTimeout=d,this.lastPongReceived=0}startPingInterval(){this.options.pingInterval===0||this.pingInterval!==void 0||(this.lastPongReceived=Date.now(),this.visibilityCleanup=onVisibilityChange(e=>{e&&this.resyncPingInterval()}),this.pingInterval=setInterval(()=>{let e=this.getWebSocket();if(!e||e.readyState!==WebSocket.OPEN){this.stopPingInterval();return}if(Date.now()-this.lastPongReceived>this.options.pongTimeout+this.options.pingInterval){this.stopPingInterval(),e.close(1006,`Pong timeout`);return}try{e.send(encodeClientMessage({type:`ping`}))}catch{}},this.options.pingInterval))}resyncPingInterval(){let e=this.getWebSocket();if(!(!e||e.readyState!==WebSocket.OPEN)){this.pingInterval!==void 0&&(clearInterval(this.pingInterval),this.pingInterval=void 0);try{e.send(encodeClientMessage({type:`ping`}))}catch{}this.lastPongReceived=Date.now(),this.pingInterval=setInterval(()=>{let e=this.getWebSocket();if(!e||e.readyState!==WebSocket.OPEN){this.stopPingInterval();return}if(Date.now()-this.lastPongReceived>this.options.pongTimeout+this.options.pingInterval){this.stopPingInterval(),e.close(1006,`Pong timeout`);return}try{e.send(encodeClientMessage({type:`ping`}))}catch{}},this.options.pingInterval)}}stopPingInterval(){this.pingInterval!==void 0&&(clearInterval(this.pingInterval),this.pingInterval=void 0),this.pongTimeout!==void 0&&(clearTimeout(this.pongTimeout),this.pongTimeout=void 0),this.visibilityCleanup&&=(this.visibilityCleanup(),void 0)}recordPong(){this.lastPongReceived=Date.now()}},EventEmitter=class{constructor(){this.listeners=new Map}on(e,u){this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(u)}off(e,u){let d=this.listeners.get(e);d&&(d.delete(u),d.size===0&&this.listeners.delete(e))}once(e,u){let d=f=>{this.off(e,d),u(f)};this.on(e,d)}emitLifecycleEvent(e,u){let d=this.listeners.get(e);if(d)for(let e of d)try{e(u)}catch{}}dispatch(e,u){let d=this.listeners.get(e);if(d)for(let e of d)try{e(u)}catch{}}clear(){this.listeners.clear()}};function handleWebSocketOpen(e,u,d,f,p,m,h,g){e.clear(),u.setState(`connected`),u.resetReconnection(),d.startPingInterval(),f.flushMessageQueue(p),m.resolve&&(m.resolve(),m.clear()),h.emitLifecycleEvent(`open`),h.emitLifecycleEvent(`connected`),g?.()}function handleWebSocketMessage(e,u,d){let p=decodeServerMessage(e.data);if(!p)return;if(p.type===`pong`){u.recordPong();return}let m=p.type,h=p.data;p.type===`event`&&p.data&&typeof p.data==`object`&&`type`in p.data&&(m=p.data.type,h=p.data),d.dispatch(m,h)}function handleWebSocketClose(e,u,d,f,p,m,h,g){h&&(h.value=!1),u.clear(),d.setState(`disconnected`),f.reject&&(f.reject(Error(`Connection closed: ${e.reason||`Unknown reason`}`)),f.clear()),p.emitLifecycleEvent(`close`,e),p.emitLifecycleEvent(`disconnected`,e),g?.(e),e.code!==1e3&&e.code!==1001&&d.scheduleReconnect(m)&&p.emitLifecycleEvent(`reconnecting`)}function handleWebSocketError(e,u,d,f,p,m){p&&(p.value=!1),u.clear(),d.emitLifecycleEvent(`error`,e),m?.(e),f(Error(`WebSocket error`))}function handleConnectionError(e,u,d,f,p,m,h){h&&(h.value=!1),u.clear(),d.reject&&(d.reject(e),d.clear()),p.emitLifecycleEvent(`error`,e),f.scheduleReconnect(m)&&p.emitLifecycleEvent(`reconnecting`)}var ConnectionHandler=class{constructor(e,u,d,f,p,m,h,g,_,v,y,b){this.url=e,this.options=u,this.connectionManager=d,this.keepalive=f,this.eventEmitter=p,this.messageQueue=m,this.connectionPromise=h,this.isConnectingRef=g,this.isConnectedFn=_,this.onOpenCallback=v,this.onCloseCallback=y,this.onErrorCallback=b,this.connectionId=0,this.connectionTimeoutState={value:void 0,clear:()=>{this.connectionTimeoutState.value!==void 0&&(clearTimeout(this.connectionTimeoutState.value),this.connectionTimeoutState.value=void 0)}}}connect(){if(!this.isConnectingRef.value&&!this.isConnectedFn()){this.cleanupWebSocket();try{this.isConnectingRef.value=!0,this.connectionId++;let e=this.connectionId;this.connectionManager.setState(`connecting`),this.eventEmitter.emitLifecycleEvent(`connecting`),this.ws=new WebSocket(this.url),this.connectionTimeoutState.value=setTimeout(()=>{this.isConnectingRef.value&&this.connectionId===e&&(this.ws?.close(),this.handleConnectionErrorInternal(Error(`Connection timeout`)))},this.options.connectionTimeout),this.ws.addEventListener(`open`,()=>{this.connectionId===e&&this.handleOpenInternal()}),this.ws.addEventListener(`message`,u=>{this.connectionId===e&&handleWebSocketMessage(u,this.keepalive,this.eventEmitter)}),this.ws.addEventListener(`close`,u=>{this.connectionId===e&&handleWebSocketClose(u,this.connectionTimeoutState,this.connectionManager,this.connectionPromise,this.eventEmitter,()=>this.connect(),this.isConnectingRef,this.onCloseCallback)}),this.ws.addEventListener(`error`,u=>{this.connectionId===e&&handleWebSocketError(u,this.connectionTimeoutState,this.eventEmitter,e=>this.handleConnectionErrorInternal(e),this.isConnectingRef,this.onErrorCallback)})}catch(e){this.isConnectingRef.value=!1,this.handleConnectionErrorInternal(e)}}}cleanupWebSocket(){if(this.keepalive.stopPingInterval(),this.connectionTimeoutState.clear(),this.ws){let e=this.ws;if(this.ws=void 0,e.readyState===WebSocket.OPEN||e.readyState===WebSocket.CONNECTING)try{e.close(1e3,`Cleanup`)}catch{}}}getWebSocket(){return this.ws}getConnectionId(){return this.connectionId}handleOpenInternal(){handleWebSocketOpen(this.connectionTimeoutState,this.connectionManager,this.keepalive,this.messageQueue,this.ws,this.connectionPromise,this.eventEmitter,this.onOpenCallback),this.isConnectingRef.value=!1}handleConnectionErrorInternal(e){handleConnectionError(e,this.connectionTimeoutState,this.connectionPromise,this.connectionManager,this.eventEmitter,()=>this.connect(),this.isConnectingRef)}},VeraniClient=class{constructor(e,u={}){this.url=e,this.connectionPromiseState={promise:void 0,resolve:void 0,reject:void 0,clear:()=>{this.connectionPromiseState.promise=void 0,this.connectionPromiseState.resolve=void 0,this.connectionPromiseState.reject=void 0}},this.options=resolveClientOptions(u),this.connectionManager=new ConnectionManager(this.options.reconnection,e=>{this.onStateChangeCallback?.(e)}),this.messageQueue=new MessageQueue(this.options.maxQueueSize),this.eventEmitter=new EventEmitter,this.isConnectingRef={value:!1},this.keepalive=new KeepaliveManager(this.options,()=>this.connectionHandler.getWebSocket(),()=>{let e=this.connectionHandler.getWebSocket();e&&e.close(1006,`Pong timeout`)}),this.connectionHandler=new ConnectionHandler(this.url,this.options,this.connectionManager,this.keepalive,this.eventEmitter,this.messageQueue,this.connectionPromiseState,this.isConnectingRef,()=>this.isConnected(),this.onOpenCallback,this.onCloseCallback,this.onErrorCallback),Object.defineProperty(this,`isConnecting`,{get:()=>this.isConnectingRef.value,enumerable:!0,configurable:!0}),this.connect()}connect(){this.connectionHandler.connect()}cleanupWebSocket(){this.connectionHandler.cleanupWebSocket()}getState(){return this.connectionManager.getState()}isConnected(){return this.connectionHandler.getWebSocket()?.readyState===WebSocket.OPEN&&this.connectionManager.getState()===`connected`}getConnectionState(){return{state:this.connectionManager.getState(),isConnected:this.isConnected(),isConnecting:this.isConnectingRef.value,reconnectAttempts:this.connectionManager.getReconnectAttempts(),connectionId:this.connectionHandler.getConnectionId()}}waitForConnection(){return this.isConnected()?Promise.resolve():(this.connectionPromiseState.promise||(this.connectionPromiseState.promise=new Promise((e,u)=>{this.connectionPromiseState.resolve=e,this.connectionPromiseState.reject=u;let d=setTimeout(()=>{this.connectionPromiseState.reject&&(this.connectionPromiseState.reject(Error(`Connection wait timeout`)),this.connectionPromiseState.clear())},this.options.connectionTimeout*2);this.connectionPromiseState.promise&&this.connectionPromiseState.promise.finally(()=>{clearTimeout(d)})})),this.connectionPromiseState.promise)}on(e,u){this.eventEmitter.on(e,u)}off(e,u){this.eventEmitter.off(e,u)}once(e,u){this.eventEmitter.once(e,u)}emit(e,u){let f={type:e,data:u};if(this.isConnected()){let e=this.connectionHandler.getWebSocket();if(e)try{e.send(encodeClientMessage(f))}catch{this.messageQueue.queueMessage(f)}}else this.messageQueue.queueMessage(f)}onOpen(e){this.onOpenCallback=e}onClose(e){this.onCloseCallback=e}onError(e){this.onErrorCallback=e}onStateChange(e){this.onStateChangeCallback=e}reconnect(){this.connectionManager.resetReconnection(),this.connectionManager.cancelReconnect(),this.cleanupWebSocket(),this.isConnectingRef.value=!1,this.connectionManager.setState(`disconnected`),this.connect()}disconnect(){this.connectionManager.cancelReconnect(),this.isConnectingRef.value=!1,this.connectionPromiseState.reject&&(this.connectionPromiseState.reject(Error(`Connection disconnected`)),this.connectionPromiseState.clear()),this.cleanupWebSocket(),this.connectionManager.setState(`disconnected`)}close(){this.connectionPromiseState.reject&&(this.connectionPromiseState.reject(Error(`Client closed`)),this.connectionPromiseState.clear()),this.disconnect(),this.eventEmitter.clear(),this.messageQueue.clear(),this.connectionManager.destroy()}};export{ConnectionManager as n,DEFAULT_RECONNECTION_CONFIG as r,VeraniClient as t};