Skip to content

Commit acaa6ad

Browse files
committed
entity adapter - draftable entity state
improve typing to account for draftable entity state
1 parent e454251 commit acaa6ad

File tree

6 files changed

+389
-373
lines changed

6 files changed

+389
-373
lines changed
Lines changed: 45 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,45 @@
1-
import type {
2-
EntityDefinition,
3-
Comparer,
4-
IdSelector,
5-
EntityAdapter,
6-
} from './models'
7-
import { createInitialStateFactory } from './entity_state'
8-
import { createSelectorsFactory } from './state_selectors'
9-
import { createSortedStateAdapter } from './sorted_state_adapter'
10-
import { createUnsortedStateAdapter } from './unsorted_state_adapter'
11-
12-
/**
13-
*
14-
* @param options
15-
*
16-
* @public
17-
*/
18-
export function createEntityAdapter<T>(
19-
options: {
20-
selectId?: IdSelector<T>
21-
sortComparer?: false | Comparer<T>
22-
} = {}
23-
): EntityAdapter<T> {
24-
const { selectId, sortComparer }: EntityDefinition<T> = {
25-
sortComparer: false,
26-
selectId: (instance: any) => instance.id,
27-
...options,
28-
}
29-
30-
const stateFactory = createInitialStateFactory<T>()
31-
const selectorsFactory = createSelectorsFactory<T>()
32-
const stateAdapter = sortComparer
33-
? createSortedStateAdapter(selectId, sortComparer)
34-
: createUnsortedStateAdapter(selectId)
35-
36-
return {
37-
selectId,
38-
sortComparer,
39-
...stateFactory,
40-
...selectorsFactory,
41-
...stateAdapter,
42-
}
43-
}
1+
import type { Draft } from 'immer'
2+
import type {
3+
EntityDefinition,
4+
Comparer,
5+
IdSelector,
6+
EntityAdapter,
7+
DraftableIdSelector,
8+
} from './models'
9+
import { createInitialStateFactory } from './entity_state'
10+
import { createSelectorsFactory } from './state_selectors'
11+
import { createSortedStateAdapter } from './sorted_state_adapter'
12+
import { createUnsortedStateAdapter } from './unsorted_state_adapter'
13+
14+
/**
15+
*
16+
* @param options
17+
*
18+
* @public
19+
*/
20+
export function createEntityAdapter<T>(
21+
options: {
22+
selectId?: DraftableIdSelector<T>
23+
sortComparer?: false | Comparer<T>
24+
} = {}
25+
): EntityAdapter<T> {
26+
const { selectId, sortComparer }: EntityDefinition<T> = {
27+
sortComparer: false,
28+
selectId: (instance: any) => instance.id,
29+
...options,
30+
}
31+
32+
const stateFactory = createInitialStateFactory<T>()
33+
const selectorsFactory = createSelectorsFactory<T>()
34+
const stateAdapter = sortComparer
35+
? createSortedStateAdapter(selectId, sortComparer)
36+
: createUnsortedStateAdapter(selectId)
37+
38+
return {
39+
selectId,
40+
sortComparer,
41+
...stateFactory,
42+
...selectorsFactory,
43+
...stateAdapter,
44+
}
45+
}

packages/toolkit/src/entities/models.ts

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { Draft } from 'immer'
12
import type { PayloadAction } from '../createAction'
23
import type { IsAny } from '../tsHelpers'
34

@@ -47,99 +48,103 @@ export interface EntityState<T> {
4748
* @public
4849
*/
4950
export interface EntityDefinition<T> {
50-
selectId: IdSelector<T>
51+
selectId: IdSelector<T | Draft<T>>
5152
sortComparer: false | Comparer<T>
5253
}
5354

5455
export type PreventAny<S, T> = IsAny<S, EntityState<T>, S>
5556

57+
export type DraftableEntityState<T> = EntityState<T> | Draft<EntityState<T>>
58+
59+
export type DraftableIdSelector<T> = IdSelector<T | Draft<T>>
60+
5661
/**
5762
* @public
5863
*/
5964
export interface EntityStateAdapter<T> {
60-
addOne<S extends EntityState<T>>(state: PreventAny<S, T>, entity: T): S
61-
addOne<S extends EntityState<T>>(
65+
addOne<S extends DraftableEntityState<T>>(state: PreventAny<S, T>, entity: T): S
66+
addOne<S extends DraftableEntityState<T>>(
6267
state: PreventAny<S, T>,
6368
action: PayloadAction<T>
6469
): S
6570

66-
addMany<S extends EntityState<T>>(
71+
addMany<S extends DraftableEntityState<T>>(
6772
state: PreventAny<S, T>,
6873
entities: readonly T[] | Record<EntityId, T>
6974
): S
70-
addMany<S extends EntityState<T>>(
75+
addMany<S extends DraftableEntityState<T>>(
7176
state: PreventAny<S, T>,
7277
entities: PayloadAction<readonly T[] | Record<EntityId, T>>
7378
): S
7479

75-
setOne<S extends EntityState<T>>(state: PreventAny<S, T>, entity: T): S
76-
setOne<S extends EntityState<T>>(
80+
setOne<S extends DraftableEntityState<T>>(state: PreventAny<S, T>, entity: T): S
81+
setOne<S extends DraftableEntityState<T>>(
7782
state: PreventAny<S, T>,
7883
action: PayloadAction<T>
7984
): S
80-
setMany<S extends EntityState<T>>(
85+
setMany<S extends DraftableEntityState<T>>(
8186
state: PreventAny<S, T>,
8287
entities: readonly T[] | Record<EntityId, T>
8388
): S
84-
setMany<S extends EntityState<T>>(
89+
setMany<S extends DraftableEntityState<T>>(
8590
state: PreventAny<S, T>,
8691
entities: PayloadAction<readonly T[] | Record<EntityId, T>>
8792
): S
88-
setAll<S extends EntityState<T>>(
93+
setAll<S extends DraftableEntityState<T>>(
8994
state: PreventAny<S, T>,
9095
entities: readonly T[] | Record<EntityId, T>
9196
): S
92-
setAll<S extends EntityState<T>>(
97+
setAll<S extends DraftableEntityState<T>>(
9398
state: PreventAny<S, T>,
9499
entities: PayloadAction<readonly T[] | Record<EntityId, T>>
95100
): S
96101

97-
removeOne<S extends EntityState<T>>(state: PreventAny<S, T>, key: EntityId): S
98-
removeOne<S extends EntityState<T>>(
102+
removeOne<S extends DraftableEntityState<T>>(state: PreventAny<S, T>, key: EntityId): S
103+
removeOne<S extends DraftableEntityState<T>>(
99104
state: PreventAny<S, T>,
100105
key: PayloadAction<EntityId>
101106
): S
102107

103-
removeMany<S extends EntityState<T>>(
108+
removeMany<S extends DraftableEntityState<T>>(
104109
state: PreventAny<S, T>,
105110
keys: readonly EntityId[]
106111
): S
107-
removeMany<S extends EntityState<T>>(
112+
removeMany<S extends DraftableEntityState<T>>(
108113
state: PreventAny<S, T>,
109114
keys: PayloadAction<readonly EntityId[]>
110115
): S
111116

112-
removeAll<S extends EntityState<T>>(state: PreventAny<S, T>): S
117+
removeAll<S extends DraftableEntityState<T>>(state: PreventAny<S, T>): S
113118

114-
updateOne<S extends EntityState<T>>(
119+
updateOne<S extends DraftableEntityState<T>>(
115120
state: PreventAny<S, T>,
116121
update: Update<T>
117122
): S
118-
updateOne<S extends EntityState<T>>(
123+
updateOne<S extends DraftableEntityState<T>>(
119124
state: PreventAny<S, T>,
120125
update: PayloadAction<Update<T>>
121126
): S
122127

123-
updateMany<S extends EntityState<T>>(
128+
updateMany<S extends DraftableEntityState<T>>(
124129
state: PreventAny<S, T>,
125130
updates: ReadonlyArray<Update<T>>
126131
): S
127-
updateMany<S extends EntityState<T>>(
132+
updateMany<S extends DraftableEntityState<T>>(
128133
state: PreventAny<S, T>,
129134
updates: PayloadAction<ReadonlyArray<Update<T>>>
130135
): S
131136

132-
upsertOne<S extends EntityState<T>>(state: PreventAny<S, T>, entity: T): S
133-
upsertOne<S extends EntityState<T>>(
137+
upsertOne<S extends DraftableEntityState<T>>(state: PreventAny<S, T>, entity: T): S
138+
upsertOne<S extends DraftableEntityState<T>>(
134139
state: PreventAny<S, T>,
135140
entity: PayloadAction<T>
136141
): S
137142

138-
upsertMany<S extends EntityState<T>>(
143+
upsertMany<S extends DraftableEntityState<T>>(
139144
state: PreventAny<S, T>,
140145
entities: readonly T[] | Record<EntityId, T>
141146
): S
142-
upsertMany<S extends EntityState<T>>(
147+
upsertMany<S extends DraftableEntityState<T>>(
143148
state: PreventAny<S, T>,
144149
entities: PayloadAction<readonly T[] | Record<EntityId, T>>
145150
): S

packages/toolkit/src/entities/sorted_state_adapter.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
import type {Draft} from 'immer'
12
import type {
23
EntityState,
34
IdSelector,
45
Comparer,
56
EntityStateAdapter,
67
Update,
78
EntityId,
9+
DraftableEntityState,
10+
DraftableIdSelector,
811
} from './models'
912
import { createStateOperator } from './state_adapter'
1013
import { createUnsortedStateAdapter } from './unsorted_state_adapter'
@@ -15,10 +18,10 @@ import {
1518
} from './utils'
1619

1720
export function createSortedStateAdapter<T>(
18-
selectId: IdSelector<T>,
21+
selectId: DraftableIdSelector<T>,
1922
sort: Comparer<T>
2023
): EntityStateAdapter<T> {
21-
type R = EntityState<T>
24+
type R = DraftableEntityState<T>
2225

2326
const { removeOne, removeMany, removeAll } =
2427
createUnsortedStateAdapter(selectId)
Lines changed: 60 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,60 @@
1-
import createNextState, { isDraft } from 'immer'
2-
import type { EntityState, PreventAny } from './models'
3-
import type { PayloadAction } from '../createAction'
4-
import { isFSA } from '../createAction'
5-
import { IsAny } from '../tsHelpers'
6-
7-
export function createSingleArgumentStateOperator<V>(
8-
mutator: (state: EntityState<V>) => void
9-
) {
10-
const operator = createStateOperator((_: undefined, state: EntityState<V>) =>
11-
mutator(state)
12-
)
13-
14-
return function operation<S extends EntityState<V>>(
15-
state: PreventAny<S, V>
16-
): S {
17-
return operator(state as S, undefined)
18-
}
19-
}
20-
21-
export function createStateOperator<V, R>(
22-
mutator: (arg: R, state: EntityState<V>) => void
23-
) {
24-
return function operation<S extends EntityState<V>>(
25-
state: S,
26-
arg: R | PayloadAction<R>
27-
): S {
28-
function isPayloadActionArgument(
29-
arg: R | PayloadAction<R>
30-
): arg is PayloadAction<R> {
31-
return isFSA(arg)
32-
}
33-
34-
const runMutator = (draft: EntityState<V>) => {
35-
if (isPayloadActionArgument(arg)) {
36-
mutator(arg.payload, draft)
37-
} else {
38-
mutator(arg, draft)
39-
}
40-
}
41-
42-
if (isDraft(state)) {
43-
// we must already be inside a `createNextState` call, likely because
44-
// this is being wrapped in `createReducer` or `createSlice`.
45-
// It's safe to just pass the draft to the mutator.
46-
runMutator(state)
47-
48-
// since it's a draft, we'll just return it
49-
return state
50-
} else {
51-
// @ts-ignore createNextState() produces an Immutable<Draft<S>> rather
52-
// than an Immutable<S>, and TypeScript cannot find out how to reconcile
53-
// these two types.
54-
return createNextState(state, runMutator)
55-
}
56-
}
57-
}
1+
import createNextState, { isDraft } from 'immer'
2+
import type { Draft } from 'immer'
3+
import type { DraftableEntityState, EntityState, PreventAny } from './models'
4+
import type { PayloadAction } from '../createAction'
5+
import { isFSA } from '../createAction'
6+
import { IsAny } from '../tsHelpers'
7+
8+
export const isDraftTyped = isDraft as <T>(value: any) => value is Draft<T>
9+
10+
export function createSingleArgumentStateOperator<V>(
11+
mutator: (state: DraftableEntityState<V>) => void
12+
) {
13+
const operator = createStateOperator((_: undefined, state: DraftableEntityState<V>) =>
14+
mutator(state)
15+
)
16+
17+
return function operation<S extends DraftableEntityState<V>>(
18+
state: PreventAny<S, V>
19+
): S {
20+
return operator(state as S, undefined)
21+
}
22+
}
23+
24+
export function createStateOperator<V, R>(
25+
mutator: (arg: R, state: DraftableEntityState<V>) => void
26+
) {
27+
return function operation<S extends DraftableEntityState<V>>(
28+
state: S,
29+
arg: R | PayloadAction<R>
30+
): S {
31+
function isPayloadActionArgument(
32+
arg: R | PayloadAction<R>
33+
): arg is PayloadAction<R> {
34+
return isFSA(arg)
35+
}
36+
37+
const runMutator = (draft: DraftableEntityState<V>) => {
38+
if (isPayloadActionArgument(arg)) {
39+
mutator(arg.payload, draft)
40+
} else {
41+
mutator(arg, draft)
42+
}
43+
}
44+
45+
if (isDraftTyped<EntityState<V>>(state)) {
46+
// we must already be inside a `createNextState` call, likely because
47+
// this is being wrapped in `createReducer` or `createSlice`.
48+
// It's safe to just pass the draft to the mutator.
49+
runMutator(state)
50+
51+
// since it's a draft, we'll just return it
52+
return state
53+
} else {
54+
// @ts-ignore createNextState() produces an Immutable<Draft<S>> rather
55+
// than an Immutable<S>, and TypeScript cannot find out how to reconcile
56+
// these two types.
57+
return createNextState(state, runMutator)
58+
}
59+
}
60+
}

0 commit comments

Comments
 (0)