@@ -34,6 +34,7 @@ type ConnectionAttachments = {
3434 // TODO: remove this once we have
3535 // durable object level setState
3636 server : string ;
37+ tags : string [ ] ;
3738 } ;
3839 __user ?: unknown ;
3940} ;
@@ -57,11 +58,20 @@ function tryGetPartyServerMeta(
5758 if ( ! pk || typeof pk !== "object" ) {
5859 return null ;
5960 }
60- const { id, server } = pk as { id ?: unknown ; server ?: unknown } ;
61+ const { id, server, tags } = pk as {
62+ id ?: unknown ;
63+ server ?: unknown ;
64+ tags ?: unknown ;
65+ } ;
6166 if ( typeof id !== "string" || typeof server !== "string" ) {
6267 return null ;
6368 }
64- return pk as ConnectionAttachments [ "__pk" ] ;
69+ // Default tags to [] for connections created before tags were stored
70+ return {
71+ id,
72+ server,
73+ tags : Array . isArray ( tags ) ? tags : [ ]
74+ } as ConnectionAttachments [ "__pk" ] ;
6575 } catch {
6676 return null ;
6777 }
@@ -138,6 +148,12 @@ export const createLazyConnection = (
138148 return attachments . get ( ws ) . __pk . server ;
139149 }
140150 } ,
151+ tags : {
152+ get ( ) {
153+ // Default to [] for connections accepted before tags were stored
154+ return attachments . get ( ws ) . __pk . tags ?? [ ] ;
155+ }
156+ } ,
141157 socket : {
142158 get ( ) {
143159 return ws ;
@@ -233,6 +249,36 @@ class HibernatingConnectionIterator<T> implements IterableIterator<
233249 }
234250}
235251
252+ /**
253+ * Deduplicate and validate connection tags.
254+ * Returns the final tag array (always includes the connection id as the first tag).
255+ */
256+ function prepareTags ( connectionId : string , userTags : string [ ] ) : string [ ] {
257+ const tags = [ connectionId , ...userTags . filter ( ( t ) => t !== connectionId ) ] ;
258+
259+ // validate tags against documented restrictions
260+ // https://developers.cloudflare.com/durable-objects/api/hibernatable-websockets-api/#state-methods-for-websockets
261+ if ( tags . length > 10 ) {
262+ throw new Error (
263+ "A connection can only have 10 tags, including the default id tag."
264+ ) ;
265+ }
266+
267+ for ( const tag of tags ) {
268+ if ( typeof tag !== "string" ) {
269+ throw new Error ( `A connection tag must be a string. Received: ${ tag } ` ) ;
270+ }
271+ if ( tag === "" ) {
272+ throw new Error ( "A connection tag must not be an empty string." ) ;
273+ }
274+ if ( tag . length > 256 ) {
275+ throw new Error ( "A connection tag must not exceed 256 characters" ) ;
276+ }
277+ }
278+
279+ return tags ;
280+ }
281+
236282export interface ConnectionManager {
237283 getCount ( ) : number ;
238284 getConnection < TState > ( id : string ) : Connection < TState > | undefined ;
@@ -280,12 +326,16 @@ export class InMemoryConnectionManager<TState> implements ConnectionManager {
280326 accept ( connection : Connection , options : { tags : string [ ] ; server : string } ) {
281327 connection . accept ( ) ;
282328
329+ const tags = prepareTags ( connection . id , options . tags ) ;
330+
283331 this . #connections. set ( connection . id , connection ) ;
284- this . tags . set ( connection , [
285- // make sure we have id tag
286- connection . id ,
287- ...options . tags . filter ( ( t ) => t !== connection . id )
288- ] ) ;
332+ this . tags . set ( connection , tags ) ;
333+
334+ // Expose tags on the connection object itself
335+ Object . defineProperty ( connection , "tags" , {
336+ get : ( ) => tags ,
337+ configurable : true
338+ } ) ;
289339
290340 const removeConnection = ( ) => {
291341 this . #connections. delete ( connection . id ) ;
@@ -336,37 +386,14 @@ export class HibernatingConnectionManager<TState> implements ConnectionManager {
336386 }
337387
338388 accept ( connection : Connection , options : { tags : string [ ] ; server : string } ) {
339- // dedupe tags in case user already provided id tag
340- const tags = [
341- connection . id ,
342- ...options . tags . filter ( ( t ) => t !== connection . id )
343- ] ;
344-
345- // validate tags against documented restrictions
346- // shttps://developers.cloudflare.com/durable-objects/api/hibernatable-websockets-api/#state-methods-for-websockets
347- if ( tags . length > 10 ) {
348- throw new Error (
349- "A connection can only have 10 tags, including the default id tag."
350- ) ;
351- }
352-
353- for ( const tag of tags ) {
354- if ( typeof tag !== "string" ) {
355- throw new Error ( `A connection tag must be a string. Received: ${ tag } ` ) ;
356- }
357- if ( tag === "" ) {
358- throw new Error ( "A connection tag must not be an empty string." ) ;
359- }
360- if ( tag . length > 256 ) {
361- throw new Error ( "A connection tag must not exceed 256 characters" ) ;
362- }
363- }
389+ const tags = prepareTags ( connection . id , options . tags ) ;
364390
365391 this . controller . acceptWebSocket ( connection , tags ) ;
366392 connection . serializeAttachment ( {
367393 __pk : {
368394 id : connection . id ,
369- server : options . server
395+ server : options . server ,
396+ tags
370397 } ,
371398 __user : null
372399 } ) ;
0 commit comments