assertie 0.3.2 → 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/src/index.ts CHANGED
@@ -1,75 +1,39 @@
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
-
5
- type PrimitiveTypes = {
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;
19
- }[keyof T];
20
-
21
- type PropsNonNullable<T, N extends NullableKeys<T>> = T & {
22
- [K in N]-?: NonNullable<T[K]>;
1
+ import type {
2
+ UnknownFunction,
3
+ Constructor,
4
+ PrimitiveTypes,
5
+ PrimitiveTypeStrings,
6
+ NullableKeys,
7
+ PropsNonNullable,
8
+ AllJSTypes,
9
+ ResolveAnyJSType,
10
+ ResolveTuple,
11
+ ResolveReadonlyTuple,
12
+ Tuple,
13
+ ReadonlyTuple
14
+ } from "./types";
15
+ import { getNameOfExpectedType, getTypeNameOfUnknown, isType } from "./assert-helpers";
16
+
17
+ export type {
18
+ PrimitiveTypes,
19
+ PrimitiveTypeStrings,
20
+ NullableKeys,
21
+ PropsNonNullable,
22
+ AllJSTypes,
23
+ ResolveAnyJSType,
24
+ ResolveTuple,
25
+ ResolveReadonlyTuple,
26
+ Tuple,
27
+ ReadonlyTuple
23
28
  };
24
29
 
25
- type Constructor<T> = new (...args: any[]) => 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
- }
68
-
69
- class AssertionError extends Error {
30
+ /**
31
+ * Error thrown by all assertie assertions when they fail.
32
+ */
33
+ export class AssertieError extends Error {
70
34
  constructor(msg: string) {
71
35
  super(`Assertion failed: ${msg}`);
72
- this.name = AssertionError.name;
36
+ this.name = AssertieError.name;
73
37
  }
74
38
  }
75
39
 
@@ -77,21 +41,21 @@ class AssertionError extends Error {
77
41
  * Asserts that the provided boolean is true.
78
42
  * @param {boolean} hasToBeTrue - The boolean to assert.
79
43
  * @param {string} msg - The message of the Error if the assertion fails.
80
- * @throws {AssertionError} if the assertion fails.
44
+ * @throws {AssertieError} if the assertion fails.
81
45
  */
82
46
  export function assert(
83
47
  hasToBeTrue: boolean,
84
48
  msg: string = "No specific message provided."
85
49
  ): asserts hasToBeTrue is true {
86
50
  if (!import.meta.env.DEV) return;
87
- if (!hasToBeTrue) throw new AssertionError(msg);
51
+ if (!hasToBeTrue) throw new AssertieError(msg);
88
52
  }
89
53
 
90
54
  /**
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.
55
+ * Asserts that the provided item is of the expectedType.
56
+ * @param {unknown} item - The item which ought to be of the expectedType.
57
+ * @param {AllJSTypes} expectedType - The expected type of the item. 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".
58
+ * @throws {AssertieError} if the type isn't as expected.
95
59
  */
96
60
  export function assertType<T extends AllJSTypes>(
97
61
  item: unknown,
@@ -99,7 +63,7 @@ export function assertType<T extends AllJSTypes>(
99
63
  ): asserts item is ResolveAnyJSType<T> {
100
64
  if (!import.meta.env.DEV) return;
101
65
  if (!isType(item, expectedType))
102
- throw new AssertionError(
66
+ throw new AssertieError(
103
67
  `Provided object was not of type ${getNameOfExpectedType(
104
68
  expectedType
105
69
  )}. Was: ${getTypeNameOfUnknown(item)}, value: ${item}`
@@ -107,24 +71,32 @@ export function assertType<T extends AllJSTypes>(
107
71
  }
108
72
 
109
73
  /**
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[]
74
+ * Asserts that all elements of the provided array are of the expected type. It ensures that the array is not sparse up to arr.length (even when the expectedType is undefined).
75
+ * @param {unknown[]} arr - The array which ought to be an array of the expectedType, i.e. expectedType: "number" means `arr: number[]`. Preserves readonly when specified.
112
76
  * @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.
77
+ * @throws {AssertieError} if the type isn't as expected.
114
78
  */
115
79
  export function assertArrayType<T extends AllJSTypes>(
116
80
  arr: unknown[],
117
81
  expectedType: T
118
- ): asserts arr is ResolveAnyJSType<T>[] {
82
+ ): asserts arr is ResolveAnyJSType<T>[];
83
+ export function assertArrayType<T extends AllJSTypes>(
84
+ arr: readonly unknown[],
85
+ expectedType: T
86
+ ): asserts arr is readonly ResolveAnyJSType<T>[];
87
+ export function assertArrayType<T extends AllJSTypes>(
88
+ arr: unknown[] | readonly unknown[],
89
+ expectedType: T
90
+ ): asserts arr is ResolveAnyJSType<T>[] | readonly ResolveAnyJSType<T>[] {
119
91
  if (!import.meta.env.DEV) return;
120
92
  for (let i = 0; i < arr.length; i++) {
121
93
  if (!(i in arr))
122
- throw new AssertionError(
94
+ throw new AssertieError(
123
95
  `Array to assert type of was sparse with a missing item at index ${i}`
124
96
  );
125
97
  const item = arr[i];
126
98
  if (!isType(item, expectedType))
127
- throw new AssertionError(
99
+ throw new AssertieError(
128
100
  `Provided array had item at index ${i} not of type ${getNameOfExpectedType(
129
101
  expectedType
130
102
  )}. Was: ${getTypeNameOfUnknown(item)}, value: ${item}`
@@ -134,33 +106,53 @@ export function assertArrayType<T extends AllJSTypes>(
134
106
 
135
107
  /**
136
108
  * 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.
109
+ * @param {unknown[] | [unknown, ...]} arrayOrTuple - The tuple which ought to be an array of the expected length and types. Preserves readonly when specified.
110
+ * @param {[AllJSTypes, ...]} expectedTypes - A tuple of expected types of individual items, e.g., `expectedTypes = ["number", "string", Date]` means `arrayOrTuple: [number, string, Date]`. The individual entries can be JS primitive types, null, undefined, and constructors.
111
+ * @throws {AssertieError} if the type of any element of the tuple isn't as expected.
140
112
  */
141
113
  export function assertTupleTypes<
142
114
  T extends readonly AllJSTypes[],
143
115
  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
116
+ | { [K in keyof T]: unknown } // [...unknown] matching length of [...T]
117
+ | (number extends U["length"] ? unknown[] : never) // Array with compile time unknown length
118
+ >(
119
+ arrayOrTuple: U,
120
+ expectedTypes: readonly [...T]
121
+ ): asserts arrayOrTuple is ResolveTuple<T, U>;
122
+ export function assertTupleTypes<
123
+ T extends readonly AllJSTypes[],
124
+ U extends
125
+ | { readonly [K in keyof T]: unknown } // [...unknown] matching length of [...T]
126
+ | (number extends U["length"] ? readonly unknown[] : never) // Array with compile time unknown length
127
+ >(
128
+ arrayOrTuple: U,
129
+ expectedTypes: readonly [...T]
130
+ ): asserts arrayOrTuple is ResolveReadonlyTuple<T, U>;
131
+ export function assertTupleTypes<
132
+ T extends readonly AllJSTypes[],
133
+ U extends
134
+ | { [K in keyof T]: unknown } // [...unknown] matching length of [...T]
135
+ | { readonly [K in keyof T]: unknown } // [...unknown] matching length of [...T]
136
+ | (number extends U["length"] ? unknown[] : never) // Array with compile time unknown length
137
+ | (number extends U["length"] ? readonly unknown[] : never) // Array with compile time unknown length
146
138
  >(
147
139
  arrayOrTuple: U,
148
140
  expectedTypes: readonly [...T]
149
- ): asserts arrayOrTuple is U & { [K in keyof T]: ResolveAnyJSType<T[K]> } {
141
+ ): asserts arrayOrTuple is ResolveTuple<T, U> | ResolveReadonlyTuple<T, U> {
150
142
  if (!import.meta.env.DEV) return;
151
143
  if (arrayOrTuple.length !== expectedTypes.length) {
152
- throw new AssertionError(
144
+ throw new AssertieError(
153
145
  `Provided tuple length mismatch: expected ${expectedTypes.length}, but got ${arrayOrTuple.length}`
154
146
  );
155
147
  }
156
148
  for (let i = 0; i < expectedTypes.length; i++) {
157
149
  if (!(i in arrayOrTuple))
158
- throw new AssertionError(
150
+ throw new AssertieError(
159
151
  `Provided tuple was sparse with a missing item at required index ${i}`
160
152
  );
161
153
  const item = arrayOrTuple[i];
162
154
  if (!isType(item, expectedTypes[i])) {
163
- throw new AssertionError(
155
+ throw new AssertieError(
164
156
  `Provided tuple had item at index ${i} not of type ${getNameOfExpectedType(
165
157
  expectedTypes[i]
166
158
  )}. Was: ${getTypeNameOfUnknown(item)}, value: ${item}`
@@ -172,12 +164,12 @@ export function assertTupleTypes<
172
164
  /**
173
165
  * Asserts that the provided item is of type string.
174
166
  * @param {unknown} item - The item which ought to be of type string.
175
- * @throws {AssertionError} if the type isn't string.
167
+ * @throws {AssertieError} if the type isn't string.
176
168
  */
177
169
  export function assertTypeOfString(item: unknown): asserts item is string {
178
170
  if (!import.meta.env.DEV) return;
179
171
  if (typeof item !== "string")
180
- throw new AssertionError(
172
+ throw new AssertieError(
181
173
  `Provided item was not of type string. Was: ${getTypeNameOfUnknown(item)}`
182
174
  );
183
175
  }
@@ -185,12 +177,12 @@ export function assertTypeOfString(item: unknown): asserts item is string {
185
177
  /**
186
178
  * Asserts that the provided item is of type number.
187
179
  * @param {unknown} item - The item which ought to be of type number.
188
- * @throws {AssertionError} if the type isn't number.
180
+ * @throws {AssertieError} if the type isn't number.
189
181
  */
190
182
  export function assertTypeOfNumber(item: unknown): asserts item is number {
191
183
  if (!import.meta.env.DEV) return;
192
184
  if (typeof item !== "number")
193
- throw new AssertionError(
185
+ throw new AssertieError(
194
186
  `Provided item was not of type number. Was: ${getTypeNameOfUnknown(item)}`
195
187
  );
196
188
  }
@@ -198,12 +190,12 @@ export function assertTypeOfNumber(item: unknown): asserts item is number {
198
190
  /**
199
191
  * Asserts that the provided item is of type boolean.
200
192
  * @param {unknown} item - The item which ought to be of type boolean.
201
- * @throws {AssertionError} if the type isn't boolean.
193
+ * @throws {AssertieError} if the type isn't boolean.
202
194
  */
203
195
  export function assertTypeOfBoolean(item: unknown): asserts item is boolean {
204
196
  if (!import.meta.env.DEV) return;
205
197
  if (typeof item !== "boolean")
206
- throw new AssertionError(
198
+ throw new AssertieError(
207
199
  `Provided item was not of type boolean. Was: ${getTypeNameOfUnknown(item)}`
208
200
  );
209
201
  }
@@ -211,12 +203,12 @@ export function assertTypeOfBoolean(item: unknown): asserts item is boolean {
211
203
  /**
212
204
  * Asserts that the provided item is of type bigint.
213
205
  * @param {unknown} item - The item which ought to be of type bigint.
214
- * @throws {AssertionError} if the type isn't bigint.
206
+ * @throws {AssertieError} if the type isn't bigint.
215
207
  */
216
208
  export function assertTypeOfBigint(item: unknown): asserts item is bigint {
217
209
  if (!import.meta.env.DEV) return;
218
210
  if (typeof item !== "bigint")
219
- throw new AssertionError(
211
+ throw new AssertieError(
220
212
  `Provided item was not of type bigint. Was: ${getTypeNameOfUnknown(item)}`
221
213
  );
222
214
  }
@@ -224,12 +216,12 @@ export function assertTypeOfBigint(item: unknown): asserts item is bigint {
224
216
  /**
225
217
  * Asserts that the provided item is of type undefined.
226
218
  * @param {unknown} item - The item which ought to be of type undefined.
227
- * @throws {AssertionError} if the type isn't undefined.
219
+ * @throws {AssertieError} if the type isn't undefined.
228
220
  */
229
221
  export function assertTypeOfUndefined(item: unknown): asserts item is undefined {
230
222
  if (!import.meta.env.DEV) return;
231
223
  if (typeof item !== "undefined")
232
- throw new AssertionError(
224
+ throw new AssertieError(
233
225
  `Provided item was not of type undefined. Was: ${getTypeNameOfUnknown(item)}`
234
226
  );
235
227
  }
@@ -237,12 +229,12 @@ export function assertTypeOfUndefined(item: unknown): asserts item is undefined
237
229
  /**
238
230
  * Asserts that the provided item is of type function.
239
231
  * @param {unknown} item - The item which ought to be of type function.
240
- * @throws {AssertionError} if the type isn't function.
232
+ * @throws {AssertieError} if the type isn't function.
241
233
  */
242
- export function assertTypeOfFunction(item: unknown): asserts item is Function {
234
+ export function assertTypeOfFunction(item: unknown): asserts item is UnknownFunction {
243
235
  if (!import.meta.env.DEV) return;
244
236
  if (typeof item !== "function")
245
- throw new AssertionError(
237
+ throw new AssertieError(
246
238
  `Provided item was not of type function. Was: ${getTypeNameOfUnknown(item)}`
247
239
  );
248
240
  }
@@ -250,12 +242,12 @@ export function assertTypeOfFunction(item: unknown): asserts item is Function {
250
242
  /**
251
243
  * Asserts that the provided item is of type object.
252
244
  * @param {unknown} item - The item which ought to be of type object.
253
- * @throws {AssertionError} if the type isn't object.
245
+ * @throws {AssertieError} if the type isn't object.
254
246
  */
255
247
  export function assertTypeOfObject(item: unknown): asserts item is object {
256
248
  if (!import.meta.env.DEV) return;
257
249
  if (typeof item !== "object")
258
- throw new AssertionError(
250
+ throw new AssertieError(
259
251
  `Provided item was not of type object. Was: ${getTypeNameOfUnknown(item)}`
260
252
  );
261
253
  }
@@ -263,12 +255,12 @@ export function assertTypeOfObject(item: unknown): asserts item is object {
263
255
  /**
264
256
  * Asserts that the provided item is of type symbol.
265
257
  * @param {unknown} item - The item which ought to be of type symbol.
266
- * @throws {AssertionError} if the type isn't symbol.
258
+ * @throws {AssertieError} if the type isn't symbol.
267
259
  */
268
260
  export function assertTypeOfSymbol(item: unknown): asserts item is symbol {
269
261
  if (!import.meta.env.DEV) return;
270
262
  if (typeof item !== "symbol")
271
- throw new AssertionError(
263
+ throw new AssertieError(
272
264
  `Provided item was not of type symbol. Was: ${getTypeNameOfUnknown(item)}`
273
265
  );
274
266
  }
@@ -276,12 +268,12 @@ export function assertTypeOfSymbol(item: unknown): asserts item is symbol {
276
268
  /**
277
269
  * Asserts that the provided item is null.
278
270
  * @param {unknown} item - The item which ought to be null.
279
- * @throws {AssertionError} if the value isn't null.
271
+ * @throws {AssertieError} if the value isn't null.
280
272
  */
281
273
  export function assertNull(item: unknown): asserts item is null {
282
274
  if (!import.meta.env.DEV) return;
283
275
  if (item !== null)
284
- throw new AssertionError(
276
+ throw new AssertieError(
285
277
  `Provided item was not null. Was type: ${getTypeNameOfUnknown(item)}, value: ${item}`
286
278
  );
287
279
  }
@@ -290,12 +282,12 @@ export function assertNull(item: unknown): asserts item is null {
290
282
  * Asserts that the provided item is an instance of the provided constructor.
291
283
  * @param {unknown} item - The item which ought to be an instance of the constructor.
292
284
  * @param {Constructor<T>} constructor - Anything that can be after an instanceof operator.
293
- * @throws {AssertionError} if item instanceof constructor is false.
285
+ * @throws {AssertieError} if item instanceof constructor is false.
294
286
  */
295
287
  export function assertInstanceOf<T>(item: unknown, constructor: Constructor<T>): asserts item is T {
296
288
  if (!import.meta.env.DEV) return;
297
289
  if (!(item instanceof constructor))
298
- throw new AssertionError(
290
+ throw new AssertieError(
299
291
  `Provided item was not of type ${constructor.name} but was type: ${getTypeNameOfUnknown(
300
292
  item
301
293
  )}, value: ${item}`
@@ -306,21 +298,29 @@ export function assertInstanceOf<T>(item: unknown, constructor: Constructor<T>):
306
298
  * Asserts that the provided array is a tuple of exactly the expected length.
307
299
  * @param {unknown[]} arr - The array which ought to be a tuple.
308
300
  * @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.
301
+ * @throws {AssertieError} if the array isn't of the expected length or is sparse.
310
302
  */
311
303
  export function assertIsTuple<
312
- T extends number extends T["length"] ? unknown[] : never,
304
+ T,
305
+ N extends number
306
+ >(arr: T[], expectedLength: N): asserts arr is Tuple<T, N>;
307
+ export function assertIsTuple<
308
+ T,
309
+ N extends number
310
+ >(arr: readonly T[], expectedLength: N): asserts arr is ReadonlyTuple<T, N>;
311
+ export function assertIsTuple<
312
+ T,
313
313
  N extends number
314
- >(arr: [...T], expectedLength: N): asserts arr is T & Tuple<T[number], N> {
314
+ >(arr: T[] | readonly T[], expectedLength: N): asserts arr is Tuple<T, N> | ReadonlyTuple<T, N> {
315
315
  if (!import.meta.env.DEV) return;
316
316
  if (arr.length !== expectedLength) {
317
- throw new AssertionError(
317
+ throw new AssertieError(
318
318
  `Provided array is not a tuple of expected length ${expectedLength}. It has length ${arr.length}.`
319
319
  );
320
320
  }
321
321
  for (let i = 0; i < expectedLength; i++) {
322
322
  if (!(i in arr))
323
- throw new AssertionError(
323
+ throw new AssertieError(
324
324
  `Provided tuple is sparse and therefore not a tuple. Index ${i} is missing.`
325
325
  );
326
326
  }
@@ -330,37 +330,34 @@ export function assertIsTuple<
330
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
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
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.
333
+ * @throws {AssertieError} if at runtime the function call was reached. This should only happen if TypeScript types are inaccurate somewhere.
334
334
  */
335
335
  export function assertUnreachable(
336
336
  item: never,
337
337
  msg: string = "Unreachable code of type never was reached. TypeScript types are inaccurate somewhere."
338
338
  ): asserts item is never {
339
339
  if (!import.meta.env.DEV) return;
340
- throw new AssertionError(
341
- msg +
342
- `\nValue of type never was actually of type: ${getTypeNameOfUnknown(
343
- item
344
- )}, value: ${item}`
340
+ throw new AssertieError(
341
+ msg + `\nValue of type never was actually of type: ${getTypeNameOfUnknown(item)}, value: ${item}`
345
342
  );
346
343
  }
347
344
 
348
345
  /**
349
346
  * Asserts that the provided item is neither null nor undefined.
350
347
  * @param {unknown} item - The item which ought to be non-null.
351
- * @throws {AssertionError} if the item is null or undefined.
348
+ * @throws {AssertieError} if the item is null or undefined.
352
349
  */
353
350
  export function assertNonNullable<T>(item: T): asserts item is NonNullable<T> {
354
351
  if (!import.meta.env.DEV) return;
355
352
  if (item === undefined || item === null)
356
- throw new AssertionError(`Provided item should've been non-null but was: ${item}`);
353
+ throw new AssertieError(`Provided item should've been non-null but was: ${item}`);
357
354
  }
358
355
 
359
356
  /**
360
357
  * Asserts that the provided object has non-null values for the properties passed as keys in the propKeys array.
361
358
  * @param {object} obj - The object which ought to have the properties.
362
359
  * @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.
360
+ * @throws {AssertieError} if any of the properties was null, undefined, or not present in the object.
364
361
  */
365
362
  export function assertPropsNonNullable<T extends object, N extends NullableKeys<T>>(
366
363
  obj: T,
@@ -369,39 +366,39 @@ export function assertPropsNonNullable<T extends object, N extends NullableKeys<
369
366
  if (!import.meta.env.DEV) return;
370
367
  for (const propKey of propKeys) {
371
368
  if (!(propKey in obj))
372
- throw new AssertionError(
369
+ throw new AssertieError(
373
370
  `Provided object prop ${String(
374
371
  propKey
375
372
  )} should've been non-null but was not present at all.`
376
373
  );
377
374
  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
- }`
375
+ throw new AssertieError(
376
+ `Provided object prop ${String(propKey)} should've been non-null but was: ${obj[propKey]}`
382
377
  );
383
378
  }
384
379
  }
385
380
 
386
381
  /**
387
382
  * 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.
383
+ * @param {unknown[]} arr - The array which ought to be non-sparse, and have only non-null elements. Preserves readonly when specified.
384
+ * @throws {AssertieError} if any of the elements was null, undefined, or not present in the array.
390
385
  */
391
- export function assertArrayNonNullable<T>(arr: T[]): asserts arr is NonNullable<T>[] {
386
+ export function assertArrayNonNullable<T>(arr: T[]): asserts arr is NonNullable<T>[];
387
+ export function assertArrayNonNullable<T>(arr: readonly T[]): asserts arr is readonly NonNullable<T>[];
388
+ export function assertArrayNonNullable<T>(arr: T[] | readonly T[]): asserts arr is NonNullable<T>[] | readonly NonNullable<T>[] {
392
389
  if (!import.meta.env.DEV) return;
393
390
  for (let i = 0; i < arr.length; i++) {
394
391
  if (!(i in arr))
395
- throw new AssertionError(
392
+ throw new AssertieError(
396
393
  `Provided array should've been non-null but was sparse with a missing item at index ${i}`
397
394
  );
398
395
  const item = arr[i];
399
396
  if (item === null)
400
- throw new AssertionError(
397
+ throw new AssertieError(
401
398
  `Provided array should've been non-null but had an item with value null at index ${i}`
402
399
  );
403
400
  if (item === undefined)
404
- throw new AssertionError(
401
+ throw new AssertieError(
405
402
  `Provided array should've been non-null but had an undefined item at index ${i}`
406
403
  );
407
404
  }
@@ -410,23 +407,29 @@ export function assertArrayNonNullable<T>(arr: T[]): asserts arr is NonNullable<
410
407
  /**
411
408
  * 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
409
  * @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.
410
+ * @throws {AssertieError} if any of the elements was null, undefined, or an index not present in the tuple.
414
411
  */
412
+ export function assertTupleNonNullable<T extends number extends T["length"] ? never : unknown[]>(
413
+ tuple: T
414
+ ): asserts tuple is { [K in keyof T]: NonNullable<T[K]> };
415
+ export function assertTupleNonNullable<T extends number extends T["length"] ? never : readonly unknown[]>(
416
+ tuple: T
417
+ ): asserts tuple is { [K in keyof T]: NonNullable<T[K]> };
415
418
  export function assertTupleNonNullable<T extends number extends T["length"] ? never : unknown[]>(
416
419
  tuple: T
417
420
  ): asserts tuple is { [K in keyof T]: NonNullable<T[K]> } {
418
421
  if (!import.meta.env.DEV) return;
419
422
  for (let i = 0; i < tuple.length; i++) {
420
423
  if (!(i in tuple))
421
- throw new AssertionError(
424
+ throw new AssertieError(
422
425
  `Provided tuple should've been non-null but is sparse. Index ${i} is missing.`
423
426
  );
424
427
  if (tuple[i] === null)
425
- throw new AssertionError(
428
+ throw new AssertieError(
426
429
  `Provided tuple should've been non-null but had an item with value null at index ${i}`
427
430
  );
428
431
  if (tuple[i] === undefined)
429
- throw new AssertionError(
432
+ throw new AssertieError(
430
433
  `Provided tuple should've been non-null but had an undefined item at index ${i}`
431
434
  );
432
435
  }
@@ -436,13 +439,13 @@ export function assertTupleNonNullable<T extends number extends T["length"] ? ne
436
439
  /**
437
440
  * Asserts that the provided item is a finite number. Use to prevent NaN propagation.
438
441
  * @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.
442
+ * @throws {AssertieError} if the item is not of type number, or isFinite(item) is false, i.e., if the item is NaN, Infinity, or -Infinity.
440
443
  */
441
444
  export function assertFiniteNumber(item: unknown): asserts item is number {
442
445
  if (!import.meta.env.DEV) return;
443
446
  if (typeof item !== "number")
444
- throw new AssertionError(
447
+ throw new AssertieError(
445
448
  `Provided item was not of type number. Was: ${getTypeNameOfUnknown(item)}`
446
449
  );
447
- if (!isFinite(item)) throw new AssertionError(`Provided number was not finite. Was: ${item}`);
450
+ if (!isFinite(item)) throw new AssertieError(`Provided number was not finite. Was: ${item}`);
448
451
  }
package/src/types.ts ADDED
@@ -0,0 +1,39 @@
1
+ export type UnknownFunction = (...args: any[]) => unknown;
2
+ export type Constructor<T> = abstract new (...args: any[]) => T;
3
+
4
+ export type PrimitiveTypes = {
5
+ "string": string;
6
+ "number": number;
7
+ "boolean": boolean;
8
+ "bigint": bigint;
9
+ "undefined": undefined;
10
+ "function": UnknownFunction;
11
+ "object": object;
12
+ "symbol": symbol;
13
+ };
14
+ export type PrimitiveTypeStrings = keyof PrimitiveTypes;
15
+
16
+ export type NullableKeys<T> = {
17
+ [K in keyof T]-?: undefined extends T[K] ? K : null extends T[K] ? K : never;
18
+ }[keyof T];
19
+
20
+ export type PropsNonNullable<T, N extends NullableKeys<T>> = T & {
21
+ [K in N]-?: NonNullable<T[K]>;
22
+ };
23
+
24
+ export type AllJSTypes = PrimitiveTypeStrings | null | undefined | Constructor<unknown>;
25
+
26
+ export type ResolveAnyJSType<T extends AllJSTypes> = T extends
27
+ | PrimitiveTypeStrings ? PrimitiveTypes[T]
28
+ : T extends null ? null
29
+ : T extends undefined ? undefined
30
+ : T extends Constructor<infer U> ? U
31
+ : never;
32
+
33
+ export type ResolveTuple<T extends readonly AllJSTypes[], U> = U & { [K in keyof T]: ResolveAnyJSType<T[K]> };
34
+ export type ResolveReadonlyTuple<T extends readonly AllJSTypes[], U> = U & { readonly [K in keyof T]: ResolveAnyJSType<T[K]> };
35
+
36
+ export type Tuple<T, N extends number, A extends unknown[] = []> =
37
+ A["length"] extends N ? A : Tuple<T, N, [...A, T]>;
38
+ export type ReadonlyTuple<T, N extends number, A extends readonly unknown[] = readonly []> =
39
+ A["length"] extends N ? A : ReadonlyTuple<T, N, readonly [...A, T]>;