@@ -750,4 +750,148 @@ describe(`Collection Auto-Indexing`, () => {
750750
751751 subscription . unsubscribe ( )
752752 } )
753+
754+ it ( `should create auto-indexes for nested field paths` , async ( ) => {
755+ interface NestedTestItem {
756+ id : string
757+ name : string
758+ profile ?: {
759+ score : number
760+ bio : string
761+ }
762+ metadata ?: {
763+ tags : Array < string >
764+ stats : {
765+ views : number
766+ likes : number
767+ }
768+ }
769+ }
770+
771+ const nestedTestData : Array < NestedTestItem > = [
772+ {
773+ id : `1` ,
774+ name : `Alice` ,
775+ profile : { score : 85 , bio : `Developer` } ,
776+ metadata : {
777+ tags : [ `tech` , `coding` ] ,
778+ stats : { views : 100 , likes : 50 } ,
779+ } ,
780+ } ,
781+ {
782+ id : `2` ,
783+ name : `Bob` ,
784+ profile : { score : 92 , bio : `Designer` } ,
785+ metadata : {
786+ tags : [ `design` , `ui` ] ,
787+ stats : { views : 200 , likes : 75 } ,
788+ } ,
789+ } ,
790+ {
791+ id : `3` ,
792+ name : `Charlie` ,
793+ profile : { score : 78 , bio : `Manager` } ,
794+ metadata : {
795+ tags : [ `management` , `leadership` ] ,
796+ stats : { views : 150 , likes : 60 } ,
797+ } ,
798+ } ,
799+ ]
800+
801+ const collection = createCollection < NestedTestItem , string > ( {
802+ getKey : ( item ) => item . id ,
803+ autoIndex : `eager` ,
804+ startSync : true ,
805+ sync : {
806+ sync : ( { begin, write, commit, markReady } ) => {
807+ begin ( )
808+ for ( const item of nestedTestData ) {
809+ write ( {
810+ type : `insert` ,
811+ value : item ,
812+ } )
813+ }
814+ commit ( )
815+ markReady ( )
816+ } ,
817+ } ,
818+ } )
819+
820+ await collection . stateWhenReady ( )
821+
822+ // Should have no indexes initially
823+ expect ( collection . indexes . size ) . toBe ( 0 )
824+
825+ // Test 1: Nested field one level deep (profile.score)
826+ const changes1 : Array < any > = [ ]
827+ const subscription1 = collection . subscribeChanges (
828+ ( items ) => {
829+ changes1 . push ( ...items )
830+ } ,
831+ {
832+ includeInitialState : true ,
833+ whereExpression : gt ( new PropRef ( [ `profile` , `score` ] ) , 80 ) ,
834+ }
835+ )
836+
837+ // Should have created an auto-index for profile.score
838+ const profileScoreIndex = Array . from ( collection . indexes . values ( ) ) . find (
839+ ( index ) =>
840+ index . expression . type === `ref` &&
841+ ( index . expression as any ) . path . length === 2 &&
842+ ( index . expression as any ) . path [ 0 ] === `profile` &&
843+ ( index . expression as any ) . path [ 1 ] === `score`
844+ )
845+ expect ( profileScoreIndex ) . toBeDefined ( )
846+
847+ // Verify the filtered results are correct
848+ expect ( changes1 . filter ( ( c ) => c . type === `insert` ) . length ) . toBe ( 2 ) // Alice (85) and Bob (92)
849+
850+ subscription1 . unsubscribe ( )
851+
852+ // Test 2: Deeply nested field (metadata.stats.views)
853+ const changes2 : Array < any > = [ ]
854+ const subscription2 = collection . subscribeChanges (
855+ ( items ) => {
856+ changes2 . push ( ...items )
857+ } ,
858+ {
859+ includeInitialState : true ,
860+ whereExpression : eq ( new PropRef ( [ `metadata` , `stats` , `views` ] ) , 200 ) ,
861+ }
862+ )
863+
864+ // Should have created an auto-index for metadata.stats.views
865+ const viewsIndex = Array . from ( collection . indexes . values ( ) ) . find (
866+ ( index ) =>
867+ index . expression . type === `ref` &&
868+ ( index . expression as any ) . path . length === 3 &&
869+ ( index . expression as any ) . path [ 0 ] === `metadata` &&
870+ ( index . expression as any ) . path [ 1 ] === `stats` &&
871+ ( index . expression as any ) . path [ 2 ] === `views`
872+ )
873+ expect ( viewsIndex ) . toBeDefined ( )
874+
875+ // Verify the filtered results are correct
876+ expect ( changes2 . filter ( ( c ) => c . type === `insert` ) . length ) . toBe ( 1 ) // Only Bob has 200 views
877+
878+ subscription2 . unsubscribe ( )
879+
880+ // Test 3: Index usage verification with tracker
881+ withIndexTracking ( collection , ( tracker ) => {
882+ const result = collection . currentStateAsChanges ( {
883+ where : gt ( new PropRef ( [ `profile` , `score` ] ) , 80 ) ,
884+ } ) !
885+
886+ expect ( result . length ) . toBe ( 2 ) // Alice and Bob
887+
888+ // Verify it used the auto-created index
889+ expectIndexUsage ( tracker . stats , {
890+ shouldUseIndex : true ,
891+ shouldUseFullScan : false ,
892+ indexCallCount : 1 ,
893+ fullScanCallCount : 0 ,
894+ } )
895+ } )
896+ } )
753897} )
0 commit comments