assertie 0.2.0 → 0.3.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.1](https://github.com/OfficialHalfwayDead/assertie/compare/v0.3.0...v0.3.1) (2025-02-23)
4
+
5
+ * [`0d59837`](https://github.com/OfficialHalfwayDead/assertie/commit/0d59837841707c963ce174e79a527fcb1379ac4f) Add array and tuple assertions to the README.
6
+
7
+
8
+ ## [0.3.0](https://github.com/OfficialHalfwayDead/assertie/compare/v0.2.0...v0.3.0) (2025-02-23)
9
+
10
+ ### Breaking Changes
11
+
12
+ * [`9906290`](https://github.com/OfficialHalfwayDead/assertie/commit/990629053a10e519c4d73ece8825f8edf7003489) assertie >= 0.3.0 requires TypeScript 4.7.0 or higher in your project.
13
+ * [`b8ca296`](https://github.com/OfficialHalfwayDead/assertie/commit/b8ca296cb80c8e46db49af8256ea8bc1f76532e4) `assertPropsNonNullable` now only accepts objects as the first argument. This is technically a breaking change because TypeScript would have previously allowed passing anything to it, but it never made sense.
14
+
15
+ ### Features
16
+
17
+ * [`7188088`](https://github.com/OfficialHalfwayDead/assertie/commit/71880883ecc0ecc110a6074b955dc2fc5fe806a5) More accurate print of the actual type of items when an assertion fails and significantly improved error messages overall.
18
+ * [`7188088`](https://github.com/OfficialHalfwayDead/assertie/commit/71880883ecc0ecc110a6074b955dc2fc5fe806a5) Add type and non-null assertions for arrays that check all elements in one call and provide better typing and error messages: `assertArrayType`, `assertArrayNonNullable`.
19
+ * [`9906290`](https://github.com/OfficialHalfwayDead/assertie/commit/990629053a10e519c4d73ece8825f8edf7003489) Add assertions for tuples that can narrow types of arrays or tuples with unknown types: `assertIsTuple`, `assertTupleTypes`, `assertTupleNonNullable`.
20
+
21
+ ### Other
22
+
23
+ * [`075fecd`](https://github.com/OfficialHalfwayDead/assertie/commit/075fecd105030191eac671d8731765f4f5af2cd4) Add JSDoc comments to all functions and improve documentation overall.
24
+
25
+
3
26
  ## [0.2.0](https://github.com/OfficialHalfwayDead/assertie/compare/v0.1.0...v0.2.0) (2025-02-17)
4
27
 
5
28
  ### Breaking Changes
@@ -0,0 +1,12 @@
1
+ # Migration Guide
2
+
3
+ Scroll down to your previous version and then follow all the steps upwards in order until the version you're targeting.
4
+
5
+ ## [0.2.x to 0.3.0](https://github.com/OfficialHalfwayDead/assertie/compare/v0.2.0...v0.3.0)
6
+
7
+ * New features mean assertie >= 0.3.0 requires TypeScript 4.7.0 or higher in your project. (Previous minimum version was 3.7.0)
8
+ * Ensure that you aren't passing things that aren't objects to `assertPropsNonNullable`. It never made sense, but the TS type previously allowed it.
9
+
10
+ ## [0.1.x to 0.2.0](https://github.com/OfficialHalfwayDead/assertie/compare/v0.1.0...v0.2.0)
11
+
12
+ * All functions which had `Typeof` in the name changed capitalization to `TypeOf` for consistency. Change all existing calls to the new spelling.
package/README.md CHANGED
@@ -154,6 +154,28 @@ const safeObj = obj;
154
154
 
155
155
  The reason an array is needed here is because undefined properties may not be present in `Object.keys`, so the caller needs to provide all keys to check. Don't worry about the safety, though. If you forget to pass a key, its type will remain nullable after the assert and TypeScript will not consider it safe to access.
156
156
 
157
+ ### Arrays and tuples
158
+
159
+ Arrays and tuples have equivalent versions of `assertType` and `assertNonNullable`. These make it easy to check every element at once and get great errors and type narrowing.
160
+
161
+ ```ts
162
+ const arr: (number | string | null)[] = [1, 2, 3];
163
+ assertArrayNonNullable(arr); // narrows to (number | string)[]
164
+ assertArrayType(arr, "number"); // narrows to number[]
165
+ ```
166
+
167
+ ```ts
168
+ const arr: number[] = [1, 2, 3];
169
+ assertIsTuple(arr, 3); // narrows to [number, number, number]
170
+
171
+ const arrMixed: (number | string | null)[] = [1, "a"];
172
+ assertIsTuple(arrMixed, 2); // narrows to [T, T]
173
+ // where T = number | string | null;
174
+ assertTupleNonNullable(arrMixed); // narrows T to number | string
175
+ assertTupleTypes(arrMixed, ["number", "string"]);
176
+ const tup: [number, string] = arrMixed;
177
+ ```
178
+
157
179
  ### Asserting unreachable code
158
180
 
159
181
  The unreachable assertion will
@@ -186,4 +208,12 @@ assertFiniteNumber(123);
186
208
  // DO NOT USE FOR INPUT VALIDATION OF USER PROVIDED VALUES!
187
209
  ```
188
210
 
189
- Useful for string-to-number conversions when you expect **valid** number strings. Prevents accidental usage of strings or invalid numbers due to JS's loose equality (`123 == "123"`).
211
+ Can prevent `NaN` propagation and accidental infinities in calculations. Also useful for string-to-number conversions when you expect **valid** number strings:
212
+
213
+ ```ts
214
+ const str = "CustomObject123";
215
+ const numStr = str.substring(11); // oops "t123"
216
+ const num = Number(numStr); // NaN
217
+ assertFiniteNumber(num); // throws
218
+ arr[num] = "yup" // disaster averted
219
+ ```
package/lib/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- type PrimitiveTypeStrings = "string" | "number" | "boolean" | "bigint" | "undefined" | "function" | "object" | "symbol";
2
- type PrimitiveTypes = {
1
+ declare type Tuple<T, N extends number, A extends unknown[] = []> = A["length"] extends N ? A : Tuple<T, N, [...A, T]>;
2
+ declare type PrimitiveTypes = {
3
3
  "string": string;
4
4
  "number": number;
5
5
  "boolean": boolean;
@@ -9,29 +9,154 @@ type PrimitiveTypes = {
9
9
  "object": object;
10
10
  "symbol": symbol;
11
11
  };
12
- type NullableKeys<T> = {
12
+ declare type PrimitiveTypeStrings = keyof PrimitiveTypes;
13
+ declare type NullableKeys<T> = {
13
14
  [K in keyof T]-?: undefined extends T[K] ? K : null extends T[K] ? K : never;
14
15
  }[keyof T];
15
- type PropsNonNullable<T, N extends NullableKeys<T>> = T & {
16
+ declare type PropsNonNullable<T, N extends NullableKeys<T>> = T & {
16
17
  [K in N]-?: NonNullable<T[K]>;
17
18
  };
18
- type Constructor<T> = new (...args: unknown[]) => T;
19
- type AllJSTypes = PrimitiveTypeStrings | null | undefined | Constructor<unknown>;
20
- type ResolveAnyJSType<T extends AllJSTypes> = T extends PrimitiveTypeStrings ? PrimitiveTypes[T] : T extends null ? null : T extends undefined ? undefined : T extends Constructor<infer U> ? U : never;
19
+ declare type Constructor<T> = new (...args: unknown[]) => T;
20
+ declare type AllJSTypes = PrimitiveTypeStrings | null | undefined | Constructor<unknown>;
21
+ declare type ResolveAnyJSType<T extends AllJSTypes> = T extends PrimitiveTypeStrings ? PrimitiveTypes[T] : T extends null ? null : T extends undefined ? undefined : T extends Constructor<infer U> ? U : never;
22
+ /**
23
+ * Asserts that the provided boolean is true.
24
+ * @param {boolean} hasToBeTrue - The boolean to assert.
25
+ * @param {string} msg - The message of the Error if the assertion fails.
26
+ * @throws {AssertionError} if the assertion fails.
27
+ */
21
28
  export declare function assert(hasToBeTrue: boolean, msg?: string): asserts hasToBeTrue is true;
22
- export declare function assertType<T extends AllJSTypes>(obj: unknown, expectedType: T): asserts obj is ResolveAnyJSType<T>;
23
- export declare function assertTypeOfString(obj: unknown): asserts obj is string;
24
- export declare function assertTypeOfNumber(obj: unknown): asserts obj is number;
25
- export declare function assertTypeOfBoolean(obj: unknown): asserts obj is boolean;
26
- export declare function assertTypeOfBigint(obj: unknown): asserts obj is bigint;
27
- export declare function assertTypeOfUndefined(obj: unknown): asserts obj is undefined;
28
- export declare function assertTypeOfFunction(obj: unknown): asserts obj is Function;
29
- export declare function assertTypeOfObject(obj: unknown): asserts obj is object;
30
- export declare function assertTypeOfSymbol(obj: unknown): asserts obj is symbol;
31
- export declare function assertNull(obj: unknown): asserts obj is null;
32
- export declare function assertInstanceOf<T>(obj: unknown, constructable: Constructor<T>): asserts obj is T;
33
- export declare function assertUnreachable(obj: never, msg?: string): asserts obj is never;
34
- export declare function assertPropsNonNullable<T, N extends NullableKeys<T>>(obj: T, props: N[]): asserts obj is PropsNonNullable<T, N>;
35
- export declare function assertNonNullable<T>(obj: T): asserts obj is NonNullable<T>;
36
- export declare function assertFiniteNumber(obj: unknown): asserts obj is number;
29
+ /**
30
+ * Asserts that the provided object is of the expectedType.
31
+ * @param {unknown} item - The object which ought to be of the expectedType.
32
+ * @param {AllJSTypes} expectedType - The expected type of the object. JS primitive types, null, undefined, and constructable types are supported. JS primitive types are passed as the string they return from typeof, e.g., "number".
33
+ * @throws {AssertionError} if the type isn't as expected.
34
+ */
35
+ export declare function assertType<T extends AllJSTypes>(item: unknown, expectedType: T): asserts item is ResolveAnyJSType<T>;
36
+ /**
37
+ * Asserts that all elements of the provided array are of the expected type. It ensures that the array is not sparse (even when the expectedType is undefined).
38
+ * @param {unknown[]} arr - The array which ought to be an array of the expectedType, i.e. expectedType: "number" => arr: number[]
39
+ * @param {AllJSTypes} expectedType - The expected type of individual items. JS primitive types, null, undefined, and constructable types are supported.
40
+ * @throws {AssertionError} if the type isn't as expected.
41
+ */
42
+ export declare function assertArrayType<T extends AllJSTypes>(arr: unknown[], expectedType: T): asserts arr is ResolveAnyJSType<T>[];
43
+ /**
44
+ * Asserts that the array or tuple has the expected types at each index.
45
+ * @param {unknown[] | [unknown, ...]} arrayOrTuple - The tuple which ought to be an array of the length and types.
46
+ * @param {[AllJSTypes, ...]} expectedTypes - A tuple of expected types of individual items, e.g., expectedTypes = ["number", "string", Date] => arrayOrTuple: [number, string, Date]. The individual entries can be JS primitive types, null, undefined, and constructors.
47
+ * @throws {AssertionError} if the type of any element of the tuple isn't as expected.
48
+ */
49
+ export declare function assertTupleTypes<T extends readonly AllJSTypes[], U extends {
50
+ [K in keyof T]: unknown;
51
+ } | (number extends U["length"] ? unknown[] : never)>(arrayOrTuple: U, expectedTypes: readonly [...T]): asserts arrayOrTuple is U & {
52
+ [K in keyof T]: ResolveAnyJSType<T[K]>;
53
+ };
54
+ /**
55
+ * Asserts that the provided item is of type string.
56
+ * @param {unknown} item - The item which ought to be of type string.
57
+ * @throws {AssertionError} if the type isn't string.
58
+ */
59
+ export declare function assertTypeOfString(item: unknown): asserts item is string;
60
+ /**
61
+ * Asserts that the provided item is of type number.
62
+ * @param {unknown} item - The item which ought to be of type number.
63
+ * @throws {AssertionError} if the type isn't number.
64
+ */
65
+ export declare function assertTypeOfNumber(item: unknown): asserts item is number;
66
+ /**
67
+ * Asserts that the provided item is of type boolean.
68
+ * @param {unknown} item - The item which ought to be of type boolean.
69
+ * @throws {AssertionError} if the type isn't boolean.
70
+ */
71
+ export declare function assertTypeOfBoolean(item: unknown): asserts item is boolean;
72
+ /**
73
+ * Asserts that the provided item is of type bigint.
74
+ * @param {unknown} item - The item which ought to be of type bigint.
75
+ * @throws {AssertionError} if the type isn't bigint.
76
+ */
77
+ export declare function assertTypeOfBigint(item: unknown): asserts item is bigint;
78
+ /**
79
+ * Asserts that the provided item is of type undefined.
80
+ * @param {unknown} item - The item which ought to be of type undefined.
81
+ * @throws {AssertionError} if the type isn't undefined.
82
+ */
83
+ export declare function assertTypeOfUndefined(item: unknown): asserts item is undefined;
84
+ /**
85
+ * Asserts that the provided item is of type function.
86
+ * @param {unknown} item - The item which ought to be of type function.
87
+ * @throws {AssertionError} if the type isn't function.
88
+ */
89
+ export declare function assertTypeOfFunction(item: unknown): asserts item is Function;
90
+ /**
91
+ * Asserts that the provided item is of type object.
92
+ * @param {unknown} item - The item which ought to be of type object.
93
+ * @throws {AssertionError} if the type isn't object.
94
+ */
95
+ export declare function assertTypeOfObject(item: unknown): asserts item is object;
96
+ /**
97
+ * Asserts that the provided item is of type symbol.
98
+ * @param {unknown} item - The item which ought to be of type symbol.
99
+ * @throws {AssertionError} if the type isn't symbol.
100
+ */
101
+ export declare function assertTypeOfSymbol(item: unknown): asserts item is symbol;
102
+ /**
103
+ * Asserts that the provided item is null.
104
+ * @param {unknown} item - The item which ought to be null.
105
+ * @throws {AssertionError} if the value isn't null.
106
+ */
107
+ export declare function assertNull(item: unknown): asserts item is null;
108
+ /**
109
+ * Asserts that the provided item is an instance of the provided constructor.
110
+ * @param {unknown} item - The item which ought to be an instance of the constructor.
111
+ * @param {Constructor<T>} constructor - Anything that can be after an instanceof operator.
112
+ * @throws {AssertionError} if item instanceof constructor is false.
113
+ */
114
+ export declare function assertInstanceOf<T>(item: unknown, constructor: Constructor<T>): asserts item is T;
115
+ /**
116
+ * Asserts that the provided array is a tuple of exactly the expected length.
117
+ * @param {unknown[]} arr - The array which ought to be a tuple.
118
+ * @param {number} expectedLength - The exact expected length of the tuple.
119
+ * @throws {AssertionError} if the array isn't of the expected length or is sparse.
120
+ */
121
+ export declare function assertIsTuple<T extends number extends T["length"] ? unknown[] : never, N extends number>(arr: [...T], expectedLength: N): asserts arr is T & Tuple<T[number], N>;
122
+ /**
123
+ * Used to assert that code can never be reached. Pass a value which has already been checked for all types that should be possible. If the range of possible values increases, TypeScript will throw an error at compile time because the value won't be of type never.
124
+ * @param {never} item - An exhausted value, of which all cases are accounted for in other branches of the code, such as at the end of a switch statement.
125
+ * @param {string} msg - Override the default error message. Even if you do, the error message will include the value and type of item.
126
+ * @throws {AssertionError} if at runtime the function call was reached. This should only happen if TypeScript types are inaccurate somewhere.
127
+ */
128
+ export declare function assertUnreachable(item: never, msg?: string): asserts item is never;
129
+ /**
130
+ * Asserts that the provided item is neither null nor undefined.
131
+ * @param {unknown} item - The item which ought to be non-null.
132
+ * @throws {AssertionError} if the item is null or undefined.
133
+ */
134
+ export declare function assertNonNullable<T>(item: T): asserts item is NonNullable<T>;
135
+ /**
136
+ * Asserts that the provided object has non-null values for the properties passed as keys in the propKeys array.
137
+ * @param {object} obj - The object which ought to have the properties.
138
+ * @param {NullableKeys<T>} propKeys - An array of the stringified keys of the properties which ought to be non-null in the object.
139
+ * @throws {AssertionError} if any of the properties was null, undefined, or not present in the object.
140
+ */
141
+ export declare function assertPropsNonNullable<T extends object, N extends NullableKeys<T>>(obj: T, propKeys: N[]): asserts obj is PropsNonNullable<T, N>;
142
+ /**
143
+ * Asserts that all elements of the provided array are neither null nor undefined, or not present.
144
+ * @param {unknown[]} arr - The array which ought to be non-sparse, and have only non-null elements.
145
+ * @throws {AssertionError} if any of the elements was null, undefined, or not present in the array.
146
+ */
147
+ export declare function assertArrayNonNullable<T>(arr: T[]): asserts arr is NonNullable<T>[];
148
+ /**
149
+ * Asserts that the provided tuple has non-null values for all elements. This function does not take a length. So if you want to assert that the typescript tuple type is of the correct length, call @see assertIsTuple first.
150
+ * @param {[unknown, ...]} tuple - The tuple which ought to have only non-null values.
151
+ * @throws {AssertionError} if any of the elements was null, undefined, or an index not present in the tuple.
152
+ */
153
+ export declare function assertTupleNonNullable<T extends number extends T["length"] ? never : unknown[]>(tuple: T): asserts tuple is {
154
+ [K in keyof T]: NonNullable<T[K]>;
155
+ };
156
+ /**
157
+ * Asserts that the provided item is a finite number. Use to prevent NaN propagation.
158
+ * @param {unknown} item - The item which ought to be a finite number.
159
+ * @throws {AssertionError} if the item is not of type number, or isFinite(item) is false, i.e., if the item is NaN, Infinity, or -Infinity.
160
+ */
161
+ export declare function assertFiniteNumber(item: unknown): asserts item is number;
37
162
  export {};
package/lib/index.js CHANGED
@@ -1,113 +1,319 @@
1
+ function getNameOfExpectedType(expectedType) {
2
+ if (expectedType === null)
3
+ return "null";
4
+ if (expectedType === undefined)
5
+ return "undefined";
6
+ if (typeof expectedType === "string")
7
+ return expectedType;
8
+ return expectedType.name;
9
+ }
10
+ function getTypeNameOfUnknown(item) {
11
+ if (item === null)
12
+ return "null";
13
+ if (item === undefined)
14
+ return "undefined";
15
+ try {
16
+ if (item instanceof item.constructor && item.constructor.name !== "Function") {
17
+ // I'd like function to match the primitive name "function"
18
+ // because that's how the asserts are written.
19
+ return item.constructor.name;
20
+ }
21
+ }
22
+ finally {
23
+ return typeof item;
24
+ }
25
+ }
26
+ function isType(item, expectedType) {
27
+ if (typeof item === expectedType)
28
+ return true;
29
+ const reducedExpectedType = expectedType;
30
+ if (item === reducedExpectedType)
31
+ return true;
32
+ const remainingOption = expectedType;
33
+ if (item instanceof remainingOption)
34
+ return true;
35
+ return false;
36
+ }
1
37
  class AssertionError extends Error {
2
38
  constructor(msg) {
3
39
  super(`Assertion failed: ${msg}`);
4
40
  this.name = AssertionError.name;
5
41
  }
6
42
  }
43
+ /**
44
+ * Asserts that the provided boolean is true.
45
+ * @param {boolean} hasToBeTrue - The boolean to assert.
46
+ * @param {string} msg - The message of the Error if the assertion fails.
47
+ * @throws {AssertionError} if the assertion fails.
48
+ */
7
49
  export function assert(hasToBeTrue, msg = "No specific message provided.") {
8
50
  if (!import.meta.env.DEV)
9
51
  return;
10
52
  if (!hasToBeTrue)
11
53
  throw new AssertionError(msg);
12
54
  }
13
- export function assertType(obj, expectedType) {
55
+ /**
56
+ * Asserts that the provided object is of the expectedType.
57
+ * @param {unknown} item - The object which ought to be of the expectedType.
58
+ * @param {AllJSTypes} expectedType - The expected type of the object. JS primitive types, null, undefined, and constructable types are supported. JS primitive types are passed as the string they return from typeof, e.g., "number".
59
+ * @throws {AssertionError} if the type isn't as expected.
60
+ */
61
+ export function assertType(item, expectedType) {
14
62
  if (!import.meta.env.DEV)
15
63
  return;
16
- if (typeof obj === expectedType)
64
+ if (!isType(item, expectedType))
65
+ throw new AssertionError(`Provided object was not of type ${getNameOfExpectedType(expectedType)}. Was: ${getTypeNameOfUnknown(item)}, value: ${item}`);
66
+ }
67
+ /**
68
+ * Asserts that all elements of the provided array are of the expected type. It ensures that the array is not sparse (even when the expectedType is undefined).
69
+ * @param {unknown[]} arr - The array which ought to be an array of the expectedType, i.e. expectedType: "number" => arr: number[]
70
+ * @param {AllJSTypes} expectedType - The expected type of individual items. JS primitive types, null, undefined, and constructable types are supported.
71
+ * @throws {AssertionError} if the type isn't as expected.
72
+ */
73
+ export function assertArrayType(arr, expectedType) {
74
+ if (!import.meta.env.DEV)
17
75
  return;
18
- const reducedExpectedType = expectedType;
19
- if (obj === reducedExpectedType)
76
+ for (let i = 0; i < arr.length; i++) {
77
+ if (!(i in arr))
78
+ throw new AssertionError(`Array to assert type of was sparse with a missing item at index ${i}`);
79
+ const item = arr[i];
80
+ if (!isType(item, expectedType))
81
+ throw new AssertionError(`Provided array had item at index ${i} not of type ${getNameOfExpectedType(expectedType)}. Was: ${getTypeNameOfUnknown(item)}, value: ${item}`);
82
+ }
83
+ }
84
+ /**
85
+ * Asserts that the array or tuple has the expected types at each index.
86
+ * @param {unknown[] | [unknown, ...]} arrayOrTuple - The tuple which ought to be an array of the length and types.
87
+ * @param {[AllJSTypes, ...]} expectedTypes - A tuple of expected types of individual items, e.g., expectedTypes = ["number", "string", Date] => arrayOrTuple: [number, string, Date]. The individual entries can be JS primitive types, null, undefined, and constructors.
88
+ * @throws {AssertionError} if the type of any element of the tuple isn't as expected.
89
+ */
90
+ export function assertTupleTypes(arrayOrTuple, expectedTypes) {
91
+ if (!import.meta.env.DEV)
20
92
  return;
21
- const remainingOption = expectedType;
22
- if (obj instanceof remainingOption)
93
+ if (arrayOrTuple.length !== expectedTypes.length) {
94
+ throw new AssertionError(`Provided tuple length mismatch: expected ${expectedTypes.length}, but got ${arrayOrTuple.length}`);
95
+ }
96
+ for (let i = 0; i < expectedTypes.length; i++) {
97
+ if (!(i in arrayOrTuple))
98
+ throw new AssertionError(`Provided tuple was sparse with a missing item at required index ${i}`);
99
+ const item = arrayOrTuple[i];
100
+ if (!isType(item, expectedTypes[i])) {
101
+ throw new AssertionError(`Provided tuple had item at index ${i} not of type ${getNameOfExpectedType(expectedTypes[i])}. Was: ${getTypeNameOfUnknown(item)}, value: ${item}`);
102
+ }
103
+ }
104
+ }
105
+ /**
106
+ * Asserts that the provided item is of type string.
107
+ * @param {unknown} item - The item which ought to be of type string.
108
+ * @throws {AssertionError} if the type isn't string.
109
+ */
110
+ export function assertTypeOfString(item) {
111
+ if (!import.meta.env.DEV)
112
+ return;
113
+ if (typeof item !== "string")
114
+ throw new AssertionError(`Provided item was not of type string. Was: ${getTypeNameOfUnknown(item)}`);
115
+ }
116
+ /**
117
+ * Asserts that the provided item is of type number.
118
+ * @param {unknown} item - The item which ought to be of type number.
119
+ * @throws {AssertionError} if the type isn't number.
120
+ */
121
+ export function assertTypeOfNumber(item) {
122
+ if (!import.meta.env.DEV)
23
123
  return;
24
- throw new AssertionError(`Provided object was not of type ${(typeof expectedType !== "string") ? expectedType?.name : expectedType ?? expectedType}. Was: ${(obj === null) ? "null" : obj?.constructor?.name ?? typeof obj}, value: ${obj}`);
124
+ if (typeof item !== "number")
125
+ throw new AssertionError(`Provided item was not of type number. Was: ${getTypeNameOfUnknown(item)}`);
25
126
  }
26
- export function assertTypeOfString(obj) {
127
+ /**
128
+ * Asserts that the provided item is of type boolean.
129
+ * @param {unknown} item - The item which ought to be of type boolean.
130
+ * @throws {AssertionError} if the type isn't boolean.
131
+ */
132
+ export function assertTypeOfBoolean(item) {
27
133
  if (!import.meta.env.DEV)
28
134
  return;
29
- if (typeof obj !== "string")
30
- throw new AssertionError(`Provided object was not of type string. Was: ${typeof obj}`);
135
+ if (typeof item !== "boolean")
136
+ throw new AssertionError(`Provided item was not of type boolean. Was: ${getTypeNameOfUnknown(item)}`);
31
137
  }
32
- export function assertTypeOfNumber(obj) {
138
+ /**
139
+ * Asserts that the provided item is of type bigint.
140
+ * @param {unknown} item - The item which ought to be of type bigint.
141
+ * @throws {AssertionError} if the type isn't bigint.
142
+ */
143
+ export function assertTypeOfBigint(item) {
33
144
  if (!import.meta.env.DEV)
34
145
  return;
35
- if (typeof obj !== "number")
36
- throw new AssertionError(`Provided object was not of type number. Was: ${typeof obj}`);
146
+ if (typeof item !== "bigint")
147
+ throw new AssertionError(`Provided item was not of type bigint. Was: ${getTypeNameOfUnknown(item)}`);
37
148
  }
38
- export function assertTypeOfBoolean(obj) {
149
+ /**
150
+ * Asserts that the provided item is of type undefined.
151
+ * @param {unknown} item - The item which ought to be of type undefined.
152
+ * @throws {AssertionError} if the type isn't undefined.
153
+ */
154
+ export function assertTypeOfUndefined(item) {
39
155
  if (!import.meta.env.DEV)
40
156
  return;
41
- if (typeof obj !== "boolean")
42
- throw new AssertionError(`Provided object was not of type boolean. Was: ${typeof obj}`);
157
+ if (typeof item !== "undefined")
158
+ throw new AssertionError(`Provided item was not of type undefined. Was: ${getTypeNameOfUnknown(item)}`);
43
159
  }
44
- export function assertTypeOfBigint(obj) {
160
+ /**
161
+ * Asserts that the provided item is of type function.
162
+ * @param {unknown} item - The item which ought to be of type function.
163
+ * @throws {AssertionError} if the type isn't function.
164
+ */
165
+ export function assertTypeOfFunction(item) {
45
166
  if (!import.meta.env.DEV)
46
167
  return;
47
- if (typeof obj !== "bigint")
48
- throw new AssertionError(`Provided object was not of type bigint. Was: ${typeof obj}`);
168
+ if (typeof item !== "function")
169
+ throw new AssertionError(`Provided item was not of type function. Was: ${getTypeNameOfUnknown(item)}`);
49
170
  }
50
- export function assertTypeOfUndefined(obj) {
171
+ /**
172
+ * Asserts that the provided item is of type object.
173
+ * @param {unknown} item - The item which ought to be of type object.
174
+ * @throws {AssertionError} if the type isn't object.
175
+ */
176
+ export function assertTypeOfObject(item) {
51
177
  if (!import.meta.env.DEV)
52
178
  return;
53
- if (typeof obj !== "undefined")
54
- throw new AssertionError(`Provided object was not of type undefined. Was: ${typeof obj}`);
179
+ if (typeof item !== "object")
180
+ throw new AssertionError(`Provided item was not of type object. Was: ${getTypeNameOfUnknown(item)}`);
55
181
  }
56
- // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
57
- export function assertTypeOfFunction(obj) {
182
+ /**
183
+ * Asserts that the provided item is of type symbol.
184
+ * @param {unknown} item - The item which ought to be of type symbol.
185
+ * @throws {AssertionError} if the type isn't symbol.
186
+ */
187
+ export function assertTypeOfSymbol(item) {
58
188
  if (!import.meta.env.DEV)
59
189
  return;
60
- if (typeof obj !== "function")
61
- throw new AssertionError(`Provided object was not of type function. Was: ${typeof obj}`);
190
+ if (typeof item !== "symbol")
191
+ throw new AssertionError(`Provided item was not of type symbol. Was: ${getTypeNameOfUnknown(item)}`);
62
192
  }
63
- export function assertTypeOfObject(obj) {
193
+ /**
194
+ * Asserts that the provided item is null.
195
+ * @param {unknown} item - The item which ought to be null.
196
+ * @throws {AssertionError} if the value isn't null.
197
+ */
198
+ export function assertNull(item) {
64
199
  if (!import.meta.env.DEV)
65
200
  return;
66
- if (typeof obj !== "object")
67
- throw new AssertionError(`Provided object was not of type object. Was: ${typeof obj}`);
201
+ if (item !== null)
202
+ throw new AssertionError(`Provided item was not null. Was type: ${getTypeNameOfUnknown(item)}, value: ${item}`);
68
203
  }
69
- export function assertTypeOfSymbol(obj) {
204
+ /**
205
+ * Asserts that the provided item is an instance of the provided constructor.
206
+ * @param {unknown} item - The item which ought to be an instance of the constructor.
207
+ * @param {Constructor<T>} constructor - Anything that can be after an instanceof operator.
208
+ * @throws {AssertionError} if item instanceof constructor is false.
209
+ */
210
+ export function assertInstanceOf(item, constructor) {
70
211
  if (!import.meta.env.DEV)
71
212
  return;
72
- if (typeof obj !== "symbol")
73
- throw new AssertionError(`Provided object was not of type symbol. Was: ${typeof obj}`);
213
+ if (!(item instanceof constructor))
214
+ throw new AssertionError(`Provided item was not of type ${constructor.name} but was type: ${getTypeNameOfUnknown(item)}, value: ${item}`);
74
215
  }
75
- export function assertNull(obj) {
216
+ /**
217
+ * Asserts that the provided array is a tuple of exactly the expected length.
218
+ * @param {unknown[]} arr - The array which ought to be a tuple.
219
+ * @param {number} expectedLength - The exact expected length of the tuple.
220
+ * @throws {AssertionError} if the array isn't of the expected length or is sparse.
221
+ */
222
+ export function assertIsTuple(arr, expectedLength) {
76
223
  if (!import.meta.env.DEV)
77
224
  return;
78
- if (obj !== null)
79
- throw new AssertionError(`Provided object was not null. Was: ${obj}`);
225
+ if (arr.length !== expectedLength) {
226
+ throw new AssertionError(`Provided array is not a tuple of expected length ${expectedLength}. It has length ${arr.length}.`);
227
+ }
228
+ for (let i = 0; i < expectedLength; i++) {
229
+ if (!(i in arr))
230
+ throw new AssertionError(`Provided tuple is sparse and therefore not a tuple. Index ${i} is missing.`);
231
+ }
80
232
  }
81
- export function assertInstanceOf(obj, constructable) {
233
+ /**
234
+ * Used to assert that code can never be reached. Pass a value which has already been checked for all types that should be possible. If the range of possible values increases, TypeScript will throw an error at compile time because the value won't be of type never.
235
+ * @param {never} item - An exhausted value, of which all cases are accounted for in other branches of the code, such as at the end of a switch statement.
236
+ * @param {string} msg - Override the default error message. Even if you do, the error message will include the value and type of item.
237
+ * @throws {AssertionError} if at runtime the function call was reached. This should only happen if TypeScript types are inaccurate somewhere.
238
+ */
239
+ export function assertUnreachable(item, msg = "Unreachable code of type never was reached. TypeScript types are inaccurate somewhere.") {
82
240
  if (!import.meta.env.DEV)
83
241
  return;
84
- if (!(obj instanceof constructable))
85
- throw new AssertionError(`Provided object was not of type ${constructable.name} but was type: ${(obj === null) ? "null" : obj?.constructor?.name ?? typeof obj}, value: ${obj}`);
242
+ throw new AssertionError(msg +
243
+ `\nValue of type never was actually of type: ${getTypeNameOfUnknown(item)}, value: ${item}`);
86
244
  }
87
- export function assertUnreachable(obj, msg = "Unreachable code of type never was reached. TypeScript types are inaccurate somewhere.") {
245
+ /**
246
+ * Asserts that the provided item is neither null nor undefined.
247
+ * @param {unknown} item - The item which ought to be non-null.
248
+ * @throws {AssertionError} if the item is null or undefined.
249
+ */
250
+ export function assertNonNullable(item) {
88
251
  if (!import.meta.env.DEV)
89
252
  return;
90
- throw new AssertionError(msg);
253
+ if (item === undefined || item === null)
254
+ throw new AssertionError(`Provided item should've been non-null but was: ${item}`);
91
255
  }
92
- export function assertPropsNonNullable(obj, props) {
256
+ /**
257
+ * Asserts that the provided object has non-null values for the properties passed as keys in the propKeys array.
258
+ * @param {object} obj - The object which ought to have the properties.
259
+ * @param {NullableKeys<T>} propKeys - An array of the stringified keys of the properties which ought to be non-null in the object.
260
+ * @throws {AssertionError} if any of the properties was null, undefined, or not present in the object.
261
+ */
262
+ export function assertPropsNonNullable(obj, propKeys) {
93
263
  if (!import.meta.env.DEV)
94
264
  return;
95
- for (const prop of props) {
96
- if (obj[prop] === null || obj[prop] === undefined)
97
- throw new AssertionError(`Provided object prop ${String(prop)} should've been non-null but was: ${obj[prop]}`);
265
+ for (const propKey of propKeys) {
266
+ if (!(propKey in obj))
267
+ throw new AssertionError(`Provided object prop ${String(propKey)} should've been non-null but was not present at all.`);
268
+ if (obj[propKey] === null || obj[propKey] === undefined)
269
+ throw new AssertionError(`Provided object prop ${String(propKey)} should've been non-null but was: ${obj[propKey]}`);
98
270
  }
99
271
  }
100
- export function assertNonNullable(obj) {
272
+ /**
273
+ * Asserts that all elements of the provided array are neither null nor undefined, or not present.
274
+ * @param {unknown[]} arr - The array which ought to be non-sparse, and have only non-null elements.
275
+ * @throws {AssertionError} if any of the elements was null, undefined, or not present in the array.
276
+ */
277
+ export function assertArrayNonNullable(arr) {
101
278
  if (!import.meta.env.DEV)
102
279
  return;
103
- if (obj === undefined || obj === null)
104
- throw new AssertionError(`Provided object should've been non-null but was: ${obj}`);
280
+ for (let i = 0; i < arr.length; i++) {
281
+ if (!(i in arr))
282
+ throw new AssertionError(`Provided array should've been non-null but was sparse with a missing item at index ${i}`);
283
+ const item = arr[i];
284
+ if (item === null)
285
+ throw new AssertionError(`Provided array should've been non-null but had an item with value null at index ${i}`);
286
+ if (item === undefined)
287
+ throw new AssertionError(`Provided array should've been non-null but had an undefined item at index ${i}`);
288
+ }
289
+ }
290
+ /**
291
+ * Asserts that the provided tuple has non-null values for all elements. This function does not take a length. So if you want to assert that the typescript tuple type is of the correct length, call @see assertIsTuple first.
292
+ * @param {[unknown, ...]} tuple - The tuple which ought to have only non-null values.
293
+ * @throws {AssertionError} if any of the elements was null, undefined, or an index not present in the tuple.
294
+ */
295
+ export function assertTupleNonNullable(tuple) {
296
+ if (!import.meta.env.DEV)
297
+ return;
298
+ for (let i = 0; i < tuple.length; i++) {
299
+ if (!(i in tuple))
300
+ throw new AssertionError(`Provided tuple should've been non-null but is sparse. Index ${i} is missing.`);
301
+ if (tuple[i] === null)
302
+ throw new AssertionError(`Provided tuple should've been non-null but had an item with value null at index ${i}`);
303
+ if (tuple[i] === undefined)
304
+ throw new AssertionError(`Provided tuple should've been non-null but had an undefined item at index ${i}`);
305
+ }
105
306
  }
106
- export function assertFiniteNumber(obj) {
307
+ /**
308
+ * Asserts that the provided item is a finite number. Use to prevent NaN propagation.
309
+ * @param {unknown} item - The item which ought to be a finite number.
310
+ * @throws {AssertionError} if the item is not of type number, or isFinite(item) is false, i.e., if the item is NaN, Infinity, or -Infinity.
311
+ */
312
+ export function assertFiniteNumber(item) {
107
313
  if (!import.meta.env.DEV)
108
314
  return;
109
- if (typeof obj !== "number")
110
- throw new AssertionError(`Provided object was not of type number. Was: ${typeof obj}`);
111
- if (!isFinite(obj))
112
- throw new AssertionError(`Provided number was not finite. Was: ${obj}`);
315
+ if (typeof item !== "number")
316
+ throw new AssertionError(`Provided item was not of type number. Was: ${getTypeNameOfUnknown(item)}`);
317
+ if (!isFinite(item))
318
+ throw new AssertionError(`Provided number was not finite. Was: ${item}`);
113
319
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "assertie",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Debug assertions for TypeScript, auto tree-shaken by vite for production.",
5
5
  "keywords": [
6
6
  "TypeScript",
@@ -29,12 +29,12 @@
29
29
  },
30
30
  "sideEffects": false,
31
31
  "peerDependencies": {
32
- "typescript": ">=3.7.0",
32
+ "typescript": ">=4.7.0",
33
33
  "vite": ">=2.0.0"
34
34
  },
35
35
  "devDependencies": {
36
- "typescript": "^5.7.3",
37
- "vite": "^6.1.0"
36
+ "typescript": "~4.7.0",
37
+ "vite": "~2.0.0"
38
38
  },
39
39
  "scripts": {
40
40
  "build": "tsc"
package/src/index.ts CHANGED
@@ -1,21 +1,70 @@
1
- type PrimitiveTypeStrings = "string" | "number" | "boolean" | "bigint" | "undefined" | "function" | "object" | "symbol";
1
+ // Creates a tuple of length N with all elements of type T
2
+ type Tuple<T, N extends number, A extends unknown[] = []> =
3
+ A["length"] extends N ? A : Tuple<T, N, [...A, T]>;
4
+
2
5
  type PrimitiveTypes = {
3
- "string": string,
4
- "number": number,
5
- "boolean": boolean,
6
- "bigint": bigint,
7
- "undefined": undefined,
8
- // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
9
- "function": Function,
10
- "object": object,
11
- "symbol": symbol
12
- }
13
-
14
- type NullableKeys<T> = {
15
- [K in keyof T]-?: undefined extends T[K] ? K : null extends T[K] ? K : never
6
+ "string": string;
7
+ "number": number;
8
+ "boolean": boolean;
9
+ "bigint": bigint;
10
+ "undefined": undefined;
11
+ "function": Function;
12
+ "object": object;
13
+ "symbol": symbol;
14
+ };
15
+ type PrimitiveTypeStrings = keyof PrimitiveTypes;
16
+
17
+ type NullableKeys<T> = {
18
+ [K in keyof T]-?: undefined extends T[K] ? K : null extends T[K] ? K : never;
16
19
  }[keyof T];
17
20
 
18
- type PropsNonNullable<T, N extends NullableKeys<T>> = T & { [K in N]-?: NonNullable<T[K]> };
21
+ type PropsNonNullable<T, N extends NullableKeys<T>> = T & {
22
+ [K in N]-?: NonNullable<T[K]>;
23
+ };
24
+
25
+ type Constructor<T> = new (...args: unknown[]) => T;
26
+
27
+ type AllJSTypes = PrimitiveTypeStrings | null | undefined | Constructor<unknown>;
28
+
29
+ type ResolveAnyJSType<T extends AllJSTypes> = T extends
30
+ | PrimitiveTypeStrings ? PrimitiveTypes[T]
31
+ : T extends null ? null
32
+ : T extends undefined ? undefined
33
+ : T extends Constructor<infer U> ? U
34
+ : never;
35
+
36
+ function getNameOfExpectedType(expectedType: AllJSTypes): string {
37
+ if (expectedType === null) return "null";
38
+ if (expectedType === undefined) return "undefined";
39
+ if (typeof expectedType === "string") return expectedType;
40
+ return expectedType.name;
41
+ }
42
+
43
+ function getTypeNameOfUnknown(item: unknown): string {
44
+ if (item === null) return "null";
45
+ if (item === undefined) return "undefined";
46
+ try {
47
+ if (item instanceof item.constructor && item.constructor.name !== "Function") {
48
+ // I'd like function to match the primitive name "function"
49
+ // because that's how the asserts are written.
50
+ return item.constructor.name;
51
+ }
52
+ } finally {
53
+ return typeof item;
54
+ }
55
+ }
56
+
57
+ function isType<T extends AllJSTypes>(item: unknown, expectedType: T): item is ResolveAnyJSType<T> {
58
+ if (typeof item === expectedType) return true;
59
+ const reducedExpectedType = expectedType as Exclude<typeof expectedType, PrimitiveTypeStrings>;
60
+
61
+ if (item === reducedExpectedType) return true;
62
+ const remainingOption = expectedType as Exclude<typeof reducedExpectedType, null | undefined>;
63
+
64
+ if (item instanceof remainingOption) return true;
65
+
66
+ return false;
67
+ }
19
68
 
20
69
  class AssertionError extends Error {
21
70
  constructor(msg: string) {
@@ -24,103 +73,376 @@ class AssertionError extends Error {
24
73
  }
25
74
  }
26
75
 
27
- type Constructor<T> = new (...args: unknown[]) => T;
28
- type AllJSTypes = PrimitiveTypeStrings | null | undefined | Constructor<unknown>;
29
- type ResolveAnyJSType<T extends AllJSTypes> = T extends PrimitiveTypeStrings ? PrimitiveTypes[T]
30
- : T extends null ? null : T extends undefined ? undefined
31
- : T extends Constructor<infer U> ? U : never;
76
+ /**
77
+ * Asserts that the provided boolean is true.
78
+ * @param {boolean} hasToBeTrue - The boolean to assert.
79
+ * @param {string} msg - The message of the Error if the assertion fails.
80
+ * @throws {AssertionError} if the assertion fails.
81
+ */
82
+ export function assert(
83
+ hasToBeTrue: boolean,
84
+ msg: string = "No specific message provided."
85
+ ): asserts hasToBeTrue is true {
86
+ if (!import.meta.env.DEV) return;
87
+ if (!hasToBeTrue) throw new AssertionError(msg);
88
+ }
32
89
 
90
+ /**
91
+ * Asserts that the provided object is of the expectedType.
92
+ * @param {unknown} item - The object which ought to be of the expectedType.
93
+ * @param {AllJSTypes} expectedType - The expected type of the object. JS primitive types, null, undefined, and constructable types are supported. JS primitive types are passed as the string they return from typeof, e.g., "number".
94
+ * @throws {AssertionError} if the type isn't as expected.
95
+ */
96
+ export function assertType<T extends AllJSTypes>(
97
+ item: unknown,
98
+ expectedType: T
99
+ ): asserts item is ResolveAnyJSType<T> {
100
+ if (!import.meta.env.DEV) return;
101
+ if (!isType(item, expectedType))
102
+ throw new AssertionError(
103
+ `Provided object was not of type ${getNameOfExpectedType(
104
+ expectedType
105
+ )}. Was: ${getTypeNameOfUnknown(item)}, value: ${item}`
106
+ );
107
+ }
33
108
 
34
- export function assert(hasToBeTrue: boolean, msg: string = "No specific message provided."): asserts hasToBeTrue is true {
109
+ /**
110
+ * Asserts that all elements of the provided array are of the expected type. It ensures that the array is not sparse (even when the expectedType is undefined).
111
+ * @param {unknown[]} arr - The array which ought to be an array of the expectedType, i.e. expectedType: "number" => arr: number[]
112
+ * @param {AllJSTypes} expectedType - The expected type of individual items. JS primitive types, null, undefined, and constructable types are supported.
113
+ * @throws {AssertionError} if the type isn't as expected.
114
+ */
115
+ export function assertArrayType<T extends AllJSTypes>(
116
+ arr: unknown[],
117
+ expectedType: T
118
+ ): asserts arr is ResolveAnyJSType<T>[] {
35
119
  if (!import.meta.env.DEV) return;
36
- if (!hasToBeTrue) throw new AssertionError(msg);
120
+ for (let i = 0; i < arr.length; i++) {
121
+ if (!(i in arr))
122
+ throw new AssertionError(
123
+ `Array to assert type of was sparse with a missing item at index ${i}`
124
+ );
125
+ const item = arr[i];
126
+ if (!isType(item, expectedType))
127
+ throw new AssertionError(
128
+ `Provided array had item at index ${i} not of type ${getNameOfExpectedType(
129
+ expectedType
130
+ )}. Was: ${getTypeNameOfUnknown(item)}, value: ${item}`
131
+ );
132
+ }
37
133
  }
38
134
 
39
- export function assertType<T extends AllJSTypes>(obj: unknown, expectedType: T): asserts obj is ResolveAnyJSType<T> {
135
+ /**
136
+ * Asserts that the array or tuple has the expected types at each index.
137
+ * @param {unknown[] | [unknown, ...]} arrayOrTuple - The tuple which ought to be an array of the length and types.
138
+ * @param {[AllJSTypes, ...]} expectedTypes - A tuple of expected types of individual items, e.g., expectedTypes = ["number", "string", Date] => arrayOrTuple: [number, string, Date]. The individual entries can be JS primitive types, null, undefined, and constructors.
139
+ * @throws {AssertionError} if the type of any element of the tuple isn't as expected.
140
+ */
141
+ export function assertTupleTypes<
142
+ T extends readonly AllJSTypes[],
143
+ U extends
144
+ | { [K in keyof T]: unknown } // [...unknown] matching length of [...T]
145
+ | (number extends U["length"] ? unknown[] : never) // Array with compile time unknown length
146
+ >(
147
+ arrayOrTuple: U,
148
+ expectedTypes: readonly [...T]
149
+ ): asserts arrayOrTuple is U & { [K in keyof T]: ResolveAnyJSType<T[K]> } {
40
150
  if (!import.meta.env.DEV) return;
151
+ if (arrayOrTuple.length !== expectedTypes.length) {
152
+ throw new AssertionError(
153
+ `Provided tuple length mismatch: expected ${expectedTypes.length}, but got ${arrayOrTuple.length}`
154
+ );
155
+ }
156
+ for (let i = 0; i < expectedTypes.length; i++) {
157
+ if (!(i in arrayOrTuple))
158
+ throw new AssertionError(
159
+ `Provided tuple was sparse with a missing item at required index ${i}`
160
+ );
161
+ const item = arrayOrTuple[i];
162
+ if (!isType(item, expectedTypes[i])) {
163
+ throw new AssertionError(
164
+ `Provided tuple had item at index ${i} not of type ${getNameOfExpectedType(
165
+ expectedTypes[i]
166
+ )}. Was: ${getTypeNameOfUnknown(item)}, value: ${item}`
167
+ );
168
+ }
169
+ }
170
+ }
41
171
 
42
- if (typeof obj === expectedType) return;
43
- const reducedExpectedType = expectedType as Exclude<typeof expectedType, PrimitiveTypeStrings>;
172
+ /**
173
+ * Asserts that the provided item is of type string.
174
+ * @param {unknown} item - The item which ought to be of type string.
175
+ * @throws {AssertionError} if the type isn't string.
176
+ */
177
+ export function assertTypeOfString(item: unknown): asserts item is string {
178
+ if (!import.meta.env.DEV) return;
179
+ if (typeof item !== "string")
180
+ throw new AssertionError(
181
+ `Provided item was not of type string. Was: ${getTypeNameOfUnknown(item)}`
182
+ );
183
+ }
44
184
 
45
- if (obj === reducedExpectedType) return;
46
- const remainingOption = expectedType as Exclude<typeof reducedExpectedType, null | undefined>;
185
+ /**
186
+ * Asserts that the provided item is of type number.
187
+ * @param {unknown} item - The item which ought to be of type number.
188
+ * @throws {AssertionError} if the type isn't number.
189
+ */
190
+ export function assertTypeOfNumber(item: unknown): asserts item is number {
191
+ if (!import.meta.env.DEV) return;
192
+ if (typeof item !== "number")
193
+ throw new AssertionError(
194
+ `Provided item was not of type number. Was: ${getTypeNameOfUnknown(item)}`
195
+ );
196
+ }
47
197
 
48
- if (obj instanceof remainingOption) return;
49
-
50
- throw new AssertionError(`Provided object was not of type ${(typeof expectedType !== "string") ? expectedType?.name : expectedType ?? expectedType}. Was: ${(obj === null) ? "null" : obj?.constructor?.name ?? typeof obj}, value: ${obj}`);
198
+ /**
199
+ * Asserts that the provided item is of type boolean.
200
+ * @param {unknown} item - The item which ought to be of type boolean.
201
+ * @throws {AssertionError} if the type isn't boolean.
202
+ */
203
+ export function assertTypeOfBoolean(item: unknown): asserts item is boolean {
204
+ if (!import.meta.env.DEV) return;
205
+ if (typeof item !== "boolean")
206
+ throw new AssertionError(
207
+ `Provided item was not of type boolean. Was: ${getTypeNameOfUnknown(item)}`
208
+ );
51
209
  }
52
210
 
53
- export function assertTypeOfString(obj: unknown): asserts obj is string {
211
+ /**
212
+ * Asserts that the provided item is of type bigint.
213
+ * @param {unknown} item - The item which ought to be of type bigint.
214
+ * @throws {AssertionError} if the type isn't bigint.
215
+ */
216
+ export function assertTypeOfBigint(item: unknown): asserts item is bigint {
54
217
  if (!import.meta.env.DEV) return;
55
- if (typeof obj !== "string") throw new AssertionError(`Provided object was not of type string. Was: ${typeof obj}`);
218
+ if (typeof item !== "bigint")
219
+ throw new AssertionError(
220
+ `Provided item was not of type bigint. Was: ${getTypeNameOfUnknown(item)}`
221
+ );
56
222
  }
57
223
 
58
- export function assertTypeOfNumber(obj: unknown): asserts obj is number {
224
+ /**
225
+ * Asserts that the provided item is of type undefined.
226
+ * @param {unknown} item - The item which ought to be of type undefined.
227
+ * @throws {AssertionError} if the type isn't undefined.
228
+ */
229
+ export function assertTypeOfUndefined(item: unknown): asserts item is undefined {
59
230
  if (!import.meta.env.DEV) return;
60
- if (typeof obj !== "number") throw new AssertionError(`Provided object was not of type number. Was: ${typeof obj}`);
231
+ if (typeof item !== "undefined")
232
+ throw new AssertionError(
233
+ `Provided item was not of type undefined. Was: ${getTypeNameOfUnknown(item)}`
234
+ );
61
235
  }
62
236
 
63
- export function assertTypeOfBoolean(obj: unknown): asserts obj is boolean {
237
+ /**
238
+ * Asserts that the provided item is of type function.
239
+ * @param {unknown} item - The item which ought to be of type function.
240
+ * @throws {AssertionError} if the type isn't function.
241
+ */
242
+ export function assertTypeOfFunction(item: unknown): asserts item is Function {
64
243
  if (!import.meta.env.DEV) return;
65
- if (typeof obj !== "boolean") throw new AssertionError(`Provided object was not of type boolean. Was: ${typeof obj}`);
244
+ if (typeof item !== "function")
245
+ throw new AssertionError(
246
+ `Provided item was not of type function. Was: ${getTypeNameOfUnknown(item)}`
247
+ );
66
248
  }
67
249
 
68
- export function assertTypeOfBigint(obj: unknown): asserts obj is bigint {
250
+ /**
251
+ * Asserts that the provided item is of type object.
252
+ * @param {unknown} item - The item which ought to be of type object.
253
+ * @throws {AssertionError} if the type isn't object.
254
+ */
255
+ export function assertTypeOfObject(item: unknown): asserts item is object {
69
256
  if (!import.meta.env.DEV) return;
70
- if (typeof obj !== "bigint") throw new AssertionError(`Provided object was not of type bigint. Was: ${typeof obj}`);
257
+ if (typeof item !== "object")
258
+ throw new AssertionError(
259
+ `Provided item was not of type object. Was: ${getTypeNameOfUnknown(item)}`
260
+ );
71
261
  }
72
262
 
73
- export function assertTypeOfUndefined(obj: unknown): asserts obj is undefined {
263
+ /**
264
+ * Asserts that the provided item is of type symbol.
265
+ * @param {unknown} item - The item which ought to be of type symbol.
266
+ * @throws {AssertionError} if the type isn't symbol.
267
+ */
268
+ export function assertTypeOfSymbol(item: unknown): asserts item is symbol {
74
269
  if (!import.meta.env.DEV) return;
75
- if (typeof obj !== "undefined") throw new AssertionError(`Provided object was not of type undefined. Was: ${typeof obj}`);
270
+ if (typeof item !== "symbol")
271
+ throw new AssertionError(
272
+ `Provided item was not of type symbol. Was: ${getTypeNameOfUnknown(item)}`
273
+ );
76
274
  }
77
275
 
78
- // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
79
- export function assertTypeOfFunction(obj: unknown): asserts obj is Function {
276
+ /**
277
+ * Asserts that the provided item is null.
278
+ * @param {unknown} item - The item which ought to be null.
279
+ * @throws {AssertionError} if the value isn't null.
280
+ */
281
+ export function assertNull(item: unknown): asserts item is null {
80
282
  if (!import.meta.env.DEV) return;
81
- if (typeof obj !== "function") throw new AssertionError(`Provided object was not of type function. Was: ${typeof obj}`);
283
+ if (item !== null)
284
+ throw new AssertionError(
285
+ `Provided item was not null. Was type: ${getTypeNameOfUnknown(item)}, value: ${item}`
286
+ );
82
287
  }
83
288
 
84
- export function assertTypeOfObject(obj: unknown): asserts obj is object {
289
+ /**
290
+ * Asserts that the provided item is an instance of the provided constructor.
291
+ * @param {unknown} item - The item which ought to be an instance of the constructor.
292
+ * @param {Constructor<T>} constructor - Anything that can be after an instanceof operator.
293
+ * @throws {AssertionError} if item instanceof constructor is false.
294
+ */
295
+ export function assertInstanceOf<T>(item: unknown, constructor: Constructor<T>): asserts item is T {
85
296
  if (!import.meta.env.DEV) return;
86
- if (typeof obj !== "object") throw new AssertionError(`Provided object was not of type object. Was: ${typeof obj}`);
297
+ if (!(item instanceof constructor))
298
+ throw new AssertionError(
299
+ `Provided item was not of type ${constructor.name} but was type: ${getTypeNameOfUnknown(
300
+ item
301
+ )}, value: ${item}`
302
+ );
87
303
  }
88
304
 
89
- export function assertTypeOfSymbol(obj: unknown): asserts obj is symbol {
305
+ /**
306
+ * Asserts that the provided array is a tuple of exactly the expected length.
307
+ * @param {unknown[]} arr - The array which ought to be a tuple.
308
+ * @param {number} expectedLength - The exact expected length of the tuple.
309
+ * @throws {AssertionError} if the array isn't of the expected length or is sparse.
310
+ */
311
+ export function assertIsTuple<
312
+ T extends number extends T["length"] ? unknown[] : never,
313
+ N extends number
314
+ >(arr: [...T], expectedLength: N): asserts arr is T & Tuple<T[number], N> {
90
315
  if (!import.meta.env.DEV) return;
91
- if (typeof obj !== "symbol") throw new AssertionError(`Provided object was not of type symbol. Was: ${typeof obj}`);
316
+ if (arr.length !== expectedLength) {
317
+ throw new AssertionError(
318
+ `Provided array is not a tuple of expected length ${expectedLength}. It has length ${arr.length}.`
319
+ );
320
+ }
321
+ for (let i = 0; i < expectedLength; i++) {
322
+ if (!(i in arr))
323
+ throw new AssertionError(
324
+ `Provided tuple is sparse and therefore not a tuple. Index ${i} is missing.`
325
+ );
326
+ }
92
327
  }
93
328
 
94
- export function assertNull(obj: unknown): asserts obj is null {
329
+ /**
330
+ * Used to assert that code can never be reached. Pass a value which has already been checked for all types that should be possible. If the range of possible values increases, TypeScript will throw an error at compile time because the value won't be of type never.
331
+ * @param {never} item - An exhausted value, of which all cases are accounted for in other branches of the code, such as at the end of a switch statement.
332
+ * @param {string} msg - Override the default error message. Even if you do, the error message will include the value and type of item.
333
+ * @throws {AssertionError} if at runtime the function call was reached. This should only happen if TypeScript types are inaccurate somewhere.
334
+ */
335
+ export function assertUnreachable(
336
+ item: never,
337
+ msg: string = "Unreachable code of type never was reached. TypeScript types are inaccurate somewhere."
338
+ ): asserts item is never {
95
339
  if (!import.meta.env.DEV) return;
96
- if (obj !== null) throw new AssertionError(`Provided object was not null. Was: ${obj}`);
340
+ throw new AssertionError(
341
+ msg +
342
+ `\nValue of type never was actually of type: ${getTypeNameOfUnknown(
343
+ item
344
+ )}, value: ${item}`
345
+ );
97
346
  }
98
347
 
99
- export function assertInstanceOf<T>(obj: unknown, constructable: Constructor<T>): asserts obj is T {
348
+ /**
349
+ * Asserts that the provided item is neither null nor undefined.
350
+ * @param {unknown} item - The item which ought to be non-null.
351
+ * @throws {AssertionError} if the item is null or undefined.
352
+ */
353
+ export function assertNonNullable<T>(item: T): asserts item is NonNullable<T> {
100
354
  if (!import.meta.env.DEV) return;
101
- if (!(obj instanceof constructable)) throw new AssertionError(`Provided object was not of type ${constructable.name} but was type: ${(obj === null) ? "null" : obj?.constructor?.name ?? typeof obj}, value: ${obj}`);
355
+ if (item === undefined || item === null)
356
+ throw new AssertionError(`Provided item should've been non-null but was: ${item}`);
102
357
  }
103
358
 
104
- export function assertUnreachable(obj: never, msg: string = "Unreachable code of type never was reached. TypeScript types are inaccurate somewhere."): asserts obj is never {
359
+ /**
360
+ * Asserts that the provided object has non-null values for the properties passed as keys in the propKeys array.
361
+ * @param {object} obj - The object which ought to have the properties.
362
+ * @param {NullableKeys<T>} propKeys - An array of the stringified keys of the properties which ought to be non-null in the object.
363
+ * @throws {AssertionError} if any of the properties was null, undefined, or not present in the object.
364
+ */
365
+ export function assertPropsNonNullable<T extends object, N extends NullableKeys<T>>(
366
+ obj: T,
367
+ propKeys: N[]
368
+ ): asserts obj is PropsNonNullable<T, N> {
105
369
  if (!import.meta.env.DEV) return;
106
- throw new AssertionError(msg);
370
+ for (const propKey of propKeys) {
371
+ if (!(propKey in obj))
372
+ throw new AssertionError(
373
+ `Provided object prop ${String(
374
+ propKey
375
+ )} should've been non-null but was not present at all.`
376
+ );
377
+ if (obj[propKey] === null || obj[propKey] === undefined)
378
+ throw new AssertionError(
379
+ `Provided object prop ${String(propKey)} should've been non-null but was: ${
380
+ obj[propKey]
381
+ }`
382
+ );
383
+ }
107
384
  }
108
385
 
109
- export function assertPropsNonNullable<T, N extends NullableKeys<T>>(obj: T, props: N[]): asserts obj is PropsNonNullable<T, N> {
386
+ /**
387
+ * Asserts that all elements of the provided array are neither null nor undefined, or not present.
388
+ * @param {unknown[]} arr - The array which ought to be non-sparse, and have only non-null elements.
389
+ * @throws {AssertionError} if any of the elements was null, undefined, or not present in the array.
390
+ */
391
+ export function assertArrayNonNullable<T>(arr: T[]): asserts arr is NonNullable<T>[] {
110
392
  if (!import.meta.env.DEV) return;
111
- for (const prop of props) {
112
- if (obj[prop] === null || obj[prop] === undefined)
113
- throw new AssertionError(`Provided object prop ${String(prop)} should've been non-null but was: ${obj[prop]}`);
393
+ for (let i = 0; i < arr.length; i++) {
394
+ if (!(i in arr))
395
+ throw new AssertionError(
396
+ `Provided array should've been non-null but was sparse with a missing item at index ${i}`
397
+ );
398
+ const item = arr[i];
399
+ if (item === null)
400
+ throw new AssertionError(
401
+ `Provided array should've been non-null but had an item with value null at index ${i}`
402
+ );
403
+ if (item === undefined)
404
+ throw new AssertionError(
405
+ `Provided array should've been non-null but had an undefined item at index ${i}`
406
+ );
114
407
  }
115
408
  }
116
409
 
117
- export function assertNonNullable<T>(obj: T): asserts obj is NonNullable<T> {
410
+ /**
411
+ * Asserts that the provided tuple has non-null values for all elements. This function does not take a length. So if you want to assert that the typescript tuple type is of the correct length, call @see assertIsTuple first.
412
+ * @param {[unknown, ...]} tuple - The tuple which ought to have only non-null values.
413
+ * @throws {AssertionError} if any of the elements was null, undefined, or an index not present in the tuple.
414
+ */
415
+ export function assertTupleNonNullable<T extends number extends T["length"] ? never : unknown[]>(
416
+ tuple: T
417
+ ): asserts tuple is { [K in keyof T]: NonNullable<T[K]> } {
118
418
  if (!import.meta.env.DEV) return;
119
- if (obj === undefined || obj === null) throw new AssertionError(`Provided object should've been non-null but was: ${obj}`);
419
+ for (let i = 0; i < tuple.length; i++) {
420
+ if (!(i in tuple))
421
+ throw new AssertionError(
422
+ `Provided tuple should've been non-null but is sparse. Index ${i} is missing.`
423
+ );
424
+ if (tuple[i] === null)
425
+ throw new AssertionError(
426
+ `Provided tuple should've been non-null but had an item with value null at index ${i}`
427
+ );
428
+ if (tuple[i] === undefined)
429
+ throw new AssertionError(
430
+ `Provided tuple should've been non-null but had an undefined item at index ${i}`
431
+ );
432
+ }
120
433
  }
121
434
 
122
- export function assertFiniteNumber(obj: unknown): asserts obj is number {
435
+
436
+ /**
437
+ * Asserts that the provided item is a finite number. Use to prevent NaN propagation.
438
+ * @param {unknown} item - The item which ought to be a finite number.
439
+ * @throws {AssertionError} if the item is not of type number, or isFinite(item) is false, i.e., if the item is NaN, Infinity, or -Infinity.
440
+ */
441
+ export function assertFiniteNumber(item: unknown): asserts item is number {
123
442
  if (!import.meta.env.DEV) return;
124
- if (typeof obj !== "number") throw new AssertionError(`Provided object was not of type number. Was: ${typeof obj}`);
125
- if (!isFinite(obj)) throw new AssertionError(`Provided number was not finite. Was: ${obj}`);
443
+ if (typeof item !== "number")
444
+ throw new AssertionError(
445
+ `Provided item was not of type number. Was: ${getTypeNameOfUnknown(item)}`
446
+ );
447
+ if (!isFinite(item)) throw new AssertionError(`Provided number was not finite. Was: ${item}`);
126
448
  }