@wszerad/items 0.1.1 → 0.1.2

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.
@@ -0,0 +1,77 @@
1
+ import { diff } from "ohash/utils";
2
+
3
+ //#region src/selectId.d.ts
4
+ type StrOrNum = string | number;
5
+ type SelectId<E> = (entity: E) => StrOrNum;
6
+ //#endregion
7
+ //#region src/diff.d.ts
8
+ interface ItemDiff<I> {
9
+ id: I;
10
+ changes: ReturnType<typeof diff>;
11
+ }
12
+ interface ItemsDiff<I> {
13
+ added: I[];
14
+ removed: I[];
15
+ updated: ItemDiff<I>[];
16
+ }
17
+ //#endregion
18
+ //#region src/selector.d.ts
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;
22
+ //#endregion
23
+ //#region src/updater.d.ts
24
+ type UpdateFn<E> = (entity: E) => E;
25
+ type Updater<E> = UpdateFn<E> | Partial<E>;
26
+ //#endregion
27
+ //#region src/Items.d.ts
28
+ type ItemsOptions<E> = {
29
+ selectId?: SelectId<E>;
30
+ sortComparer?: false | ((a: E, b: E) => number);
31
+ };
32
+ interface ItemsState<E, I> {
33
+ ids: I[];
34
+ entities: Map<I, E>;
35
+ }
36
+ declare class Items<E, I extends StrOrNum = StrOrNum> {
37
+ private options;
38
+ private state;
39
+ constructor(items?: Iterable<E>, options?: ItemsOptions<E>);
40
+ getIds(): I[];
41
+ getEntities(): Map<I, E>;
42
+ get length(): number;
43
+ select(id: I): E | undefined;
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;
52
+ has(id: I): boolean;
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>;
60
+ page(page: number, pageSize: number): {
61
+ items: NonNullable<E>[];
62
+ page: number;
63
+ pageSize: number;
64
+ hasNext: boolean;
65
+ hasPrevious: boolean;
66
+ total: number;
67
+ totalPages: number;
68
+ };
69
+ diff(base: Items<E, I>): ItemsDiff<I>;
70
+ private sortIds;
71
+ private selectId;
72
+ private get sortComparer();
73
+ [Symbol.iterator](): Iterator<E>;
74
+ }
75
+ //#endregion
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 };
77
+ //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs ADDED
@@ -0,0 +1,194 @@
1
+ import { diff } from "ohash/utils";
2
+
3
+ //#region src/selectId.ts
4
+ function defaultSelectId(entity) {
5
+ return entity.id;
6
+ }
7
+
8
+ //#endregion
9
+ //#region src/selector.ts
10
+ function selector(items, selector$1, operation) {
11
+ if (typeof selector$1 === "function") items.getEntities().forEach((entity, id) => {
12
+ if (selector$1(entity)) operation(entity, id);
13
+ });
14
+ else Array.from(selector$1).forEach((id) => operation(items.select(id), id));
15
+ }
16
+
17
+ //#endregion
18
+ //#region src/diff.ts
19
+ function itemsDiff(fromItems, toItems) {
20
+ const ids = new Set(toItems.getIds());
21
+ const baseIds = new Set(fromItems.getIds());
22
+ const added = [];
23
+ const updated = [];
24
+ ids.forEach((id) => {
25
+ if (!baseIds.has(id)) added.push(id);
26
+ else {
27
+ const changes = diff(fromItems.select(id), toItems.select(id));
28
+ if (changes.length > 0) updated.push({
29
+ id,
30
+ changes
31
+ });
32
+ baseIds.delete(id);
33
+ }
34
+ });
35
+ return {
36
+ added,
37
+ removed: [...baseIds],
38
+ updated
39
+ };
40
+ }
41
+
42
+ //#endregion
43
+ //#region src/updater.ts
44
+ function update(entity, updater) {
45
+ return {
46
+ ...entity,
47
+ ...typeof updater === "function" ? updater(entity) : updater
48
+ };
49
+ }
50
+
51
+ //#endregion
52
+ //#region src/Items.ts
53
+ var Items = class Items {
54
+ state;
55
+ constructor(items = [], options = {}) {
56
+ this.options = options;
57
+ const entities = new Map(Array.from(items).map((item) => {
58
+ return [this.selectId(item), item];
59
+ }));
60
+ this.state = {
61
+ ids: this.sortIds([...entities.keys()], entities),
62
+ entities
63
+ };
64
+ }
65
+ getIds() {
66
+ return [...this.state.ids];
67
+ }
68
+ getEntities() {
69
+ return new Map(this.state.entities);
70
+ }
71
+ get length() {
72
+ return this.state.ids.length;
73
+ }
74
+ select(id) {
75
+ return this.state.entities.get(id);
76
+ }
77
+ insert(entity) {
78
+ return this.insertMany([entity]);
79
+ }
80
+ insertMany(entities) {
81
+ return new Items([...this, ...Array.from(entities).filter((entity) => !this.has(this.selectId(entity)))], this.options);
82
+ }
83
+ upsert(entity) {
84
+ return this.upsertMany([entity]);
85
+ }
86
+ upsertMany(entities) {
87
+ const clone = this.getEntities();
88
+ Array.from(entities).forEach((entity) => {
89
+ const id = this.selectId(entity);
90
+ const existing = clone.get(id);
91
+ if (existing) clone.set(id, {
92
+ ...existing,
93
+ ...entity
94
+ });
95
+ else clone.set(id, entity);
96
+ });
97
+ return new Items(clone.values(), this.options);
98
+ }
99
+ set(entity) {
100
+ return this.setMany([entity]);
101
+ }
102
+ setMany(entities) {
103
+ return new Items([...this, ...entities], this.options);
104
+ }
105
+ every(check) {
106
+ return this.getIds().every((id) => check(this.select(id)));
107
+ }
108
+ some(check) {
109
+ return this.getIds().some((id) => check(this.select(id)));
110
+ }
111
+ has(id) {
112
+ return this.state.ids.includes(id);
113
+ }
114
+ hasMany(select) {
115
+ let failToFind = false;
116
+ let result = false;
117
+ selector(this, select, (entity) => {
118
+ if (entity) result = true;
119
+ else failToFind = true;
120
+ });
121
+ return !failToFind && result;
122
+ }
123
+ update(id, updater) {
124
+ const entity = this.select(id);
125
+ if (!entity) return this;
126
+ const clone = this.getEntities();
127
+ clone.set(id, update(entity, updater));
128
+ return new Items(clone.values(), this.options);
129
+ }
130
+ updateMany(select, updater) {
131
+ const clone = this.getEntities();
132
+ selector(this, select, (entity, id) => {
133
+ if (entity) clone.set(id, update(entity, updater));
134
+ });
135
+ return new Items(clone.values(), this.options);
136
+ }
137
+ remove(id) {
138
+ const clone = this.getEntities();
139
+ clone.delete(id);
140
+ return new Items(clone.values(), this.options);
141
+ }
142
+ removeMany(select) {
143
+ const clone = this.getEntities();
144
+ selector(this, select, (_, id) => {
145
+ clone.delete(id);
146
+ });
147
+ return new Items(clone.values(), this.options);
148
+ }
149
+ clear() {
150
+ return new Items([], this.options);
151
+ }
152
+ filter(select) {
153
+ const clone = /* @__PURE__ */ new Map();
154
+ selector(this, select, (entity, id) => {
155
+ if (entity) clone.set(id, entity);
156
+ });
157
+ return new Items(clone.values(), this.options);
158
+ }
159
+ page(page, pageSize) {
160
+ const totalPages = Math.ceil(this.length / pageSize);
161
+ return {
162
+ items: this.getIds().slice(page * pageSize, (page + 1) * pageSize).map((id) => this.select(id)),
163
+ page,
164
+ pageSize,
165
+ hasNext: page < totalPages - 1,
166
+ hasPrevious: page > 0,
167
+ total: this.length,
168
+ totalPages
169
+ };
170
+ }
171
+ diff(base) {
172
+ return itemsDiff(base, this);
173
+ }
174
+ sortIds(ids, entities) {
175
+ if (this.sortComparer === false) return ids;
176
+ const sorter = this.sortComparer;
177
+ return [...ids].sort((aId, bId) => {
178
+ return sorter(entities.get(aId), entities.get(bId));
179
+ });
180
+ }
181
+ selectId(entity) {
182
+ return this.options?.selectId?.(entity) || defaultSelectId(entity);
183
+ }
184
+ get sortComparer() {
185
+ return this.options.sortComparer || false;
186
+ }
187
+ [Symbol.iterator]() {
188
+ return this.state.entities.values();
189
+ }
190
+ };
191
+
192
+ //#endregion
193
+ export { Items };
194
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wszerad/items",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./dist/index.mjs",
@@ -9,6 +9,9 @@
9
9
  "main": "./dist/index.mjs",
10
10
  "module": "./dist/index.mjs",
11
11
  "types": "./dist/index.d.mts",
12
+ "files": [
13
+ "dist"
14
+ ],
12
15
  "engines": {
13
16
  "node": ">=20"
14
17
  },
@@ -1,15 +0,0 @@
1
- # To get started with Dependabot version updates, you'll need to specify which
2
- # package ecosystems to update and where the package manifests are located.
3
- # Please see the documentation for all configuration options:
4
- # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
5
-
6
- version: 2
7
- updates:
8
- - package-ecosystem: "github-actions"
9
- directory: "/"
10
- schedule:
11
- interval: "weekly"
12
- - package-ecosystem: npm
13
- directory: "/"
14
- schedule:
15
- interval: "weekly"
@@ -1,22 +0,0 @@
1
- name: Publish
2
-
3
- on:
4
- release:
5
- types: [created]
6
-
7
- jobs:
8
- publish-chain:
9
- runs-on: ubuntu-latest
10
- permissions:
11
- id-token: write
12
- contents: read
13
- packages: write
14
- steps:
15
- - uses: actions/checkout@v6
16
- - uses: actions/setup-node@v6
17
- with:
18
- node-version: "24"
19
- - run: npm ci
20
- - run: npm test
21
- - run: npm run build
22
- - run: npm publish --access public
@@ -1,21 +0,0 @@
1
- name: Test
2
-
3
- on:
4
- pull_request:
5
- branches:
6
- - master
7
- push:
8
- branches:
9
- - master
10
-
11
- jobs:
12
- test:
13
- name: Run tests for each push
14
- runs-on: ubuntu-latest
15
- steps:
16
- - uses: actions/checkout@v6
17
- - uses: actions/setup-node@v6
18
- with:
19
- node-version: "22"
20
- - run: npm ci
21
- - run: npm test
@@ -1,6 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="Ask2AgentMigrationStateService">
4
- <option name="migrationStatus" value="COMPLETED" />
5
- </component>
6
- </project>
package/.idea/items.iml DELETED
File without changes
package/.idea/modules.xml DELETED
@@ -1,8 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="ProjectModuleManager">
4
- <modules>
5
- <module fileurl="file://$PROJECT_DIR$/.idea/items.iml" filepath="$PROJECT_DIR$/.idea/items.iml" />
6
- </modules>
7
- </component>
8
- </project>
package/.idea/vcs.xml DELETED
@@ -1,6 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="VcsDirectoryMappings">
4
- <mapping directory="" vcs="Git" />
5
- </component>
6
- </project>
package/.oxlintrc.json DELETED
@@ -1,13 +0,0 @@
1
- {
2
- "$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json",
3
- "env": {
4
- "es2022": true,
5
- "node": true
6
- },
7
- "plugins": ["typescript"],
8
- "rules": {
9
- "eslint/no-unused-vars": "error",
10
- "typescript/no-explicit-any": "warn"
11
- }
12
- }
13
-
package/src/Items.ts DELETED
@@ -1,215 +0,0 @@
1
- import { defaultSelectId, SelectId, StrOrNum } from './selectId'
2
- import { selector, Selector, SelectorFn } from './selector'
3
- import { Updater } from './updater'
4
- import { itemsDiff } from './diff'
5
- import { update } from './updater'
6
-
7
- export type ItemsOptions<E> = {
8
- selectId?: SelectId<E>
9
- sortComparer?: false | ((a: E, b: E) => number)
10
- }
11
-
12
- export interface ItemsState<E, I> {
13
- ids: I[]
14
- entities: Map<I, E>
15
- }
16
-
17
- export class Items<E, I extends StrOrNum = StrOrNum> {
18
- private state: ItemsState<E, I>
19
-
20
- constructor(
21
- items: Iterable<E> = [],
22
- private options: ItemsOptions<E> = {}
23
- ) {
24
- const entities = new Map(
25
- Array
26
- .from(items)
27
- .map((item) => {
28
- const id = this.selectId(item)
29
- return [id, item]
30
- })
31
- )
32
-
33
- this.state = {
34
- ids: this.sortIds([...entities.keys()], entities),
35
- entities
36
- }
37
- }
38
-
39
- getIds(): I[] {
40
- return [...this.state.ids]
41
- }
42
-
43
- getEntities(): Map<I, E> {
44
- return new Map(this.state.entities)
45
- }
46
-
47
- get length(): number {
48
- return this.state.ids.length
49
- }
50
-
51
- select(id: I): E | undefined {
52
- return this.state.entities.get(id)
53
- }
54
-
55
- insert(entity: E) {
56
- return this.insertMany([entity])
57
- }
58
-
59
- insertMany(entities: Iterable<E>) {
60
- return new Items<E, I>([
61
- ...this,
62
- ...Array.from(entities).filter(entity => !this.has(this.selectId(entity)))
63
- ], this.options)
64
- }
65
-
66
- upsert(entity: Partial<E>) {
67
- return this.upsertMany([entity])
68
- }
69
-
70
- upsertMany(entities: Iterable<Partial<E>>) {
71
- const clone = this.getEntities()
72
- Array.from(entities).forEach(entity => {
73
- const id = this.selectId(entity as E)
74
- const existing = clone.get(id)
75
- if (existing) {
76
- clone.set(id, { ...existing, ...entity } as E)
77
- } else {
78
- clone.set(id, entity as E)
79
- }
80
- })
81
- return new Items<E, I>(clone.values(), this.options)
82
- }
83
-
84
- set(entity: E) {
85
- return this.setMany([entity])
86
- }
87
-
88
- setMany(entities: Iterable<E>) {
89
- return new Items<E, I>([
90
- ...this,
91
- ...entities
92
- ], this.options)
93
- }
94
-
95
- every(check: SelectorFn<E>) {
96
- return this.getIds().every(id => check(this.select(id)!))
97
- }
98
-
99
- some(check: SelectorFn<E>) {
100
- return this.getIds().some(id => check(this.select(id)!))
101
- }
102
-
103
- has(id: I) {
104
- return this.state.ids.includes(id)
105
- }
106
-
107
- hasMany(select: Selector<E, I>) {
108
- let failToFind = false
109
- let result = false
110
- selector(this, select, (entity) => {
111
- if (entity) {
112
- result = true
113
- } else {
114
- failToFind = true
115
- }
116
- })
117
- return !failToFind && result
118
- }
119
-
120
- update(id: I, updater: Updater<E>) {
121
- const entity = this.select(id)
122
- if (!entity) {
123
- return this
124
- }
125
- const clone = this.getEntities()
126
- clone.set(id, update(entity, updater))
127
- return new Items<E, I>(clone.values(), this.options)
128
- }
129
-
130
- updateMany(select: Selector<E, I>, updater: Updater<E>) {
131
- const clone = this.getEntities()
132
- selector(this, select, (entity, id) => {
133
- if (entity) {
134
- clone.set(id, update(entity, updater))
135
- }
136
- })
137
- return new Items<E, I>(clone.values(), this.options)
138
- }
139
-
140
- remove(id: I) {
141
- const clone = this.getEntities()
142
- clone.delete(id)
143
- return new Items<E, I>(clone.values(), this.options)
144
- }
145
-
146
- removeMany(select: Selector<E, I>) {
147
- const clone = this.getEntities()
148
- selector(this, select, (_, id) => {
149
- clone.delete(id)
150
- })
151
- return new Items<E, I>(clone.values(), this.options)
152
- }
153
-
154
- clear() {
155
- return new Items<E, I>([], this.options)
156
- }
157
-
158
- filter(select: Selector<E, I>) {
159
- const clone = new Map<I, E>()
160
-
161
- selector(this, select, (entity, id) => {
162
- if (entity) {
163
- clone.set(id, entity)
164
- }
165
- })
166
-
167
- return new Items<E, I>(clone.values(), this.options)
168
- }
169
-
170
- page(page: number, pageSize: number) {
171
- const totalPages = Math.ceil(this.length / pageSize)
172
- return {
173
- items: this.getIds()
174
- .slice(page * pageSize, (page + 1) * pageSize)
175
- .map(id => this.select(id)!),
176
- page,
177
- pageSize,
178
- hasNext: page < totalPages - 1,
179
- hasPrevious: page > 0,
180
- total: this.length,
181
- totalPages
182
- }
183
- }
184
-
185
- diff(base: Items<E, I>) {
186
- return itemsDiff(base, this)
187
- }
188
-
189
- private sortIds(
190
- ids: I[],
191
- entities: Map<I, E>
192
- ): Array<I> {
193
- if (this.sortComparer === false) {
194
- return ids
195
- }
196
- const sorter = this.sortComparer
197
- return [...ids].sort((aId, bId) => {
198
- const a = entities.get(aId)!
199
- const b = entities.get(bId)!
200
- return sorter(a, b)
201
- })
202
- }
203
-
204
- private selectId(entity: E): I {
205
- return this.options?.selectId?.(entity) as undefined || defaultSelectId(entity as E & { id: I })
206
- }
207
-
208
- private get sortComparer() {
209
- return this.options.sortComparer || false
210
- }
211
-
212
- [Symbol.iterator](): Iterator<E> {
213
- return this.state.entities.values()
214
- }
215
- }
package/src/diff.ts DELETED
@@ -1,44 +0,0 @@
1
- import { diff } from 'ohash/utils'
2
- import { Items } from './Items'
3
- import { StrOrNum } from './selectId'
4
-
5
- export interface ItemDiff<I> {
6
- id: I
7
- changes: ReturnType<typeof diff>
8
- }
9
-
10
- export interface ItemsDiff<I> {
11
- added: I[]
12
- removed: I[]
13
- updated: ItemDiff<I>[]
14
- }
15
-
16
- export function itemsDiff<E, I extends StrOrNum>(fromItems: Items<E, I>, toItems: Items<E, I>): ItemsDiff<I> {
17
- const ids = new Set(toItems.getIds())
18
- const baseIds = new Set(fromItems.getIds())
19
-
20
- const added: I[] = []
21
- const updated: ItemDiff<I>[] = []
22
-
23
- ids.forEach(id => {
24
- if (!baseIds.has(id)) {
25
- added.push(id)
26
- } else {
27
- const changes = diff(fromItems.select(id), toItems.select(id))
28
-
29
- if (changes.length > 0) {
30
- updated.push({ id, changes })
31
- }
32
-
33
- baseIds.delete(id)
34
- }
35
- })
36
-
37
- const removed = [...baseIds]
38
-
39
- return {
40
- added,
41
- removed,
42
- updated
43
- }
44
- }
package/src/index.ts DELETED
@@ -1,6 +0,0 @@
1
- export { Items } from './Items'
2
- export type { ItemsOptions, ItemsState } from './Items'
3
- export type { SelectId, StrOrNum } from './selectId'
4
- export type { Selector, SelectorFn, Operation } from './selector'
5
- export type { Updater, UpdateFn } from './updater'
6
- export type { ItemDiff, ItemsDiff } from './diff'
package/src/selectId.ts DELETED
@@ -1,7 +0,0 @@
1
- export type StrOrNum = string | number
2
-
3
- export type SelectId<E> = (entity: E) => StrOrNum
4
-
5
- export function defaultSelectId<E extends { id: I }, I = E['id']>(entity: E) {
6
- return entity.id
7
- }