Skip to content

Commit

Permalink
feat(redux): full support
Browse files Browse the repository at this point in the history
  • Loading branch information
satanTime committed Jan 2, 2021
1 parent 9142b12 commit aeb3a45
Show file tree
Hide file tree
Showing 8 changed files with 207 additions and 9 deletions.
5 changes: 5 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ export * from './operators/relationships';

export * from './store';

export * from './selectByIds';
export * from './stateKeys';

export {
HANDLER_ENTITY,
HANDLER_ENTITIES,
ENTITY_SELECTOR,
ENTITY_STATE,
FEATURE_SELECTOR,
Expand Down
4 changes: 4 additions & 0 deletions src/selectByIds.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const selectByIds = <S, K, Props = any>(
selector: (state: S, ids: Props) => K,
ids: Props,
): ((state: S) => K) => state => selector(state, ids);
27 changes: 27 additions & 0 deletions src/stateKeys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {ENTITY_STATE, ENTITY_STATE_CUSTOM, STORE_SELECTOR} from './types';

export const stateKeys = <
SELECTOR extends STORE_SELECTOR<any, any>,
STORE extends SELECTOR extends STORE_SELECTOR<infer U, any> ? U : never,
SLICE extends SELECTOR extends STORE_SELECTOR<any, infer U> ? U : never,
E_KEY extends keyof SLICE,
I_KEY extends keyof SLICE,
ENTITY extends SLICE extends ENTITY_STATE_CUSTOM<E_KEY, I_KEY, infer U> ? U : never
>(
selector: SELECTOR,
entitiesKey: E_KEY,
idsKey: I_KEY,
): STORE_SELECTOR<STORE, ENTITY_STATE<ENTITY>> => {
const callback: STORE_SELECTOR<STORE, ENTITY_STATE<ENTITY>> = (s: STORE): ENTITY_STATE<ENTITY> => {
const slice = selector(s);
return {
ids: slice[idsKey],
entities: slice[entitiesKey],
};
};
callback.idsKey = idsKey;
callback.entitiesKey = entitiesKey;
callback.originalSelector = selector;

return callback;
};
17 changes: 11 additions & 6 deletions src/store/injectEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@ export const func = (state: any, selector: ENTITY_SELECTOR, entity: any, meta?:
throw new Error('Cannot detect id of an entity');
}

const originalState = selector.collectionSelector(state);
const originalEntity = originalState.entities[id];
const originalSelector = selector.collectionSelector.originalSelector || selector.collectionSelector;
const entitiesKey = selector.collectionSelector.entitiesKey || 'entities';
const idsKey = selector.collectionSelector.idsKey || 'ids';

// Any because keys may be different because of entitiesKey.
const originalState: any = originalSelector(state);
const originalEntity = originalState[entitiesKey][id];
let featureState = originalState;

// creating a clone
Expand All @@ -33,10 +38,10 @@ export const func = (state: any, selector: ENTITY_SELECTOR, entity: any, meta?:
clone[key] = originalEntity[key];
}

if (featureState.ids.indexOf(id) === -1) {
if (featureState[idsKey] && featureState[idsKey].indexOf(id) === -1) {
featureState = {
...featureState,
ids: [...featureState.ids, id],
[idsKey]: [...featureState[idsKey], id],
};
}

Expand All @@ -50,8 +55,8 @@ export const func = (state: any, selector: ENTITY_SELECTOR, entity: any, meta?:
if (isEntityChanged) {
featureState = {
...featureState,
entities: {
...featureState.entities,
[entitiesKey]: {
...featureState[entitiesKey],
[id]: clone,
},
};
Expand Down
18 changes: 16 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ declare global {

export type UNKNOWN = any;

export type STORE_SELECTOR<T, V> = (state: T) => V;
export type STORE_SELECTOR<T, V> = {
(state: T): V;
idsKey?: keyof any;
entitiesKey?: keyof any;
originalSelector?: (state: T) => V;
};

export type ID_SELECTOR<E> = (entity: E) => ID_TYPES;

Expand All @@ -25,10 +30,19 @@ export type FILTER_PROPS<Base, Condition> = {
[Key in keyof Base]: Base[Key] extends Condition ? Key : never;
}[keyof Base];

export type ENTITY_STATE_CUSTOM<EK extends keyof any, IK extends keyof any, ENTITY> = {
[key in EK]: {
[id in ID_TYPES]?: ENTITY | undefined;
};
} &
{
[key in IK]: Array<ID_TYPES>;
};

export type ENTITY_STATE<E> = {
ids: Array<ID_TYPES>;
entities: {
[id in ID_TYPES]: E | undefined;
[id in ID_TYPES]?: E;
};
};

Expand Down
85 changes: 85 additions & 0 deletions test/e2e/custom-keys.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import {
ngrxEntityRelationshipReducer,
reduceGraph,
relatedEntitySelector,
rootEntitySelector,
stateKeys,
} from 'ngrx-entity-relationship';

describe('custom-keys', () => {
interface Item {
id: string;
name: string;
parentId?: string;
parent?: Item;
}

const state: {
records: {
allIds: Array<string>;
byId: Record<string, Item | undefined>;
};
} = {
records: {
allIds: [],
byId: {},
},
};

const selectItemsState = (s: typeof state) => s.records;
const featureSelector = stateKeys(selectItemsState, 'byId', 'allIds');

const sItem = rootEntitySelector(featureSelector);
const sItemParent = relatedEntitySelector(featureSelector, 'parentId', 'parent');

const selector = sItem(sItemParent());

it('handles custom values correctly', () => {
const action = reduceGraph({
data: [
{
id: 'i1',
name: '1',
parentId: 'i2',
parent: {
id: 'i2',
name: '2',
},
},
],
selector,
});

const reducer = ngrxEntityRelationshipReducer<typeof state>(s => s);
const testingState = reducer(state, action);

// data has been reduced.
expect(testingState).toEqual({
records: {
allIds: ['i1', 'i2'],
byId: {
i1: {
id: 'i1',
name: '1',
parentId: 'i2',
},
i2: {
id: 'i2',
name: '2',
},
},
},
});

// data has been reached.
expect(selector(testingState, 'i1')).toEqual({
id: 'i1',
name: '1',
parentId: 'i2',
parent: {
id: 'i2',
name: '2',
},
});
});
});
58 changes: 58 additions & 0 deletions test/e2e/select-by-ids.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import {relatedEntity, rootEntities, rootEntity, selectByIds} from 'ngrx-entity-relationship';

describe('select-by-ids', () => {
interface Item {
id: string;
name: string;
parentId?: string;
parent?: Item;
}

const state: {
records: {
ids: Array<string>;
entities: Record<string, Item | undefined>;
};
} = {
records: {
ids: ['i1', 'i2'],
entities: {
i1: {
id: 'i1',
name: '1',
parentId: 'i2',
},
i2: {
id: 'i2',
name: '2',
},
},
},
};
const stateSelector = (s: typeof state) => s.records;
const record = rootEntity(stateSelector, relatedEntity(stateSelector, 'parentId', 'parent'));
const records = rootEntities(record);

it('selects right entity', () => {
const selector = selectByIds(record, 'i2');
expect(selector(state)).toEqual({
id: 'i2',
name: '2',
});
});

it('selects right entities', () => {
const selector = selectByIds(records, ['i1']);
expect(selector(state)).toEqual([
{
id: 'i1',
name: '1',
parentId: 'i2',
parent: {
id: 'i2',
name: '2',
},
},
]);
});
});
2 changes: 1 addition & 1 deletion tslint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ rules:
prefer-conditional-expression: true
prefer-const: true
prefer-for-of: true
prefer-method-signature: true
prefer-method-signature: false
prefer-object-spread: true
prefer-readonly: true
prefer-template: true
Expand Down

0 comments on commit aeb3a45

Please sign in to comment.