1+ import { current , isDraft } from 'immer'
12import type {
23 IdSelector ,
34 Comparer ,
@@ -12,11 +13,46 @@ import {
1213 selectIdValue ,
1314 ensureEntitiesArray ,
1415 splitAddedUpdatedEntities ,
16+ getCurrent ,
1517} from './utils'
1618
19+ // Borrowed from Replay
20+ export function findInsertIndex < T > (
21+ sortedItems : T [ ] ,
22+ item : T ,
23+ comparisonFunction : Comparer < T > ,
24+ ) : number {
25+ let lowIndex = 0
26+ let highIndex = sortedItems . length
27+ while ( lowIndex < highIndex ) {
28+ let middleIndex = ( lowIndex + highIndex ) >>> 1
29+ const currentItem = sortedItems [ middleIndex ]
30+ const res = comparisonFunction ( item , currentItem )
31+ if ( res >= 0 ) {
32+ lowIndex = middleIndex + 1
33+ } else {
34+ highIndex = middleIndex
35+ }
36+ }
37+
38+ return lowIndex
39+ }
40+
41+ export function insert < T > (
42+ sortedItems : T [ ] ,
43+ item : T ,
44+ comparisonFunction : Comparer < T > ,
45+ ) : T [ ] {
46+ const insertAtIndex = findInsertIndex ( sortedItems , item , comparisonFunction )
47+
48+ sortedItems . splice ( insertAtIndex , 0 , item )
49+
50+ return sortedItems
51+ }
52+
1753export function createSortedStateAdapter < T , Id extends EntityId > (
1854 selectId : IdSelector < T , Id > ,
19- sort : Comparer < T > ,
55+ comparer : Comparer < T > ,
2056) : EntityStateAdapter < T , Id > {
2157 type R = DraftableEntityState < T , Id >
2258
@@ -30,15 +66,20 @@ export function createSortedStateAdapter<T, Id extends EntityId>(
3066 function addManyMutably (
3167 newEntities : readonly T [ ] | Record < Id , T > ,
3268 state : R ,
69+ existingIds ?: Id [ ] ,
3370 ) : void {
3471 newEntities = ensureEntitiesArray ( newEntities )
3572
73+ const existingKeys = new Set < Id > (
74+ existingIds ?? ( current ( state . ids ) as Id [ ] ) ,
75+ )
76+
3677 const models = newEntities . filter (
37- ( model ) => ! ( selectIdValue ( model , selectId ) in state . entities ) ,
78+ ( model ) => ! existingKeys . has ( selectIdValue ( model , selectId ) ) ,
3879 )
3980
4081 if ( models . length !== 0 ) {
41- merge ( models , state )
82+ mergeFunction ( state , models )
4283 }
4384 }
4485
@@ -52,7 +93,10 @@ export function createSortedStateAdapter<T, Id extends EntityId>(
5293 ) : void {
5394 newEntities = ensureEntitiesArray ( newEntities )
5495 if ( newEntities . length !== 0 ) {
55- merge ( newEntities , state )
96+ for ( const item of newEntities ) {
97+ delete ( state . entities as Record < Id , T > ) [ selectId ( item ) ]
98+ }
99+ mergeFunction ( state , newEntities )
56100 }
57101 }
58102
@@ -64,7 +108,7 @@ export function createSortedStateAdapter<T, Id extends EntityId>(
64108 state . entities = { } as Record < Id , T >
65109 state . ids = [ ]
66110
67- addManyMutably ( newEntities , state )
111+ addManyMutably ( newEntities , state , [ ] )
68112 }
69113
70114 function updateOneMutably ( update : Update < T , Id > , state : R ) : void {
@@ -76,6 +120,7 @@ export function createSortedStateAdapter<T, Id extends EntityId>(
76120 state : R ,
77121 ) : void {
78122 let appliedUpdates = false
123+ let replacedIds = false
79124
80125 for ( let update of updates ) {
81126 const entity : T | undefined = ( state . entities as Record < Id , T > ) [ update . id ]
@@ -87,14 +132,20 @@ export function createSortedStateAdapter<T, Id extends EntityId>(
87132
88133 Object . assign ( entity , update . changes )
89134 const newId = selectId ( entity )
135+
90136 if ( update . id !== newId ) {
137+ // We do support the case where updates can change an item's ID.
138+ // This makes things trickier - go ahead and swap the IDs in state now.
139+ replacedIds = true
91140 delete ( state . entities as Record < Id , T > ) [ update . id ]
141+ const oldIndex = ( state . ids as Id [ ] ) . indexOf ( update . id )
142+ state . ids [ oldIndex ] = newId
92143 ; ( state . entities as Record < Id , T > ) [ newId ] = entity
93144 }
94145 }
95146
96147 if ( appliedUpdates ) {
97- resortEntities ( state )
148+ mergeFunction ( state , [ ] , appliedUpdates , replacedIds )
98149 }
99150 }
100151
@@ -106,14 +157,18 @@ export function createSortedStateAdapter<T, Id extends EntityId>(
106157 newEntities : readonly T [ ] | Record < Id , T > ,
107158 state : R ,
108159 ) : void {
109- const [ added , updated ] = splitAddedUpdatedEntities < T , Id > (
160+ const [ added , updated , existingIdsArray ] = splitAddedUpdatedEntities < T , Id > (
110161 newEntities ,
111162 selectId ,
112163 state ,
113164 )
114165
115- updateManyMutably ( updated , state )
116- addManyMutably ( added , state )
166+ if ( updated . length ) {
167+ updateManyMutably ( updated , state )
168+ }
169+ if ( added . length ) {
170+ addManyMutably ( added , state , existingIdsArray )
171+ }
117172 }
118173
119174 function areArraysEqual ( a : readonly unknown [ ] , b : readonly unknown [ ] ) {
@@ -130,27 +185,65 @@ export function createSortedStateAdapter<T, Id extends EntityId>(
130185 return true
131186 }
132187
133- function merge ( models : readonly T [ ] , state : R ) : void {
188+ type MergeFunction = (
189+ state : R ,
190+ addedItems : readonly T [ ] ,
191+ appliedUpdates ?: boolean ,
192+ replacedIds ?: boolean ,
193+ ) => void
194+
195+ const mergeInsertion : MergeFunction = (
196+ state ,
197+ addedItems ,
198+ appliedUpdates ,
199+ replacedIds ,
200+ ) => {
201+ const currentEntities = getCurrent ( state . entities ) as Record < Id , T >
202+ const currentIds = getCurrent ( state . ids ) as Id [ ]
203+
204+ const stateEntities = state . entities as Record < Id , T >
205+
206+ let ids = currentIds
207+ if ( replacedIds ) {
208+ ids = Array . from ( new Set ( currentIds ) )
209+ }
210+
211+ let sortedEntities : T [ ] = [ ]
212+ for ( const id of ids ) {
213+ const entity = currentEntities [ id ]
214+ if ( entity ) {
215+ sortedEntities . push ( entity )
216+ }
217+ }
218+ const wasPreviouslyEmpty = sortedEntities . length === 0
219+
134220 // Insert/overwrite all new/updated
135- models . forEach ( ( model ) => {
136- ; ( state . entities as Record < Id , T > ) [ selectId ( model ) ] = model
137- } )
221+ for ( const item of addedItems ) {
222+ stateEntities [ selectId ( item ) ] = item
138223
139- resortEntities ( state )
140- }
224+ if ( ! wasPreviouslyEmpty ) {
225+ // Binary search insertion generally requires fewer comparisons
226+ insert ( sortedEntities , item , comparer )
227+ }
228+ }
141229
142- function resortEntities ( state : R ) {
143- const allEntities = Object . values ( state . entities ) as T [ ]
144- allEntities . sort ( sort )
230+ if ( wasPreviouslyEmpty ) {
231+ // All we have is the incoming values, sort them
232+ sortedEntities = addedItems . slice ( ) . sort ( comparer )
233+ } else if ( appliedUpdates ) {
234+ // We should have a _mostly_-sorted array already
235+ sortedEntities . sort ( comparer )
236+ }
145237
146- const newSortedIds = allEntities . map ( selectId )
147- const { ids } = state
238+ const newSortedIds = sortedEntities . map ( selectId )
148239
149- if ( ! areArraysEqual ( ids , newSortedIds ) ) {
240+ if ( ! areArraysEqual ( currentIds , newSortedIds ) ) {
150241 state . ids = newSortedIds
151242 }
152243 }
153244
245+ const mergeFunction : MergeFunction = mergeInsertion
246+
154247 return {
155248 removeOne,
156249 removeMany,
0 commit comments