complete-common 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.
@@ -0,0 +1,238 @@
1
+ // When regexes are located at the root instead of inside the function, the functions are tested to
2
+ // perform 11% faster.
3
+
4
+ const FLOAT_REGEX = /^-?\d*\.?\d+$/;
5
+ const INTEGER_REGEX = /^-?\d+$/;
6
+
7
+ /**
8
+ * Helper function to throw an error if the provided value is equal to `undefined`.
9
+ *
10
+ * This is useful to have TypeScript narrow a `T | undefined` value to `T` in a concise way.
11
+ */
12
+ export function assertDefined<T>(
13
+ value: T,
14
+ ...[msg]: [undefined] extends [T]
15
+ ? [string]
16
+ : [
17
+ "The assertion is useless because the provided value does not contain undefined.",
18
+ ]
19
+ ): asserts value is Exclude<T, undefined> {
20
+ if (value === undefined) {
21
+ throw new TypeError(msg);
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Helper function to throw an error if the provided value is equal to `null`.
27
+ *
28
+ * This is useful to have TypeScript narrow a `T | null` value to `T` in a concise way.
29
+ */
30
+ export function assertNotNull<T>(
31
+ value: T,
32
+ ...[msg]: [null] extends [T]
33
+ ? [string]
34
+ : [
35
+ "The assertion is useless because the provided value does not contain null.",
36
+ ]
37
+ ): asserts value is Exclude<T, null> {
38
+ if (value === null) {
39
+ throw new TypeError(msg);
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Helper function to get an iterator of integers with the specified range, inclusive on the lower
45
+ * end and exclusive on the high end. (The "e" in the function name stands for exclusive.) Thus,
46
+ * this function works in the same way as the built-in `range` function from Python.
47
+ *
48
+ * If the end is lower than the start, then an empty range will be returned.
49
+ *
50
+ * For example:
51
+ *
52
+ * - `eRange(2)` returns `[0, 1]`.
53
+ * - `eRange(3)` returns `[0, 1, 2]`.
54
+ * - `eRange(-3)` returns `[0, -1, -2]`.
55
+ * - `eRange(1, 3)` returns `[1, 2]`.
56
+ * - `eRange(2, 5)` returns `[2, 3, 4]`.
57
+ * - `eRange(5, 2)` returns `[]`.
58
+ * - `eRange(3, 3)` returns `[]`.
59
+ *
60
+ * If you want an array instead of an iterator, use the spread operator like this:
61
+ *
62
+ * ```ts
63
+ * const myArray = [...eRange(1, 3)];
64
+ * ```
65
+ *
66
+ * @param start The integer to start at.
67
+ * @param end Optional. The integer to end at. If not specified, then the start will be 0 and the
68
+ * first argument will be the end.
69
+ * @param increment Optional. The increment to use. Default is 1.
70
+ */
71
+ export function* eRange(
72
+ start: number,
73
+ end?: number,
74
+ increment = 1,
75
+ ): Generator<number> {
76
+ if (end === undefined) {
77
+ yield* eRange(0, start, increment);
78
+ return;
79
+ }
80
+
81
+ for (let i = start; i < end; i += increment) {
82
+ yield i;
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Helper function to get an array of integers with the specified range, inclusive on both ends.
88
+ * (The "i" in the function name stands for inclusive.)
89
+ *
90
+ * If the end is lower than the start, then an empty range will be returned.
91
+ *
92
+ * For example:
93
+ *
94
+ * - `iRange(2)` returns `[0, 1, 2]`.
95
+ * - `iRange(3)` returns `[0, 1, 2, 3]`.
96
+ * - `iRange(-3)` returns `[0, -1, -2, -3]`.
97
+ * - `iRange(1, 3)` returns `[1, 2, 3]`.
98
+ * - `iRange(2, 5)` returns `[2, 3, 4, 5]`.
99
+ * - `iRange(5, 2)` returns `[]`.
100
+ * - `iRange(3, 3)` returns `[3]`.
101
+ *
102
+ * If you want an array instead of an iterator, use the spread operator like this:
103
+ *
104
+ * ```ts
105
+ * const myArray = [...eRange(1, 3)];
106
+ * ```
107
+ *
108
+ * @param start The integer to start at.
109
+ * @param end Optional. The integer to end at. If not specified, then the start will be 0 and the
110
+ * first argument will be the end.
111
+ * @param increment Optional. The increment to use. Default is 1.
112
+ */
113
+ export function* iRange(
114
+ start: number,
115
+ end?: number,
116
+ increment = 1,
117
+ ): Generator<number> {
118
+ if (end === undefined) {
119
+ yield* iRange(0, start, increment);
120
+ return;
121
+ }
122
+
123
+ const exclusiveEnd = end + 1;
124
+ yield* eRange(start, exclusiveEnd, increment);
125
+ }
126
+
127
+ /** From: https://stackoverflow.com/questions/61526746 */
128
+ export function isKeyOf<T extends object>(
129
+ key: PropertyKey,
130
+ target: T,
131
+ ): key is keyof T {
132
+ return key in target;
133
+ }
134
+
135
+ /**
136
+ * Helper function to perform a no-op. This can be useful in order to make a trailing return valid
137
+ * in functions that use the early return pattern.
138
+ */
139
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
140
+ export function noop(): void {}
141
+
142
+ /**
143
+ * This is a more reliable version of `Number.parseFloat`:
144
+ *
145
+ * - `undefined` is returned instead of `Number.NaN`, which is helpful in conjunction with
146
+ * TypeScript type narrowing patterns.
147
+ * - Strings that are a mixture of numbers and letters will result in undefined instead of the part
148
+ * of the string that is the number. (e.g. "1a" --> undefined instead of "1a" --> 1)
149
+ * - Non-strings will result in undefined instead of being coerced to a number.
150
+ *
151
+ * @param string A string to convert to an integer.
152
+ */
153
+ export function parseFloatSafe(string: string): number | undefined {
154
+ if (typeof string !== "string") {
155
+ return undefined;
156
+ }
157
+
158
+ const trimmedString = string.trim();
159
+
160
+ // If the string does not entirely consist of numbers, return undefined.
161
+ if (FLOAT_REGEX.exec(trimmedString) === null) {
162
+ return undefined;
163
+ }
164
+
165
+ const number = Number.parseFloat(trimmedString);
166
+ return Number.isNaN(number) ? undefined : number;
167
+ }
168
+
169
+ /**
170
+ * This is a more reliable version of `Number.parseInt`:
171
+ *
172
+ * - `undefined` is returned instead of `Number.NaN`, which is helpful in conjunction with
173
+ * TypeScript type narrowing patterns.
174
+ * - Strings that are a mixture of numbers and letters will result in undefined instead of the part
175
+ * of the string that is the number. (e.g. "1a" --> undefined instead of "1a" --> 1)
176
+ * - Non-strings will result in undefined instead of being coerced to a number.
177
+ *
178
+ * If you have to use a radix other than 10, use the vanilla `Number.parseInt` function instead,
179
+ * because this function ensures that the string contains no letters.
180
+ */
181
+ export function parseIntSafe(string: string): number | undefined {
182
+ if (typeof string !== "string") {
183
+ return undefined;
184
+ }
185
+
186
+ const trimmedString = string.trim();
187
+
188
+ // If the string does not entirely consist of numbers, return undefined.
189
+ if (INTEGER_REGEX.exec(trimmedString) === null) {
190
+ return undefined;
191
+ }
192
+
193
+ const number = Number.parseInt(trimmedString, 10);
194
+ return Number.isNaN(number) ? undefined : number;
195
+ }
196
+
197
+ /**
198
+ * Helper function to repeat code N times. This is faster to type and cleaner than using a for loop.
199
+ *
200
+ * For example:
201
+ *
202
+ * ```ts
203
+ * repeat(10, () => {
204
+ * foo();
205
+ * });
206
+ * ```
207
+ *
208
+ * The repeated function is passed the index of the iteration, if needed:
209
+ *
210
+ * ```ts
211
+ * repeat(3, (i) => {
212
+ * console.log(i); // Prints "0", "1", "2"
213
+ * });
214
+ * ```
215
+ */
216
+ export function repeat(num: number, func: (i: number) => void): void {
217
+ for (let i = 0; i < num; i++) {
218
+ func(i);
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Helper function to signify that the enclosing code block is not yet complete. Using this function
224
+ * is similar to writing a "TODO" comment, but it has the benefit of preventing ESLint errors due to
225
+ * unused variables or early returns.
226
+ *
227
+ * When you see this function, it simply means that the programmer intends to add in more code to
228
+ * this spot later.
229
+ *
230
+ * This function is variadic, meaning that you can pass as many arguments as you want. (This is
231
+ * useful as a means to prevent unused variables.)
232
+ *
233
+ * This function does not actually do anything. (It is an "empty" function.)
234
+ *
235
+ * @allowEmptyVariadic
236
+ */
237
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
238
+ export function todo(...args: readonly unknown[]): void {}
package/src/index.ts ADDED
@@ -0,0 +1,27 @@
1
+ export * from "./constants.js";
2
+ export * from "./functions/array.js";
3
+ export * from "./functions/enums.js";
4
+ export * from "./functions/map.js";
5
+ export * from "./functions/math.js";
6
+ export * from "./functions/object.js";
7
+ export * from "./functions/random.js";
8
+ export * from "./functions/set.js";
9
+ export * from "./functions/sort.js";
10
+ export * from "./functions/string.js";
11
+ export * from "./functions/tuple.js";
12
+ export * from "./functions/types.js";
13
+ export * from "./functions/utils.js";
14
+ export * from "./types/AddSubtract.js";
15
+ export * from "./types/CompositionTypeSatisfiesEnum.js";
16
+ export * from "./types/ERange.js";
17
+ export * from "./types/Immutable.js";
18
+ export * from "./types/IRange.js";
19
+ export * from "./types/NaturalNumbersLessThan.js";
20
+ export * from "./types/NaturalNumbersLessThanOrEqualTo.js";
21
+ export * from "./types/ObjectValues.js";
22
+ export * from "./types/ReadonlyMap.js";
23
+ export * from "./types/ReadonlyRecord.js";
24
+ export * from "./types/ReadonlySet.js";
25
+ export * from "./types/Tuple.js";
26
+ export * from "./types/WidenLiteral.js";
27
+ export * from "./types/Writeable.js";
@@ -0,0 +1,19 @@
1
+ /** From: https://gist.github.com/ryandabler/8b4ff4f36aed47bc09acc03174638468 */
2
+ export type Add<A extends number, B extends number> = Length<
3
+ [...BuildTuple<A>, ...BuildTuple<B>]
4
+ >;
5
+
6
+ /** From: https://gist.github.com/ryandabler/8b4ff4f36aed47bc09acc03174638468 */
7
+ export type Subtract<A extends number, B extends number> = A extends A
8
+ ? BuildTuple<A> extends [...infer U, ...BuildTuple<B>]
9
+ ? Length<U>
10
+ : never
11
+ : never;
12
+
13
+ type BuildTuple<L extends number, T extends unknown[] = []> = T extends {
14
+ length: L;
15
+ }
16
+ ? T
17
+ : BuildTuple<L, [...T, unknown]>;
18
+
19
+ type Length<T extends unknown[]> = T extends { length: infer L } ? L : never;
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Helper type to validate that a union of interfaces with a field of `type` that is based on an
3
+ * enum is complete.
4
+ *
5
+ * For example:
6
+ *
7
+ * ```ts
8
+ * enum ObjectiveType {
9
+ * Foo,
10
+ * Bar,
11
+ * Baz,
12
+ * }
13
+ *
14
+ * interface FooObjective {
15
+ * type: ObjectiveType.Foo;
16
+ * fooThing: number;
17
+ * }
18
+ *
19
+ * interface BarObjective {
20
+ * type: ObjectiveType.Bar;
21
+ * barThing: string;
22
+ * }
23
+ *
24
+ * type Objective = FooObjective | BarObjective;
25
+ * type _Test = CompositionTypeSatisfiesEnum<Objective, ObjectiveType>;
26
+ * ```
27
+ *
28
+ * In this example, `Test` would be flagged by TypeScript because `Objective` does not contain an
29
+ * entry for `BazObjective`.
30
+ */
31
+ export type CompositionTypeSatisfiesEnum<
32
+ T extends { type: unknown },
33
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
34
+ Enum extends T["type"],
35
+ > = unknown;
36
+
37
+ // -----
38
+ // Tests
39
+ // -----
40
+
41
+ enum ObjectiveType {
42
+ Foo = "Foo",
43
+ Bar = "Bar",
44
+ Baz = "Baz",
45
+ }
46
+
47
+ interface FooObjective {
48
+ type: ObjectiveType.Foo;
49
+ fooThing: number;
50
+ }
51
+
52
+ interface BarObjective {
53
+ type: ObjectiveType.Bar;
54
+ barThing: string;
55
+ }
56
+
57
+ interface BazObjective {
58
+ type: ObjectiveType.Baz;
59
+ bazThing: string;
60
+ }
61
+
62
+ type Objective1 = FooObjective | BarObjective | BazObjective;
63
+ type _Test1 = CompositionTypeSatisfiesEnum<Objective1, ObjectiveType>;
64
+
65
+ type Objective2 = FooObjective | BarObjective;
66
+ // @ts-expect-error Missing "Baz".
67
+ type _Test2 = CompositionTypeSatisfiesEnum<Objective2, ObjectiveType>;
@@ -0,0 +1,15 @@
1
+ import type { NaturalNumbersLessThan } from "./NaturalNumbersLessThan.js";
2
+
3
+ /**
4
+ * Helper type to get a range of integers. It is inclusive on the lower end and exclusive on the
5
+ * high end. (The "E" in the type name stands for exclusive.)
6
+ *
7
+ * For example, `ERange<3, 5>` will return `3 | 4`.
8
+ *
9
+ * From:
10
+ * https://stackoverflow.com/questions/39494689/is-it-possible-to-restrict-number-to-a-certain-range
11
+ */
12
+ export type ERange<Low extends number, High extends number> = Exclude<
13
+ NaturalNumbersLessThan<High>,
14
+ NaturalNumbersLessThan<Low>
15
+ >;
@@ -0,0 +1,16 @@
1
+ import type { NaturalNumbersLessThan } from "./NaturalNumbersLessThan.js";
2
+ import type { NaturalNumbersLessThanOrEqualTo } from "./NaturalNumbersLessThanOrEqualTo.js";
3
+
4
+ /**
5
+ * Helper type to get a range of integers. It is inclusive on both ends. (The "I" in the type name
6
+ * stands for inclusive.)
7
+ *
8
+ * For example, `IRange<3, 5>` will return `3 | 4 | 5`.
9
+ *
10
+ * From:
11
+ * https://stackoverflow.com/questions/39494689/is-it-possible-to-restrict-number-to-a-certain-range
12
+ */
13
+ export type IRange<Low extends number, High extends number> = Exclude<
14
+ NaturalNumbersLessThanOrEqualTo<High>,
15
+ NaturalNumbersLessThan<Low>
16
+ >;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Immutable is a utility type that will make the given array/map/set/object recursively read-only.
3
+ *
4
+ * You can use this type to easily build safe data structures.
5
+ *
6
+ * From: https://stackoverflow.com/questions/41879327/deepreadonly-object-typescript
7
+ */
8
+ export type Immutable<T> = T extends ImmutablePrimitive
9
+ ? T
10
+ : T extends Array<infer U>
11
+ ? ImmutableArray<U>
12
+ : T extends Map<infer K, infer V>
13
+ ? ImmutableMap<K, V>
14
+ : T extends Set<infer M>
15
+ ? ImmutableSet<M>
16
+ : ImmutableObject<T>;
17
+
18
+ type ImmutablePrimitive =
19
+ | undefined
20
+ | null
21
+ | boolean
22
+ | string
23
+ | number
24
+ | Function; // eslint-disable-line @typescript-eslint/no-unsafe-function-type
25
+ type ImmutableArray<T> = ReadonlyArray<Immutable<T>>;
26
+ type ImmutableMap<K, V> = ReadonlyMap<Immutable<K>, Immutable<V>>;
27
+ type ImmutableSet<T> = ReadonlySet<Immutable<T>>;
28
+ type ImmutableObject<T> = { readonly [K in keyof T]: Immutable<T[K]> };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Helper type to get a range of integers between 0 and N - 1.
3
+ *
4
+ * From:
5
+ * https://stackoverflow.com/questions/39494689/is-it-possible-to-restrict-number-to-a-certain-range
6
+ */
7
+ export type NaturalNumbersLessThan<
8
+ N extends number,
9
+ Acc extends number[] = [],
10
+ > = Acc["length"] extends N
11
+ ? Acc[number]
12
+ : NaturalNumbersLessThan<N, [...Acc, Acc["length"]]>;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Helper type to get a range of integers between 0 and N.
3
+ *
4
+ * From:
5
+ * https://stackoverflow.com/questions/39494689/is-it-possible-to-restrict-number-to-a-certain-range
6
+ */
7
+ export type NaturalNumbersLessThanOrEqualTo<
8
+ N extends number,
9
+ T extends number[] = [],
10
+ > = T extends [unknown, ...infer Tail]
11
+ ? Tail["length"] extends N
12
+ ? T[number]
13
+ : NaturalNumbersLessThanOrEqualTo<N, [...T, T["length"]]>
14
+ : NaturalNumbersLessThanOrEqualTo<N, [...T, T["length"]]>;
@@ -0,0 +1 @@
1
+ export type ObjectValues<T> = T[keyof T];
@@ -0,0 +1,12 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+
3
+ interface ReadonlyMapConstructor {
4
+ new (): ReadonlyMap<any, any>;
5
+ new <K, V>(
6
+ entries?: ReadonlyArray<readonly [K, V]> | Iterable<readonly [K, V]> | null,
7
+ ): ReadonlyMap<K, V>;
8
+ readonly prototype: ReadonlyMap<any, any>;
9
+ }
10
+
11
+ /** An alias for the `Map` constructor that returns a read-only map. */
12
+ export const ReadonlyMap = Map as ReadonlyMapConstructor;
@@ -0,0 +1,3 @@
1
+ export type ReadonlyRecord<K extends string | number | symbol, V> = Readonly<
2
+ Record<K, V>
3
+ >;
@@ -0,0 +1,9 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+
3
+ interface ReadonlySetConstructor {
4
+ new <T = any>(values?: readonly T[] | Iterable<T> | null): ReadonlySet<T>;
5
+ readonly prototype: ReadonlySet<any>;
6
+ }
7
+
8
+ /** An alias for the `Set` constructor that returns a read-only set. */
9
+ export const ReadonlySet = Set as ReadonlySetConstructor;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Helper type to represent a tuple of length N.
3
+ *
4
+ * From:
5
+ * https://stackoverflow.com/questions/52489261/typescript-can-i-define-an-n-length-tuple-type/52490977#52490977
6
+ */
7
+ export type Tuple<T, N extends number> = N extends N
8
+ ? number extends N
9
+ ? T[]
10
+ : _TupleOf<T, N, []>
11
+ : never;
12
+ type _TupleOf<T, N extends number, R extends unknown[]> = R["length"] extends N
13
+ ? R
14
+ : _TupleOf<T, N, [T, ...R]>;
@@ -0,0 +1,11 @@
1
+ export type WidenLiteral<T> = T extends string
2
+ ? string
3
+ : T extends number
4
+ ? number
5
+ : T extends boolean
6
+ ? boolean
7
+ : T extends bigint
8
+ ? bigint
9
+ : T extends symbol
10
+ ? symbol
11
+ : T;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Helper type to convert a read-only object into a writable object.
3
+ *
4
+ * This is the opposite of the built-in `Readonly` utility type.
5
+ */
6
+ export type Writeable<T> = { -readonly [P in keyof T]: T[P] };