Skip to content

Commit

Permalink
feat(Entity): Add support for string or number type for ID (#441)
Browse files Browse the repository at this point in the history
  • Loading branch information
brandonroberts authored and MikeRyanDev committed Oct 5, 2017
1 parent b80aa6e commit 46d6f2f
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 34 deletions.
2 changes: 1 addition & 1 deletion modules/entity/src/entity_state.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { EntityState } from './models';
import { EntityState, EntityStateStr, EntityStateNum } from './models';

export function getInitialEntityState<V>(): EntityState<V> {
return {
Expand Down
61 changes: 54 additions & 7 deletions modules/entity/src/models.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,57 @@
export type Comparer<T> = {
export type ComparerStr<T> = {
(a: T, b: T): string;
};

export type ComparerNum<T> = {
(a: T, b: T): number;
};

export type IdSelector<T> = {
export type Comparer<T> = ComparerNum<T> | ComparerStr<T>;

export type IdSelectorStr<T> = {
(model: T): string;
};

export type Dictionary<T> = {
export type IdSelectorNum<T> = {
(model: T): number;
};

export type IdSelector<T> = IdSelectorStr<T> | IdSelectorNum<T>;

export type DictionaryStr<T> = {
[id: string]: T;
};

export type Update<T> = {
export type DictionaryNum<T> = {
[id: number]: T;
};

export type Dictionary<T> = DictionaryStr<T> | DictionaryNum<T>;

export type UpdateStr<T> = {
id: string;
changes: Partial<T>;
};

export interface EntityState<T> {
export type UpdateNum<T> = {
id: number;
changes: Partial<T>;
};

export type Update<T> = UpdateStr<T> | UpdateNum<T>;

export interface EntityStateStr<T> {
ids: string[];
entities: Dictionary<T>;
}

export interface EntityStateNum<T> {
ids: number[];
entities: Dictionary<T>;
}

export type EntityState<T> = EntityStateStr<T> | EntityStateNum<T>;

export interface EntityDefinition<T> {
selectId: IdSelector<T>;
sortComparer: false | Comparer<T>;
Expand All @@ -31,20 +63,35 @@ export interface EntityStateAdapter<T> {
addAll<S extends EntityState<T>>(entities: T[], state: S): S;

removeOne<S extends EntityState<T>>(key: string, state: S): S;
removeOne<S extends EntityState<T>>(key: number, state: S): S;

removeMany<S extends EntityState<T>>(keys: string[], state: S): S;
removeMany<S extends EntityState<T>>(keys: number[], state: S): S;

removeAll<S extends EntityState<T>>(state: S): S;

updateOne<S extends EntityState<T>>(update: Update<T>, state: S): S;
updateMany<S extends EntityState<T>>(updates: Update<T>[], state: S): S;
}

export type EntitySelectors<T, V> = {
selectIds: (state: V) => string[];
export type EntitySelectorsBase<T, V> = {
selectEntities: (state: V) => Dictionary<T>;
selectAll: (state: V) => T[];
selectTotal: (state: V) => number;
};

export interface EntitySelectorsStr<T, V> extends EntitySelectorsBase<T, V> {
selectIds: (state: V) => string[];
}

export interface EntitySelectorsNum<T, V> extends EntitySelectorsBase<T, V> {
selectIds: (state: V) => number[];
}

export type EntitySelectors<T, V> =
| EntitySelectorsNum<T, V>
| EntitySelectorsStr<T, V>;

export interface EntityAdapter<T> extends EntityStateAdapter<T> {
getInitialState(): EntityState<T>;
getInitialState<S extends object>(state: S): EntityState<T> & S;
Expand Down
28 changes: 18 additions & 10 deletions modules/entity/src/sorted_state_adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,30 @@ import { createUnsortedStateAdapter } from './unsorted_state_adapter';
export function createSortedStateAdapter<T>(
selectId: IdSelector<T>,
sort: Comparer<T>
): EntityStateAdapter<T> {
): EntityStateAdapter<T>;
export function createSortedStateAdapter<T>(selectId: any, sort: any): any {
type R = EntityState<T>;

const { removeOne, removeMany, removeAll } = createUnsortedStateAdapter(
selectId
);

function addOneMutably(entity: T, state: R): boolean {
function addOneMutably(entity: T, state: R): boolean;
function addOneMutably(entity: any, state: any): boolean {
return addManyMutably([entity], state);
}

function addManyMutably(newModels: T[], state: R): boolean {
function addManyMutably(newModels: T[], state: R): boolean;
function addManyMutably(newModels: any[], state: any): boolean {
const models = newModels.filter(
model => !(selectId(model) in state.entities)
);

return merge(models, state);
}

function addAllMutably(models: T[], state: R): boolean {
function addAllMutably(models: T[], state: R): boolean;
function addAllMutably(models: any[], state: any): boolean {
state.entities = {};
state.ids = [];

Expand All @@ -40,11 +44,13 @@ export function createSortedStateAdapter<T>(
return true;
}

function updateOneMutably(update: Update<T>, state: R): boolean {
function updateOneMutably(update: Update<T>, state: R): boolean;
function updateOneMutably(update: any, state: any): boolean {
return updateManyMutably([update], state);
}

function takeUpdatedModel(models: T[], update: Update<T>, state: R): void {
function takeUpdatedModel(models: T[], update: Update<T>, state: R): void;
function takeUpdatedModel(models: any[], update: any, state: any): void {
if (!(update.id in state.entities)) {
return;
}
Expand All @@ -57,26 +63,28 @@ export function createSortedStateAdapter<T>(
models.push(updated);
}

function updateManyMutably(updates: Update<T>[], state: R): boolean {
function updateManyMutably(updates: Update<T>[], state: R): boolean;
function updateManyMutably(updates: any[], state: any): boolean {
const models: T[] = [];

updates.forEach(update => takeUpdatedModel(models, update, state));

if (models.length) {
state.ids = state.ids.filter(id => id in state.entities);
state.ids = state.ids.filter((id: any) => id in state.entities);
}

return merge(models, state);
}

function merge(models: T[], state: R): boolean {
function merge(models: T[], state: R): boolean;
function merge(models: any[], state: any): boolean {
if (models.length === 0) {
return false;
}

models.sort(sort);

const ids: string[] = [];
const ids: any[] = [];

let i = 0;
let j = 0;
Expand Down
7 changes: 5 additions & 2 deletions modules/entity/src/state_adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ import { EntityState, EntityStateAdapter } from './models';

export function createStateOperator<V, R>(
mutator: (arg: R, state: EntityState<V>) => boolean
) {
return function operation<S extends EntityState<V>>(arg: R, state: S): S {
): EntityState<V>;
export function createStateOperator<V, R>(
mutator: (arg: any, state: any) => boolean
): any {
return function operation<S extends EntityState<V>>(arg: R, state: any): S {
const clonedEntityState: EntityState<V> = {
ids: [...state.ids],
entities: { ...state.entities },
Expand Down
7 changes: 4 additions & 3 deletions modules/entity/src/state_selectors.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { createSelector } from '@ngrx/store';
import { EntityState, EntitySelectors } from './models';
import { EntityState, EntitySelectors, Dictionary } from './models';

export function createSelectorsFactory<T>() {
return {
getSelectors<V>(
selectState: (state: V) => EntityState<T>
): EntitySelectors<T, V> {
const selectIds = (state: EntityState<T>) => state.ids;
const selectIds = (state: any) => state.ids;
const selectEntities = (state: EntityState<T>) => state.entities;
const selectAll = createSelector(
selectIds,
selectEntities,
(ids, entities) => ids.map(id => entities[id])
(ids: T[], entities: Dictionary<T>): any =>
ids.map((id: any) => (entities as any)[id])
);

const selectTotal = createSelector(selectIds, ids => ids.length);
Expand Down
36 changes: 25 additions & 11 deletions modules/entity/src/unsorted_state_adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import { createStateOperator } from './state_adapter';

export function createUnsortedStateAdapter<T>(
selectId: IdSelector<T>
): EntityStateAdapter<T> {
): EntityStateAdapter<T>;
export function createUnsortedStateAdapter<T>(selectId: IdSelector<T>): any {
type R = EntityState<T>;

function addOneMutably(entity: T, state: R): boolean {
function addOneMutably(entity: T, state: R): boolean;
function addOneMutably(entity: any, state: any): boolean {
const key = selectId(entity);

if (key in state.entities) {
Expand All @@ -19,7 +21,8 @@ export function createUnsortedStateAdapter<T>(
return true;
}

function addManyMutably(entities: T[], state: R): boolean {
function addManyMutably(entities: T[], state: R): boolean;
function addManyMutably(entities: any[], state: any): boolean {
let didMutate = false;

for (let index in entities) {
Expand All @@ -29,7 +32,8 @@ export function createUnsortedStateAdapter<T>(
return didMutate;
}

function addAllMutably(entities: T[], state: R): boolean {
function addAllMutably(entities: T[], state: R): boolean;
function addAllMutably(entities: any[], state: any): boolean {
state.ids = [];
state.entities = {};

Expand All @@ -38,24 +42,27 @@ export function createUnsortedStateAdapter<T>(
return true;
}

function removeOneMutably(key: string, state: R): boolean {
function removeOneMutably(key: T, state: R): boolean;
function removeOneMutably(key: any, state: any): boolean {
return removeManyMutably([key], state);
}

function removeManyMutably(keys: string[], state: R): boolean {
function removeManyMutably(keys: T[], state: R): boolean;
function removeManyMutably(keys: any[], state: any): boolean {
const didMutate =
keys
.filter(key => key in state.entities)
.map(key => delete state.entities[key]).length > 0;

if (didMutate) {
state.ids = state.ids.filter(id => id in state.entities);
state.ids = state.ids.filter((id: any) => id in state.entities);
}

return didMutate;
}

function removeAll<S extends R>(state: S): S {
function removeAll<S extends R>(state: S): S;
function removeAll<S extends R>(state: any): S {
return Object.assign({}, state, {
ids: [],
entities: {},
Expand All @@ -66,6 +73,11 @@ export function createUnsortedStateAdapter<T>(
keys: { [id: string]: string },
update: Update<T>,
state: R
): void;
function takeNewKey(
keys: { [id: string]: any },
update: Update<T>,
state: any
): void {
const original = state.entities[update.id];
const updated: T = Object.assign({}, original, update.changes);
Expand All @@ -79,11 +91,13 @@ export function createUnsortedStateAdapter<T>(
state.entities[newKey] = updated;
}

function updateOneMutably(update: Update<T>, state: R): boolean {
function updateOneMutably(update: Update<T>, state: R): boolean;
function updateOneMutably(update: any, state: any): boolean {
return updateManyMutably([update], state);
}

function updateManyMutably(updates: Update<T>[], state: R): boolean {
function updateManyMutably(updates: Update<T>[], state: R): boolean;
function updateManyMutably(updates: any[], state: any): boolean {
const newKeys: { [id: string]: string } = {};

const didMutate =
Expand All @@ -92,7 +106,7 @@ export function createUnsortedStateAdapter<T>(
.map(update => takeNewKey(newKeys, update, state)).length > 0;

if (didMutate) {
state.ids = state.ids.map(id => newKeys[id] || id);
state.ids = state.ids.map((id: any) => newKeys[id] || id);
}

return didMutate;
Expand Down

0 comments on commit 46d6f2f

Please sign in to comment.