@@ -17,6 +17,7 @@ export interface LiveSignalsConfiguration {
1717}
1818
1919const SNAPSHOT_KEY = "snapshot"
20+ const PAYLOADS_KEY = "payloads"
2021
2122let lingerMilliseconds = 1_000
2223let retryMilliseconds = 1_000
@@ -65,6 +66,11 @@ class LiveActorEntry {
6566 readonly #mirrors = new Map < string , LiveSignal < DeepReadonly < JsonValue > | undefined > > ( )
6667 readonly #snapshotState = new Signal . State < ActorSnapshot < Actor > | undefined > ( undefined )
6768 #snapshotMirror: LiveSignal < ActorSnapshot < Actor > | undefined > | undefined
69+ readonly #payloadStates = new Map < string , Signal . State < DeepReadonly < JsonValue > | undefined > > ( )
70+ readonly #payloadMirrors = new Map < string , LiveSignal < DeepReadonly < JsonValue > | undefined > > ( )
71+ readonly #payloadWatcherCounts = new Map < string , number > ( )
72+ readonly #payloadFences = new Map < string , { instanceId : string ; revision : bigint } > ( )
73+ #payloadsProxy: object | undefined
6874 #watcherCount = 0
6975 #snapshotWatcherCount = 0
7076 #session: LiveSession | undefined
@@ -91,6 +97,49 @@ class LiveActorEntry {
9197 return mirror
9298 }
9399
100+ payloadsProxy ( ) : object {
101+ this . #payloadsProxy ??= new Proxy (
102+ { } ,
103+ {
104+ get : ( _target , property ) => {
105+ if ( typeof property !== "string" ) return undefined
106+ return this . payloadSignalFor ( property )
107+ } ,
108+ has : ( _target , property ) => typeof property === "string" ,
109+ } ,
110+ )
111+ return this . #payloadsProxy
112+ }
113+
114+ payloadSignalFor ( name : string ) : LiveSignal < DeepReadonly < JsonValue > | undefined > {
115+ const existing = this . #payloadMirrors. get ( name )
116+ if ( existing ) return existing
117+ const state = this . payloadStateFor ( name )
118+ const mirror = new Signal . Computed ( ( ) => state . get ( ) , {
119+ [ Signal . subtle . watched ] : ( ) => {
120+ this . #payloadWatcherCounts. set ( name , ( this . #payloadWatcherCounts. get ( name ) ?? 0 ) + 1 )
121+ this . retain ( { snapshot : false , payloadName : name } )
122+ } ,
123+ [ Signal . subtle . unwatched ] : ( ) => {
124+ const count = ( this . #payloadWatcherCounts. get ( name ) ?? 1 ) - 1
125+ if ( count <= 0 ) this . #payloadWatcherCounts. delete ( name )
126+ else this . #payloadWatcherCounts. set ( name , count )
127+ this . release ( { snapshot : false } )
128+ } ,
129+ } )
130+ this . #payloadMirrors. set ( name , mirror )
131+ return mirror
132+ }
133+
134+ private payloadStateFor ( name : string ) {
135+ let state = this . #payloadStates. get ( name )
136+ if ( ! state ) {
137+ state = new Signal . State < DeepReadonly < JsonValue > | undefined > ( undefined )
138+ this . #payloadStates. set ( name , state )
139+ }
140+ return state
141+ }
142+
94143 snapshotSignal ( ) : LiveSignal < ActorSnapshot < Actor > | undefined > {
95144 this . #snapshotMirror ??= new Signal . Computed ( ( ) => this . #snapshotState. get ( ) , {
96145 [ Signal . subtle . watched ] : ( ) => this . retain ( { snapshot : true } ) ,
@@ -108,14 +157,15 @@ class LiveActorEntry {
108157 return state
109158 }
110159
111- private retain ( options : { snapshot : boolean } ) : void {
160+ private retain ( options : { snapshot : boolean ; payloadName ?: string } ) : void {
112161 this . #watcherCount += 1
113162 if ( options . snapshot ) this . #snapshotWatcherCount += 1
114163 if ( this . #linger !== undefined ) {
115164 clearTimeout ( this . #linger)
116165 this . #linger = undefined
117166 }
118167 if ( ! this . #session) this . open ( )
168+ else if ( options . payloadName !== undefined ) this . sendSubscribe ( this . #session)
119169 else if ( options . snapshot ) void this . refreshSnapshot ( )
120170 }
121171
@@ -141,13 +191,7 @@ class LiveActorEntry {
141191 } ) as LiveSession
142192 this . #session = session
143193 openSessions . add ( this )
144- void session
145- . receive ( {
146- version : 1 ,
147- action : "subscribe" ,
148- actorType : this . #reference. actorType ,
149- actorId : this . #reference. actorId ,
150- } )
194+ void this . sendSubscribe ( session )
151195 . then ( ( ) => this . refreshSnapshot ( ) )
152196 . catch ( ( error : unknown ) => {
153197 this . #reference. runtime . settings . logger . warn ( {
@@ -166,6 +210,17 @@ class LiveActorEntry {
166210 } )
167211 }
168212
213+ private sendSubscribe ( session : LiveSession ) : Promise < void > {
214+ const payloadNames = [ ...this . #payloadWatcherCounts. keys ( ) ]
215+ return session . receive ( {
216+ version : 1 ,
217+ action : "subscribe" ,
218+ actorType : this . #reference. actorType ,
219+ actorId : this . #reference. actorId ,
220+ ...( payloadNames . length > 0 ? { payloads : payloadNames } : { } ) ,
221+ } )
222+ }
223+
169224 private close ( ) : void {
170225 if ( this . #retry !== undefined ) {
171226 clearTimeout ( this . #retry)
@@ -179,6 +234,10 @@ class LiveActorEntry {
179234 }
180235
181236 private receive ( envelope : RealtimeEnvelope ) : void {
237+ if ( envelope . kind === "payload" ) {
238+ this . receivePayload ( envelope )
239+ return
240+ }
182241 if ( envelope . kind !== "invalidation" ) return
183242 const revision = BigInt ( envelope . revision )
184243 if ( this . #instanceId === envelope . instanceId && revision <= this . #revision) return
@@ -190,6 +249,14 @@ class LiveActorEntry {
190249 void this . refreshSnapshot ( )
191250 }
192251
252+ private receivePayload ( envelope : RealtimeEnvelope & { kind : "payload" } ) : void {
253+ const revision = BigInt ( envelope . revision )
254+ const fence = this . #payloadFences. get ( envelope . name )
255+ if ( fence && fence . instanceId === envelope . instanceId && revision <= fence . revision ) return
256+ this . #payloadFences. set ( envelope . name , { instanceId : envelope . instanceId , revision } )
257+ this . payloadStateFor ( envelope . name ) . set ( envelope . payload as DeepReadonly < JsonValue > )
258+ }
259+
193260 private async refreshSnapshot ( ) : Promise < void > {
194261 if ( this . #snapshotWatcherCount === 0 ) return
195262 if ( this . #refreshing) {
@@ -245,6 +312,7 @@ installLiveSignals((reference) => {
245312 get ( _target , property ) {
246313 if ( typeof property !== "string" ) return undefined
247314 if ( property === SNAPSHOT_KEY ) return resolved . snapshotSignal ( )
315+ if ( property === PAYLOADS_KEY ) return resolved . payloadsProxy ( )
248316 return resolved . signalFor ( property )
249317 } ,
250318 has ( _target , property ) {
0 commit comments