verani 0.7.0 → 0.8.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.
@@ -1,8 +1,104 @@
1
1
  import { n as ConnectionMeta, r as MessageFrame } from "./types-Ds_V-GWZ.mjs";
2
2
  import { Actor, ActorConfiguration } from "@cloudflare/actors";
3
3
 
4
- //#region src/actor/types.d.ts
4
+ //#region src/actor/persist.d.ts
5
5
 
6
+ declare const PERSIST_ERROR_HANDLER: unique symbol;
7
+ declare const PERSISTED_STATE: unique symbol;
8
+ declare const STATE_READY: unique symbol;
9
+ /**
10
+ * Error thrown when persisted state is accessed before initialization
11
+ */
12
+ declare class PersistNotReadyError extends Error {
13
+ constructor(key: string);
14
+ }
15
+ /**
16
+ * Error thrown when persistence operation fails
17
+ */
18
+ declare class PersistError extends Error {
19
+ readonly originalCause: Error;
20
+ constructor(key: string, cause: Error);
21
+ }
22
+ /**
23
+ * Options for SafePersist behavior
24
+ */
25
+ interface SafePersistOptions {
26
+ /**
27
+ * If true, only track top-level property changes (no deep proxy).
28
+ * This is safer and more predictable. Default: true
29
+ */
30
+ shallow?: boolean;
31
+ /**
32
+ * If true, throw errors instead of swallowing them. Default: true
33
+ */
34
+ throwOnError?: boolean;
35
+ }
36
+ /**
37
+ * Safely serialize a value for storage.
38
+ * Handles circular references and special types gracefully.
39
+ */
40
+ declare function safeSerialize(value: unknown): string;
41
+ /**
42
+ * Safely deserialize a value from storage.
43
+ * Restores special types that were serialized.
44
+ */
45
+ declare function safeDeserialize(json: string): unknown;
46
+ /**
47
+ * Interface for actors that support safe persistence
48
+ */
49
+ interface PersistableActor {
50
+ /** Durable Object storage interface */
51
+ ctx: {
52
+ storage: DurableObjectStorage;
53
+ };
54
+ /** Whether the state has been initialized from storage */
55
+ [STATE_READY]?: boolean;
56
+ /** The persisted state object */
57
+ [PERSISTED_STATE]?: Record<string, unknown>;
58
+ /** Error handler for persistence failures */
59
+ [PERSIST_ERROR_HANDLER]?: (key: string, error: Error) => void;
60
+ }
61
+ /**
62
+ * Initialize persisted state from Durable Object storage.
63
+ * Call this in the actor's onInit or constructor after storage is available.
64
+ *
65
+ * @param actor - The actor instance
66
+ * @param initialState - Default state values
67
+ * @param persistedKeys - Keys to persist (empty = all keys)
68
+ * @param options - Persistence options
69
+ */
70
+ declare function initializePersistedState<T extends Record<string, unknown>>(actor: PersistableActor, initialState: T, persistedKeys?: (keyof T)[], options?: SafePersistOptions): Promise<T>;
71
+ /**
72
+ * Helper to check if state is ready for access
73
+ */
74
+ declare function isStateReady(actor: PersistableActor): boolean;
75
+ /**
76
+ * Helper to get persisted state with type safety.
77
+ * Throws if state is not yet initialized.
78
+ */
79
+ declare function getPersistedState<T extends Record<string, unknown>>(actor: PersistableActor): T;
80
+ /**
81
+ * Set the error handler for persistence failures
82
+ */
83
+ declare function setPeristErrorHandler(actor: PersistableActor, handler: (key: string, error: Error) => void): void;
84
+ /**
85
+ * Manually persist a specific key (useful for batch updates)
86
+ */
87
+ declare function persistKey(actor: PersistableActor, key: string, value: unknown): Promise<void>;
88
+ /**
89
+ * Manually delete a persisted key
90
+ */
91
+ declare function deletePersistedKey(actor: PersistableActor, key: string): Promise<void>;
92
+ /**
93
+ * Get all persisted keys from storage
94
+ */
95
+ declare function getPersistedKeys(actor: PersistableActor): Promise<string[]>;
96
+ /**
97
+ * Clear all persisted state
98
+ */
99
+ declare function clearPersistedState(actor: PersistableActor): Promise<void>;
100
+ //#endregion
101
+ //#region src/actor/types.d.ts
6
102
  /**
7
103
  * Options for broadcasting messages to connections
8
104
  */
@@ -101,7 +197,7 @@ interface ActorStub {
101
197
  /**
102
198
  * Extended Actor interface with Verani-specific methods
103
199
  */
104
- interface VeraniActor<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown> extends Actor<E> {
200
+ interface VeraniActor<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown, TState extends Record<string, unknown> = Record<string, unknown>> extends Actor<E> {
105
201
  /**
106
202
  * Map of active WebSocket sessions keyed by their WebSocket instance.
107
203
  * Each entry contains the WebSocket and its associated metadata.
@@ -111,6 +207,17 @@ interface VeraniActor<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown
111
207
  ws: WebSocket;
112
208
  meta: TMeta;
113
209
  }>;
210
+ /**
211
+ * User-defined persisted state for this actor.
212
+ * Access this after onInit completes. Changes to tracked keys are automatically persisted.
213
+ * @see RoomDefinition.state and RoomDefinition.persistedKeys
214
+ */
215
+ roomState: TState;
216
+ /**
217
+ * Check if the persisted state has been initialized.
218
+ * Returns true after onInit completes and state is loaded from storage.
219
+ */
220
+ isStateReady(): boolean;
114
221
  /**
115
222
  * Broadcast a message to all connections in a channel.
116
223
  * Performs channel, userId, clientId, and exclusion filtering.
@@ -161,37 +268,37 @@ interface VeraniActor<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown
161
268
  /**
162
269
  * Event handler function type for socket.io-like event handling
163
270
  */
164
- type EventHandler<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown> = (ctx: MessageContext<TMeta, E>, data: any) => void | Promise<void>;
271
+ type EventHandler<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown, TState extends Record<string, unknown> = Record<string, unknown>> = (ctx: MessageContext<TMeta, E, TState>, data: any) => void | Promise<void>;
165
272
  /**
166
273
  * Event emitter interface for room-level event handling
167
274
  */
168
- interface RoomEventEmitter<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown> {
275
+ interface RoomEventEmitter<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown, TState extends Record<string, unknown> = Record<string, unknown>> {
169
276
  /**
170
277
  * Register an event handler
171
278
  * @param event - Event name (supports wildcard "*")
172
279
  * @param handler - Handler function
173
280
  */
174
- on(event: string, handler: EventHandler<TMeta, E>): void;
281
+ on(event: string, handler: EventHandler<TMeta, E, TState>): void;
175
282
  /**
176
283
  * Remove an event handler
177
284
  * @param event - Event name
178
285
  * @param handler - Optional specific handler to remove, or remove all handlers for event
179
286
  */
180
- off(event: string, handler?: EventHandler<TMeta, E>): void;
287
+ off(event: string, handler?: EventHandler<TMeta, E, TState>): void;
181
288
  /**
182
289
  * Emit an event to registered handlers
183
290
  * @param event - Event name
184
291
  * @param ctx - Message context
185
292
  * @param data - Event data
186
293
  */
187
- emit(event: string, ctx: MessageContext<TMeta, E>, data: any): Promise<void>;
294
+ emit(event: string, ctx: MessageContext<TMeta, E, TState>, data: any): Promise<void>;
188
295
  /**
189
296
  * Rebuild handlers from static storage.
190
297
  * Called after hibernation to restore handlers from the room definition.
191
298
  * This method MUST be implemented by all event emitter implementations.
192
299
  * @param staticHandlers - Map of event names to handler sets from static storage
193
300
  */
194
- rebuildHandlers(staticHandlers: Map<string, Set<EventHandler<TMeta, E>>>): void;
301
+ rebuildHandlers(staticHandlers: Map<string, Set<EventHandler<TMeta, E, TState>>>): void;
195
302
  }
196
303
  /**
197
304
  * Builder interface for targeting specific scopes when emitting
@@ -245,9 +352,9 @@ interface ActorEmit<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown>
245
352
  /**
246
353
  * Context provided to room lifecycle hooks
247
354
  */
248
- interface RoomContext<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown> {
355
+ interface RoomContext<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown, TState extends Record<string, unknown> = Record<string, unknown>> {
249
356
  /** The actor instance handling this connection */
250
- actor: VeraniActor<TMeta, E>;
357
+ actor: VeraniActor<TMeta, E, TState>;
251
358
  /** The WebSocket connection */
252
359
  ws: WebSocket;
253
360
  /** Connection metadata */
@@ -258,7 +365,7 @@ interface RoomContext<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown
258
365
  /**
259
366
  * Context for onMessage hook with frame included
260
367
  */
261
- interface MessageContext<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown> extends RoomContext<TMeta, E> {
368
+ interface MessageContext<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown, TState extends Record<string, unknown> = Record<string, unknown>> extends RoomContext<TMeta, E, TState> {
262
369
  /** The received message frame */
263
370
  frame: MessageFrame;
264
371
  }
@@ -268,8 +375,12 @@ interface MessageContext<TMeta extends ConnectionMeta = ConnectionMeta, E = unkn
268
375
  * **Important:** All lifecycle hooks are properly awaited if they return a Promise.
269
376
  * This ensures async operations complete before the actor proceeds to the next step
270
377
  * or potentially enters hibernation.
378
+ *
379
+ * @template TMeta - Connection metadata type
380
+ * @template E - Environment type
381
+ * @template TState - Room state type (for persistence)
271
382
  */
272
- interface RoomDefinition<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown> {
383
+ interface RoomDefinition<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown, TState extends Record<string, unknown> = Record<string, unknown>> {
273
384
  /** Optional room name for debugging */
274
385
  name?: string;
275
386
  /** WebSocket upgrade path (default: "/ws") */
@@ -285,13 +396,13 @@ interface RoomDefinition<TMeta extends ConnectionMeta = ConnectionMeta, E = unkn
285
396
  * sessions map after this hook completes successfully. If this hook throws, the
286
397
  * connection is closed and no orphaned session is created.
287
398
  */
288
- onConnect?(ctx: RoomContext<TMeta, E>): void | Promise<void>;
399
+ onConnect?(ctx: RoomContext<TMeta, E, TState>): void | Promise<void>;
289
400
  /**
290
401
  * Called when a WebSocket connection is closed.
291
402
  * This hook is awaited if it returns a Promise. The session is removed from the
292
403
  * sessions map before this hook is called.
293
404
  */
294
- onDisconnect?(ctx: RoomContext<TMeta, E>): void | Promise<void>;
405
+ onDisconnect?(ctx: RoomContext<TMeta, E, TState>): void | Promise<void>;
295
406
  /**
296
407
  * Called when a message is received from a connection.
297
408
  * This hook is awaited if it returns a Promise. The actor will not process
@@ -300,30 +411,71 @@ interface RoomDefinition<TMeta extends ConnectionMeta = ConnectionMeta, E = unkn
300
411
  * **Note:** If event handlers are registered via `eventEmitter`, they take priority.
301
412
  * This hook is used as a fallback when no matching event handler is found.
302
413
  */
303
- onMessage?(ctx: MessageContext<TMeta, E>, frame: MessageFrame): void | Promise<void>;
414
+ onMessage?(ctx: MessageContext<TMeta, E, TState>, frame: MessageFrame): void | Promise<void>;
304
415
  /**
305
416
  * Called when an error occurs in a lifecycle hook.
306
417
  * This hook is also awaited if it returns a Promise.
307
418
  */
308
- onError?(error: Error, ctx: RoomContext<TMeta, E>): void | Promise<void>;
419
+ onError?(error: Error, ctx: RoomContext<TMeta, E, TState>): void | Promise<void>;
309
420
  /**
310
421
  * Called after actor wakes from hibernation and sessions are restored.
311
422
  * This hook is awaited if it returns a Promise. It is called even if some
312
423
  * sessions failed to restore, allowing you to handle partial restoration scenarios.
313
424
  */
314
- onHibernationRestore?(actor: VeraniActor<TMeta, E>): void | Promise<void>;
425
+ onHibernationRestore?(actor: VeraniActor<TMeta, E, TState>): void | Promise<void>;
315
426
  /**
316
427
  * Event emitter for socket.io-like event handling.
317
428
  * If provided, event handlers registered here will be called for matching message types.
318
429
  * If not provided, a default event emitter will be created.
319
430
  */
320
- eventEmitter?: RoomEventEmitter<TMeta, E>;
431
+ eventEmitter?: RoomEventEmitter<TMeta, E, TState>;
321
432
  /**
322
433
  * Static handler storage that persists across hibernation.
323
434
  * Handlers registered via room.on() are stored here and rebuilt in onInit.
324
435
  * @internal
325
436
  */
326
- _staticHandlers?: Map<string, Set<EventHandler<TMeta, E>>>;
437
+ _staticHandlers?: Map<string, Set<EventHandler<TMeta, E, TState>>>;
438
+ /**
439
+ * Initial state for this room. This object defines the default values
440
+ * for your room's state. Access via `actor.roomState` in lifecycle hooks.
441
+ *
442
+ * @example
443
+ * ```typescript
444
+ * const room = defineRoom({
445
+ * state: {
446
+ * messageCount: 0,
447
+ * lastActivity: null as Date | null,
448
+ * settings: { maxUsers: 100 }
449
+ * },
450
+ * persistedKeys: ['messageCount', 'settings'],
451
+ * // ...
452
+ * });
453
+ * ```
454
+ */
455
+ state?: TState;
456
+ /**
457
+ * Keys from `state` to persist to Durable Object storage.
458
+ * If empty or undefined, no state is persisted.
459
+ * Changes to these keys are automatically saved and restored on hibernation wake.
460
+ *
461
+ * @example
462
+ * ```typescript
463
+ * persistedKeys: ['messageCount', 'settings'] // Only these keys are persisted
464
+ * ```
465
+ */
466
+ persistedKeys?: (string & keyof TState)[];
467
+ /**
468
+ * Options for state persistence behavior.
469
+ */
470
+ persistOptions?: SafePersistOptions;
471
+ /**
472
+ * Called when persistence fails for a key.
473
+ * Use this to handle errors gracefully (e.g., notify admins, fallback behavior).
474
+ *
475
+ * @param key - The state key that failed to persist
476
+ * @param error - The error that occurred
477
+ */
478
+ onPersistError?(key: string, error: Error): void;
327
479
  }
328
480
  //#endregion
329
481
  //#region src/actor/actor-runtime.d.ts
@@ -340,6 +492,6 @@ type ActorHandlerClass<E = unknown> = {
340
492
  * @param room - The room definition with lifecycle hooks
341
493
  * @returns Actor class for Cloudflare Workers (extends DurableObject)
342
494
  */
343
- declare function createActorHandler<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown>(room: RoomDefinition<TMeta, E>): ActorHandlerClass<E>;
495
+ declare function createActorHandler<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown, TState extends Record<string, unknown> = Record<string, unknown>>(room: RoomDefinition<TMeta, E, TState>): ActorHandlerClass<E>;
344
496
  //#endregion
345
- export { EventHandler as a, RoomDefinition as c, BroadcastOptions as i, RpcBroadcastOptions as l, createActorHandler as n, MessageContext as o, ActorStub as r, RoomContext as s, ActorHandlerClass as t, VeraniActor as u };
497
+ export { safeSerialize as C, safeDeserialize as S, getPersistedKeys as _, EventHandler as a, isStateReady as b, RoomDefinition as c, PersistError as d, PersistNotReadyError as f, deletePersistedKey as g, clearPersistedState as h, BroadcastOptions as i, RpcBroadcastOptions as l, SafePersistOptions as m, createActorHandler as n, MessageContext as o, PersistableActor as p, ActorStub as r, RoomContext as s, ActorHandlerClass as t, VeraniActor as u, getPersistedState as v, setPeristErrorHandler as w, persistKey as x, initializePersistedState as y };
@@ -0,0 +1 @@
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,8 +1,104 @@
1
1
  import { n as ConnectionMeta, r as MessageFrame } from "./types-BefWc1M_.cjs";
2
2
  import { Actor, ActorConfiguration } from "@cloudflare/actors";
3
3
 
4
- //#region src/actor/types.d.ts
4
+ //#region src/actor/persist.d.ts
5
5
 
6
+ declare const PERSIST_ERROR_HANDLER: unique symbol;
7
+ declare const PERSISTED_STATE: unique symbol;
8
+ declare const STATE_READY: unique symbol;
9
+ /**
10
+ * Error thrown when persisted state is accessed before initialization
11
+ */
12
+ declare class PersistNotReadyError extends Error {
13
+ constructor(key: string);
14
+ }
15
+ /**
16
+ * Error thrown when persistence operation fails
17
+ */
18
+ declare class PersistError extends Error {
19
+ readonly originalCause: Error;
20
+ constructor(key: string, cause: Error);
21
+ }
22
+ /**
23
+ * Options for SafePersist behavior
24
+ */
25
+ interface SafePersistOptions {
26
+ /**
27
+ * If true, only track top-level property changes (no deep proxy).
28
+ * This is safer and more predictable. Default: true
29
+ */
30
+ shallow?: boolean;
31
+ /**
32
+ * If true, throw errors instead of swallowing them. Default: true
33
+ */
34
+ throwOnError?: boolean;
35
+ }
36
+ /**
37
+ * Safely serialize a value for storage.
38
+ * Handles circular references and special types gracefully.
39
+ */
40
+ declare function safeSerialize(value: unknown): string;
41
+ /**
42
+ * Safely deserialize a value from storage.
43
+ * Restores special types that were serialized.
44
+ */
45
+ declare function safeDeserialize(json: string): unknown;
46
+ /**
47
+ * Interface for actors that support safe persistence
48
+ */
49
+ interface PersistableActor {
50
+ /** Durable Object storage interface */
51
+ ctx: {
52
+ storage: DurableObjectStorage;
53
+ };
54
+ /** Whether the state has been initialized from storage */
55
+ [STATE_READY]?: boolean;
56
+ /** The persisted state object */
57
+ [PERSISTED_STATE]?: Record<string, unknown>;
58
+ /** Error handler for persistence failures */
59
+ [PERSIST_ERROR_HANDLER]?: (key: string, error: Error) => void;
60
+ }
61
+ /**
62
+ * Initialize persisted state from Durable Object storage.
63
+ * Call this in the actor's onInit or constructor after storage is available.
64
+ *
65
+ * @param actor - The actor instance
66
+ * @param initialState - Default state values
67
+ * @param persistedKeys - Keys to persist (empty = all keys)
68
+ * @param options - Persistence options
69
+ */
70
+ declare function initializePersistedState<T extends Record<string, unknown>>(actor: PersistableActor, initialState: T, persistedKeys?: (keyof T)[], options?: SafePersistOptions): Promise<T>;
71
+ /**
72
+ * Helper to check if state is ready for access
73
+ */
74
+ declare function isStateReady(actor: PersistableActor): boolean;
75
+ /**
76
+ * Helper to get persisted state with type safety.
77
+ * Throws if state is not yet initialized.
78
+ */
79
+ declare function getPersistedState<T extends Record<string, unknown>>(actor: PersistableActor): T;
80
+ /**
81
+ * Set the error handler for persistence failures
82
+ */
83
+ declare function setPeristErrorHandler(actor: PersistableActor, handler: (key: string, error: Error) => void): void;
84
+ /**
85
+ * Manually persist a specific key (useful for batch updates)
86
+ */
87
+ declare function persistKey(actor: PersistableActor, key: string, value: unknown): Promise<void>;
88
+ /**
89
+ * Manually delete a persisted key
90
+ */
91
+ declare function deletePersistedKey(actor: PersistableActor, key: string): Promise<void>;
92
+ /**
93
+ * Get all persisted keys from storage
94
+ */
95
+ declare function getPersistedKeys(actor: PersistableActor): Promise<string[]>;
96
+ /**
97
+ * Clear all persisted state
98
+ */
99
+ declare function clearPersistedState(actor: PersistableActor): Promise<void>;
100
+ //#endregion
101
+ //#region src/actor/types.d.ts
6
102
  /**
7
103
  * Options for broadcasting messages to connections
8
104
  */
@@ -101,7 +197,7 @@ interface ActorStub {
101
197
  /**
102
198
  * Extended Actor interface with Verani-specific methods
103
199
  */
104
- interface VeraniActor<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown> extends Actor<E> {
200
+ interface VeraniActor<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown, TState extends Record<string, unknown> = Record<string, unknown>> extends Actor<E> {
105
201
  /**
106
202
  * Map of active WebSocket sessions keyed by their WebSocket instance.
107
203
  * Each entry contains the WebSocket and its associated metadata.
@@ -111,6 +207,17 @@ interface VeraniActor<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown
111
207
  ws: WebSocket;
112
208
  meta: TMeta;
113
209
  }>;
210
+ /**
211
+ * User-defined persisted state for this actor.
212
+ * Access this after onInit completes. Changes to tracked keys are automatically persisted.
213
+ * @see RoomDefinition.state and RoomDefinition.persistedKeys
214
+ */
215
+ roomState: TState;
216
+ /**
217
+ * Check if the persisted state has been initialized.
218
+ * Returns true after onInit completes and state is loaded from storage.
219
+ */
220
+ isStateReady(): boolean;
114
221
  /**
115
222
  * Broadcast a message to all connections in a channel.
116
223
  * Performs channel, userId, clientId, and exclusion filtering.
@@ -161,37 +268,37 @@ interface VeraniActor<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown
161
268
  /**
162
269
  * Event handler function type for socket.io-like event handling
163
270
  */
164
- type EventHandler<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown> = (ctx: MessageContext<TMeta, E>, data: any) => void | Promise<void>;
271
+ type EventHandler<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown, TState extends Record<string, unknown> = Record<string, unknown>> = (ctx: MessageContext<TMeta, E, TState>, data: any) => void | Promise<void>;
165
272
  /**
166
273
  * Event emitter interface for room-level event handling
167
274
  */
168
- interface RoomEventEmitter<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown> {
275
+ interface RoomEventEmitter<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown, TState extends Record<string, unknown> = Record<string, unknown>> {
169
276
  /**
170
277
  * Register an event handler
171
278
  * @param event - Event name (supports wildcard "*")
172
279
  * @param handler - Handler function
173
280
  */
174
- on(event: string, handler: EventHandler<TMeta, E>): void;
281
+ on(event: string, handler: EventHandler<TMeta, E, TState>): void;
175
282
  /**
176
283
  * Remove an event handler
177
284
  * @param event - Event name
178
285
  * @param handler - Optional specific handler to remove, or remove all handlers for event
179
286
  */
180
- off(event: string, handler?: EventHandler<TMeta, E>): void;
287
+ off(event: string, handler?: EventHandler<TMeta, E, TState>): void;
181
288
  /**
182
289
  * Emit an event to registered handlers
183
290
  * @param event - Event name
184
291
  * @param ctx - Message context
185
292
  * @param data - Event data
186
293
  */
187
- emit(event: string, ctx: MessageContext<TMeta, E>, data: any): Promise<void>;
294
+ emit(event: string, ctx: MessageContext<TMeta, E, TState>, data: any): Promise<void>;
188
295
  /**
189
296
  * Rebuild handlers from static storage.
190
297
  * Called after hibernation to restore handlers from the room definition.
191
298
  * This method MUST be implemented by all event emitter implementations.
192
299
  * @param staticHandlers - Map of event names to handler sets from static storage
193
300
  */
194
- rebuildHandlers(staticHandlers: Map<string, Set<EventHandler<TMeta, E>>>): void;
301
+ rebuildHandlers(staticHandlers: Map<string, Set<EventHandler<TMeta, E, TState>>>): void;
195
302
  }
196
303
  /**
197
304
  * Builder interface for targeting specific scopes when emitting
@@ -245,9 +352,9 @@ interface ActorEmit<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown>
245
352
  /**
246
353
  * Context provided to room lifecycle hooks
247
354
  */
248
- interface RoomContext<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown> {
355
+ interface RoomContext<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown, TState extends Record<string, unknown> = Record<string, unknown>> {
249
356
  /** The actor instance handling this connection */
250
- actor: VeraniActor<TMeta, E>;
357
+ actor: VeraniActor<TMeta, E, TState>;
251
358
  /** The WebSocket connection */
252
359
  ws: WebSocket;
253
360
  /** Connection metadata */
@@ -258,7 +365,7 @@ interface RoomContext<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown
258
365
  /**
259
366
  * Context for onMessage hook with frame included
260
367
  */
261
- interface MessageContext<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown> extends RoomContext<TMeta, E> {
368
+ interface MessageContext<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown, TState extends Record<string, unknown> = Record<string, unknown>> extends RoomContext<TMeta, E, TState> {
262
369
  /** The received message frame */
263
370
  frame: MessageFrame;
264
371
  }
@@ -268,8 +375,12 @@ interface MessageContext<TMeta extends ConnectionMeta = ConnectionMeta, E = unkn
268
375
  * **Important:** All lifecycle hooks are properly awaited if they return a Promise.
269
376
  * This ensures async operations complete before the actor proceeds to the next step
270
377
  * or potentially enters hibernation.
378
+ *
379
+ * @template TMeta - Connection metadata type
380
+ * @template E - Environment type
381
+ * @template TState - Room state type (for persistence)
271
382
  */
272
- interface RoomDefinition<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown> {
383
+ interface RoomDefinition<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown, TState extends Record<string, unknown> = Record<string, unknown>> {
273
384
  /** Optional room name for debugging */
274
385
  name?: string;
275
386
  /** WebSocket upgrade path (default: "/ws") */
@@ -285,13 +396,13 @@ interface RoomDefinition<TMeta extends ConnectionMeta = ConnectionMeta, E = unkn
285
396
  * sessions map after this hook completes successfully. If this hook throws, the
286
397
  * connection is closed and no orphaned session is created.
287
398
  */
288
- onConnect?(ctx: RoomContext<TMeta, E>): void | Promise<void>;
399
+ onConnect?(ctx: RoomContext<TMeta, E, TState>): void | Promise<void>;
289
400
  /**
290
401
  * Called when a WebSocket connection is closed.
291
402
  * This hook is awaited if it returns a Promise. The session is removed from the
292
403
  * sessions map before this hook is called.
293
404
  */
294
- onDisconnect?(ctx: RoomContext<TMeta, E>): void | Promise<void>;
405
+ onDisconnect?(ctx: RoomContext<TMeta, E, TState>): void | Promise<void>;
295
406
  /**
296
407
  * Called when a message is received from a connection.
297
408
  * This hook is awaited if it returns a Promise. The actor will not process
@@ -300,30 +411,71 @@ interface RoomDefinition<TMeta extends ConnectionMeta = ConnectionMeta, E = unkn
300
411
  * **Note:** If event handlers are registered via `eventEmitter`, they take priority.
301
412
  * This hook is used as a fallback when no matching event handler is found.
302
413
  */
303
- onMessage?(ctx: MessageContext<TMeta, E>, frame: MessageFrame): void | Promise<void>;
414
+ onMessage?(ctx: MessageContext<TMeta, E, TState>, frame: MessageFrame): void | Promise<void>;
304
415
  /**
305
416
  * Called when an error occurs in a lifecycle hook.
306
417
  * This hook is also awaited if it returns a Promise.
307
418
  */
308
- onError?(error: Error, ctx: RoomContext<TMeta, E>): void | Promise<void>;
419
+ onError?(error: Error, ctx: RoomContext<TMeta, E, TState>): void | Promise<void>;
309
420
  /**
310
421
  * Called after actor wakes from hibernation and sessions are restored.
311
422
  * This hook is awaited if it returns a Promise. It is called even if some
312
423
  * sessions failed to restore, allowing you to handle partial restoration scenarios.
313
424
  */
314
- onHibernationRestore?(actor: VeraniActor<TMeta, E>): void | Promise<void>;
425
+ onHibernationRestore?(actor: VeraniActor<TMeta, E, TState>): void | Promise<void>;
315
426
  /**
316
427
  * Event emitter for socket.io-like event handling.
317
428
  * If provided, event handlers registered here will be called for matching message types.
318
429
  * If not provided, a default event emitter will be created.
319
430
  */
320
- eventEmitter?: RoomEventEmitter<TMeta, E>;
431
+ eventEmitter?: RoomEventEmitter<TMeta, E, TState>;
321
432
  /**
322
433
  * Static handler storage that persists across hibernation.
323
434
  * Handlers registered via room.on() are stored here and rebuilt in onInit.
324
435
  * @internal
325
436
  */
326
- _staticHandlers?: Map<string, Set<EventHandler<TMeta, E>>>;
437
+ _staticHandlers?: Map<string, Set<EventHandler<TMeta, E, TState>>>;
438
+ /**
439
+ * Initial state for this room. This object defines the default values
440
+ * for your room's state. Access via `actor.roomState` in lifecycle hooks.
441
+ *
442
+ * @example
443
+ * ```typescript
444
+ * const room = defineRoom({
445
+ * state: {
446
+ * messageCount: 0,
447
+ * lastActivity: null as Date | null,
448
+ * settings: { maxUsers: 100 }
449
+ * },
450
+ * persistedKeys: ['messageCount', 'settings'],
451
+ * // ...
452
+ * });
453
+ * ```
454
+ */
455
+ state?: TState;
456
+ /**
457
+ * Keys from `state` to persist to Durable Object storage.
458
+ * If empty or undefined, no state is persisted.
459
+ * Changes to these keys are automatically saved and restored on hibernation wake.
460
+ *
461
+ * @example
462
+ * ```typescript
463
+ * persistedKeys: ['messageCount', 'settings'] // Only these keys are persisted
464
+ * ```
465
+ */
466
+ persistedKeys?: (string & keyof TState)[];
467
+ /**
468
+ * Options for state persistence behavior.
469
+ */
470
+ persistOptions?: SafePersistOptions;
471
+ /**
472
+ * Called when persistence fails for a key.
473
+ * Use this to handle errors gracefully (e.g., notify admins, fallback behavior).
474
+ *
475
+ * @param key - The state key that failed to persist
476
+ * @param error - The error that occurred
477
+ */
478
+ onPersistError?(key: string, error: Error): void;
327
479
  }
328
480
  //#endregion
329
481
  //#region src/actor/actor-runtime.d.ts
@@ -340,6 +492,6 @@ type ActorHandlerClass<E = unknown> = {
340
492
  * @param room - The room definition with lifecycle hooks
341
493
  * @returns Actor class for Cloudflare Workers (extends DurableObject)
342
494
  */
343
- declare function createActorHandler<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown>(room: RoomDefinition<TMeta, E>): ActorHandlerClass<E>;
495
+ declare function createActorHandler<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown, TState extends Record<string, unknown> = Record<string, unknown>>(room: RoomDefinition<TMeta, E, TState>): ActorHandlerClass<E>;
344
496
  //#endregion
345
- export { EventHandler as a, RoomDefinition as c, BroadcastOptions as i, RpcBroadcastOptions as l, createActorHandler as n, MessageContext as o, ActorStub as r, RoomContext as s, ActorHandlerClass as t, VeraniActor as u };
497
+ export { safeSerialize as C, safeDeserialize as S, getPersistedKeys as _, EventHandler as a, isStateReady as b, RoomDefinition as c, PersistError as d, PersistNotReadyError as f, deletePersistedKey as g, clearPersistedState as h, BroadcastOptions as i, RpcBroadcastOptions as l, SafePersistOptions as m, createActorHandler as n, MessageContext as o, PersistableActor as p, ActorStub as r, RoomContext as s, ActorHandlerClass as t, VeraniActor as u, getPersistedState as v, setPeristErrorHandler as w, persistKey as x, initializePersistedState as y };
@@ -0,0 +1 @@
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};
package/dist/typed.cjs CHANGED
@@ -1 +1 @@
1
- const require_actor_runtime=require(`./actor-runtime-v4YHMnw9.cjs`);require(`./encode-BYFW_6TI.cjs`);const require_validation=require(`./validation-DvWPGkXK.cjs`);function createTypedSocketEmit(e){let n=((n,r)=>{e.emit(n,r)});return n.to=n=>{let r=e.to(n);return{emit:(e,n)=>r.emit(e,n)}},n}function createTypedActorEmit(e){let n=((n,r)=>e.emit.emit(n,r));return n.to=n=>{let r=e.emit.to(n);return{emit:(e,n)=>r.emit(e,n)}},n}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(r,i){let o=require_actor_runtime.i({name:i.name,websocketPath:i.websocketPath??`/ws`,extractMeta:i.extractMeta,onConnect:i.onConnect?e=>i.onConnect(wrapContext(e)):void 0,onDisconnect:i.onDisconnect?e=>i.onDisconnect(wrapContext(e)):void 0,onError:i.onError?(e,n)=>i.onError(e,wrapContext(n)):void 0,onHibernationRestore:i.onHibernationRestore}),s=require_validation.o(r),c=s?require_validation.a(r):void 0;return{on(e,i){o.on(e,(a,o)=>{let l=wrapMessageContext(a);if(s){let a=require_validation.r(r,e);if(a){let r=require_validation.s(a,o,e,`client`,c);return r===void 0?void 0:i(l,r)}}return i(l,o)})},off(e,n){o.off(e)},get definition(){return o},contract:r}}exports.createActorHandler=require_actor_runtime.t,exports.createTypedRoom=createTypedRoom,exports.createValidatedHandler=require_validation.t,exports.defineContract=require_validation.l,exports.getClientValidator=require_validation.r,exports.getValidationErrorHandler=require_validation.a,exports.isContract=require_validation.u,exports.isValidatedContract=require_validation.o,exports.payload=require_validation.d,exports.validateData=require_validation.s,exports.withValidation=require_validation.c;
1
+ const require_actor_runtime=require(`./actor-runtime-Fr0n1XGB.cjs`);require(`./encode-BYFW_6TI.cjs`);const require_validation=require(`./validation-DvWPGkXK.cjs`);function createTypedSocketEmit(e){let n=((n,r)=>{e.emit(n,r)});return n.to=n=>{let r=e.to(n);return{emit:(e,n)=>r.emit(e,n)}},n}function createTypedActorEmit(e){let n=((n,r)=>e.emit.emit(n,r));return n.to=n=>{let r=e.emit.to(n);return{emit:(e,n)=>r.emit(e,n)}},n}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(r,i){let o=require_actor_runtime.g({name:i.name,websocketPath:i.websocketPath??`/ws`,extractMeta:i.extractMeta,onConnect:i.onConnect?e=>i.onConnect(wrapContext(e)):void 0,onDisconnect:i.onDisconnect?e=>i.onDisconnect(wrapContext(e)):void 0,onError:i.onError?(e,n)=>i.onError(e,wrapContext(n)):void 0,onHibernationRestore:i.onHibernationRestore}),s=require_validation.o(r),c=s?require_validation.a(r):void 0;return{on(e,i){o.on(e,(a,o)=>{let l=wrapMessageContext(a);if(s){let a=require_validation.r(r,e);if(a){let r=require_validation.s(a,o,e,`client`,c);return r===void 0?void 0:i(l,r)}}return i(l,o)})},off(e,n){o.off(e)},get definition(){return o},contract:r}}exports.createActorHandler=require_actor_runtime.t,exports.createTypedRoom=createTypedRoom,exports.createValidatedHandler=require_validation.t,exports.defineContract=require_validation.l,exports.getClientValidator=require_validation.r,exports.getValidationErrorHandler=require_validation.a,exports.isContract=require_validation.u,exports.isValidatedContract=require_validation.o,exports.payload=require_validation.d,exports.validateData=require_validation.s,exports.withValidation=require_validation.c;
package/dist/typed.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { n as ConnectionMeta } from "./types-BefWc1M_.cjs";
2
2
  import { A as TypedServerEmit, B as payload, C as InferServerEvents, D as ServerPayloadMap, E as ServerPayload, F as EventPayloads, I as ExtractPayload, L as PayloadMarker, M as Contract, N as ContractDefinition, O as TypedClientEmit, P as EventMap, R as defineContract, S as InferClientEvents, T as ServerEventNames, _ as ClientEventHandler, a as ValidationResult, b as ClientPayloadMap, c as createValidatedHandler, f as getValidationErrorHandler, g as AllEventNames, h as withValidation, i as ValidationIssue, j as TypedServerListener, k as TypedClientListener, m as validateData, n as ValidationConfig, o as Validator, p as isValidatedContract, r as ValidationError, s as ValidatorMap, t as ValidatedContract, u as getClientValidator, v as ClientEventNames, w as ServerEventHandler, x as InferChannels, y as ClientPayload, z as isContract } from "./validation-Dw-KMz9Q.cjs";
3
- import { c as RoomDefinition, n as createActorHandler, r as ActorStub, u as VeraniActor } from "./actor-runtime-98JPmjaf.cjs";
3
+ import { c as RoomDefinition, n as createActorHandler, r as ActorStub, u as VeraniActor } from "./actor-runtime-gMSqKdY5.cjs";
4
4
 
5
5
  //#region src/typed/server.d.ts
6
6
 
package/dist/typed.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { n as ConnectionMeta } from "./types-Ds_V-GWZ.mjs";
2
2
  import { A as TypedServerEmit, B as payload, C as InferServerEvents, D as ServerPayloadMap, E as ServerPayload, F as EventPayloads, I as ExtractPayload, L as PayloadMarker, M as Contract, N as ContractDefinition, O as TypedClientEmit, P as EventMap, R as defineContract, S as InferClientEvents, T as ServerEventNames, _ as ClientEventHandler, a as ValidationResult, b as ClientPayloadMap, c as createValidatedHandler, f as getValidationErrorHandler, g as AllEventNames, h as withValidation, i as ValidationIssue, j as TypedServerListener, k as TypedClientListener, m as validateData, n as ValidationConfig, o as Validator, p as isValidatedContract, r as ValidationError, s as ValidatorMap, t as ValidatedContract, u as getClientValidator, v as ClientEventNames, w as ServerEventHandler, x as InferChannels, y as ClientPayload, z as isContract } from "./validation-DCongzPL.mjs";
3
- import { c as RoomDefinition, n as createActorHandler, r as ActorStub, u as VeraniActor } from "./actor-runtime-D_xHVOcH.mjs";
3
+ import { c as RoomDefinition, n as createActorHandler, r as ActorStub, u as VeraniActor } from "./actor-runtime-Dkz-u49p.mjs";
4
4
 
5
5
  //#region src/typed/server.d.ts
6
6
 
package/dist/typed.mjs CHANGED
@@ -1 +1 @@
1
- import{i as defineRoom,t as createActorHandler}from"./actor-runtime-DamIMiBw.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{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};
package/dist/verani.cjs CHANGED
@@ -1 +1 @@
1
- const require_actor_runtime=require(`./actor-runtime-v4YHMnw9.cjs`),require_encode=require(`./encode-BYFW_6TI.cjs`),require_types=require(`./types-DUO6RVw1.cjs`);exports.PROTOCOL_VERSION=require_types.t,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.i,exports.encodeClientMessage=require_encode.t,exports.encodeFrame=require_encode.n,exports.encodeServerMessage=require_encode.r,exports.restoreSessions=require_actor_runtime.n,exports.storeAttachment=require_actor_runtime.r;
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;
package/dist/verani.d.cts CHANGED
@@ -1,34 +1,34 @@
1
1
  import { a as ServerMessage, i as PROTOCOL_VERSION, n as ConnectionMeta, o as VeraniMessage, r as MessageFrame, t as ClientMessage } from "./types-BefWc1M_.cjs";
2
2
  import { a as encodeFrame, i as encodeClientMessage, n as decodeFrame, o as encodeServerMessage, r as decodeServerMessage, t as decodeClientMessage } from "./decode-D5ZPxqwe.cjs";
3
- import { a as EventHandler, c as RoomDefinition, i as BroadcastOptions, l as RpcBroadcastOptions, n as createActorHandler, o as MessageContext, r as ActorStub, s as RoomContext, t as ActorHandlerClass, u as VeraniActor } from "./actor-runtime-98JPmjaf.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-gMSqKdY5.cjs";
4
4
 
5
5
  //#region src/actor/router.d.ts
6
6
  /**
7
7
  * Extended room definition with socket.io-like convenience methods
8
8
  */
9
- interface RoomDefinitionWithHandlers<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown> extends RoomDefinition<TMeta, E> {
9
+ interface RoomDefinitionWithHandlers<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown, TState extends Record<string, unknown> = Record<string, unknown>> extends RoomDefinition<TMeta, E, TState> {
10
10
  /**
11
11
  * Register an event handler (socket.io-like API)
12
12
  * @param event - Event name
13
13
  * @param handler - Handler function
14
14
  */
15
- on(event: string, handler: EventHandler<TMeta, E>): void;
15
+ on(event: string, handler: EventHandler<TMeta, E, TState>): void;
16
16
  /**
17
17
  * Remove an event handler (socket.io-like API)
18
18
  * @param event - Event name
19
19
  * @param handler - Optional specific handler to remove
20
20
  */
21
- off(event: string, handler?: EventHandler<TMeta, E>): void;
21
+ off(event: string, handler?: EventHandler<TMeta, E, TState>): void;
22
22
  }
23
23
  /**
24
24
  * Defines a room with lifecycle hooks and metadata extraction
25
25
  * @param def - Room definition with optional hooks
26
26
  * @returns Normalized room definition with defaults and socket.io-like event handler methods
27
27
  */
28
- declare function defineRoom<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown>(def: RoomDefinition<TMeta, E>): RoomDefinitionWithHandlers<TMeta, E>;
28
+ declare function defineRoom<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown, TState extends Record<string, unknown> = Record<string, unknown>>(def: RoomDefinition<TMeta, E, TState>): RoomDefinitionWithHandlers<TMeta, E, TState>;
29
29
  //#endregion
30
30
  //#region src/actor/attachment.d.ts
31
31
  declare function storeAttachment(ws: WebSocket, meta: ConnectionMeta): void;
32
32
  declare function restoreSessions(actor: any): void;
33
33
  //#endregion
34
- export { type ActorHandlerClass, type ActorStub, type BroadcastOptions, type ClientMessage, type ConnectionMeta, type MessageContext, type MessageFrame, PROTOCOL_VERSION, type RoomContext, type RoomDefinition, type RpcBroadcastOptions, type ServerMessage, type VeraniActor, type VeraniMessage, createActorHandler, decodeClientMessage, decodeFrame, decodeServerMessage, defineRoom, encodeClientMessage, encodeFrame, encodeServerMessage, restoreSessions, storeAttachment };
34
+ 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 };
package/dist/verani.d.mts CHANGED
@@ -1,34 +1,34 @@
1
1
  import { a as ServerMessage, i as PROTOCOL_VERSION, n as ConnectionMeta, o as VeraniMessage, r as MessageFrame, t as ClientMessage } from "./types-Ds_V-GWZ.mjs";
2
2
  import { a as encodeFrame, i as encodeClientMessage, n as decodeFrame, o as encodeServerMessage, r as decodeServerMessage, t as decodeClientMessage } from "./decode-E39NapB5.mjs";
3
- import { a as EventHandler, c as RoomDefinition, i as BroadcastOptions, l as RpcBroadcastOptions, n as createActorHandler, o as MessageContext, r as ActorStub, s as RoomContext, t as ActorHandlerClass, u as VeraniActor } from "./actor-runtime-D_xHVOcH.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-Dkz-u49p.mjs";
4
4
 
5
5
  //#region src/actor/router.d.ts
6
6
  /**
7
7
  * Extended room definition with socket.io-like convenience methods
8
8
  */
9
- interface RoomDefinitionWithHandlers<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown> extends RoomDefinition<TMeta, E> {
9
+ interface RoomDefinitionWithHandlers<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown, TState extends Record<string, unknown> = Record<string, unknown>> extends RoomDefinition<TMeta, E, TState> {
10
10
  /**
11
11
  * Register an event handler (socket.io-like API)
12
12
  * @param event - Event name
13
13
  * @param handler - Handler function
14
14
  */
15
- on(event: string, handler: EventHandler<TMeta, E>): void;
15
+ on(event: string, handler: EventHandler<TMeta, E, TState>): void;
16
16
  /**
17
17
  * Remove an event handler (socket.io-like API)
18
18
  * @param event - Event name
19
19
  * @param handler - Optional specific handler to remove
20
20
  */
21
- off(event: string, handler?: EventHandler<TMeta, E>): void;
21
+ off(event: string, handler?: EventHandler<TMeta, E, TState>): void;
22
22
  }
23
23
  /**
24
24
  * Defines a room with lifecycle hooks and metadata extraction
25
25
  * @param def - Room definition with optional hooks
26
26
  * @returns Normalized room definition with defaults and socket.io-like event handler methods
27
27
  */
28
- declare function defineRoom<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown>(def: RoomDefinition<TMeta, E>): RoomDefinitionWithHandlers<TMeta, E>;
28
+ declare function defineRoom<TMeta extends ConnectionMeta = ConnectionMeta, E = unknown, TState extends Record<string, unknown> = Record<string, unknown>>(def: RoomDefinition<TMeta, E, TState>): RoomDefinitionWithHandlers<TMeta, E, TState>;
29
29
  //#endregion
30
30
  //#region src/actor/attachment.d.ts
31
31
  declare function storeAttachment(ws: WebSocket, meta: ConnectionMeta): void;
32
32
  declare function restoreSessions(actor: any): void;
33
33
  //#endregion
34
- export { type ActorHandlerClass, type ActorStub, type BroadcastOptions, type ClientMessage, type ConnectionMeta, type MessageContext, type MessageFrame, PROTOCOL_VERSION, type RoomContext, type RoomDefinition, type RpcBroadcastOptions, type ServerMessage, type VeraniActor, type VeraniMessage, createActorHandler, decodeClientMessage, decodeFrame, decodeServerMessage, defineRoom, encodeClientMessage, encodeFrame, encodeServerMessage, restoreSessions, storeAttachment };
34
+ 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 };
package/dist/verani.mjs CHANGED
@@ -1 +1 @@
1
- import{i as defineRoom,n as restoreSessions,r as storeAttachment,t as createActorHandler}from"./actor-runtime-DamIMiBw.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,createActorHandler,decodeClientMessage,decodeFrame,decodeServerMessage,defineRoom,encodeClientMessage,encodeFrame,encodeServerMessage,restoreSessions,storeAttachment};
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};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "verani",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "A simple, focused realtime SDK for Cloudflare Actors with Socket.io-like semantics",
5
5
  "license": "ISC",
6
6
  "keywords": [
@@ -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(e,w){this.handlers.has(e)||this.handlers.set(e,new Set),this.handlers.get(e).add(w)}off(e,w){let T=this.handlers.get(e);T&&(w?(T.delete(w),T.size===0&&this.handlers.delete(e)):this.handlers.delete(e))}async emit(e,w,T){let E=this.handlers.get(e);if(E&&E.size>0){let e=[];for(let D of E)try{let E=D(w,T);E instanceof Promise&&e.push(E)}catch{}await Promise.all(e)}let D=this.handlers.get(`*`);if(D&&D.size>0){let e=[];for(let E of D)try{let D=E(w,T);D instanceof Promise&&e.push(D)}catch{}await Promise.all(e)}}hasHandlers(e){return this.handlers.has(e)&&this.handlers.get(e).size>0||this.handlers.has(`*`)&&this.handlers.get(`*`).size>0}getEventNames(){return Array.from(this.handlers.keys())}rebuildHandlers(e){this.handlers.clear();for(let[w,T]of e.entries())this.handlers.set(w,new Set(T))}};function createRoomEventEmitter(){return new RoomEventEmitterImpl}function defaultExtractMeta(e){let w=crypto.randomUUID(),T=crypto.randomUUID(),E=new URL(e.url).searchParams.get(`channels`);return{userId:w,clientId:T,channels:E?E.split(`,`).map(e=>e.trim()).filter(Boolean):[`default`]}}function defineRoom(e){let w=e.eventEmitter||createRoomEventEmitter(),T=e._staticHandlers||new Map;return{name:e.name,websocketPath:e.websocketPath,extractMeta:e.extractMeta||(e=>defaultExtractMeta(e)),onConnect:e.onConnect,onDisconnect:e.onDisconnect,onMessage:e.onMessage,onError:e.onError,onHibernationRestore:e.onHibernationRestore,eventEmitter:w,_staticHandlers:T,on(e,E){w.on(e,E),T.has(e)||T.set(e,new Set),T.get(e).add(E)},off(e,E){w.off(e,E);let D=T.get(e);D&&(E?(D.delete(E),D.size===0&&T.delete(e)):T.delete(e))}}}function cleanupStaleSessions(e){let w=0,T=[];for(let[w,E]of e.entries())w.readyState!==WebSocket.OPEN&&T.push(w);for(let E of T)e.delete(E),w++;return w}function decodeFrame(w){return decodeFrame$1(w)??{type:`invalid`}}function encodeFrame(e){return encodeFrame$1(e)}function broadcast(e,w,T,E){let D=0,O=encodeFrame({type:`event`,channel:w,data:T}),k=[];for(let{ws:T,meta:A}of e.values())if(A.channels.includes(w)&&!(E?.except&&T===E.except)&&!(E?.userIds&&!E.userIds.includes(A.userId))&&!(E?.clientIds&&!E.clientIds.includes(A.clientId))){if(T.readyState!==WebSocket.OPEN){k.push(T);continue}try{T.send(O),D++}catch{k.push(T)}}for(let w of k)e.delete(w);return k.length,D}function sendToUser(e,w,T,E){let D=0,O=encodeFrame({type:`event`,channel:T,data:E}),k=[];for(let{ws:E,meta:A}of e.values())if(A.userId===w&&A.channels.includes(T)){if(E.readyState!==WebSocket.OPEN){k.push(E);continue}try{E.send(O),D++}catch{k.push(E)}}for(let w of k)e.delete(w);return k.length,D}function getSessionCount(e){return e.size}function getConnectedUserIds(e){let w=new Set;for(let{meta:T}of e.values())w.add(T.userId);return Array.from(w)}function getUserSessions(e,w){let T=[];for(let{ws:E,meta:D}of e.values())D.userId===w&&T.push(E);return T}function getStorage(e){return e.storage}function sanitizeToClassName(e){return e.replace(/^\/+/,``).split(/[-_\/\s]+/).map(e=>e.replace(/[^a-zA-Z0-9]/g,``)).filter(e=>e.length>0).map(e=>e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()).join(``)||`VeraniActor`}function createConfiguration(e){return function(w){return{locationHint:`me`,sockets:{upgradePath:e.websocketPath}}}}function isValidConnectionMeta(e){return!(!e||typeof e!=`object`||typeof e.userId!=`string`||!e.userId||typeof e.clientId!=`string`||!e.clientId||!Array.isArray(e.channels)||!e.channels.every(e=>typeof e==`string`))}function storeAttachment(e,w){e.serializeAttachment(w)}function restoreSessions(e){let w=0,T=0;for(let E of e.ctx.getWebSockets()){if(E.readyState!==WebSocket.OPEN){T++;continue}let D=E.deserializeAttachment();if(!D){T++;continue}if(!isValidConnectionMeta(D)){T++;continue}e.sessions.set(E,{ws:E,meta:D}),w++}}async function onInit(e,w){if(w.eventEmitter&&w._staticHandlers)try{w.eventEmitter.rebuildHandlers(w._staticHandlers)}catch{}try{restoreSessions(e)}catch{}if(w.onHibernationRestore&&e.sessions.size>0)try{await w.onHibernationRestore(e)}catch{}else w.onHibernationRestore&&e.sessions.size}function createUserEmitBuilder(e,w,T){return{emit(E,D){return sendToUser(w,e,T,{type:E,...D})}}}function createChannelEmitBuilder(e,w,T){return{emit(E,D){return broadcast(w,e,{type:E,...D},T)}}}function createSocketEmit(e){let w=e.meta.channels[0]||`default`;return{emit(T,E){if(e.ws.readyState===WebSocket.OPEN)try{let D={type:`event`,channel:w,data:{type:T,...E}};e.ws.send(encodeFrame(D))}catch{}},to(T){return e.meta.channels.includes(T)?createChannelEmitBuilder(T,e.actor.sessions,{except:e.ws}):createUserEmitBuilder(T,e.actor.sessions,w)}}}function createActorEmit(e){return{emit(w,T){let E={type:w,...T};return broadcast(e.sessions,`default`,E)},to(w){return createChannelEmitBuilder(w,e.sessions)}}}async function onWebSocketConnect(e,w,T,E){let D;try{D=w.extractMeta?await w.extractMeta(E):{userId:`anonymous`,clientId:crypto.randomUUID(),channels:[`default`]},storeAttachment(T,D);let O={actor:e,ws:T,meta:D,frame:{type:`connect`}};if(w.onConnect){let E={actor:e,ws:T,meta:D,emit:createSocketEmit(O)};await w.onConnect(E)}e.sessions.set(T,{ws:T,meta:D})}catch(E){if(w.onError&&D)try{let O={actor:e,ws:T,meta:D,frame:{type:`error`}};await w.onError(E,{actor:e,ws:T,meta:D,emit:createSocketEmit(O)})}catch{}T.close(1011,`Internal server error`)}}async function onWebSocketMessage(e,w,T,E){let D;try{let O=decodeFrame(E);if(O&&O.type===`ping`){if(T.readyState===WebSocket.OPEN)try{T.send(encodeFrame({type:`pong`}))}catch{}return}if(!O||O.type===`invalid`||(D=e.sessions.get(T),!D))return;let k={actor:e,ws:T,meta:D.meta,frame:O,emit:createSocketEmit({actor:e,ws:T,meta:D.meta,frame:O})},A=w.eventEmitter;A&&A.hasHandlers&&A.hasHandlers(O.type)?await A.emit(O.type,k,O.data||{}):w.onMessage&&await w.onMessage(k,O)}catch(E){if(w.onError&&D)try{await w.onError(E,{actor:e,ws:T,meta:D.meta,emit:createSocketEmit({actor:e,ws:T,meta:D.meta,frame:{type:`error`}})})}catch{}}}async function onWebSocketDisconnect(e,w,T){try{let E=e.sessions.get(T);if(e.sessions.delete(T),E&&w.onDisconnect){let D={actor:e,ws:T,meta:E.meta,frame:{type:`disconnect`}},O={actor:e,ws:T,meta:E.meta,emit:createSocketEmit(D)};await w.onDisconnect(O)}}catch{}}function createFetch(e,w){return async function(T){let E=new URL(T.url),D=T.headers.get(`Upgrade`);return E.pathname===e.websocketPath&&D===`websocket`&&await w.shouldUpgradeWebSocket(T)?w.onWebSocketUpgrade(T):w.onRequest(T)}}function createActorHandler(e){let w=sanitizeToClassName(e.name||e.websocketPath||`VeraniActor`);class E extends Actor{constructor(...w){super(...w),this.sessions=new Map,this.emit=createActorEmit(this),this.fetch=createFetch(e,this)}static{this.configuration=createConfiguration(e)}async shouldUpgradeWebSocket(e){return!0}async onInit(){await onInit(this,e)}async onWebSocketConnect(w,T){await onWebSocketConnect(this,e,w,T)}async onWebSocketMessage(w,T){await onWebSocketMessage(this,e,w,T)}async onWebSocketDisconnect(w){await onWebSocketDisconnect(this,e,w)}cleanupStaleSessions(){return cleanupStaleSessions(this.sessions)}broadcast(e,w,T){return broadcast(this.sessions,e,w,T)}getSessionCount(){return getSessionCount(this.sessions)}getConnectedUserIds(){return getConnectedUserIds(this.sessions)}getUserSessions(e){return getUserSessions(this.sessions,e)}sendToUser(e,w,T){return sendToUser(this.sessions,e,w,T)}emitToChannel(e,w,T){let E={type:w,...T};return broadcast(this.sessions,e,E)}emitToUser(e,w,T){let E=encodeFrame({type:`event`,channel:`default`,data:{type:w,...T}}),D=0,O=[];for(let{ws:w,meta:T}of this.sessions.values())if(T.userId===e){if(w.readyState!==WebSocket.OPEN){O.push(w);continue}try{w.send(E),D++}catch{O.push(w)}}for(let e of O)this.sessions.delete(e);return D}getStorage(){return getStorage(this.ctx)}}return Object.defineProperty(E,`name`,{value:w,writable:!1,configurable:!0}),E}export{defineRoom as i,restoreSessions as n,storeAttachment as r,createActorHandler as t};
@@ -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(e,S){this.handlers.has(e)||this.handlers.set(e,new Set),this.handlers.get(e).add(S)}off(e,S){let C=this.handlers.get(e);C&&(S?(C.delete(S),C.size===0&&this.handlers.delete(e)):this.handlers.delete(e))}async emit(e,S,C){let w=this.handlers.get(e);if(w&&w.size>0){let e=[];for(let T of w)try{let w=T(S,C);w instanceof Promise&&e.push(w)}catch{}await Promise.all(e)}let T=this.handlers.get(`*`);if(T&&T.size>0){let e=[];for(let w of T)try{let T=w(S,C);T instanceof Promise&&e.push(T)}catch{}await Promise.all(e)}}hasHandlers(e){return this.handlers.has(e)&&this.handlers.get(e).size>0||this.handlers.has(`*`)&&this.handlers.get(`*`).size>0}getEventNames(){return Array.from(this.handlers.keys())}rebuildHandlers(e){this.handlers.clear();for(let[S,C]of e.entries())this.handlers.set(S,new Set(C))}};function createRoomEventEmitter(){return new RoomEventEmitterImpl}function defaultExtractMeta(e){let S=crypto.randomUUID(),C=crypto.randomUUID(),w=new URL(e.url).searchParams.get(`channels`);return{userId:S,clientId:C,channels:w?w.split(`,`).map(e=>e.trim()).filter(Boolean):[`default`]}}function defineRoom(e){let S=e.eventEmitter||createRoomEventEmitter(),C=e._staticHandlers||new Map;return{name:e.name,websocketPath:e.websocketPath,extractMeta:e.extractMeta||(e=>defaultExtractMeta(e)),onConnect:e.onConnect,onDisconnect:e.onDisconnect,onMessage:e.onMessage,onError:e.onError,onHibernationRestore:e.onHibernationRestore,eventEmitter:S,_staticHandlers:C,on(e,w){S.on(e,w),C.has(e)||C.set(e,new Set),C.get(e).add(w)},off(e,w){S.off(e,w);let T=C.get(e);T&&(w?(T.delete(w),T.size===0&&C.delete(e)):C.delete(e))}}}function cleanupStaleSessions(e){let S=0,C=[];for(let[S,w]of e.entries())S.readyState!==WebSocket.OPEN&&C.push(S);for(let w of C)e.delete(w),S++;return S}function decodeFrame(S){return require_encode.a(S)??{type:`invalid`}}function encodeFrame(S){return require_encode.n(S)}function broadcast(e,S,C,w){let T=0,E=encodeFrame({type:`event`,channel:S,data:C}),D=[];for(let{ws:C,meta:O}of e.values())if(O.channels.includes(S)&&!(w?.except&&C===w.except)&&!(w?.userIds&&!w.userIds.includes(O.userId))&&!(w?.clientIds&&!w.clientIds.includes(O.clientId))){if(C.readyState!==WebSocket.OPEN){D.push(C);continue}try{C.send(E),T++}catch{D.push(C)}}for(let S of D)e.delete(S);return D.length,T}function sendToUser(e,S,C,w){let T=0,E=encodeFrame({type:`event`,channel:C,data:w}),D=[];for(let{ws:w,meta:O}of e.values())if(O.userId===S&&O.channels.includes(C)){if(w.readyState!==WebSocket.OPEN){D.push(w);continue}try{w.send(E),T++}catch{D.push(w)}}for(let S of D)e.delete(S);return D.length,T}function getSessionCount(e){return e.size}function getConnectedUserIds(e){let S=new Set;for(let{meta:C}of e.values())S.add(C.userId);return Array.from(S)}function getUserSessions(e,S){let C=[];for(let{ws:w,meta:T}of e.values())T.userId===S&&C.push(w);return C}function getStorage(e){return e.storage}function sanitizeToClassName(e){return e.replace(/^\/+/,``).split(/[-_\/\s]+/).map(e=>e.replace(/[^a-zA-Z0-9]/g,``)).filter(e=>e.length>0).map(e=>e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()).join(``)||`VeraniActor`}function createConfiguration(e){return function(S){return{locationHint:`me`,sockets:{upgradePath:e.websocketPath}}}}function isValidConnectionMeta(e){return!(!e||typeof e!=`object`||typeof e.userId!=`string`||!e.userId||typeof e.clientId!=`string`||!e.clientId||!Array.isArray(e.channels)||!e.channels.every(e=>typeof e==`string`))}function storeAttachment(e,S){e.serializeAttachment(S)}function restoreSessions(e){let S=0,C=0;for(let w of e.ctx.getWebSockets()){if(w.readyState!==WebSocket.OPEN){C++;continue}let T=w.deserializeAttachment();if(!T){C++;continue}if(!isValidConnectionMeta(T)){C++;continue}e.sessions.set(w,{ws:w,meta:T}),S++}}async function onInit(e,S){if(S.eventEmitter&&S._staticHandlers)try{S.eventEmitter.rebuildHandlers(S._staticHandlers)}catch{}try{restoreSessions(e)}catch{}if(S.onHibernationRestore&&e.sessions.size>0)try{await S.onHibernationRestore(e)}catch{}else S.onHibernationRestore&&e.sessions.size}function createUserEmitBuilder(e,S,C){return{emit(w,T){return sendToUser(S,e,C,{type:w,...T})}}}function createChannelEmitBuilder(e,S,C){return{emit(w,T){return broadcast(S,e,{type:w,...T},C)}}}function createSocketEmit(e){let S=e.meta.channels[0]||`default`;return{emit(C,w){if(e.ws.readyState===WebSocket.OPEN)try{let T={type:`event`,channel:S,data:{type:C,...w}};e.ws.send(encodeFrame(T))}catch{}},to(C){return e.meta.channels.includes(C)?createChannelEmitBuilder(C,e.actor.sessions,{except:e.ws}):createUserEmitBuilder(C,e.actor.sessions,S)}}}function createActorEmit(e){return{emit(S,C){let w={type:S,...C};return broadcast(e.sessions,`default`,w)},to(S){return createChannelEmitBuilder(S,e.sessions)}}}async function onWebSocketConnect(e,S,C,w){let T;try{T=S.extractMeta?await S.extractMeta(w):{userId:`anonymous`,clientId:crypto.randomUUID(),channels:[`default`]},storeAttachment(C,T);let E={actor:e,ws:C,meta:T,frame:{type:`connect`}};if(S.onConnect){let w={actor:e,ws:C,meta:T,emit:createSocketEmit(E)};await S.onConnect(w)}e.sessions.set(C,{ws:C,meta:T})}catch(w){if(S.onError&&T)try{let E={actor:e,ws:C,meta:T,frame:{type:`error`}};await S.onError(w,{actor:e,ws:C,meta:T,emit:createSocketEmit(E)})}catch{}C.close(1011,`Internal server error`)}}async function onWebSocketMessage(e,S,C,w){let T;try{let E=decodeFrame(w);if(E&&E.type===`ping`){if(C.readyState===WebSocket.OPEN)try{C.send(encodeFrame({type:`pong`}))}catch{}return}if(!E||E.type===`invalid`||(T=e.sessions.get(C),!T))return;let D={actor:e,ws:C,meta:T.meta,frame:E,emit:createSocketEmit({actor:e,ws:C,meta:T.meta,frame:E})},k=S.eventEmitter;k&&k.hasHandlers&&k.hasHandlers(E.type)?await k.emit(E.type,D,E.data||{}):S.onMessage&&await S.onMessage(D,E)}catch(w){if(S.onError&&T)try{await S.onError(w,{actor:e,ws:C,meta:T.meta,emit:createSocketEmit({actor:e,ws:C,meta:T.meta,frame:{type:`error`}})})}catch{}}}async function onWebSocketDisconnect(e,S,C){try{let w=e.sessions.get(C);if(e.sessions.delete(C),w&&S.onDisconnect){let T={actor:e,ws:C,meta:w.meta,frame:{type:`disconnect`}},E={actor:e,ws:C,meta:w.meta,emit:createSocketEmit(T)};await S.onDisconnect(E)}}catch{}}function createFetch(e,S){return async function(C){let w=new URL(C.url),T=C.headers.get(`Upgrade`);return w.pathname===e.websocketPath&&T===`websocket`&&await S.shouldUpgradeWebSocket(C)?S.onWebSocketUpgrade(C):S.onRequest(C)}}function createActorHandler(e){let C=sanitizeToClassName(e.name||e.websocketPath||`VeraniActor`);class w extends __cloudflare_actors.Actor{constructor(...S){super(...S),this.sessions=new Map,this.emit=createActorEmit(this),this.fetch=createFetch(e,this)}static{this.configuration=createConfiguration(e)}async shouldUpgradeWebSocket(e){return!0}async onInit(){await onInit(this,e)}async onWebSocketConnect(S,C){await onWebSocketConnect(this,e,S,C)}async onWebSocketMessage(S,C){await onWebSocketMessage(this,e,S,C)}async onWebSocketDisconnect(S){await onWebSocketDisconnect(this,e,S)}cleanupStaleSessions(){return cleanupStaleSessions(this.sessions)}broadcast(e,S,C){return broadcast(this.sessions,e,S,C)}getSessionCount(){return getSessionCount(this.sessions)}getConnectedUserIds(){return getConnectedUserIds(this.sessions)}getUserSessions(e){return getUserSessions(this.sessions,e)}sendToUser(e,S,C){return sendToUser(this.sessions,e,S,C)}emitToChannel(e,S,C){let w={type:S,...C};return broadcast(this.sessions,e,w)}emitToUser(e,S,C){let w=encodeFrame({type:`event`,channel:`default`,data:{type:S,...C}}),T=0,E=[];for(let{ws:S,meta:C}of this.sessions.values())if(C.userId===e){if(S.readyState!==WebSocket.OPEN){E.push(S);continue}try{S.send(w),T++}catch{E.push(S)}}for(let e of E)this.sessions.delete(e);return T}getStorage(){return getStorage(this.ctx)}}return Object.defineProperty(w,`name`,{value:C,writable:!1,configurable:!0}),w}Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return defineRoom}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return restoreSessions}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return storeAttachment}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return createActorHandler}});