Skip to content

Commit

Permalink
fix: PrimaryKeyCollection not include idIndex when buildIndex is called
Browse files Browse the repository at this point in the history
Summary:

Test Plan:
  • Loading branch information
Tangent Lin committed Mar 19, 2024
1 parent 5c02887 commit 5cae086
Show file tree
Hide file tree
Showing 15 changed files with 394 additions and 57 deletions.
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ module.exports = {
collectCoverage: true,
verbose: true,
coverageReporters: ['cobertura', 'html', 'lcov'],
coveragePathIgnorePatterns: ['index.ts$'],
testTimeout: 500,
};
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "indexed-collection",
"version": "1.6.0",
"version": "1.8.0",
"description": "A zero-dependency library of classes that make filtering, sorting and observing changes to arrays easier and more efficient.",
"license": "MIT",
"keywords": [
Expand Down
53 changes: 41 additions & 12 deletions src/collections/CollectionViewBase.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,30 @@
import { ICollectionAndView } from '../core/ICollectionAndView';
import { ICollectionViewOption } from '../core/ICollectionViewOption';
import { IReadonlyCollection } from '../core/IReadonlyCollection';
import { Optional } from '../core/Optional';
import { CollectionChangeSignal } from '../signals/CollectionChangeSignal';
import { SignalObserver } from '../signals/SignalObserver';
import { ICollectionViewOption } from '../core/ICollectionViewOption';
import {
defaultSort,
defaultCollectionViewOption,
defaultSort,
} from '../core/defaultCollectionViewOption';
import { ICollectionAndView } from '../core/ICollectionAndView';
import { CollectionAddSignal } from '../signals/CollectionAddSignal';
import { CollectionChangeSignal } from '../signals/CollectionChangeSignal';
import { CollectionRemoveSignal } from '../signals/CollectionRemoveSignal';
import { CollectionUpdateSignal } from '../signals/CollectionUpdateSignal';
import { SignalObserver } from '../signals/SignalObserver';
import { filterCollectionChangeDetail } from './util';

/**
* CollectionView is a view onto a collection of data. Most common use case would be
* having the collection reduced by filter and/or sorted according to various criteria
* without modifying the underlying data.
*/
export abstract class CollectionViewBase<
T,
SourceCollectionT extends ICollectionAndView<T>
> extends SignalObserver implements IReadonlyCollection<T> {
T,
SourceCollectionT extends ICollectionAndView<T>
>
extends SignalObserver
implements IReadonlyCollection<T>
{
private readonly _source: SourceCollectionT;
private readonly _option: ICollectionViewOption<T>;
private _cachedItems: T[] = [];
Expand Down Expand Up @@ -66,13 +73,35 @@ export abstract class CollectionViewBase<
return this.filter(item) ? item : undefined;
}

protected source_onChange(): void {
protected source_onChange(signal: CollectionChangeSignal<T>): void {
this.rebuildCache();
this.notifyChange();
this.notifyChange(signal);
}

public notifyChange(): void {
this.notifyObservers(new CollectionChangeSignal(this));
public notifyChange(signal: CollectionChangeSignal<T>): void {
const changes = filterCollectionChangeDetail(signal.detail, this.filter);

const addedCount = changes.added.length;
const removedCount = changes.removed.length;
const updatedCount = changes.updated.length;

if (addedCount === 0 && removedCount === 0 && updatedCount === 0) {
return;
}

this.notifyObservers(new CollectionChangeSignal(this, changes));

if (addedCount > 0) {
this.notifyObservers(new CollectionAddSignal(this, changes.added));
}

if (removedCount > 0) {
this.notifyObservers(new CollectionRemoveSignal(this, changes.removed));
}

if (updatedCount > 0) {
this.notifyObservers(new CollectionUpdateSignal(this, changes.updated));
}
}

get count(): number {
Expand Down
47 changes: 37 additions & 10 deletions src/collections/IndexedCollectionBase.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CollectionNature } from '../core/CollectionNature';
import { ICollectionChangeDetail } from '../core/ICollectionChangeDetail';
import { ICollectionOption } from '../core/ICollectionOption';
import { IIndex } from '../core/IIndex';
import { IMutableCollection } from '../core/IMutableCollection';
Expand All @@ -7,8 +8,12 @@ import { defaultCollectionOption } from '../core/defaultCollectionOption';
import { IInternalList } from '../core/internals/IInternalList';
import { InternalList } from '../core/internals/InternalList';
import { InternalSetList } from '../core/internals/InternalSetList';
import { CollectionAddSignal } from '../signals/CollectionAddSignal';
import { CollectionChangeSignal } from '../signals/CollectionChangeSignal';
import { CollectionRemoveSignal } from '../signals/CollectionRemoveSignal';
import { CollectionUpdateSignal } from '../signals/CollectionUpdateSignal';
import { SignalObserver } from '../signals/SignalObserver';
import { mergeCollectionChangeDetail } from './util';

export abstract class IndexedCollectionBase<T>
extends SignalObserver
Expand All @@ -20,6 +25,7 @@ export abstract class IndexedCollectionBase<T>

private _pauseChangeSignal: boolean = false;
private _hasPendingChangeSignal: boolean = false;
private _pendingChange: Partial<ICollectionChangeDetail<T>> = {};

public readonly option: Readonly<ICollectionOption>;

Expand Down Expand Up @@ -83,17 +89,15 @@ export abstract class IndexedCollectionBase<T>
for (const index of this.indexes) {
index.index(item);
}
this.notifyChange();
this.notifyChange({
added: [item],
});
return true;
}

public addRange(
items: readonly T[] | IReadonlyCollection<T> | ReadonlySet<T>
): boolean[] {
// const rawItems: readonly T[] = Array.isArray(items)
// ? items
// : (items as IReadonlyCollection<T>).items;

let rawItems: Readonly<Iterable<T>>;
if (Array.isArray(items)) {
rawItems = items;
Expand Down Expand Up @@ -131,7 +135,9 @@ export abstract class IndexedCollectionBase<T>
for (const index of this.indexes) {
index.unIndex(item);
}
this.notifyChange();
this.notifyChange({
removed: [item],
});
return true;
}

Expand All @@ -149,7 +155,9 @@ export abstract class IndexedCollectionBase<T>
for (const index of this.indexes) {
index.index(newItem);
}
this.notifyChange();
this.notifyChange({
updated: [{ oldValue: oldItem, newValue: newItem }],
});
return true;
}

Expand All @@ -161,12 +169,30 @@ export abstract class IndexedCollectionBase<T>
return this._allItemList.count;
}

protected notifyChange(): void {
protected notifyChange(change: Partial<ICollectionChangeDetail<T>>): void {
if (this._pauseChangeSignal) {
this._hasPendingChangeSignal = true;
this._pendingChange = mergeCollectionChangeDetail(
this._pendingChange,
change
);
return;
}
this.notifyObservers(new CollectionChangeSignal(this));

const changes = mergeCollectionChangeDetail({}, change);
this.notifyObservers(new CollectionChangeSignal(this, changes));

if (changes.added.length > 0) {
this.notifyObservers(new CollectionAddSignal(this, changes.added));
}

if (changes.removed.length > 0) {
this.notifyObservers(new CollectionRemoveSignal(this, changes.removed));
}

if (changes.updated.length > 0) {
this.notifyObservers(new CollectionUpdateSignal(this, changes.updated));
}
}

/**
Expand Down Expand Up @@ -194,7 +220,8 @@ export abstract class IndexedCollectionBase<T>
this._pauseChangeSignal = false;
if (this._hasPendingChangeSignal) {
this._hasPendingChangeSignal = false;
this.notifyChange();
this.notifyChange(this._pendingChange);
this._pendingChange = {};
}
}
}
Expand Down
15 changes: 15 additions & 0 deletions src/collections/PrimaryKeyCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,21 @@ export class PrimaryKeyCollection<
}
}

protected override buildIndexes(
indexes: readonly IIndex<T>[],
autoReindex?: boolean
): void {
const combinedIndex: IIndex<T>[] = [];
if (this.idIndex != null) {
// this.idIndex can be null during instantiation
combinedIndex.push(this.idIndex);
}
if (indexes != null && indexes.length > 0) {
combinedIndex.push(...indexes);
}
super.buildIndexes(combinedIndex, autoReindex);
}

exists(item: T): boolean {
const key = this.primaryKeyExtract(item);
return Boolean(this.byPrimaryKey(key));
Expand Down
41 changes: 41 additions & 0 deletions src/collections/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { ICollectionChangeDetail } from '../core/ICollectionChangeDetail';

export function mergeCollectionChangeDetail<T>(
a: Partial<ICollectionChangeDetail<T>>,
b: Partial<ICollectionChangeDetail<T>>
): ICollectionChangeDetail<T> {
const added = a.added
? b.added
? a.added.concat(b.added)
: a.added
: b.added;
const removed = a.removed
? b.removed
? a.removed.concat(b.removed)
: a.removed
: b.removed;
const updated = a.updated
? b.updated
? a.updated.concat(b.updated)
: a.updated
: b.updated;
return {
added: added ?? [],
removed: removed ?? [],
updated: updated ?? [],
};
}

export function filterCollectionChangeDetail<T>(
change: Readonly<ICollectionChangeDetail<T>>,
filter: (item: T) => boolean
): ICollectionChangeDetail<T> {
const added = change.added.filter(filter);
const removed = change.removed.filter(filter);
const updated = change.updated.filter((item) => filter(item.newValue));
return {
added,
removed,
updated,
};
}
10 changes: 10 additions & 0 deletions src/core/ICollectionChangeDetail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export interface ICollectionUpdateLineItem<T> {
oldValue: T;
newValue: T;
}

export interface ICollectionChangeDetail<T> {
readonly added: T[];
readonly removed: T[];
readonly updated: ICollectionUpdateLineItem<T>[];
}
12 changes: 12 additions & 0 deletions src/signals/CollectionAddSignal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { IReadonlyCollection } from '../core/IReadonlyCollection';
import { Signal } from './Signal';

export class CollectionAddSignal<T> extends Signal {
static readonly type = Symbol('COLLECTION_ADD');
constructor(
target: IReadonlyCollection<T>,
public readonly added: readonly T[]
) {
super(CollectionAddSignal.type, target);
}
}
8 changes: 6 additions & 2 deletions src/signals/CollectionChangeSignal.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { Signal } from './Signal';
import { ICollectionChangeDetail } from '../core/ICollectionChangeDetail';
import { IReadonlyCollection } from '../core/IReadonlyCollection';
import { Signal } from './Signal';

export class CollectionChangeSignal<T> extends Signal {
static readonly type = Symbol('COLLECTION_CHANGE');
constructor(target: IReadonlyCollection<T>) {
constructor(
target: IReadonlyCollection<T>,
public readonly detail: Readonly<ICollectionChangeDetail<T>>
) {
super(CollectionChangeSignal.type, target);
}
}
12 changes: 12 additions & 0 deletions src/signals/CollectionRemoveSignal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { IReadonlyCollection } from '../core/IReadonlyCollection';
import { Signal } from './Signal';

export class CollectionRemoveSignal<T> extends Signal {
static readonly type = Symbol('COLLECTION_REMOVE');
constructor(
target: IReadonlyCollection<T>,
public readonly removed: readonly T[]
) {
super(CollectionRemoveSignal.type, target);
}
}
13 changes: 13 additions & 0 deletions src/signals/CollectionUpdateSignal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ICollectionUpdateLineItem } from '../core/ICollectionChangeDetail';
import { IReadonlyCollection } from '../core/IReadonlyCollection';
import { Signal } from './Signal';

export class CollectionUpdateSignal<T> extends Signal {
static readonly type = Symbol('COLLECTION_UPDATE');
constructor(
target: IReadonlyCollection<T>,
public readonly updated: readonly Readonly<ICollectionUpdateLineItem<T>>[]
) {
super(CollectionUpdateSignal.type, target);
}
}
3 changes: 3 additions & 0 deletions src/signals/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
export { CollectionAddSignal } from './CollectionAddSignal';
export { CollectionChangeSignal } from './CollectionChangeSignal';
export { CollectionRemoveSignal } from './CollectionRemoveSignal';
export { CollectionUpdateSignal } from './CollectionUpdateSignal';
export { SignalHandler } from './ISignalObserver';
export { Signal, SignalType } from './Signal';
export { SignalObserver } from './SignalObserver';
Loading

0 comments on commit 5cae086

Please sign in to comment.