@wszerad/items 0.0.6 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -30
- package/dist/index.d.mts +34 -33
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Lightweight, immutable collection manager inspired by NgRx Entity Adapter.
|
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- ✅ Immutable operations (insert/insertMany, upsert/upsertMany, set/setMany, update, remove, filter, etc.)
|
|
7
|
+
- ✅ Immutable operations (insert/insertMany, upsert/upsertMany, set/setMany, update, remove/removeMany, filter, etc.)
|
|
8
8
|
- ✅ Single entity and batch operations
|
|
9
9
|
- ✅ Custom ID selection (`selectId`)
|
|
10
10
|
- ✅ Optional sorting (`sortComparer`)
|
|
@@ -19,7 +19,7 @@ Lightweight, immutable collection manager inspired by NgRx Entity Adapter.
|
|
|
19
19
|
## Installation
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
npm install items
|
|
22
|
+
npm install @wszerad/items
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
## Quick Start
|
|
@@ -722,40 +722,45 @@ users = users.setMany([
|
|
|
722
722
|
### Checking Entity Existence
|
|
723
723
|
|
|
724
724
|
```typescript
|
|
725
|
-
|
|
725
|
+
interface Product {
|
|
726
|
+
sku: string
|
|
727
|
+
name: string
|
|
728
|
+
price: number
|
|
729
|
+
}
|
|
726
730
|
|
|
727
|
-
const
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
+
const products = new Items<string, Product>([], {
|
|
732
|
+
selectId: (product) => product.sku
|
|
733
|
+
})
|
|
734
|
+
|
|
735
|
+
const updated = products.insert([
|
|
736
|
+
{ sku: 'ABC-123', name: 'Widget', price: 19.99 }
|
|
731
737
|
])
|
|
732
738
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
console.log('User 1 exists')
|
|
736
|
-
}
|
|
739
|
+
console.log(updated.select('ABC-123'))
|
|
740
|
+
```
|
|
737
741
|
|
|
738
|
-
|
|
739
|
-
console.log('User 99 does not exist')
|
|
740
|
-
}
|
|
742
|
+
### With Sorting
|
|
741
743
|
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
}
|
|
744
|
+
```typescript
|
|
745
|
+
const items = new Items<number, User>(
|
|
746
|
+
[
|
|
747
|
+
{ id: 3, name: 'Charlie', age: 35 },
|
|
748
|
+
{ id: 1, name: 'Alice', age: 25 },
|
|
749
|
+
{ id: 2, name: 'Bob', age: 30 }
|
|
750
|
+
],
|
|
751
|
+
{ sortComparer: (a, b) => a.name.localeCompare(b.name) }
|
|
752
|
+
)
|
|
746
753
|
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
}
|
|
754
|
+
console.log(items.getIds()) // [1, 2, 3] - sorted by name
|
|
755
|
+
```
|
|
750
756
|
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
}
|
|
757
|
+
---
|
|
758
|
+
|
|
759
|
+
## Development
|
|
755
760
|
|
|
756
|
-
|
|
757
|
-
const userExists = users.has(1)
|
|
758
|
-
const allExist = users.hasMany([1, 2, 3])
|
|
759
|
-
const hasAdults = users.hasMany(user => user.age >= 18)
|
|
761
|
+
### Scripts
|
|
760
762
|
|
|
761
|
-
|
|
763
|
+
- `npm test` – runs tests (Vitest)
|
|
764
|
+
- `npm run test:watch` – watch mode
|
|
765
|
+
- `npm run build` – typecheck + bundling (tsc + tsdown)
|
|
766
|
+
- `npm run typecheck` – TypeScript type checking (
|
package/dist/index.d.mts
CHANGED
|
@@ -1,61 +1,62 @@
|
|
|
1
1
|
import { diff } from "ohash/utils";
|
|
2
2
|
|
|
3
|
+
//#region src/selectId.d.ts
|
|
4
|
+
type StrOrNum = string | number;
|
|
5
|
+
type SelectId<E> = (entity: E) => StrOrNum;
|
|
6
|
+
//#endregion
|
|
3
7
|
//#region src/diff.d.ts
|
|
4
|
-
interface ItemDiff<I
|
|
8
|
+
interface ItemDiff<I> {
|
|
5
9
|
id: I;
|
|
6
10
|
changes: ReturnType<typeof diff>;
|
|
7
11
|
}
|
|
8
|
-
interface ItemsDiff<I
|
|
12
|
+
interface ItemsDiff<I> {
|
|
9
13
|
added: I[];
|
|
10
14
|
removed: I[];
|
|
11
|
-
updated: ItemDiff<I
|
|
15
|
+
updated: ItemDiff<I>[];
|
|
12
16
|
}
|
|
13
17
|
//#endregion
|
|
14
|
-
//#region src/selectId.d.ts
|
|
15
|
-
type SelectId<I, E> = (entity: E) => I;
|
|
16
|
-
//#endregion
|
|
17
18
|
//#region src/selector.d.ts
|
|
18
|
-
type SelectorFn<
|
|
19
|
-
type Selector<
|
|
20
|
-
type Operation<
|
|
19
|
+
type SelectorFn<E> = (entity: E) => boolean;
|
|
20
|
+
type Selector<E, I> = Iterable<I> | SelectorFn<E>;
|
|
21
|
+
type Operation<E, I> = (entity: E | undefined, id: I) => void;
|
|
21
22
|
//#endregion
|
|
22
23
|
//#region src/updater.d.ts
|
|
23
24
|
type UpdateFn<E> = (entity: E) => E;
|
|
24
|
-
type Updater<
|
|
25
|
+
type Updater<E> = UpdateFn<E> | Partial<E>;
|
|
25
26
|
//#endregion
|
|
26
27
|
//#region src/Items.d.ts
|
|
27
|
-
|
|
28
|
-
selectId?: SelectId<
|
|
28
|
+
type ItemsOptions<E> = {
|
|
29
|
+
selectId?: SelectId<E>;
|
|
29
30
|
sortComparer?: false | ((a: E, b: E) => number);
|
|
30
|
-
}
|
|
31
|
-
interface ItemsState<
|
|
31
|
+
};
|
|
32
|
+
interface ItemsState<E, I> {
|
|
32
33
|
ids: I[];
|
|
33
34
|
entities: Map<I, E>;
|
|
34
35
|
}
|
|
35
|
-
declare class Items<
|
|
36
|
+
declare class Items<E, I extends StrOrNum = StrOrNum> {
|
|
36
37
|
private options;
|
|
37
38
|
private state;
|
|
38
|
-
constructor(items?: Iterable<E>, options?: ItemsOptions<
|
|
39
|
+
constructor(items?: Iterable<E>, options?: ItemsOptions<E>);
|
|
39
40
|
getIds(): I[];
|
|
40
41
|
getEntities(): Map<I, E>;
|
|
41
42
|
get length(): number;
|
|
42
43
|
select(id: I): E | undefined;
|
|
43
|
-
insert(entity: E): Items<
|
|
44
|
-
insertMany(entities: Iterable<E>): Items<
|
|
45
|
-
upsert(entity: E): Items<
|
|
46
|
-
upsertMany(entities: Iterable<E
|
|
47
|
-
set(entity: E): Items<
|
|
48
|
-
setMany(entities: Iterable<E>): Items<
|
|
49
|
-
every(check: SelectorFn<
|
|
50
|
-
some(check: SelectorFn<
|
|
44
|
+
insert(entity: E): Items<E, I>;
|
|
45
|
+
insertMany(entities: Iterable<E>): Items<E, I>;
|
|
46
|
+
upsert(entity: Partial<E>): Items<E, I>;
|
|
47
|
+
upsertMany(entities: Iterable<Partial<E>>): Items<E, I>;
|
|
48
|
+
set(entity: E): Items<E, I>;
|
|
49
|
+
setMany(entities: Iterable<E>): Items<E, I>;
|
|
50
|
+
every(check: SelectorFn<E>): boolean;
|
|
51
|
+
some(check: SelectorFn<E>): boolean;
|
|
51
52
|
has(id: I): boolean;
|
|
52
|
-
hasMany(select: Selector<
|
|
53
|
-
update(id: I, updater: Updater<
|
|
54
|
-
updateMany(select: Selector<
|
|
55
|
-
remove(id: I): Items<
|
|
56
|
-
removeMany(select: Selector<
|
|
57
|
-
clear(): Items<
|
|
58
|
-
filter(select: Selector<
|
|
53
|
+
hasMany(select: Selector<E, I>): boolean;
|
|
54
|
+
update(id: I, updater: Updater<E>): Items<E, I>;
|
|
55
|
+
updateMany(select: Selector<E, I>, updater: Updater<E>): Items<E, I>;
|
|
56
|
+
remove(id: I): Items<E, I>;
|
|
57
|
+
removeMany(select: Selector<E, I>): Items<E, I>;
|
|
58
|
+
clear(): Items<E, I>;
|
|
59
|
+
filter(select: Selector<E, I>): Items<E, I>;
|
|
59
60
|
page(page: number, pageSize: number): {
|
|
60
61
|
items: NonNullable<E>[];
|
|
61
62
|
page: number;
|
|
@@ -65,12 +66,12 @@ declare class Items<I, E> {
|
|
|
65
66
|
total: number;
|
|
66
67
|
totalPages: number;
|
|
67
68
|
};
|
|
68
|
-
diff(base: Items<
|
|
69
|
+
diff(base: Items<E, I>): ItemsDiff<I>;
|
|
69
70
|
private sortIds;
|
|
70
71
|
private selectId;
|
|
71
72
|
private get sortComparer();
|
|
72
73
|
[Symbol.iterator](): Iterator<E>;
|
|
73
74
|
}
|
|
74
75
|
//#endregion
|
|
75
|
-
export { type ItemDiff, Items, type ItemsDiff, type ItemsOptions, type ItemsState, type Operation, type SelectId, type Selector, type SelectorFn, type UpdateFn, type Updater };
|
|
76
|
+
export { type ItemDiff, Items, type ItemsDiff, type ItemsOptions, type ItemsState, type Operation, type SelectId, type Selector, type SelectorFn, type StrOrNum, type UpdateFn, type Updater };
|
|
76
77
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["selector","added: I[]","updated: ItemDiff<I, E>[]","options: ItemsOptions<I, E>"],"sources":["../src/selectId.ts","../src/selector.ts","../src/diff.ts","../src/updater.ts","../src/Items.ts"],"sourcesContent":["export type SelectId<I, E> = (entity: E) => I\n\nexport function defaultSelectId<I, E extends { id: I }>(entity: E) {\n return entity.id\n}\n","import { Items } from './Items'\n\nexport type SelectorFn<I, E> = (entity: E) => boolean\nexport type Selector<I, E> = Iterable<I> | SelectorFn<I, E>\nexport type Operation<I, E> = (entity: E | undefined, id: I) => void\n\nexport function selector<I, E>(items: Items<I, E>, selector: Selector<I, E>, operation: Operation<I, E>) {\n if (typeof selector === 'function') {\n items.getEntities().forEach((entity, id) => {\n if ((selector as SelectorFn<I, E>)(entity)) {\n operation(entity, id)\n }\n })\n } else {\n Array\n .from(selector as Iterable<I>)\n .forEach(id => operation(items.select(id), id))\n }\n}\n","import { diff } from 'ohash/utils'\nimport { Items } from './Items'\n\nexport interface ItemDiff<I, E> {\n id: I\n changes: ReturnType<typeof diff>\n}\n\nexport interface ItemsDiff<I, E> {\n added: I[]\n removed: I[]\n updated: ItemDiff<I, E>[]\n}\n\nexport function itemsDiff<I, E>(fromItems: Items<I, E>, toItems: Items<I, E>): ItemsDiff<I, E> {\n const ids = new Set(toItems.getIds())\n const baseIds = new Set(fromItems.getIds())\n\n const added: I[] = []\n const updated: ItemDiff<I, E>[] = []\n\n ids.forEach(id => {\n if (!baseIds.has(id)) {\n added.push(id)\n } else {\n const changes = diff(fromItems.select(id), toItems.select(id))\n\n if (changes.length > 0) {\n updated.push({ id, changes })\n }\n\n baseIds.delete(id)\n }\n })\n\n const removed = [...baseIds]\n\n return {\n added,\n removed,\n updated\n }\n}\n","export type UpdateFn<E> = (entity: E) => E\nexport type Updater<I, E> = UpdateFn<E> | Partial<E>\n\nexport function update<I, E>(entity: E, updater: Updater<I, E>) {\n return { ...entity, ...(typeof updater === 'function' ? updater(entity) : updater) }\n}\n","import { defaultSelectId, SelectId } from './selectId'\nimport { selector, Selector, SelectorFn } from './selector'\nimport { Updater } from './updater'\nimport { itemsDiff } from './diff'\nimport { update } from './updater'\n\nexport interface ItemsOptions<I, E> {\n selectId?: SelectId<I, E>\n sortComparer?: false | ((a: E, b: E) => number)\n}\n\nexport interface ItemsState<I, E> {\n ids: I[]\n entities: Map<I, E>\n}\n\nexport class Items<I, E> {\n private state: ItemsState<I, E>\n\n constructor(\n items: Iterable<E> = [],\n private options: ItemsOptions<I, E> = {}\n ) {\n const entities = new Map(\n Array\n .from(items)\n .map((item) => {\n const id = this.selectId(item)\n return [id, item]\n })\n )\n\n this.state = {\n ids: this.sortIds([...entities.keys()], entities),\n entities\n }\n }\n\n getIds(): I[] {\n return [...this.state.ids]\n }\n\n getEntities(): Map<I, E> {\n return new Map(this.state.entities)\n }\n\n get length(): number {\n return this.state.ids.length\n }\n\n select(id: I): E | undefined {\n return this.state.entities.get(id)\n }\n\n insert(entity: E) {\n return this.insertMany([entity])\n }\n\n insertMany(entities: Iterable<E>) {\n return new Items([\n ...this,\n ...Array.from(entities).filter(entity => !this.has(this.selectId(entity)))\n ], this.options)\n }\n\n upsert(entity: E) {\n return this.upsertMany([entity])\n }\n\n upsertMany(entities: Iterable<E>) {\n const clone = this.getEntities()\n Array.from(entities).forEach(entity => {\n const id = this.selectId(entity)\n const existing = clone.get(id)\n if (existing) {\n clone.set(id, { ...existing, ...entity })\n } else {\n clone.set(id, entity)\n }\n })\n return new Items(clone.values(), this.options)\n }\n\n set(entity: E) {\n return this.setMany([entity])\n }\n\n setMany(entities: Iterable<E>) {\n return new Items([\n ...this,\n ...entities\n ], this.options)\n }\n\n every(check: SelectorFn<I, E>) {\n return this.getIds().every(id => check(this.select(id)!))\n }\n\n some(check: SelectorFn<I, E>) {\n return this.getIds().some(id => check(this.select(id)!))\n }\n\n has(id: I) {\n return this.state.ids.includes(id)\n }\n\n hasMany(select: Selector<I, E>) {\n let failToFind = false\n let result = false\n selector(this, select, (entity) => {\n if (entity) {\n result = true\n } else {\n failToFind = true\n }\n })\n return !failToFind && result\n }\n\n update(id: I, updater: Updater<I, E>) {\n const entity = this.select(id)\n if (!entity) {\n return this\n }\n const clone = this.getEntities()\n clone.set(id, update(entity, updater))\n return new Items(clone.values(), this.options)\n }\n\n updateMany(select: Selector<I, E>, updater: Updater<I, E>) {\n const clone = this.getEntities()\n selector(this, select, (entity, id) => {\n if (entity) {\n clone.set(id, update(entity, updater))\n }\n })\n return new Items(clone.values(), this.options)\n }\n\n remove(id: I) {\n const clone = this.getEntities()\n clone.delete(id)\n return new Items(clone.values(), this.options)\n }\n\n removeMany(select: Selector<I, E>) {\n const clone = this.getEntities()\n selector(this, select, (_, id) => {\n clone.delete(id)\n })\n return new Items(clone.values(), this.options)\n }\n\n clear(): Items<I, E> {\n return new Items([], this.options)\n }\n\n filter(select: Selector<I, E>) {\n const clone = new Map<I, E>()\n\n selector(this, select, (entity, id) => {\n if (entity) {\n clone.set(id, entity)\n }\n })\n\n return new Items(clone.values(), this.options)\n }\n\n page(page: number, pageSize: number) {\n const totalPages = Math.ceil(this.length / pageSize)\n return {\n items: this.getIds()\n .slice(page * pageSize, (page + 1) * pageSize)\n .map(id => this.select(id)!),\n page,\n pageSize,\n hasNext: page < totalPages - 1,\n hasPrevious: page > 0,\n total: this.length,\n totalPages\n }\n }\n\n diff(base: Items<I, E>) {\n return itemsDiff(base, this)\n }\n\n private sortIds(\n ids: I[],\n entities: Map<I, E>\n ): Array<I> {\n if (this.sortComparer === false) {\n return ids\n }\n const sorter = this.sortComparer\n return [...ids].sort((aId, bId) => {\n const a = entities.get(aId)!\n const b = entities.get(bId)!\n return sorter(a, b)\n })\n }\n\n private selectId(entity: E): I {\n return this.options?.selectId?.(entity) || defaultSelectId(entity as E & { id: I })\n }\n\n private get sortComparer() {\n return this.options.sortComparer || false\n }\n\n [Symbol.iterator](): Iterator<E> {\n return this.state.entities.values()\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["selector","added: I[]","updated: ItemDiff<I>[]","options: ItemsOptions<E>"],"sources":["../src/selectId.ts","../src/selector.ts","../src/diff.ts","../src/updater.ts","../src/Items.ts"],"sourcesContent":["export type StrOrNum = string | number\n\nexport type SelectId<E> = (entity: E) => StrOrNum\n\nexport function defaultSelectId<E extends { id: I }, I = E['id']>(entity: E) {\n return entity.id\n}\n","import { Items } from './Items'\nimport { StrOrNum } from './selectId'\n\nexport type SelectorFn<E> = (entity: E) => boolean\nexport type Selector<E, I> = Iterable<I> | SelectorFn<E>\nexport type Operation<E, I> = (entity: E | undefined, id: I) => void\n\nexport function selector<E, I extends StrOrNum>(items: Items<E, I>, selector: Selector<E, I>, operation: Operation<E, I>) {\n if (typeof selector === 'function') {\n items.getEntities().forEach((entity, id) => {\n if ((selector as SelectorFn<E>)(entity)) {\n operation(entity, id)\n }\n })\n } else {\n Array\n .from(selector as Iterable<I>)\n .forEach(id => operation(items.select(id), id))\n }\n}\n","import { diff } from 'ohash/utils'\nimport { Items } from './Items'\nimport { StrOrNum } from './selectId'\n\nexport interface ItemDiff<I> {\n id: I\n changes: ReturnType<typeof diff>\n}\n\nexport interface ItemsDiff<I> {\n added: I[]\n removed: I[]\n updated: ItemDiff<I>[]\n}\n\nexport function itemsDiff<E, I extends StrOrNum>(fromItems: Items<E, I>, toItems: Items<E, I>): ItemsDiff<I> {\n const ids = new Set(toItems.getIds())\n const baseIds = new Set(fromItems.getIds())\n\n const added: I[] = []\n const updated: ItemDiff<I>[] = []\n\n ids.forEach(id => {\n if (!baseIds.has(id)) {\n added.push(id)\n } else {\n const changes = diff(fromItems.select(id), toItems.select(id))\n\n if (changes.length > 0) {\n updated.push({ id, changes })\n }\n\n baseIds.delete(id)\n }\n })\n\n const removed = [...baseIds]\n\n return {\n added,\n removed,\n updated\n }\n}\n","export type UpdateFn<E> = (entity: E) => E\nexport type Updater<E> = UpdateFn<E> | Partial<E>\n\nexport function update<E>(entity: E, updater: Updater<E>) {\n return { ...entity, ...(typeof updater === 'function' ? updater(entity) : updater) }\n}\n","import { defaultSelectId, SelectId, StrOrNum } from './selectId'\nimport { selector, Selector, SelectorFn } from './selector'\nimport { Updater } from './updater'\nimport { itemsDiff } from './diff'\nimport { update } from './updater'\n\nexport type ItemsOptions<E> = {\n selectId?: SelectId<E>\n sortComparer?: false | ((a: E, b: E) => number)\n}\n\nexport interface ItemsState<E, I> {\n ids: I[]\n entities: Map<I, E>\n}\n\nexport class Items<E, I extends StrOrNum = StrOrNum> {\n private state: ItemsState<E, I>\n\n constructor(\n items: Iterable<E> = [],\n private options: ItemsOptions<E> = {}\n ) {\n const entities = new Map(\n Array\n .from(items)\n .map((item) => {\n const id = this.selectId(item)\n return [id, item]\n })\n )\n\n this.state = {\n ids: this.sortIds([...entities.keys()], entities),\n entities\n }\n }\n\n getIds(): I[] {\n return [...this.state.ids]\n }\n\n getEntities(): Map<I, E> {\n return new Map(this.state.entities)\n }\n\n get length(): number {\n return this.state.ids.length\n }\n\n select(id: I): E | undefined {\n return this.state.entities.get(id)\n }\n\n insert(entity: E) {\n return this.insertMany([entity])\n }\n\n insertMany(entities: Iterable<E>) {\n return new Items<E, I>([\n ...this,\n ...Array.from(entities).filter(entity => !this.has(this.selectId(entity)))\n ], this.options)\n }\n\n upsert(entity: Partial<E>) {\n return this.upsertMany([entity])\n }\n\n upsertMany(entities: Iterable<Partial<E>>) {\n const clone = this.getEntities()\n Array.from(entities).forEach(entity => {\n const id = this.selectId(entity as E)\n const existing = clone.get(id)\n if (existing) {\n clone.set(id, { ...existing, ...entity } as E)\n } else {\n clone.set(id, entity as E)\n }\n })\n return new Items<E, I>(clone.values(), this.options)\n }\n\n set(entity: E) {\n return this.setMany([entity])\n }\n\n setMany(entities: Iterable<E>) {\n return new Items<E, I>([\n ...this,\n ...entities\n ], this.options)\n }\n\n every(check: SelectorFn<E>) {\n return this.getIds().every(id => check(this.select(id)!))\n }\n\n some(check: SelectorFn<E>) {\n return this.getIds().some(id => check(this.select(id)!))\n }\n\n has(id: I) {\n return this.state.ids.includes(id)\n }\n\n hasMany(select: Selector<E, I>) {\n let failToFind = false\n let result = false\n selector(this, select, (entity) => {\n if (entity) {\n result = true\n } else {\n failToFind = true\n }\n })\n return !failToFind && result\n }\n\n update(id: I, updater: Updater<E>) {\n const entity = this.select(id)\n if (!entity) {\n return this\n }\n const clone = this.getEntities()\n clone.set(id, update(entity, updater))\n return new Items<E, I>(clone.values(), this.options)\n }\n\n updateMany(select: Selector<E, I>, updater: Updater<E>) {\n const clone = this.getEntities()\n selector(this, select, (entity, id) => {\n if (entity) {\n clone.set(id, update(entity, updater))\n }\n })\n return new Items<E, I>(clone.values(), this.options)\n }\n\n remove(id: I) {\n const clone = this.getEntities()\n clone.delete(id)\n return new Items<E, I>(clone.values(), this.options)\n }\n\n removeMany(select: Selector<E, I>) {\n const clone = this.getEntities()\n selector(this, select, (_, id) => {\n clone.delete(id)\n })\n return new Items<E, I>(clone.values(), this.options)\n }\n\n clear() {\n return new Items<E, I>([], this.options)\n }\n\n filter(select: Selector<E, I>) {\n const clone = new Map<I, E>()\n\n selector(this, select, (entity, id) => {\n if (entity) {\n clone.set(id, entity)\n }\n })\n\n return new Items<E, I>(clone.values(), this.options)\n }\n\n page(page: number, pageSize: number) {\n const totalPages = Math.ceil(this.length / pageSize)\n return {\n items: this.getIds()\n .slice(page * pageSize, (page + 1) * pageSize)\n .map(id => this.select(id)!),\n page,\n pageSize,\n hasNext: page < totalPages - 1,\n hasPrevious: page > 0,\n total: this.length,\n totalPages\n }\n }\n\n diff(base: Items<E, I>) {\n return itemsDiff(base, this)\n }\n\n private sortIds(\n ids: I[],\n entities: Map<I, E>\n ): Array<I> {\n if (this.sortComparer === false) {\n return ids\n }\n const sorter = this.sortComparer\n return [...ids].sort((aId, bId) => {\n const a = entities.get(aId)!\n const b = entities.get(bId)!\n return sorter(a, b)\n })\n }\n\n private selectId(entity: E): I {\n return this.options?.selectId?.(entity) as undefined || defaultSelectId(entity as E & { id: I })\n }\n\n private get sortComparer() {\n return this.options.sortComparer || false\n }\n\n [Symbol.iterator](): Iterator<E> {\n return this.state.entities.values()\n }\n}\n"],"mappings":"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"}
|