ts-data-forge 1.0.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/LICENSE +201 -0
- package/README.md +534 -0
- package/package.json +101 -0
- package/src/array/array-utils-creation.test.mts +443 -0
- package/src/array/array-utils-modification.test.mts +197 -0
- package/src/array/array-utils-overload-type-error.test.mts +149 -0
- package/src/array/array-utils-reducing-value.test.mts +425 -0
- package/src/array/array-utils-search.test.mts +169 -0
- package/src/array/array-utils-set-op.test.mts +335 -0
- package/src/array/array-utils-slice-clamped.test.mts +113 -0
- package/src/array/array-utils-slicing.test.mts +316 -0
- package/src/array/array-utils-transformation.test.mts +790 -0
- package/src/array/array-utils-validation.test.mts +492 -0
- package/src/array/array-utils.mts +4000 -0
- package/src/array/array.test.mts +146 -0
- package/src/array/index.mts +2 -0
- package/src/array/tuple-utils.mts +519 -0
- package/src/array/tuple-utils.test.mts +518 -0
- package/src/collections/imap-mapped.mts +801 -0
- package/src/collections/imap-mapped.test.mts +860 -0
- package/src/collections/imap.mts +651 -0
- package/src/collections/imap.test.mts +932 -0
- package/src/collections/index.mts +6 -0
- package/src/collections/iset-mapped.mts +889 -0
- package/src/collections/iset-mapped.test.mts +1187 -0
- package/src/collections/iset.mts +682 -0
- package/src/collections/iset.test.mts +1084 -0
- package/src/collections/queue.mts +390 -0
- package/src/collections/queue.test.mts +282 -0
- package/src/collections/stack.mts +423 -0
- package/src/collections/stack.test.mts +225 -0
- package/src/expect-type.mts +206 -0
- package/src/functional/index.mts +4 -0
- package/src/functional/match.mts +300 -0
- package/src/functional/match.test.mts +177 -0
- package/src/functional/optional.mts +733 -0
- package/src/functional/optional.test.mts +619 -0
- package/src/functional/pipe.mts +212 -0
- package/src/functional/pipe.test.mts +85 -0
- package/src/functional/result.mts +1134 -0
- package/src/functional/result.test.mts +777 -0
- package/src/globals.d.mts +38 -0
- package/src/guard/has-key.mts +119 -0
- package/src/guard/has-key.test.mts +219 -0
- package/src/guard/index.mts +7 -0
- package/src/guard/is-non-empty-string.mts +108 -0
- package/src/guard/is-non-empty-string.test.mts +91 -0
- package/src/guard/is-non-null-object.mts +106 -0
- package/src/guard/is-non-null-object.test.mts +90 -0
- package/src/guard/is-primitive.mts +165 -0
- package/src/guard/is-primitive.test.mts +102 -0
- package/src/guard/is-record.mts +153 -0
- package/src/guard/is-record.test.mts +112 -0
- package/src/guard/is-type.mts +450 -0
- package/src/guard/is-type.test.mts +496 -0
- package/src/guard/key-is-in.mts +163 -0
- package/src/guard/key-is-in.test.mts +19 -0
- package/src/index.mts +10 -0
- package/src/iterator/index.mts +1 -0
- package/src/iterator/range.mts +120 -0
- package/src/iterator/range.test.mts +33 -0
- package/src/json/index.mts +1 -0
- package/src/json/json.mts +711 -0
- package/src/json/json.test.mts +628 -0
- package/src/number/branded-types/finite-number.mts +354 -0
- package/src/number/branded-types/finite-number.test.mts +135 -0
- package/src/number/branded-types/index.mts +26 -0
- package/src/number/branded-types/int.mts +278 -0
- package/src/number/branded-types/int.test.mts +140 -0
- package/src/number/branded-types/int16.mts +192 -0
- package/src/number/branded-types/int16.test.mts +170 -0
- package/src/number/branded-types/int32.mts +193 -0
- package/src/number/branded-types/int32.test.mts +170 -0
- package/src/number/branded-types/non-negative-finite-number.mts +223 -0
- package/src/number/branded-types/non-negative-finite-number.test.mts +188 -0
- package/src/number/branded-types/non-negative-int16.mts +187 -0
- package/src/number/branded-types/non-negative-int16.test.mts +201 -0
- package/src/number/branded-types/non-negative-int32.mts +187 -0
- package/src/number/branded-types/non-negative-int32.test.mts +204 -0
- package/src/number/branded-types/non-zero-finite-number.mts +229 -0
- package/src/number/branded-types/non-zero-finite-number.test.mts +198 -0
- package/src/number/branded-types/non-zero-int.mts +167 -0
- package/src/number/branded-types/non-zero-int.test.mts +177 -0
- package/src/number/branded-types/non-zero-int16.mts +196 -0
- package/src/number/branded-types/non-zero-int16.test.mts +195 -0
- package/src/number/branded-types/non-zero-int32.mts +196 -0
- package/src/number/branded-types/non-zero-int32.test.mts +197 -0
- package/src/number/branded-types/non-zero-safe-int.mts +196 -0
- package/src/number/branded-types/non-zero-safe-int.test.mts +232 -0
- package/src/number/branded-types/non-zero-uint16.mts +189 -0
- package/src/number/branded-types/non-zero-uint16.test.mts +199 -0
- package/src/number/branded-types/non-zero-uint32.mts +189 -0
- package/src/number/branded-types/non-zero-uint32.test.mts +199 -0
- package/src/number/branded-types/positive-finite-number.mts +241 -0
- package/src/number/branded-types/positive-finite-number.test.mts +204 -0
- package/src/number/branded-types/positive-int.mts +304 -0
- package/src/number/branded-types/positive-int.test.mts +176 -0
- package/src/number/branded-types/positive-int16.mts +188 -0
- package/src/number/branded-types/positive-int16.test.mts +197 -0
- package/src/number/branded-types/positive-int32.mts +188 -0
- package/src/number/branded-types/positive-int32.test.mts +197 -0
- package/src/number/branded-types/positive-safe-int.mts +187 -0
- package/src/number/branded-types/positive-safe-int.test.mts +210 -0
- package/src/number/branded-types/positive-uint16.mts +188 -0
- package/src/number/branded-types/positive-uint16.test.mts +203 -0
- package/src/number/branded-types/positive-uint32.mts +188 -0
- package/src/number/branded-types/positive-uint32.test.mts +203 -0
- package/src/number/branded-types/safe-int.mts +291 -0
- package/src/number/branded-types/safe-int.test.mts +170 -0
- package/src/number/branded-types/safe-uint.mts +187 -0
- package/src/number/branded-types/safe-uint.test.mts +176 -0
- package/src/number/branded-types/uint.mts +179 -0
- package/src/number/branded-types/uint.test.mts +158 -0
- package/src/number/branded-types/uint16.mts +186 -0
- package/src/number/branded-types/uint16.test.mts +170 -0
- package/src/number/branded-types/uint32.mts +218 -0
- package/src/number/branded-types/uint32.test.mts +170 -0
- package/src/number/enum/index.mts +2 -0
- package/src/number/enum/int8.mts +344 -0
- package/src/number/enum/int8.test.mts +180 -0
- package/src/number/enum/uint8.mts +293 -0
- package/src/number/enum/uint8.test.mts +164 -0
- package/src/number/index.mts +4 -0
- package/src/number/num.mts +604 -0
- package/src/number/num.test.mts +242 -0
- package/src/number/refined-number-utils.mts +566 -0
- package/src/object/index.mts +1 -0
- package/src/object/object.mts +447 -0
- package/src/object/object.test.mts +124 -0
- package/src/others/cast-mutable.mts +113 -0
- package/src/others/cast-readonly.mts +192 -0
- package/src/others/cast-readonly.test.mts +89 -0
- package/src/others/if-then.mts +98 -0
- package/src/others/if-then.test.mts +75 -0
- package/src/others/index.mts +7 -0
- package/src/others/map-nullable.mts +172 -0
- package/src/others/map-nullable.test.mts +297 -0
- package/src/others/memoize-function.mts +196 -0
- package/src/others/memoize-function.test.mts +168 -0
- package/src/others/tuple.mts +160 -0
- package/src/others/tuple.test.mts +11 -0
- package/src/others/unknown-to-string.mts +215 -0
- package/src/others/unknown-to-string.test.mts +114 -0
|
@@ -0,0 +1,682 @@
|
|
|
1
|
+
import { Result } from '../functional/index.mjs';
|
|
2
|
+
import { asUint32 } from '../number/index.mjs';
|
|
3
|
+
import { unknownToString } from '../others/unknown-to-string.mjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Interface for an immutable set with O(1) lookup performance and set operation support.
|
|
7
|
+
*
|
|
8
|
+
* This interface defines all methods and properties available on ISet instances. All operations
|
|
9
|
+
* that modify the set return new ISet instances, preserving immutability. The underlying implementation
|
|
10
|
+
* uses JavaScript's native Set for O(1) average-case performance on add, has, and delete operations.
|
|
11
|
+
*
|
|
12
|
+
* **Immutability Guarantees:**
|
|
13
|
+
* - All mutation operations (add, delete) return new ISet instances
|
|
14
|
+
* - Original ISet instances are never modified
|
|
15
|
+
* - Safe for concurrent access and functional programming patterns
|
|
16
|
+
*
|
|
17
|
+
* **Performance Characteristics:**
|
|
18
|
+
* - has/add/delete: O(1) average case
|
|
19
|
+
* - Set operations (union, intersection, difference): O(n)
|
|
20
|
+
* - map/filter operations: O(n)
|
|
21
|
+
* - Iteration: O(n)
|
|
22
|
+
*
|
|
23
|
+
* @template K The type of the elements in the set. Must extend MapSetKeyType (string, number, boolean, etc.)
|
|
24
|
+
*/
|
|
25
|
+
type ISetInterface<K extends MapSetKeyType> = Readonly<{
|
|
26
|
+
// Getting information
|
|
27
|
+
|
|
28
|
+
/** The number of elements in the set. */
|
|
29
|
+
size: SizeType.Arr;
|
|
30
|
+
|
|
31
|
+
/** Checks if the set is empty. */
|
|
32
|
+
isEmpty: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Checks if an element exists in the set.
|
|
35
|
+
* Allows for wider literal types for keys during checking.
|
|
36
|
+
* @param key The element to check.
|
|
37
|
+
* @returns `true` if the element exists, `false` otherwise.
|
|
38
|
+
*/
|
|
39
|
+
has: (key: K | (WidenLiteral<K> & {})) => boolean;
|
|
40
|
+
|
|
41
|
+
// Reducing a value
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Checks if all elements in the set satisfy a predicate.
|
|
45
|
+
* @param predicate A function to test each element.
|
|
46
|
+
* @returns `true` if all elements satisfy the predicate, `false` otherwise.
|
|
47
|
+
*/
|
|
48
|
+
every: ((predicate: (key: K) => boolean) => boolean) &
|
|
49
|
+
/**
|
|
50
|
+
* Checks if all elements in the set satisfy a type predicate.
|
|
51
|
+
* Narrows the type of elements in the set if the predicate returns true for all elements.
|
|
52
|
+
* @template L The narrowed type of the elements.
|
|
53
|
+
* @param predicate A type predicate function.
|
|
54
|
+
* @returns `true` if all elements satisfy the predicate, `false` otherwise.
|
|
55
|
+
*/
|
|
56
|
+
(<L extends K>(predicate: (key: K) => key is L) => this is ISet<L>);
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Checks if at least one element in the set satisfies a predicate.
|
|
60
|
+
* @param predicate A function to test each element.
|
|
61
|
+
* @returns `true` if at least one element satisfies the predicate, `false` otherwise.
|
|
62
|
+
*/
|
|
63
|
+
some: (predicate: (key: K) => boolean) => boolean;
|
|
64
|
+
|
|
65
|
+
// Mutation
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Adds an element to the set.
|
|
69
|
+
* @param key The element to add.
|
|
70
|
+
* @returns A new ISet instance with the element added.
|
|
71
|
+
*/
|
|
72
|
+
add: (key: K) => ISet<K>;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Deletes an element from the set.
|
|
76
|
+
* @param key The element to delete.
|
|
77
|
+
* @returns A new ISet instance without the specified element.
|
|
78
|
+
*/
|
|
79
|
+
delete: (key: K) => ISet<K>;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Applies a series of mutations to the set.
|
|
83
|
+
* @param actions An array of mutation actions (add or delete).
|
|
84
|
+
* @returns A new ISet instance with all mutations applied.
|
|
85
|
+
*/
|
|
86
|
+
withMutations: (
|
|
87
|
+
actions: readonly Readonly<
|
|
88
|
+
{ type: 'add'; key: K } | { type: 'delete'; key: K }
|
|
89
|
+
>[],
|
|
90
|
+
) => ISet<K>;
|
|
91
|
+
|
|
92
|
+
// Sequence algorithms
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Maps the elements of the set to new elements.
|
|
96
|
+
* @template K2 The type of the new elements.
|
|
97
|
+
* @param mapFn A function that maps an element to a new element.
|
|
98
|
+
* @returns A new ISet instance with mapped elements.
|
|
99
|
+
*/
|
|
100
|
+
map: <K2 extends MapSetKeyType>(mapFn: (key: K) => K2) => ISet<K2>;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Filters the elements of the set based on a type predicate.
|
|
104
|
+
* Narrows the type of elements in the resulting set.
|
|
105
|
+
* @template K2 The narrowed type of the elements.
|
|
106
|
+
* @param predicate A type predicate function.
|
|
107
|
+
* @returns A new ISet instance with elements that satisfy the type predicate.
|
|
108
|
+
*/
|
|
109
|
+
filter: (<K2 extends K>(predicate: (key: K) => key is K2) => ISet<K2>) &
|
|
110
|
+
/**
|
|
111
|
+
* Filters the elements of the set based on a predicate.
|
|
112
|
+
* @param predicate A function to test each element.
|
|
113
|
+
* @returns A new ISet instance with elements that satisfy the predicate.
|
|
114
|
+
*/
|
|
115
|
+
((predicate: (key: K) => boolean) => ISet<K>);
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Filters the elements of the set by excluding elements for which the predicate returns true.
|
|
119
|
+
* @param predicate A function to test each element.
|
|
120
|
+
* @returns A new ISet instance with elements for which the predicate returned `false`.
|
|
121
|
+
*/
|
|
122
|
+
filterNot: (predicate: (key: K) => boolean) => ISet<K>;
|
|
123
|
+
|
|
124
|
+
// Set operations
|
|
125
|
+
/**
|
|
126
|
+
* Checks if this set is a subset of another set.
|
|
127
|
+
* @param set The other set.
|
|
128
|
+
* @returns `true` if this set is a subset of the other set, `false` otherwise.
|
|
129
|
+
*/
|
|
130
|
+
isSubsetOf: (set: ISet<WidenLiteral<K>>) => boolean;
|
|
131
|
+
/**
|
|
132
|
+
* Checks if this set is a superset of another set.
|
|
133
|
+
* @param set The other set.
|
|
134
|
+
* @returns `true` if this set is a superset of the other set, `false` otherwise.
|
|
135
|
+
*/
|
|
136
|
+
isSupersetOf: (set: ISet<WidenLiteral<K>>) => boolean;
|
|
137
|
+
/**
|
|
138
|
+
* Returns a new set with elements that are in this set but not in another set.
|
|
139
|
+
* @param set The other set.
|
|
140
|
+
* @returns A new ISet instance representing the set difference.
|
|
141
|
+
*/
|
|
142
|
+
subtract: (set: ISet<K>) => ISet<K>;
|
|
143
|
+
/**
|
|
144
|
+
* Returns a new set with elements that are common to both this set and another set.
|
|
145
|
+
* @param set The other set.
|
|
146
|
+
* @returns A new ISet instance representing the set intersection.
|
|
147
|
+
*/
|
|
148
|
+
intersect: (set: ISet<K>) => ISet<K>;
|
|
149
|
+
/**
|
|
150
|
+
* Returns a new set with all elements from both this set and another set.
|
|
151
|
+
* @template K2 The type of elements in the other set.
|
|
152
|
+
* @param set The other set.
|
|
153
|
+
* @returns A new ISet instance representing the set union.
|
|
154
|
+
*/
|
|
155
|
+
union: <K2 extends MapSetKeyType>(set: ISet<K2>) => ISet<K | K2>;
|
|
156
|
+
|
|
157
|
+
// Side effects
|
|
158
|
+
/**
|
|
159
|
+
* Executes a callback function for each element in the set.
|
|
160
|
+
* @param callbackfn A function to execute for each element.
|
|
161
|
+
*/
|
|
162
|
+
forEach: (callbackfn: (key: K) => void) => void;
|
|
163
|
+
|
|
164
|
+
// Iterators
|
|
165
|
+
/**
|
|
166
|
+
* Returns an iterator for the elements in the set (alias for values).
|
|
167
|
+
* @returns An iterable iterator of elements.
|
|
168
|
+
*/
|
|
169
|
+
keys: () => IterableIterator<K>;
|
|
170
|
+
/**
|
|
171
|
+
* Returns an iterator for the elements in the set.
|
|
172
|
+
* @returns An iterable iterator of elements.
|
|
173
|
+
*/
|
|
174
|
+
values: () => IterableIterator<K>;
|
|
175
|
+
/**
|
|
176
|
+
* Returns an iterator for the entries (element-element pairs) in the set.
|
|
177
|
+
* @returns An iterable iterator of entries.
|
|
178
|
+
*/
|
|
179
|
+
entries: () => IterableIterator<readonly [K, K]>;
|
|
180
|
+
|
|
181
|
+
// Conversion
|
|
182
|
+
/**
|
|
183
|
+
* Converts the elements of the set to an array.
|
|
184
|
+
* @returns A readonly array of elements.
|
|
185
|
+
*/
|
|
186
|
+
toArray: () => readonly K[];
|
|
187
|
+
/**
|
|
188
|
+
* Returns the underlying readonly JavaScript Set.
|
|
189
|
+
* @returns The raw ReadonlySet instance.
|
|
190
|
+
*/
|
|
191
|
+
toRawSet: () => ReadonlySet<K>;
|
|
192
|
+
}>;
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Represents an immutable set with high-performance operations and comprehensive set algebra support.
|
|
196
|
+
*
|
|
197
|
+
* ISet is a persistent data structure that provides all the functionality of JavaScript's Set
|
|
198
|
+
* while maintaining immutability. All operations that would normally mutate the set instead
|
|
199
|
+
* return new ISet instances, making it safe for functional programming and concurrent access.
|
|
200
|
+
*
|
|
201
|
+
* **Key Features:**
|
|
202
|
+
* - **Immutable**: All mutation operations return new instances
|
|
203
|
+
* - **High Performance**: O(1) average-case for has/add/delete operations
|
|
204
|
+
* - **Set Operations**: Full support for union, intersection, difference, subset/superset checks
|
|
205
|
+
* - **Type Safe**: Full TypeScript support with generic element types
|
|
206
|
+
* - **Iterable**: Implements standard JavaScript iteration protocols
|
|
207
|
+
* - **Functional**: Rich API for map, filter, and functional composition
|
|
208
|
+
*
|
|
209
|
+
* **When to Use:**
|
|
210
|
+
* - Managing collections of unique values with immutability guarantees
|
|
211
|
+
* - Set algebra operations (unions, intersections, differences)
|
|
212
|
+
* - Membership testing with O(1) performance
|
|
213
|
+
* - Functional programming patterns requiring immutable collections
|
|
214
|
+
*
|
|
215
|
+
* @template K The type of the elements in the set. Must extend MapSetKeyType.
|
|
216
|
+
*/
|
|
217
|
+
export type ISet<K extends MapSetKeyType> = Iterable<K> & ISetInterface<K>;
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Provides utility functions for ISet.
|
|
221
|
+
*/
|
|
222
|
+
export namespace ISet {
|
|
223
|
+
/**
|
|
224
|
+
* Creates a new ISet instance from an iterable of elements.
|
|
225
|
+
*
|
|
226
|
+
* This factory function accepts any iterable of elements, including arrays,
|
|
227
|
+
* JavaScript Sets, other ISets, or custom iterables. Duplicate elements in the
|
|
228
|
+
* input iterable will be automatically deduplicated in the resulting set.
|
|
229
|
+
*
|
|
230
|
+
* **Performance:** O(n) where n is the number of elements in the iterable.
|
|
231
|
+
*
|
|
232
|
+
* @template K The type of the elements. Must extend MapSetKeyType.
|
|
233
|
+
* @param iterable An iterable of elements (e.g., Array, Set, ISet, etc.)
|
|
234
|
+
* @returns A new ISet instance containing all unique elements from the iterable.
|
|
235
|
+
*
|
|
236
|
+
* @example
|
|
237
|
+
* ```typescript
|
|
238
|
+
* // From array (duplicates automatically removed)
|
|
239
|
+
* const uniqueIds = ISet.create([1, 2, 3, 2, 1]); // Contains: 1, 2, 3
|
|
240
|
+
* console.log(uniqueIds.size); // Output: 3
|
|
241
|
+
*
|
|
242
|
+
* // From JavaScript Set
|
|
243
|
+
* const jsSet = new Set(["red", "green", "blue"]);
|
|
244
|
+
* const colors = ISet.create(jsSet);
|
|
245
|
+
* console.log(colors.has("red")); // Output: true
|
|
246
|
+
*
|
|
247
|
+
* // From another ISet (creates a copy)
|
|
248
|
+
* const originalTags = ISet.create(["typescript", "immutable"]);
|
|
249
|
+
* const copiedTags = ISet.create(originalTags);
|
|
250
|
+
* console.log(copiedTags.size); // Output: 2
|
|
251
|
+
*
|
|
252
|
+
* // Empty set
|
|
253
|
+
* const emptyPermissions = ISet.create<string>([]);
|
|
254
|
+
* console.log(emptyPermissions.isEmpty); // Output: true
|
|
255
|
+
*
|
|
256
|
+
* // Fluent operations
|
|
257
|
+
* const processedNumbers = ISet.create([1, 2, 3, 4, 5])
|
|
258
|
+
* .filter(x => x % 2 === 0) // Keep even numbers: 2, 4
|
|
259
|
+
* .add(6) // Add 6: 2, 4, 6
|
|
260
|
+
* .delete(2); // Remove 2: 4, 6
|
|
261
|
+
* console.log(processedNumbers.toArray().sort()); // Output: [4, 6]
|
|
262
|
+
*
|
|
263
|
+
* // From generator function
|
|
264
|
+
* function* generatePrimes(): Generator<number> {
|
|
265
|
+
* yield 2; yield 3; yield 5; yield 7;
|
|
266
|
+
* }
|
|
267
|
+
* const primes = ISet.create(generatePrimes());
|
|
268
|
+
* console.log(primes.size); // Output: 4
|
|
269
|
+
* ```
|
|
270
|
+
*/
|
|
271
|
+
export const create = <K extends MapSetKeyType>(
|
|
272
|
+
iterable: Iterable<K>,
|
|
273
|
+
): ISet<K> => new ISetClass<K>(iterable);
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Checks if two ISet instances are structurally equal.
|
|
277
|
+
*
|
|
278
|
+
* Two ISets are considered equal if they have the same size and contain exactly the same
|
|
279
|
+
* elements. The order of elements does not matter for equality comparison since sets are
|
|
280
|
+
* unordered collections. Elements are compared using JavaScript's `===` operator.
|
|
281
|
+
*
|
|
282
|
+
* **Performance:** O(n) where n is the size of the smaller set.
|
|
283
|
+
*
|
|
284
|
+
* @template K The type of the elements.
|
|
285
|
+
* @param a The first ISet instance to compare.
|
|
286
|
+
* @param b The second ISet instance to compare.
|
|
287
|
+
* @returns `true` if the sets contain exactly the same elements, `false` otherwise.
|
|
288
|
+
*
|
|
289
|
+
* @example
|
|
290
|
+
* ```typescript
|
|
291
|
+
* // Basic equality comparison
|
|
292
|
+
* const permissions1 = ISet.create(["read", "write", "execute"]);
|
|
293
|
+
* const permissions2 = ISet.create(["execute", "read", "write"]); // Order doesn't matter
|
|
294
|
+
* const permissions3 = ISet.create(["read", "write"]);
|
|
295
|
+
*
|
|
296
|
+
* console.log(ISet.equal(permissions1, permissions2)); // true
|
|
297
|
+
* console.log(ISet.equal(permissions1, permissions3)); // false (different sizes)
|
|
298
|
+
*
|
|
299
|
+
* // With different element types
|
|
300
|
+
* const numbers1 = ISet.create([1, 2, 3]);
|
|
301
|
+
* const numbers2 = ISet.create([3, 1, 2]);
|
|
302
|
+
* const numbers3 = ISet.create([1, 2, 4]); // Different element
|
|
303
|
+
*
|
|
304
|
+
* console.log(ISet.equal(numbers1, numbers2)); // true
|
|
305
|
+
* console.log(ISet.equal(numbers1, numbers3)); // false
|
|
306
|
+
*
|
|
307
|
+
* // Empty sets
|
|
308
|
+
* const empty1 = ISet.create<string>([]);
|
|
309
|
+
* const empty2 = ISet.create<string>([]);
|
|
310
|
+
* console.log(ISet.equal(empty1, empty2)); // true
|
|
311
|
+
*
|
|
312
|
+
* // Single element sets
|
|
313
|
+
* const single1 = ISet.create(["unique"]);
|
|
314
|
+
* const single2 = ISet.create(["unique"]);
|
|
315
|
+
* const single3 = ISet.create(["different"]);
|
|
316
|
+
*
|
|
317
|
+
* console.log(ISet.equal(single1, single2)); // true
|
|
318
|
+
* console.log(ISet.equal(single1, single3)); // false
|
|
319
|
+
* ```
|
|
320
|
+
*/
|
|
321
|
+
export const equal = <K extends MapSetKeyType>(
|
|
322
|
+
a: ISet<K>,
|
|
323
|
+
b: ISet<K>,
|
|
324
|
+
): boolean => a.size === b.size && a.every((e) => b.has(e));
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Computes the difference between two ISet instances, identifying added and deleted elements.
|
|
328
|
+
*
|
|
329
|
+
* This function performs a set difference operation to determine what elements were added
|
|
330
|
+
* and what elements were deleted when transitioning from the old set to the new set.
|
|
331
|
+
* This is useful for change detection, state management, and synchronization scenarios.
|
|
332
|
+
*
|
|
333
|
+
* **Performance:** O(n + m) where n and m are the sizes of the old and new sets respectively.
|
|
334
|
+
*
|
|
335
|
+
* @template K The type of the elements.
|
|
336
|
+
* @param oldSet The original set representing the previous state.
|
|
337
|
+
* @param newSet The new set representing the current state.
|
|
338
|
+
* @returns An object with `added` and `deleted` properties, each containing an ISet
|
|
339
|
+
* of elements that were added or removed respectively.
|
|
340
|
+
*
|
|
341
|
+
* @example
|
|
342
|
+
* ```typescript
|
|
343
|
+
* // User permission changes
|
|
344
|
+
* const oldPermissions = ISet.create(["read", "write", "delete"]);
|
|
345
|
+
* const newPermissions = ISet.create(["read", "write", "execute", "admin"]);
|
|
346
|
+
*
|
|
347
|
+
* const permissionDiff = ISet.diff(oldPermissions, newPermissions);
|
|
348
|
+
*
|
|
349
|
+
* console.log("Permissions removed:", permissionDiff.deleted.toArray());
|
|
350
|
+
* // Output: ["delete"]
|
|
351
|
+
*
|
|
352
|
+
* console.log("Permissions added:", permissionDiff.added.toArray());
|
|
353
|
+
* // Output: ["execute", "admin"]
|
|
354
|
+
*
|
|
355
|
+
* // No changes
|
|
356
|
+
* const unchanged1 = ISet.create(["a", "b", "c"]);
|
|
357
|
+
* const unchanged2 = ISet.create(["a", "b", "c"]);
|
|
358
|
+
* const noDiff = ISet.diff(unchanged1, unchanged2);
|
|
359
|
+
*
|
|
360
|
+
* console.log(noDiff.added.isEmpty); // true
|
|
361
|
+
* console.log(noDiff.deleted.isEmpty); // true
|
|
362
|
+
*
|
|
363
|
+
* // Complete replacement
|
|
364
|
+
* const oldTags = ISet.create(["javascript", "react"]);
|
|
365
|
+
* const newTags = ISet.create(["typescript", "vue"]);
|
|
366
|
+
* const tagDiff = ISet.diff(oldTags, newTags);
|
|
367
|
+
*
|
|
368
|
+
* console.log(tagDiff.deleted.toArray()); // ["javascript", "react"]
|
|
369
|
+
* console.log(tagDiff.added.toArray()); // ["typescript", "vue"]
|
|
370
|
+
* ```
|
|
371
|
+
*/
|
|
372
|
+
export const diff = <K extends MapSetKeyType>(
|
|
373
|
+
oldSet: ISet<K>,
|
|
374
|
+
newSet: ISet<K>,
|
|
375
|
+
): ReadonlyRecord<'added' | 'deleted', ISet<K>> => ({
|
|
376
|
+
deleted: oldSet.subtract(newSet),
|
|
377
|
+
added: newSet.subtract(oldSet),
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Computes the intersection of two ISet instances.
|
|
382
|
+
*
|
|
383
|
+
* Returns a new set containing only the elements that are present in both input sets.
|
|
384
|
+
* This operation is commutative: `intersection(a, b) === intersection(b, a)`.
|
|
385
|
+
*
|
|
386
|
+
* **Performance:** O(min(n, m)) where n and m are the sizes of the input sets.
|
|
387
|
+
*
|
|
388
|
+
* @template K The type of the elements.
|
|
389
|
+
* @param a The first set.
|
|
390
|
+
* @param b The second set.
|
|
391
|
+
* @returns A new ISet instance containing elements common to both sets.
|
|
392
|
+
*
|
|
393
|
+
* @example
|
|
394
|
+
* ```typescript
|
|
395
|
+
* // Finding common permissions between user and role
|
|
396
|
+
* const userPermissions = ISet.create(["read", "write", "delete", "admin"]);
|
|
397
|
+
* const rolePermissions = ISet.create(["read", "write", "execute"]);
|
|
398
|
+
*
|
|
399
|
+
* const commonPermissions = ISet.intersection(userPermissions, rolePermissions);
|
|
400
|
+
* console.log(commonPermissions.toArray()); // ["read", "write"]
|
|
401
|
+
*
|
|
402
|
+
* // No common elements
|
|
403
|
+
* const setA = ISet.create([1, 2, 3]);
|
|
404
|
+
* const setB = ISet.create([4, 5, 6]);
|
|
405
|
+
* const noCommon = ISet.intersection(setA, setB);
|
|
406
|
+
* console.log(noCommon.isEmpty); // true
|
|
407
|
+
*
|
|
408
|
+
* // Complete overlap
|
|
409
|
+
* const identical1 = ISet.create(["a", "b", "c"]);
|
|
410
|
+
* const identical2 = ISet.create(["a", "b", "c"]);
|
|
411
|
+
* const completeOverlap = ISet.intersection(identical1, identical2);
|
|
412
|
+
* console.log(ISet.equal(completeOverlap, identical1)); // true
|
|
413
|
+
*
|
|
414
|
+
* // Intersection with empty set
|
|
415
|
+
* const nonEmpty = ISet.create([1, 2, 3]);
|
|
416
|
+
* const empty = ISet.create<number>([]);
|
|
417
|
+
* const withEmpty = ISet.intersection(nonEmpty, empty);
|
|
418
|
+
* console.log(withEmpty.isEmpty); // true
|
|
419
|
+
* ```
|
|
420
|
+
*/
|
|
421
|
+
export const intersection = <K extends MapSetKeyType>(
|
|
422
|
+
a: ISet<K>,
|
|
423
|
+
b: ISet<K>,
|
|
424
|
+
): ISet<K> => a.intersect(b);
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Computes the union of two ISet instances.
|
|
428
|
+
*
|
|
429
|
+
* Returns a new set containing all elements that are present in either input set.
|
|
430
|
+
* Duplicate elements are automatically handled since sets only contain unique values.
|
|
431
|
+
* This operation is commutative: `union(a, b) === union(b, a)`.
|
|
432
|
+
*
|
|
433
|
+
* **Performance:** O(n + m) where n and m are the sizes of the input sets.
|
|
434
|
+
*
|
|
435
|
+
* @template K1 The type of elements in the first set.
|
|
436
|
+
* @template K2 The type of elements in the second set.
|
|
437
|
+
* @param a The first set.
|
|
438
|
+
* @param b The second set.
|
|
439
|
+
* @returns A new ISet instance containing all elements from both sets.
|
|
440
|
+
*
|
|
441
|
+
* @example
|
|
442
|
+
* ```typescript
|
|
443
|
+
* // Combining permissions from multiple sources
|
|
444
|
+
* const userPermissions = ISet.create(["read", "write"]);
|
|
445
|
+
* const rolePermissions = ISet.create(["write", "execute", "admin"]);
|
|
446
|
+
*
|
|
447
|
+
* const allPermissions = ISet.union(userPermissions, rolePermissions);
|
|
448
|
+
* console.log(allPermissions.toArray().sort());
|
|
449
|
+
* // Output: ["admin", "execute", "read", "write"]
|
|
450
|
+
*
|
|
451
|
+
* // Union with different types (type widening)
|
|
452
|
+
* const numbers = ISet.create([1, 2, 3]);
|
|
453
|
+
* const strings = ISet.create(["a", "b"]);
|
|
454
|
+
* const mixed = ISet.union(numbers, strings); // ISet<number | string>
|
|
455
|
+
* console.log(mixed.size); // 5
|
|
456
|
+
*
|
|
457
|
+
* // Union with empty set
|
|
458
|
+
* const nonEmpty = ISet.create(["item1", "item2"]);
|
|
459
|
+
* const empty = ISet.create<string>([]);
|
|
460
|
+
* const withEmpty = ISet.union(nonEmpty, empty);
|
|
461
|
+
* console.log(ISet.equal(withEmpty, nonEmpty)); // true
|
|
462
|
+
*
|
|
463
|
+
* // Overlapping sets
|
|
464
|
+
* const featuresA = ISet.create(["feature1", "feature2", "feature3"]);
|
|
465
|
+
* const featuresB = ISet.create(["feature2", "feature3", "feature4"]);
|
|
466
|
+
* const allFeatures = ISet.union(featuresA, featuresB);
|
|
467
|
+
* console.log(allFeatures.size); // 4 (duplicates removed)
|
|
468
|
+
* ```
|
|
469
|
+
*/
|
|
470
|
+
export const union = <K1 extends MapSetKeyType, K2 extends MapSetKeyType>(
|
|
471
|
+
a: ISet<K1>,
|
|
472
|
+
b: ISet<K2>,
|
|
473
|
+
): ISet<K1 | K2> => a.union(b);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Internal class implementation for ISet providing immutable set operations.
|
|
478
|
+
*
|
|
479
|
+
* This class implements the ISet interface using JavaScript's native Set as the underlying
|
|
480
|
+
* storage mechanism for optimal performance. All mutation operations create new instances
|
|
481
|
+
* rather than modifying the existing set, ensuring immutability.
|
|
482
|
+
*
|
|
483
|
+
* **Implementation Details:**
|
|
484
|
+
* - Uses ReadonlySet<K> internally for type safety and performance
|
|
485
|
+
* - Implements copy-on-write semantics for efficiency
|
|
486
|
+
* - Provides optional debug messaging for development
|
|
487
|
+
*
|
|
488
|
+
* @template K The type of the elements. Must extend MapSetKeyType.
|
|
489
|
+
* @implements ISet
|
|
490
|
+
* @implements Iterable
|
|
491
|
+
* @internal This class should not be used directly. Use ISet.create() instead.
|
|
492
|
+
*/
|
|
493
|
+
class ISetClass<K extends MapSetKeyType> implements ISet<K>, Iterable<K> {
|
|
494
|
+
readonly #set: ReadonlySet<K>;
|
|
495
|
+
readonly #showNotFoundMessage: boolean;
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Constructs an ISetClass instance with the given elements.
|
|
499
|
+
*
|
|
500
|
+
* @param iterable An iterable of elements to populate the set.
|
|
501
|
+
* @param showNotFoundMessage Whether to log warning messages when operations
|
|
502
|
+
* are performed on non-existent elements. Useful for debugging.
|
|
503
|
+
* Defaults to false for production use.
|
|
504
|
+
* @internal Use ISet.create() instead of calling this constructor directly.
|
|
505
|
+
*/
|
|
506
|
+
constructor(iterable: Iterable<K>, showNotFoundMessage: boolean = false) {
|
|
507
|
+
this.#set = new Set(iterable);
|
|
508
|
+
this.#showNotFoundMessage = showNotFoundMessage;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/** @inheritdoc */
|
|
512
|
+
get size(): SizeType.Arr {
|
|
513
|
+
return asUint32(this.#set.size);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/** @inheritdoc */
|
|
517
|
+
get isEmpty(): boolean {
|
|
518
|
+
return this.size === 0;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/** @inheritdoc */
|
|
522
|
+
has(key: K | (WidenLiteral<K> & {})): boolean {
|
|
523
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
524
|
+
return this.#set.has(key as K);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/** @inheritdoc */
|
|
528
|
+
every<L extends K>(predicate: (key: K) => key is L): this is ISet<L>;
|
|
529
|
+
|
|
530
|
+
/** @inheritdoc */
|
|
531
|
+
every(predicate: (key: K) => boolean): boolean;
|
|
532
|
+
|
|
533
|
+
/** @inheritdoc */
|
|
534
|
+
every(predicate: (key: K) => boolean): boolean {
|
|
535
|
+
for (const key of this.values()) {
|
|
536
|
+
if (!predicate(key)) return false;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
return true;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/** @inheritdoc */
|
|
543
|
+
some(predicate: (key: K) => boolean): boolean {
|
|
544
|
+
for (const key of this.values()) {
|
|
545
|
+
if (predicate(key)) return true;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
return false;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/** @inheritdoc */
|
|
552
|
+
add(key: K): ISet<K> {
|
|
553
|
+
if (this.has(key)) return this;
|
|
554
|
+
|
|
555
|
+
return ISet.create([...this.#set, key]);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/** @inheritdoc */
|
|
559
|
+
delete(key: K): ISet<K> {
|
|
560
|
+
if (!this.has(key)) {
|
|
561
|
+
if (this.#showNotFoundMessage) {
|
|
562
|
+
const keyStr = unknownToString(key);
|
|
563
|
+
console.warn(
|
|
564
|
+
`ISet.delete: key not found: ${Result.isOk(keyStr) ? keyStr.value : '<error converting key to string>'}`,
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
return this;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
return ISet.create(Array.from(this.#set).filter((k) => !Object.is(k, key)));
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/** @inheritdoc */
|
|
574
|
+
withMutations(
|
|
575
|
+
actions: readonly Readonly<
|
|
576
|
+
{ type: 'add'; key: K } | { type: 'delete'; key: K }
|
|
577
|
+
>[],
|
|
578
|
+
): ISet<K> {
|
|
579
|
+
const mut_result = new Set<K>(this.#set);
|
|
580
|
+
|
|
581
|
+
for (const action of actions) {
|
|
582
|
+
switch (action.type) {
|
|
583
|
+
case 'delete':
|
|
584
|
+
mut_result.delete(action.key);
|
|
585
|
+
break;
|
|
586
|
+
|
|
587
|
+
case 'add':
|
|
588
|
+
mut_result.add(action.key);
|
|
589
|
+
break;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
return ISet.create(mut_result);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/** @inheritdoc */
|
|
597
|
+
map<K2 extends MapSetKeyType>(mapFn: (key: K) => K2): ISet<K2> {
|
|
598
|
+
return ISet.create(this.toArray().map(mapFn));
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/** @inheritdoc */
|
|
602
|
+
filter<K2 extends K>(predicate: (key: K) => key is K2): ISet<K2>;
|
|
603
|
+
|
|
604
|
+
/** @inheritdoc */
|
|
605
|
+
filter(predicate: (key: K) => boolean): ISet<K>;
|
|
606
|
+
|
|
607
|
+
/** @inheritdoc */
|
|
608
|
+
filter(predicate: (key: K) => boolean): ISet<K> {
|
|
609
|
+
return ISet.create(this.toArray().filter(predicate));
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/** @inheritdoc */
|
|
613
|
+
filterNot(predicate: (key: K) => boolean): ISet<K> {
|
|
614
|
+
return ISet.create(this.toArray().filter((e) => !predicate(e)));
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/** @inheritdoc */
|
|
618
|
+
forEach(callbackfn: (key: K) => void): void {
|
|
619
|
+
for (const v of this.#set.values()) {
|
|
620
|
+
callbackfn(v);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/** @inheritdoc */
|
|
625
|
+
isSubsetOf(set: ISet<WidenLiteral<K>>): boolean {
|
|
626
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
627
|
+
return this.every((k) => set.has(k as WidenLiteral<K>));
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/** @inheritdoc */
|
|
631
|
+
isSupersetOf(set: ISet<WidenLiteral<K>>): boolean {
|
|
632
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
633
|
+
return set.every((k) => this.has(k as K));
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/** @inheritdoc */
|
|
637
|
+
subtract(set: ISet<K>): ISet<K> {
|
|
638
|
+
return ISet.create(this.toArray().filter((k) => !set.has(k)));
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/** @inheritdoc */
|
|
642
|
+
intersect(set: ISet<K>): ISet<K> {
|
|
643
|
+
return ISet.create(this.toArray().filter((k) => set.has(k)));
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/** @inheritdoc */
|
|
647
|
+
union<K2 extends MapSetKeyType>(set: ISet<K2>): ISet<K | K2> {
|
|
648
|
+
return ISet.create([...this, ...set]);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* @inheritdoc
|
|
653
|
+
*/
|
|
654
|
+
[Symbol.iterator](): Iterator<K> {
|
|
655
|
+
return this.#set[Symbol.iterator]();
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/** @inheritdoc */
|
|
659
|
+
keys(): IterableIterator<K> {
|
|
660
|
+
return this.#set.keys();
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/** @inheritdoc */
|
|
664
|
+
values(): IterableIterator<K> {
|
|
665
|
+
return this.#set.values();
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/** @inheritdoc */
|
|
669
|
+
entries(): IterableIterator<readonly [K, K]> {
|
|
670
|
+
return this.#set.entries();
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/** @inheritdoc */
|
|
674
|
+
toArray(): readonly K[] {
|
|
675
|
+
return Array.from(this.values());
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/** @inheritdoc */
|
|
679
|
+
toRawSet(): ReadonlySet<K> {
|
|
680
|
+
return this.#set;
|
|
681
|
+
}
|
|
682
|
+
}
|