@@ -3,6 +3,7 @@ import { withArrayChangeTracking, withChangeTracking } from "./proxy"
33import { Transaction , getActiveTransaction } from "./transactions"
44import { SortedMap } from "./SortedMap"
55import type {
6+ ChangeListener ,
67 ChangeMessage ,
78 CollectionConfig ,
89 Fn ,
@@ -32,24 +33,6 @@ interface PendingSyncedTransaction<T extends object = Record<string, unknown>> {
3233 operations : Array < OptimisticChangeMessage < T > >
3334}
3435
35- // Event system for collections
36- type CollectionEventType = `insert` | `update` | `delete`
37-
38- interface CollectionEvent < T , TKey extends string | number > {
39- type : CollectionEventType
40- key : TKey
41- value : T
42- previousValue ?: T
43- }
44-
45- type EventListener < T , TKey extends string | number > = (
46- event : CollectionEvent < T , TKey >
47- ) => void
48- type KeyListener < T > = (
49- value : T | undefined ,
50- previousValue : T | undefined
51- ) => void
52-
5336/**
5437 * Enhanced Collection interface that includes both data type T and utilities TUtils
5538 * @template T - The type of items in the collection
@@ -229,13 +212,8 @@ export class CollectionImpl<
229212 private _size = 0
230213
231214 // Event system
232- private eventListeners = new Set < EventListener < T , TKey > > ( )
233- private keyListeners = new Map < TKey , Set < KeyListener < T > > > ( )
234-
235- // Batching for subscribeChanges
236- private changesBatchListeners = new Set <
237- ( changes : Array < ChangeMessage < T > > ) => void
238- > ( )
215+ private changeListeners = new Set < ChangeListener < T , TKey > > ( )
216+ private changeKeyListeners = new Map < TKey , Set < ChangeListener < T , TKey > > > ( )
239217
240218 // Utilities namespace
241219 // This is populated by createCollection
@@ -389,7 +367,7 @@ export class CollectionImpl<
389367 this . _size = this . calculateSize ( )
390368
391369 // Collect events for changes
392- const events : Array < CollectionEvent < T , TKey > > = [ ]
370+ const events : Array < ChangeMessage < T , TKey > > = [ ]
393371 this . collectOptimisticChanges ( previousState , previousDeletes , events )
394372
395373 // Emit all events at once
@@ -417,7 +395,7 @@ export class CollectionImpl<
417395 private collectOptimisticChanges (
418396 previousUpserts : Map < TKey , T > ,
419397 previousDeletes : Set < TKey > ,
420- events : Array < CollectionEvent < T , TKey > >
398+ events : Array < ChangeMessage < T , TKey > >
421399 ) : void {
422400 const allKeys = new Set ( [
423401 ...previousUpserts . keys ( ) ,
@@ -473,80 +451,32 @@ export class CollectionImpl<
473451 /**
474452 * Emit multiple events at once to all listeners
475453 */
476- private emitEvents ( events : Array < CollectionEvent < T , TKey > > ) : void {
477- // Emit to individual event listeners
478- for ( const event of events ) {
479- this . emitEvent ( event )
480- }
481-
482- // Convert to ChangeMessage format and emit to subscribeChanges listeners
483- if ( events . length > 0 ) {
484- const changeMessages : Array < ChangeMessage < T > > = events . map ( ( event ) => {
485- const changeMessage : ChangeMessage < T > = {
486- type : event . type ,
487- key : event . key ,
488- value : event . value ,
489- }
490-
491- if ( event . previousValue ) {
492- ; ( changeMessage as any ) . previousValue = event . previousValue
493- }
494-
495- return changeMessage
496- } )
497-
498- for ( const listener of this . changesBatchListeners ) {
499- listener ( changeMessages )
500- }
501- }
502- }
503-
504- /**
505- * Emit an event to individual listeners (not batched)
506- */
507- private emitEvent ( event : CollectionEvent < T , TKey > ) : void {
508- // Emit to general listeners
509- for ( const listener of this . eventListeners ) {
510- listener ( event )
511- }
512-
513- // Emit to key-specific listeners
514- const keyListeners = this . keyListeners . get ( event . key )
515- if ( keyListeners ) {
516- for ( const listener of keyListeners ) {
517- listener (
518- event . type === `delete` ? undefined : event . value ,
519- event . previousValue
520- )
454+ private emitEvents ( changes : Array < ChangeMessage < T , TKey > > ) : void {
455+ if ( changes . length > 0 ) {
456+ // Emit to general listeners
457+ for ( const listener of this . changeListeners ) {
458+ listener ( changes )
521459 }
522- }
523- }
524460
525- /**
526- * Subscribe to collection events
527- */
528- public subscribe ( listener : EventListener < T , TKey > ) : ( ) => void {
529- this . eventListeners . add ( listener )
530- return ( ) => {
531- this . eventListeners . delete ( listener )
532- }
533- }
534-
535- /**
536- * Subscribe to changes for a specific key
537- */
538- public subscribeKey ( key : TKey , listener : KeyListener < T > ) : ( ) => void {
539- if ( ! this . keyListeners . has ( key ) ) {
540- this . keyListeners . set ( key , new Set ( ) )
541- }
542- this . keyListeners . get ( key ) ! . add ( listener )
461+ // Emit to key-specific listeners
462+ if ( this . changeKeyListeners . size > 0 ) {
463+ // Group changes by key, but only for keys that have listeners
464+ const changesByKey = new Map < TKey , Array < ChangeMessage < T , TKey > > > ( )
465+ for ( const change of changes ) {
466+ if ( this . changeKeyListeners . has ( change . key ) ) {
467+ if ( ! changesByKey . has ( change . key ) ) {
468+ changesByKey . set ( change . key , [ ] )
469+ }
470+ changesByKey . get ( change . key ) ! . push ( change )
471+ }
472+ }
543473
544- return ( ) => {
545- const listeners = this . keyListeners . get ( key )
546- if ( listeners ) {
547- listeners . delete ( listener )
548- if ( listeners . size === 0 ) {
549- this . keyListeners . delete ( key )
474+ // Emit batched changes to each key's listeners
475+ for ( const [ key , keyChanges ] of changesByKey ) {
476+ const keyListeners = this . changeKeyListeners . get ( key ) !
477+ for ( const listener of keyListeners ) {
478+ listener ( keyChanges )
479+ }
550480 }
551481 }
552482 }
@@ -650,7 +580,7 @@ export class CollectionImpl<
650580 )
651581 ) {
652582 const changedKeys = new Set < TKey > ( )
653- const events : Array < CollectionEvent < T , TKey > > = [ ]
583+ const events : Array < ChangeMessage < T , TKey > > = [ ]
654584
655585 for ( const transaction of this . pendingSyncedTransactions ) {
656586 for ( const operation of transaction . operations ) {
@@ -1315,16 +1245,55 @@ export class CollectionImpl<
13151245 * @returns A function that can be called to unsubscribe from the changes
13161246 */
13171247 public subscribeChanges (
1318- callback : ( changes : Array < ChangeMessage < T > > ) => void
1248+ callback : ( changes : Array < ChangeMessage < T > > ) => void ,
1249+ { includeInitialState = false } : { includeInitialState ?: boolean } = { }
13191250 ) : ( ) => void {
1320- // First send the current state as changes
1321- callback ( this . currentStateAsChanges ( ) )
1251+ if ( includeInitialState ) {
1252+ // First send the current state as changes
1253+ callback ( this . currentStateAsChanges ( ) )
1254+ }
13221255
13231256 // Add to batched listeners
1324- this . changesBatchListeners . add ( callback )
1257+ this . changeListeners . add ( callback )
1258+
1259+ return ( ) => {
1260+ this . changeListeners . delete ( callback )
1261+ }
1262+ }
1263+
1264+ /**
1265+ * Subscribe to changes for a specific key
1266+ */
1267+ public subscribeChangesKey (
1268+ key : TKey ,
1269+ listener : ChangeListener < T , TKey > ,
1270+ { includeInitialState = false } : { includeInitialState ?: boolean } = { }
1271+ ) : ( ) => void {
1272+ if ( ! this . changeKeyListeners . has ( key ) ) {
1273+ this . changeKeyListeners . set ( key , new Set ( ) )
1274+ }
1275+
1276+ if ( includeInitialState ) {
1277+ // First send the current state as changes
1278+ listener ( [
1279+ {
1280+ type : `insert` ,
1281+ key,
1282+ value : this . get ( key ) ! ,
1283+ } ,
1284+ ] )
1285+ }
1286+
1287+ this . changeKeyListeners . get ( key ) ! . add ( listener )
13251288
13261289 return ( ) => {
1327- this . changesBatchListeners . delete ( callback )
1290+ const listeners = this . changeKeyListeners . get ( key )
1291+ if ( listeners ) {
1292+ listeners . delete ( listener )
1293+ if ( listeners . size === 0 ) {
1294+ this . changeKeyListeners . delete ( key )
1295+ }
1296+ }
13281297 }
13291298 }
13301299
0 commit comments