Skip to content

Commit

Permalink
feat(store): add options with strategy param for mergeReferences (#95)
Browse files Browse the repository at this point in the history
  • Loading branch information
huanhuanwa authored Jan 17, 2023
1 parent c7090ed commit 06aedee
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 32 deletions.
17 changes: 14 additions & 3 deletions packages/store/entity-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,20 @@ import { isFunction } from '@tethys/cdk/is';
import { Observable } from 'rxjs';
import { map, shareReplay } from 'rxjs/operators';
import { Action } from './action';
import { buildReferencesKeyBy, mergeReferences, ReferenceArrayExtractAllowKeys, ReferencesIdDictionary } from './references';
import {
buildReferencesKeyBy,
mergeReferences,
MergeReferencesStrategy,
ReferenceArrayExtractAllowKeys,
ReferencesIdDictionary
} from './references';
import { Store } from './store';
import { PaginationInfo, StoreOptions } from './types';
import { coerceArray } from './utils';

export interface EntityStoreOptions<TEntity = unknown, TReferences = unknown> extends ProducerOptions<TEntity>, StoreOptions {
referencesIdKeys?: ReferenceArrayExtractAllowKeys<TReferences>;
mergeReferencesStrategy?: MergeReferencesStrategy;
}

export interface EntityAddOptions {
Expand Down Expand Up @@ -179,7 +186,9 @@ export class EntityStore<TState extends EntityState<TEntity, TReferences>, TEnti
state.entities = produce(state.entities).add(finalAddEntities, addOptions);

if (state.references) {
mergeReferences(state.references, references, this.options.referencesIdKeys);
mergeReferences(state.references, references, this.options.referencesIdKeys, {
strategy: this.options.mergeReferencesStrategy
});
this.buildReferencesIdMap();
}
if (state.pagination) {
Expand Down Expand Up @@ -257,7 +266,9 @@ export class EntityStore<TState extends EntityState<TEntity, TReferences>, TEnti
}
state.entities = [...state.entities];
if (state.references) {
mergeReferences(state.references, references, this.options.referencesIdKeys);
mergeReferences(state.references, references, this.options.referencesIdKeys, {
strategy: this.options.mergeReferencesStrategy
});
this.buildReferencesIdMap();
}
this.next(state);
Expand Down
73 changes: 46 additions & 27 deletions packages/store/references.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ export type ReferenceArrayExtractAllowNames<T> = {
[key in ReferenceArrayExtractNames<T>[keyof T]]: ReferenceExtractNames<T>[key];
};

export enum MergeReferencesStrategy {
ThrowError = 'THROW_ERROR',
Ignore = 'IGNORE',
Append = 'APPEND'
}

/**
* 根据 ReferenceExtractAllowKeys 抽取出的 Object Keys 组合新的对象
* @example ReferenceExtractAllowKeys<{
Expand Down Expand Up @@ -54,54 +60,67 @@ function getReferenceIdKey<TReferences>(referenceKey: string, idKeys: ReferenceA
* @example
* mergeReferences({departments: [{ _id: '1', name: 'name-1'}]}, {departments: [{ _id: '3', name: 'name-3'}]})
* mergeReferences({users: [{ uid: '1', name: 'name-1'}]}, {users: [{ uid: '3', name: 'name-3'}]}, { users: "uid" })
* mergeReferences({users: [{ uid: '1', name: 'name-1'}]}, {users: [{ uid: '3', name: 'name-3'}]}, { users: "uid" }, { strategy: MergeReferencesStrategy.Ignore })
* @param originalReferences original references
* @param references append references
* @param idKeys references 's id key, default is '_id'
* @param options merge configuration. Contains strategy by default, value is 'ThrowError'.
*
* @returns TReferences
*/
export function mergeReferences<TReferences>(
originalReferences: TReferences,
references: Partial<TReferences>,
idKeys?: ReferenceArrayExtractAllowKeys<TReferences>
idKeys?: ReferenceArrayExtractAllowKeys<TReferences>,
options: { strategy: MergeReferencesStrategy } = {
strategy: MergeReferencesStrategy.ThrowError
}
): TReferences {
for (const key in references) {
if (references.hasOwnProperty(key)) {
const reference = references[key];
const referenceIdKey = getReferenceIdKey<TReferences>(key, idKeys);
const originalReference = originalReferences[key];
let originalReference = originalReferences[key];
if (!originalReference) {
throw new Error(`original reference must exist when append new reference: ${key}`);
if (options.strategy === MergeReferencesStrategy.ThrowError) {
throw new Error(`original reference must exist when append new reference: ${key}`);
}
if (options.strategy === MergeReferencesStrategy.Append) {
originalReferences[key] = reference;
originalReference = originalReferences[key];
}
}
if (originalReference instanceof Array) {
// original reference id index map
const originalReferenceIdIndexMap = indexKeyBy<ReferenceExtractAllowKeys<TReferences>>(
originalReferences[key] as any,
referenceIdKey
);
// append reference is array
if (reference instanceof Array) {
reference.forEach((item: TReferences[Extract<keyof TReferences, string>]) => {
const itemId = item[referenceIdKey];
if (originalReference) {
if (originalReference instanceof Array) {
// original reference id index map
const originalReferenceIdIndexMap = indexKeyBy<ReferenceExtractAllowKeys<TReferences>>(
originalReferences[key] as any,
referenceIdKey
);
// append reference is array
if (reference instanceof Array) {
reference.forEach((item: TReferences[Extract<keyof TReferences, string>]) => {
const itemId = item[referenceIdKey];
const index = originalReferenceIdIndexMap[itemId];
if (index >= 0) {
originalReference[index] = { ...originalReference[index], ...item };
} else {
(originalReferences as any)[key] = [...(originalReferences[key] as any), item];
}
});
} else {
// append reference is not array, support append signal object to array reference
const itemId = reference[referenceIdKey];
const index = originalReferenceIdIndexMap[itemId];
if (index >= 0) {
originalReference[index] = { ...originalReference[index], ...item };
if (itemId >= 0) {
originalReference[index] = { ...originalReference[index], ...reference };
} else {
(originalReferences as any)[key] = [...(originalReferences[key] as any), item];
(originalReferences as any)[key] = [...(originalReferences[key] as any), reference];
}
});
} else {
// append reference is not array, support append signal object to array reference
const itemId = reference[referenceIdKey];
const index = originalReferenceIdIndexMap[itemId];
if (itemId >= 0) {
originalReference[index] = { ...originalReference[index], ...reference };
} else {
(originalReferences as any)[key] = [...(originalReferences[key] as any), reference];
}
} else {
originalReferences[key] = { ...originalReferences[key], ...reference };
}
} else {
originalReferences[key] = { ...originalReferences[key], ...reference };
}
}
}
Expand Down
69 changes: 67 additions & 2 deletions packages/store/test/entity-store-refs.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Injectable } from '@angular/core';
import { EntityStore, EntityState, EntityStoreOptions } from '../entity-store';
import { OnCombineRefs, ReferencesIdDictionary } from '../references';
import { MergeReferencesStrategy, OnCombineRefs, ReferencesIdDictionary } from '../references';

describe('Store: EntityStore with refs', () => {
interface UserInfo {
Expand All @@ -23,13 +23,15 @@ describe('Store: EntityStore with refs', () => {
group?: GroupInfo;
created_by: UserInfo;
project?: { _id: string; name: string };
parents: string[];
};
}

interface TaskReferences {
groups?: GroupInfo[];
users?: UserInfo[];
project?: { _id: string; name: string };
parents?: string[];
}

interface TasksState extends EntityState<TaskInfo, TaskReferences> {}
Expand Down Expand Up @@ -233,7 +235,8 @@ describe('Store: EntityStore with refs', () => {
{
referencesIdKeys: {
users: 'uid'
}
},
mergeReferencesStrategy: MergeReferencesStrategy.Ignore
}
);
});
Expand Down Expand Up @@ -345,6 +348,68 @@ describe('Store: EntityStore with refs', () => {
users: initialUsers
});
});

it('should not append new reference when mergeReferencesStrategy is Ignore', () => {
const newTaskEntity = {
_id: '3',
name: 'user 3',
created_by: '1'
};
const newProject = {
_id: '1',
name: 'project'
};
tasksStore.addWithReferences(newTaskEntity, {
project: newProject
});
expect(tasksStore.snapshot.references).toEqual({
groups: initialGroups,
users: initialUsers
});
});

it('should append new reference when mergeReferencesStrategy is Append', () => {
tasksStore = new TasksStore(
{
entities: [...initialTasks],
pagination: {
pageIndex: 1,
pageSize: 10,
count: initialTasks.length
},
references: {
groups: [...initialGroups],
users: [...initialUsers]
}
},
{
referencesIdKeys: {
users: 'uid'
},
mergeReferencesStrategy: MergeReferencesStrategy.Append
}
);
const newTaskEntity = {
_id: '3',
name: 'user 3',
created_by: '1'
};
const newProject = {
_id: '1',
name: 'project'
};
const newParents = ['001', '002'];
tasksStore.addWithReferences(newTaskEntity, {
project: newProject,
parents: newParents
});
expect(tasksStore.snapshot.references).toEqual({
groups: initialGroups,
users: initialUsers,
project: newProject,
parents: newParents
});
});
});

describe('update', () => {
Expand Down

0 comments on commit 06aedee

Please sign in to comment.