@wszerad/items 0.1.1 → 0.2.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.
@@ -0,0 +1,87 @@
1
+ //#region src/select.d.ts
2
+
3
+ declare class BaseSelect<E> {
4
+ ids: ItemId[];
5
+ self: Items<E>;
6
+ constructor(ids: ItemId[], self: Items<E>);
7
+ }
8
+ declare class SingleSelect<E> extends BaseSelect<E> {}
9
+ declare class Select<E> extends BaseSelect<E> {
10
+ take(len: number): Select<E>;
11
+ skip(len: number): Select<E>;
12
+ filter(testFn: (entry: E, id: ItemId, index: number) => boolean): Select<E>;
13
+ revert(): Select<E>;
14
+ sort(sortFn: (x: E, y: E) => number): Select<E>;
15
+ at(index: number): SingleSelect<E>;
16
+ from(entities: Iterable<E>): Select<E>;
17
+ on(entry: E): SingleSelect<E>;
18
+ }
19
+ //#endregion
20
+ //#region src/types.d.ts
21
+ type ItemId = string | number;
22
+ type CheckFn<E> = (entity: E) => boolean;
23
+ type Selector<E> = ((selector: Select<E>) => SingleSelect<E>) | ((selector: Select<E>) => Select<E>) | ItemId | Iterable<ItemId>;
24
+ type UpdateFn<E> = (entity: E | undefined) => E;
25
+ type Updater<E> = UpdateFn<E> | Partial<E>;
26
+ type SelectId<E> = (entity: E) => ItemId;
27
+ type ItemsOptions<E> = {
28
+ selectId?: SelectId<E>;
29
+ sortComparer?: false | ((a: E, b: E) => number);
30
+ };
31
+ interface ItemsState<E, I> {
32
+ ids: I[];
33
+ entities: Map<I, E>;
34
+ }
35
+ interface ItemDiff {
36
+ id: ItemId;
37
+ changes: any[];
38
+ }
39
+ interface ItemsDiff {
40
+ added: ItemId[];
41
+ removed: ItemId[];
42
+ updated: ItemDiff[];
43
+ }
44
+ //#endregion
45
+ //#region src/Items.d.ts
46
+ declare class Items<E> {
47
+ private options;
48
+ private state;
49
+ constructor(items?: Iterable<E>, options?: ItemsOptions<E>);
50
+ add(items: Iterable<E>): Items<E>;
51
+ update(select: Selector<E>, updater: Updater<E>): Items<E>;
52
+ merge(items: Iterable<E>): Items<E>;
53
+ remove(select: Selector<E>): Items<E>;
54
+ pick(select: Selector<E>): Items<E>;
55
+ select(select: ItemId): E | undefined;
56
+ select(select: Iterable<ItemId>): E[];
57
+ select(select: (selector: Select<E>) => Select<E>): E[];
58
+ select(select: (selector: Select<E>) => SingleSelect<E>): E | undefined;
59
+ selectId(select: (selector: Select<E>) => Select<E>): ItemId[];
60
+ selectId(select: (selector: Select<E>) => SingleSelect<E>): ItemId | undefined;
61
+ clear(): Items<E>;
62
+ every(check: CheckFn<E>): boolean;
63
+ some(check: CheckFn<E>): boolean;
64
+ has(id: ItemId): boolean;
65
+ get(id: ItemId): E | undefined;
66
+ getIds(): ItemId[];
67
+ getEntities(): E[];
68
+ get length(): number;
69
+ private resolveSelector;
70
+ private sortIds;
71
+ extractId(entity: E): ItemId;
72
+ private get sortComparer();
73
+ [Symbol.iterator](): Iterator<E>;
74
+ static compare<E>(base: Items<E>, to: Items<E>): {
75
+ added: ItemId[];
76
+ removed: ItemId[];
77
+ updated: any[];
78
+ };
79
+ }
80
+ //#endregion
81
+ //#region src/utils.d.ts
82
+ declare function defaultSelectId<E extends {
83
+ id: ItemId;
84
+ }>(entity: E): ItemId;
85
+ //#endregion
86
+ export { BaseSelect, type CheckFn, type ItemDiff, type ItemId, Items, type ItemsDiff, type ItemsOptions, type ItemsState, Select, type SelectId, type Selector, SingleSelect, type UpdateFn, type Updater, defaultSelectId };
87
+ //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs ADDED
@@ -0,0 +1,193 @@
1
+ import { diff } from "ohash/utils";
2
+
3
+ //#region src/utils.ts
4
+ function defaultSelectId(entity) {
5
+ return entity.id;
6
+ }
7
+
8
+ //#endregion
9
+ //#region src/select.ts
10
+ var BaseSelect = class {
11
+ constructor(ids, self) {
12
+ this.ids = ids;
13
+ this.self = self;
14
+ }
15
+ };
16
+ var SingleSelect = class extends BaseSelect {};
17
+ var Select = class Select extends BaseSelect {
18
+ take(len) {
19
+ return new Select(this.ids.slice(0, len), this.self);
20
+ }
21
+ skip(len) {
22
+ return new Select(this.ids.slice(len), this.self);
23
+ }
24
+ filter(testFn) {
25
+ return new Select(this.ids.filter((id, index) => {
26
+ return testFn(this.self.get(id), id, index);
27
+ }), this.self);
28
+ }
29
+ revert() {
30
+ return new Select([...this.ids].reverse(), this.self);
31
+ }
32
+ sort(sortFn) {
33
+ return new Select([...this.ids].sort((aId, bId) => {
34
+ return sortFn(this.self.get(aId), this.self.get(bId));
35
+ }), this.self);
36
+ }
37
+ at(index) {
38
+ const id = this.ids[index];
39
+ return new SingleSelect([id], this.self);
40
+ }
41
+ from(entities) {
42
+ return new Select(Array.from(entities).map((entry) => this.self.extractId(entry)), this.self);
43
+ }
44
+ on(entry) {
45
+ return new SingleSelect([this.self.extractId(entry)], this.self);
46
+ }
47
+ };
48
+
49
+ //#endregion
50
+ //#region src/diff.ts
51
+ function itemsDiff(fromItems, toItems) {
52
+ const ids = new Set(toItems.getIds());
53
+ const baseIds = new Set(fromItems.getIds());
54
+ const added = [];
55
+ const updated = [];
56
+ ids.forEach((id) => {
57
+ if (!baseIds.has(id)) added.push(id);
58
+ else {
59
+ const changes = diff(fromItems.select(id), toItems.select(id));
60
+ if (changes.length > 0) updated.push({
61
+ id,
62
+ changes
63
+ });
64
+ baseIds.delete(id);
65
+ }
66
+ });
67
+ return {
68
+ added,
69
+ removed: [...baseIds],
70
+ updated
71
+ };
72
+ }
73
+
74
+ //#endregion
75
+ //#region src/Items.ts
76
+ var Items = class Items {
77
+ state;
78
+ constructor(items = [], options = {}) {
79
+ this.options = options;
80
+ const entities = new Map(Array.from(items).map((item) => {
81
+ return [this.extractId(item), item];
82
+ }));
83
+ this.state = {
84
+ ids: this.sortIds([...entities.keys()], entities),
85
+ entities
86
+ };
87
+ }
88
+ add(items) {
89
+ const newEntities = new Map(this.state.entities);
90
+ Array.from(items).forEach((item) => {
91
+ const id = this.extractId(item);
92
+ if (!newEntities.has(id)) newEntities.set(id, item);
93
+ });
94
+ return new Items(newEntities.values(), this.options);
95
+ }
96
+ update(select, updater) {
97
+ const [, selectedIds] = this.resolveSelector(select);
98
+ const newEntities = new Map(this.state.entities);
99
+ const isFn = typeof updater === "function";
100
+ selectedIds.forEach((id) => {
101
+ const entity = newEntities.get(id);
102
+ if (isFn) newEntities.set(id, updater(entity));
103
+ else if (entity) newEntities.set(id, {
104
+ ...entity,
105
+ ...updater
106
+ });
107
+ });
108
+ return new Items(newEntities.values(), this.options);
109
+ }
110
+ merge(items) {
111
+ const newEntities = new Map(this.state.entities);
112
+ Array.from(items).forEach((item) => {
113
+ const id = this.extractId(item);
114
+ const entry = this.get(id);
115
+ newEntities.set(id, entry ? {
116
+ ...entry,
117
+ ...item
118
+ } : item);
119
+ });
120
+ return new Items(newEntities.values(), this.options);
121
+ }
122
+ remove(select) {
123
+ const [, selectedIds] = this.resolveSelector(select);
124
+ return new Items(this.state.ids.filter((id) => !selectedIds.includes(id)).map((id) => this.get(id)), this.options);
125
+ }
126
+ pick(select) {
127
+ const [, selectedIds] = this.resolveSelector(select);
128
+ return new Items(selectedIds.map((id) => this.get(id)), this.options);
129
+ }
130
+ select(select) {
131
+ const [single, selectedIds] = this.resolveSelector(select);
132
+ const items = selectedIds.map((id) => this.get(id)).filter(Boolean);
133
+ return single ? items[0] : items;
134
+ }
135
+ selectId(select) {
136
+ const [single, selectedIds] = this.resolveSelector(select);
137
+ return single ? selectedIds[0] : selectedIds;
138
+ }
139
+ clear() {
140
+ return new Items([], this.options);
141
+ }
142
+ every(check) {
143
+ return this.getIds().every((id) => check(this.get(id)));
144
+ }
145
+ some(check) {
146
+ return this.getIds().some((id) => check(this.get(id)));
147
+ }
148
+ has(id) {
149
+ return this.state.ids.includes(id);
150
+ }
151
+ get(id) {
152
+ return this.state.entities.get(id);
153
+ }
154
+ getIds() {
155
+ return [...this.state.ids];
156
+ }
157
+ getEntities() {
158
+ return this.state.ids.map((id) => this.get(id));
159
+ }
160
+ get length() {
161
+ return this.state.ids.length;
162
+ }
163
+ resolveSelector(select) {
164
+ if (typeof select === "function") {
165
+ const result = select(new Select(this.state.ids, this));
166
+ return [result instanceof SingleSelect, result.ids];
167
+ } else if (typeof select === "string" || typeof select === "number") return [true, [select]];
168
+ else return [false, Array.from(select)];
169
+ }
170
+ sortIds(ids, entities) {
171
+ if (this.sortComparer === false) return ids;
172
+ const sorter = this.sortComparer;
173
+ return [...ids].sort((aId, bId) => {
174
+ return sorter(entities.get(aId), entities.get(bId));
175
+ });
176
+ }
177
+ extractId(entity) {
178
+ return this.options?.selectId?.(entity) || defaultSelectId(entity);
179
+ }
180
+ get sortComparer() {
181
+ return this.options.sortComparer || false;
182
+ }
183
+ [Symbol.iterator]() {
184
+ return this.state.entities.values();
185
+ }
186
+ static compare(base, to) {
187
+ return itemsDiff(base, to);
188
+ }
189
+ };
190
+
191
+ //#endregion
192
+ export { BaseSelect, Items, Select, SingleSelect, defaultSelectId };
193
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":["ids: ItemId[]","self: Items<E>","added: ItemId[]","updated: any[]","options: ItemsOptions<E>"],"sources":["../src/utils.ts","../src/select.ts","../src/diff.ts","../src/Items.ts"],"sourcesContent":["import type { ItemId } from './types'\nexport function defaultSelectId<E extends { id: ItemId }>(entity: E): ItemId {\n return entity.id\n}\n","import type { ItemId } from './types'\nimport type { Items } from './Items'\n\nexport class BaseSelect<E> {\n constructor(\n public ids: ItemId[],\n public self: Items<E>\n ) {}\n}\n\nexport class SingleSelect<E> extends BaseSelect<E> {}\n\nexport class Select<E> extends BaseSelect<E> {\n take(len: number): Select<E> {\n return new Select(this.ids.slice(0, len), this.self)\n }\n\n skip(len: number): Select<E> {\n return new Select(this.ids.slice(len), this.self)\n }\n\n filter(testFn: (entry: E, id: ItemId, index: number) => boolean): Select<E> {\n const filteredIds = this.ids.filter((id, index) => {\n const entry = this.self.get(id)!\n return testFn(entry, id, index)\n })\n return new Select(filteredIds, this.self)\n }\n\n revert(): Select<E> {\n return new Select([...this.ids].reverse(), this.self)\n }\n\n sort(sortFn: (x: E, y: E) => number): Select<E> {\n const sortedIds = [...this.ids].sort((aId, bId) => {\n const a = this.self.get(aId)!\n const b = this.self.get(bId)!\n return sortFn(a, b)\n })\n return new Select(sortedIds, this.self)\n }\n\n at(index: number): SingleSelect<E> {\n const id = this.ids[index]\n return new SingleSelect([id], this.self)\n }\n\n from(entities: Iterable<E>): Select<E> {\n const newIds = Array.from(entities).map(entry => this.self.extractId(entry))\n return new Select(newIds, this.self)\n }\n\n on(entry: E): SingleSelect<E> {\n const id = this.self.extractId(entry)\n return new SingleSelect([id], this.self)\n }\n}\n","import type { ItemId } from './types'\nimport { Items } from './Items'\nimport { diff} from 'ohash/utils'\n\nexport function itemsDiff<E>(fromItems: Items<E>, toItems: Items<E>) {\n const ids = new Set(toItems.getIds())\n const baseIds = new Set(fromItems.getIds())\n\n const added: ItemId[] = []\n const updated: any[] = []\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","import type { CheckFn, ItemId, ItemsOptions, ItemsState, Selector, Updater } from './types'\nimport { defaultSelectId } from './utils'\nimport { Select, SingleSelect } from './select'\nimport { itemsDiff } from './diff'\n\nexport class Items<E> {\n private state: ItemsState<E, ItemId>\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.extractId(item)\n return [id, item]\n })\n )\n\n this.state = {\n ids: this.sortIds([...entities.keys()], entities),\n entities\n }\n }\n\n // NOTE: Add item if id not exists\n add(items: Iterable<E>): Items<E> {\n const newEntities = new Map(this.state.entities)\n\n Array.from(items).forEach((item) => {\n const id = this.extractId(item)\n if (!newEntities.has(id)) {\n newEntities.set(id, item)\n }\n })\n\n return new Items<E>(newEntities.values(), this.options)\n }\n\n // NOTE: update selected ids with partial data or call function (even if selected id not exists)\n update(select: Selector<E>, updater: Updater<E>): Items<E> {\n const [, selectedIds] = this.resolveSelector(select)\n const newEntities = new Map(this.state.entities)\n const isFn = typeof updater === 'function'\n\n selectedIds\n .forEach((id) => {\n const entity = newEntities.get(id)\n\n if (isFn) {\n newEntities.set(id, updater(entity))\n } else if (entity) {\n newEntities.set(id, { ...entity, ...updater })\n }\n })\n\n return new Items<E>(newEntities.values(), this.options)\n }\n\n // NOTE: add if not exists, overwrite if exists\n merge(items: Iterable<E>): Items<E> {\n const newEntities = new Map(this.state.entities)\n\n Array.from(items).forEach((item) => {\n const id = this.extractId(item)\n const entry = this.get(id)\n newEntities.set(id, entry ? { ...entry, ...item } : item)\n })\n\n return new Items<E>(newEntities.values(), this.options)\n }\n\n remove(select: Selector<E>): Items<E> {\n const [, selectedIds] = this.resolveSelector(select)\n const idsToKeep = this.state.ids.filter(id => !selectedIds.includes(id))\n const items = idsToKeep.map(id => this.get(id)!)\n return new Items<E>(items, this.options)\n }\n\n pick(select: Selector<E>): Items<E> {\n const [, selectedIds] = this.resolveSelector(select)\n const items = selectedIds.map(id => this.get(id)!)\n return new Items<E>(items, this.options)\n }\n\n select(select: ItemId): E | undefined\n select(select: Iterable<ItemId>): E[]\n select(select: (selector: Select<E>) => Select<E>): E[]\n select(select: (selector: Select<E>) => SingleSelect<E>): E | undefined\n select(select: Selector<E>): undefined | E | E[] {\n const [single, selectedIds] = this.resolveSelector(select)\n const items = selectedIds.map(id => this.get(id)).filter(Boolean) as E[]\n return single ? items[0] : items\n }\n\n selectId(select: (selector: Select<E>) => Select<E>): ItemId[]\n selectId(select: (selector: Select<E>) => SingleSelect<E>): ItemId | undefined\n selectId(select: Selector<E>): undefined | ItemId | ItemId[] {\n const [single, selectedIds] = this.resolveSelector(select)\n return single ? selectedIds[0] : selectedIds\n }\n\n clear(): Items<E> {\n return new Items<E>([], this.options)\n }\n\n every(check: CheckFn<E>) {\n return this.getIds().every(id => check(this.get(id)!))\n }\n\n some(check: CheckFn<E>) {\n return this.getIds().some(id => check(this.get(id)!))\n }\n\n has(id: ItemId) {\n return this.state.ids.includes(id)\n }\n\n get(id: ItemId): E | undefined {\n return this.state.entities.get(id)\n }\n\n getIds(): ItemId[] {\n return [...this.state.ids]\n }\n\n getEntities(): E[] {\n return this.state.ids.map(id => this.get(id)!)\n }\n\n get length(): number {\n return this.state.ids.length\n }\n\n private resolveSelector(select: Selector<E>): [boolean, ItemId[]]{\n if (typeof select === 'function') {\n const newSelect = new Select(this.state.ids, this)\n const result = select(newSelect)\n return [result instanceof SingleSelect, result.ids]\n } else if (typeof select === 'string' || typeof select === 'number') {\n return [true, [select]]\n } else {\n return [false, Array.from(select)]\n }\n }\n\n private sortIds(\n ids: ItemId[],\n entities: Map<ItemId, E>\n ): Array<ItemId> {\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 extractId(entity: E): ItemId {\n return this.options?.selectId?.(entity) as undefined || defaultSelectId(entity as E & { id: ItemId })\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 static compare<E>(base: Items<E>, to: Items<E>) {\n return itemsDiff(base, to)\n }\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.2.0",
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
- }