@@ -1783,13 +1783,13 @@ describe(`localStorage collection`, () => {
17831783 expect ( collection . get ( 2 ) ?. completed ) . toBe ( false )
17841784 expect ( collection . get ( 3 ) ?. completed ) . toBe ( false )
17851785
1786- // Verify in storage - numeric keys are prefixed with "__number__"
1786+ // Verify in storage - numeric keys are encoded with "n:" prefix
17871787 const storedData = mockStorage . getItem ( `numeric-todos` )
17881788 expect ( storedData ) . toBeDefined ( )
17891789 const parsed = JSON . parse ( storedData ! )
1790- expect ( parsed [ `__number__1 ` ] . data . completed ) . toBe ( true )
1791- expect ( parsed [ `__number__2 ` ] . data . completed ) . toBe ( false )
1792- expect ( parsed [ `__number__3 ` ] . data . completed ) . toBe ( false )
1790+ expect ( parsed [ `n:1 ` ] . data . completed ) . toBe ( true )
1791+ expect ( parsed [ `n:2 ` ] . data . completed ) . toBe ( false )
1792+ expect ( parsed [ `n:3 ` ] . data . completed ) . toBe ( false )
17931793
17941794 subscription . unsubscribe ( )
17951795 } )
@@ -1843,13 +1843,13 @@ describe(`localStorage collection`, () => {
18431843 expect ( collection . has ( 2 ) ) . toBe ( true )
18441844 expect ( collection . has ( 3 ) ) . toBe ( true )
18451845
1846- // Verify in storage - numeric keys are prefixed with "__number__"
1846+ // Verify in storage - numeric keys are encoded with "n:" prefix
18471847 const storedData = mockStorage . getItem ( `numeric-todos-delete` )
18481848 expect ( storedData ) . toBeDefined ( )
18491849 const parsed = JSON . parse ( storedData ! )
1850- expect ( parsed [ `__number__1 ` ] ) . toBeUndefined ( )
1851- expect ( parsed [ `__number__2 ` ] ) . toBeDefined ( )
1852- expect ( parsed [ `__number__3 ` ] ) . toBeDefined ( )
1850+ expect ( parsed [ `n:1 ` ] ) . toBeUndefined ( )
1851+ expect ( parsed [ `n:2 ` ] ) . toBeDefined ( )
1852+ expect ( parsed [ `n:3 ` ] ) . toBeDefined ( )
18531853
18541854 subscription . unsubscribe ( )
18551855 } )
@@ -1862,17 +1862,17 @@ describe(`localStorage collection`, () => {
18621862 }
18631863
18641864 // Pre-populate storage with numeric IDs (simulating existing data)
1865- // Numeric keys are stored with "__number__ " prefix
1865+ // Numeric keys are stored with "n: " prefix
18661866 const existingData = {
1867- __number__1 : {
1867+ "n:1" : {
18681868 versionKey : `version-1` ,
18691869 data : { id : 1 , title : `First Todo` , completed : false } ,
18701870 } ,
1871- __number__2 : {
1871+ "n:2" : {
18721872 versionKey : `version-2` ,
18731873 data : { id : 2 , title : `Second Todo` , completed : false } ,
18741874 } ,
1875- __number__3 : {
1875+ "n:3" : {
18761876 versionKey : `version-3` ,
18771877 data : { id : 3 , title : `Third Todo` , completed : false } ,
18781878 } ,
@@ -1908,13 +1908,13 @@ describe(`localStorage collection`, () => {
19081908 expect ( collection . get ( 2 ) ?. completed ) . toBe ( false )
19091909 expect ( collection . get ( 3 ) ?. completed ) . toBe ( false )
19101910
1911- // Verify in storage - numeric keys are prefixed with "__number__"
1911+ // Verify in storage - numeric keys are encoded with "n:" prefix
19121912 const storedData = mockStorage . getItem ( `numeric-todos-reload` )
19131913 expect ( storedData ) . toBeDefined ( )
19141914 const parsed = JSON . parse ( storedData ! )
1915- expect ( parsed [ `__number__1 ` ] . data . completed ) . toBe ( true )
1916- expect ( parsed [ `__number__2 ` ] . data . completed ) . toBe ( false )
1917- expect ( parsed [ `__number__3 ` ] . data . completed ) . toBe ( false )
1915+ expect ( parsed [ `n:1 ` ] . data . completed ) . toBe ( true )
1916+ expect ( parsed [ `n:2 ` ] . data . completed ) . toBe ( false )
1917+ expect ( parsed [ `n:3 ` ] . data . completed ) . toBe ( false )
19181918
19191919 subscription . unsubscribe ( )
19201920 } )
@@ -1927,17 +1927,17 @@ describe(`localStorage collection`, () => {
19271927 }
19281928
19291929 // Pre-populate storage with numeric IDs (simulating existing data)
1930- // Numeric keys are stored with "__number__ " prefix
1930+ // Numeric keys are stored with "n: " prefix
19311931 const existingData = {
1932- __number__1 : {
1932+ "n:1" : {
19331933 versionKey : `version-1` ,
19341934 data : { id : 1 , title : `First Todo` , completed : false } ,
19351935 } ,
1936- __number__2 : {
1936+ "n:2" : {
19371937 versionKey : `version-2` ,
19381938 data : { id : 2 , title : `Second Todo` , completed : false } ,
19391939 } ,
1940- __number__3 : {
1940+ "n:3" : {
19411941 versionKey : `version-3` ,
19421942 data : { id : 3 , title : `Third Todo` , completed : false } ,
19431943 } ,
@@ -1974,13 +1974,13 @@ describe(`localStorage collection`, () => {
19741974 expect ( collection . has ( 2 ) ) . toBe ( true )
19751975 expect ( collection . has ( 3 ) ) . toBe ( true )
19761976
1977- // Verify in storage - numeric keys are prefixed with "__number__"
1977+ // Verify in storage - numeric keys are encoded with "n:" prefix
19781978 const storedData = mockStorage . getItem ( `numeric-todos-reload-delete` )
19791979 expect ( storedData ) . toBeDefined ( )
19801980 const parsed = JSON . parse ( storedData ! )
1981- expect ( parsed [ `__number__1 ` ] ) . toBeUndefined ( )
1982- expect ( parsed [ `__number__2 ` ] ) . toBeDefined ( )
1983- expect ( parsed [ `__number__3 ` ] ) . toBeDefined ( )
1981+ expect ( parsed [ `n:1 ` ] ) . toBeUndefined ( )
1982+ expect ( parsed [ `n:2 ` ] ) . toBeDefined ( )
1983+ expect ( parsed [ `n:3 ` ] ) . toBeDefined ( )
19841984
19851985 subscription . unsubscribe ( )
19861986 } )
@@ -2030,12 +2030,66 @@ describe(`localStorage collection`, () => {
20302030
20312031 // There should be TWO entries in storage
20322032 expect ( Object . keys ( parsed ) . length ) . toBe ( 2 )
2033- // Numeric ID 1 is stored with key "__number__1"
2034- expect ( parsed [ `__number__1` ] ) . toBeDefined ( )
2035- expect ( parsed [ `__number__1` ] . data . title ) . toBe ( `Numeric ID` )
2036- // String ID "1" is stored with key "1"
2037- expect ( parsed [ `1` ] ) . toBeDefined ( )
2038- expect ( parsed [ `1` ] . data . title ) . toBe ( `String ID` )
2033+ // Numeric ID 1 is stored with key "n:1"
2034+ expect ( parsed [ `n:1` ] ) . toBeDefined ( )
2035+ expect ( parsed [ `n:1` ] . data . title ) . toBe ( `Numeric ID` )
2036+ // String ID "1" is stored with key "s:1"
2037+ expect ( parsed [ `s:1` ] ) . toBeDefined ( )
2038+ expect ( parsed [ `s:1` ] . data . title ) . toBe ( `String ID` )
2039+
2040+ subscription . unsubscribe ( )
2041+ } )
2042+
2043+ it ( `should prevent collision between numeric key and string key that matches the encoding pattern` , async ( ) => {
2044+ interface MixedIdTodo {
2045+ id : string | number
2046+ title : string
2047+ }
2048+
2049+ const collection = createCollection (
2050+ localStorageCollectionOptions < MixedIdTodo > ( {
2051+ storageKey : `collision-test-todos` ,
2052+ storage : mockStorage ,
2053+ storageEventApi : mockStorageEventApi ,
2054+ getKey : ( todo ) => todo . id ,
2055+ } )
2056+ )
2057+
2058+ const subscription = collection . subscribeChanges ( ( ) => { } )
2059+
2060+ // Insert item with numeric ID 1
2061+ const tx1 = collection . insert ( {
2062+ id : 1 ,
2063+ title : `Numeric 1` ,
2064+ } )
2065+ await tx1 . isPersisted . promise
2066+
2067+ // Insert item with string ID "n:1" (which would collide with old "__number__1" approach)
2068+ const tx2 = collection . insert ( {
2069+ id : `n:1` ,
2070+ title : `String n:1` ,
2071+ } )
2072+ await tx2 . isPersisted . promise
2073+
2074+ // Both should exist in collection
2075+ expect ( collection . has ( 1 ) ) . toBe ( true )
2076+ expect ( collection . has ( `n:1` ) ) . toBe ( true )
2077+ expect ( collection . get ( 1 ) ?. title ) . toBe ( `Numeric 1` )
2078+ expect ( collection . get ( `n:1` ) ?. title ) . toBe ( `String n:1` )
2079+
2080+ // Verify in storage - they should have different encoded keys
2081+ const storedData = mockStorage . getItem ( `collision-test-todos` )
2082+ expect ( storedData ) . toBeDefined ( )
2083+ const parsed = JSON . parse ( storedData ! )
2084+
2085+ // There should be TWO distinct entries
2086+ expect ( Object . keys ( parsed ) . length ) . toBe ( 2 )
2087+ // Numeric 1 → "n:1"
2088+ expect ( parsed [ `n:1` ] ) . toBeDefined ( )
2089+ expect ( parsed [ `n:1` ] . data . title ) . toBe ( `Numeric 1` )
2090+ // String "n:1" → "s:n:1"
2091+ expect ( parsed [ `s:n:1` ] ) . toBeDefined ( )
2092+ expect ( parsed [ `s:n:1` ] . data . title ) . toBe ( `String n:1` )
20392093
20402094 subscription . unsubscribe ( )
20412095 } )
0 commit comments