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/CHANGELOG.md +24 -1
- package/MIGRATION-GUIDE.md +13 -0
- package/README.md +18 -6
- package/lib/assert-helpers.d.ts +20 -0
- package/lib/assert-helpers.js +66 -0
- package/lib/index.d.ts +49 -56
- package/lib/index.js +56 -117
- package/lib/types.d.ts +29 -0
- package/lib/types.js +1 -0
- package/package.json +9 -4
- package/src/assert-helpers.ts +63 -0
- package/src/index.ts +146 -143
- package/src/types.ts +39 -0
- package/test/runtime/assert-helpers.test.ts +583 -0
- package/test/runtime/asserts.test.ts +641 -0
- package/test/runtime/main.ts +6 -0
- package/test/runtime/testing.ts +168 -0
- package/test/runtime/tsconfig.json +18 -0
- package/test/types/readonly.5+.test.ts +44 -0
- package/test/types/readonly.test.ts +238 -0
- package/test/types/run.ts +29 -0
- package/test/types/tsconfig.4.x.json +7 -0
- package/test/types/tsconfig.json +18 -0
- package/test/types/type-narrowing.5+.test.ts +32 -0
- package/test/types/type-narrowing.test.ts +654 -0
- package/tsconfig.json +1 -0
package/src/index.ts
CHANGED
|
@@ -1,75 +1,39 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
type
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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 =
|
|
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 {
|
|
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
|
|
51
|
+
if (!hasToBeTrue) throw new AssertieError(msg);
|
|
88
52
|
}
|
|
89
53
|
|
|
90
54
|
/**
|
|
91
|
-
* Asserts that the provided
|
|
92
|
-
* @param {unknown} item - The
|
|
93
|
-
* @param {AllJSTypes} expectedType - The expected type of the
|
|
94
|
-
* @throws {
|
|
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
|
|
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"
|
|
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 {
|
|
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
|
|
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
|
|
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]
|
|
139
|
-
* @throws {
|
|
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
|
-
|
|
145
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
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 {
|
|
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
|
|
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 {
|
|
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
|
|
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 {
|
|
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
|
|
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 {
|
|
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
|
|
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 {
|
|
232
|
+
* @throws {AssertieError} if the type isn't function.
|
|
241
233
|
*/
|
|
242
|
-
export function assertTypeOfFunction(item: unknown): asserts item is
|
|
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
|
|
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 {
|
|
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
|
|
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 {
|
|
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
|
|
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 {
|
|
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
|
|
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 {
|
|
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
|
|
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 {
|
|
301
|
+
* @throws {AssertieError} if the array isn't of the expected length or is sparse.
|
|
310
302
|
*/
|
|
311
303
|
export function assertIsTuple<
|
|
312
|
-
T
|
|
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: [
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
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 {
|
|
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
|
|
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 {
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
447
|
+
throw new AssertieError(
|
|
445
448
|
`Provided item was not of type number. Was: ${getTypeNameOfUnknown(item)}`
|
|
446
449
|
);
|
|
447
|
-
if (!isFinite(item)) throw new
|
|
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]>;
|