Skip to content

experiment: specific entityId type #841

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 21 additions & 4 deletions src/entities/create_adapter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { EntityDefinition, Comparer, IdSelector, EntityAdapter } from './models'
import {
EntityDefinition,
Comparer,
IdSelector,
EntityAdapter,
EntityId
} from './models'
import { createInitialStateFactory } from './entity_state'
import { createSelectorsFactory } from './state_selectors'
import { createSortedStateAdapter } from './sorted_state_adapter'
Expand All @@ -10,12 +16,23 @@ import { createUnsortedStateAdapter } from './unsorted_state_adapter'
*
* @public
*/
export function createEntityAdapter<T>(
export function createEntityAdapter<
T extends { id: EntityId },
IdType extends EntityId = T['id']
>(options?: {
selectId?: IdSelector<T, IdType>
sortComparer?: false | Comparer<T>
}): EntityAdapter<T, IdType>
export function createEntityAdapter<T, IdType extends EntityId>(options: {
selectId: IdSelector<T, IdType>
sortComparer?: false | Comparer<T>
}): EntityAdapter<T, IdType>
export function createEntityAdapter<T, IdType extends EntityId>(
options: {
selectId?: IdSelector<T>
selectId?: IdSelector<T, IdType>
sortComparer?: false | Comparer<T>
} = {}
): EntityAdapter<T> {
): EntityAdapter<T, EntityId> {
const { selectId, sortComparer }: EntityDefinition<T> = {
sortComparer: false,
selectId: (instance: any) => instance.id,
Expand Down
2 changes: 1 addition & 1 deletion src/entities/entity_state.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { createSlice } from '../createSlice'
import { BookModel } from './fixtures/book'

describe('Entity State', () => {
let adapter: EntityAdapter<BookModel>
let adapter: EntityAdapter<BookModel, string>

beforeEach(() => {
adapter = createEntityAdapter({
Expand Down
147 changes: 84 additions & 63 deletions src/entities/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ export type Comparer<T> = (a: T, b: T) => number
/**
* @public
*/
export type IdSelector<T> = (model: T) => EntityId
export type IdSelector<T, IdType extends EntityId = EntityId> = (
model: T
) => IdType

/**
* @public
Expand All @@ -33,126 +35,145 @@ export interface Dictionary<T> extends DictionaryNum<T> {
/**
* @public
*/
export type Update<T> = { id: EntityId; changes: Partial<T> }
export type Update<T, IdType extends EntityId = EntityId> = {
id: IdType
changes: Partial<T>
}

/**
* @public
*/
export interface EntityState<T> {
ids: EntityId[]
export interface EntityState<T, IdType extends EntityId = EntityId> {
ids: IdType[]
entities: Dictionary<T>
}

/**
* @public
*/
export interface EntityDefinition<T> {
selectId: IdSelector<T>
export interface EntityDefinition<T, IdType extends EntityId = EntityId> {
selectId: IdSelector<T, IdType>
sortComparer: false | Comparer<T>
}

export type PreventAny<S, T> = IsAny<S, EntityState<T>, S>
export type PreventAny<S, T, IdType extends EntityId = EntityId> = IsAny<
S,
EntityState<T, IdType>,
S
>

/**
* @public
*/
export interface EntityStateAdapter<T> {
addOne<S extends EntityState<T>>(state: PreventAny<S, T>, entity: T): S
addOne<S extends EntityState<T>>(
state: PreventAny<S, T>,
export interface EntityStateAdapter<T, IdType extends EntityId = EntityId> {
addOne<S extends EntityState<T, IdType>>(
state: PreventAny<S, T, IdType>,
entity: T
): S
addOne<S extends EntityState<T, IdType>>(
state: PreventAny<S, T, IdType>,
action: PayloadAction<T>
): S

addMany<S extends EntityState<T>>(
state: PreventAny<S, T>,
entities: T[] | Record<EntityId, T>
addMany<S extends EntityState<T, IdType>>(
state: PreventAny<S, T, IdType>,
entities: T[] | Record<IdType, T>
): S
addMany<S extends EntityState<T>>(
state: PreventAny<S, T>,
entities: PayloadAction<T[] | Record<EntityId, T>>
addMany<S extends EntityState<T, IdType>>(
state: PreventAny<S, T, IdType>,
entities: PayloadAction<T[] | Record<IdType, T>>
): S

setAll<S extends EntityState<T>>(
state: PreventAny<S, T>,
entities: T[] | Record<EntityId, T>
setAll<S extends EntityState<T, IdType>>(
state: PreventAny<S, T, IdType>,
entities: T[] | Record<IdType, T>
): S
setAll<S extends EntityState<T>>(
state: PreventAny<S, T>,
entities: PayloadAction<T[] | Record<EntityId, T>>
setAll<S extends EntityState<T, IdType>>(
state: PreventAny<S, T, IdType>,
entities: PayloadAction<T[] | Record<IdType, T>>
): S

removeOne<S extends EntityState<T>>(state: PreventAny<S, T>, key: EntityId): S
removeOne<S extends EntityState<T>>(
state: PreventAny<S, T>,
key: PayloadAction<EntityId>
removeOne<S extends EntityState<T, IdType>>(
state: PreventAny<S, T, IdType>,
key: IdType
): S
removeOne<S extends EntityState<T, IdType>>(
state: PreventAny<S, T, IdType>,
key: PayloadAction<IdType>
): S

removeMany<S extends EntityState<T>>(
state: PreventAny<S, T>,
keys: EntityId[]
removeMany<S extends EntityState<T, IdType>>(
state: PreventAny<S, T, IdType>,
keys: IdType[]
): S
removeMany<S extends EntityState<T>>(
state: PreventAny<S, T>,
keys: PayloadAction<EntityId[]>
removeMany<S extends EntityState<T, IdType>>(
state: PreventAny<S, T, IdType>,
keys: PayloadAction<IdType[]>
): S

removeAll<S extends EntityState<T>>(state: PreventAny<S, T>): S
removeAll<S extends EntityState<T, IdType>>(
state: PreventAny<S, T, IdType>
): S

updateOne<S extends EntityState<T>>(
state: PreventAny<S, T>,
update: Update<T>
updateOne<S extends EntityState<T, IdType>>(
state: PreventAny<S, T, IdType>,
update: Update<T, IdType>
): S
updateOne<S extends EntityState<T>>(
state: PreventAny<S, T>,
update: PayloadAction<Update<T>>
updateOne<S extends EntityState<T, IdType>>(
state: PreventAny<S, T, IdType>,
update: PayloadAction<Update<T, IdType>>
): S

updateMany<S extends EntityState<T>>(
state: PreventAny<S, T>,
updates: Update<T>[]
updateMany<S extends EntityState<T, IdType>>(
state: PreventAny<S, T, IdType>,
updates: Update<T, IdType>[]
): S
updateMany<S extends EntityState<T>>(
state: PreventAny<S, T>,
updates: PayloadAction<Update<T>[]>
updateMany<S extends EntityState<T, IdType>>(
state: PreventAny<S, T, IdType>,
updates: PayloadAction<Update<T, IdType>[]>
): S

upsertOne<S extends EntityState<T>>(state: PreventAny<S, T>, entity: T): S
upsertOne<S extends EntityState<T>>(
state: PreventAny<S, T>,
upsertOne<S extends EntityState<T, IdType>>(
state: PreventAny<S, T, IdType>,
entity: T
): S
upsertOne<S extends EntityState<T, IdType>>(
state: PreventAny<S, T, IdType>,
entity: PayloadAction<T>
): S

upsertMany<S extends EntityState<T>>(
state: PreventAny<S, T>,
upsertMany<S extends EntityState<T, IdType>>(
state: PreventAny<S, T, IdType>,
entities: T[] | Record<EntityId, T>
): S
upsertMany<S extends EntityState<T>>(
state: PreventAny<S, T>,
upsertMany<S extends EntityState<T, IdType>>(
state: PreventAny<S, T, IdType>,
entities: PayloadAction<T[] | Record<EntityId, T>>
): S
}

/**
* @public
*/
export interface EntitySelectors<T, V> {
selectIds: (state: V) => EntityId[]
export interface EntitySelectors<T, V, IdType extends EntityId = EntityId> {
selectIds: (state: V) => IdType[]
selectEntities: (state: V) => Dictionary<T>
selectAll: (state: V) => T[]
selectTotal: (state: V) => number
selectById: (state: V, id: EntityId) => T | undefined
selectById: (state: V, id: IdType) => T | undefined
}

/**
* @public
*/
export interface EntityAdapter<T> extends EntityStateAdapter<T> {
selectId: IdSelector<T>
export interface EntityAdapter<T, IdType extends EntityId = EntityId>
extends EntityStateAdapter<T> {
selectId: IdSelector<T, IdType>
sortComparer: false | Comparer<T>
getInitialState(): EntityState<T>
getInitialState<S extends object>(state: S): EntityState<T> & S
getSelectors(): EntitySelectors<T, EntityState<T>>
getInitialState(): EntityState<T, IdType>
getInitialState<S extends object>(state: S): EntityState<T, IdType> & S
getSelectors(): EntitySelectors<T, EntityState<T, IdType>, IdType>
getSelectors<V>(
selectState: (state: V) => EntityState<T>
): EntitySelectors<T, V>
selectState: (state: V) => EntityState<T, IdType>
): EntitySelectors<T, V, IdType>
}
35 changes: 24 additions & 11 deletions type-tests/files/createEntityAdapter.typetest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ import {
} from '@reduxjs/toolkit'
import { expectType } from './helpers'

function extractReducers<T>(
adapter: EntityAdapter<T>
): Omit<EntityStateAdapter<T>, 'map'> {
type SpecificEntityId = 1 | 3 | 'five'

function extractReducers<T, Id extends EntityId>(
adapter: EntityAdapter<T, Id>
): Omit<EntityStateAdapter<T, Id>, 'map'> {
const {
selectId,
sortComparer,
Expand All @@ -28,6 +30,7 @@ function extractReducers<T>(
*/
{
type Entity = {
id: SpecificEntityId
value: string
}
const adapter = createEntityAdapter<Entity>()
Expand All @@ -40,17 +43,23 @@ function extractReducers<T>(
})

expectType<ActionCreatorWithPayload<Entity>>(slice.actions.addOne)
expectType<ActionCreatorWithPayload<Entity[] | Record<string, Entity>>>(
slice.actions.addMany
expectType<
ActionCreatorWithPayload<Entity[] | Record<SpecificEntityId, Entity>>
>(slice.actions.addMany)
expectType<
ActionCreatorWithPayload<Entity[] | Record<SpecificEntityId, Entity>>
>(slice.actions.setAll)
expectType<ActionCreatorWithPayload<SpecificEntityId>>(
slice.actions.removeOne
)
expectType<ActionCreatorWithPayload<Entity[] | Record<string, Entity>>>(
slice.actions.setAll
expectType<ActionCreatorWithPayload<SpecificEntityId[]>>(
slice.actions.removeMany
)
expectType<ActionCreatorWithPayload<EntityId>>(slice.actions.removeOne)
expectType<ActionCreatorWithPayload<EntityId[]>>(slice.actions.removeMany)
expectType<ActionCreatorWithoutPayload>(slice.actions.removeAll)
expectType<ActionCreatorWithPayload<Update<Entity>>>(slice.actions.updateOne)
expectType<ActionCreatorWithPayload<Update<Entity>[]>>(
expectType<ActionCreatorWithPayload<Update<Entity, SpecificEntityId>>>(
slice.actions.updateOne
)
expectType<ActionCreatorWithPayload<Update<Entity, SpecificEntityId>[]>>(
slice.actions.updateMany
)
expectType<ActionCreatorWithPayload<Entity>>(slice.actions.upsertOne)
Expand All @@ -64,9 +73,11 @@ function extractReducers<T>(
*/
{
type Entity = {
id: string
value: string
}
type Entity2 = {
id: string
value2: string
}
const adapter = createEntityAdapter<Entity>()
Expand All @@ -87,6 +98,7 @@ function extractReducers<T>(
*/
{
type Entity = {
id: SpecificEntityId
value: string
}
const adapter = createEntityAdapter<Entity>()
Expand All @@ -104,6 +116,7 @@ function extractReducers<T>(
*/
{
type Entity = {
id: SpecificEntityId
value: string
}
const adapter = createEntityAdapter<Entity>()
Expand Down