Skip to content
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

feat(Entity): Add support for string or number type for entity ID #441

Merged
merged 1 commit into from
Oct 5, 2017
Merged
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
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