assertie 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +172 -0
- package/lib/index.d.ts +36 -0
- package/lib/index.js +107 -0
- package/package.json +42 -0
- package/src/index.ts +121 -0
- package/tsconfig.json +11 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 OfficialHalfwayDead
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# assertie
|
|
2
|
+
|
|
3
|
+
Debug assertions for TypeScript, auto tree-shaken by vite for production.
|
|
4
|
+
|
|
5
|
+
Why? Because asserts are simple to read and safer than casting. Use them when you know something is guaranteed to be true, and assertie will make sure it is in dev builds. As programmers, we sometimes make wrong assumptions.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { assertInstanceOf } from "assertie";
|
|
9
|
+
...
|
|
10
|
+
const original = document.getElementById("probably-mounted");
|
|
11
|
+
if (original === null) return; // This isn't the place for an assert because the element isn't guaranteed to be present.
|
|
12
|
+
const clone = original.cloneNode(true); // returns type Node but we know it'll always be an HTMLElement
|
|
13
|
+
assertInstanceOf(clone, HTMLElement);
|
|
14
|
+
clone.innerText = "No `as` cast needed! 0 overhead in production.";
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm i -D assertie
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
If you're getting errors when using the package, it's likely because your TypeScript targets are too low:
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
// tsconfig.json
|
|
27
|
+
{
|
|
28
|
+
"compilerOptions": {
|
|
29
|
+
"target": "ES6", // minimum
|
|
30
|
+
"module": "ES2020", // minimum
|
|
31
|
+
"types": ["vite/client"] // may be needed depending on your setup
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
By default, vite will tree-shake the functions for production, but make sure you haven't specifically disabled it in your vite config:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
// vite.config.ts
|
|
40
|
+
const config: UserConfig = {
|
|
41
|
+
build: {
|
|
42
|
+
rollupOptions: {
|
|
43
|
+
treeshake: true,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Usage
|
|
50
|
+
|
|
51
|
+
### Basic assert
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { assert } from "assertie";
|
|
55
|
+
|
|
56
|
+
assert(typeof "yup" === "string", "optional text if assertion fails");
|
|
57
|
+
|
|
58
|
+
// You get type narrowing from assertie's assertions
|
|
59
|
+
const x: boolean = true;
|
|
60
|
+
assert(x);
|
|
61
|
+
const y: true = x; // no error
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Type assertion
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
import { assertType } from "assertie";
|
|
68
|
+
|
|
69
|
+
// assertType can take any primitive JS type string (e.g., "number", "string"), null, undefined, or a class/constructable
|
|
70
|
+
assertType(1, "number");
|
|
71
|
+
assertType(() => {}, "function");
|
|
72
|
+
assertType(undefined, "undefined");
|
|
73
|
+
assertType(undefined, undefined);
|
|
74
|
+
assertType(null, null);
|
|
75
|
+
assertType(new Date(), Date);
|
|
76
|
+
|
|
77
|
+
// Use type assertions to replace all your type casts
|
|
78
|
+
const value: unknown = "yup";
|
|
79
|
+
const bad: string = value as string;
|
|
80
|
+
assertType(value, "string");
|
|
81
|
+
const good: string = value; // no `as` needed
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
You don't need to use strings to narrow primitives. There are specific functions for specific types if you prefer that.
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
assertTypeofString("yup");
|
|
88
|
+
assertTypeofNumber(123);
|
|
89
|
+
assertTypeofBoolean(true);
|
|
90
|
+
assertTypeofBigint(123n);
|
|
91
|
+
assertTypeofUndefined(undefined);
|
|
92
|
+
assertTypeofFunction(() => {});
|
|
93
|
+
assertTypeofObject({});
|
|
94
|
+
assertTypeofSymbol(Symbol("yup"));
|
|
95
|
+
assertInstanceOf(new Date(), Date);
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Asserting non-null
|
|
99
|
+
|
|
100
|
+
Sometimes you'll find yourself in a situation where you know from the calling context that a hoisted variable is not `null` or `undefined`, but TypeScript doesn't.
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
let hoisted: string | null | undefined = null;
|
|
104
|
+
|
|
105
|
+
const f = () => {
|
|
106
|
+
assertNonNullable(hoisted);
|
|
107
|
+
// vs.
|
|
108
|
+
if (hoisted === null || hoisted === undefined) return;
|
|
109
|
+
|
|
110
|
+
console.log(hoisted.toUpperCase());
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
hoisted = "yup";
|
|
114
|
+
f();
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
There are multiple ways in which an assert is better in this specific case:
|
|
118
|
+
|
|
119
|
+
1. Making the code intention clear. Your function was never meant to only run some of the time. It is always supposed to work. You only added the if statement to make the compiler happy.
|
|
120
|
+
2. The assert will throw an error in dev if the case that should never happen does happen. Without an error, it would be easy to change how the hoisted variable is set, and the potential behavior change is more likely to go unnoticed.
|
|
121
|
+
3. It's a little shorter, mainly if you have to check null and undefined and maybe have prettier rules for { brackets } on if statements.
|
|
122
|
+
4. The assert will be removed in production, so there's no overhead. If that makes you uncomfortable, you can just leave both in.
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
// Sometimes not your entire object is null
|
|
126
|
+
const obj: {
|
|
127
|
+
a?: string,
|
|
128
|
+
b: string,
|
|
129
|
+
c: number | null
|
|
130
|
+
} = {
|
|
131
|
+
a: "yeah",
|
|
132
|
+
b: "yup",
|
|
133
|
+
c: 123,
|
|
134
|
+
};
|
|
135
|
+
// Pass an array of all keys to check
|
|
136
|
+
assertPropsNonNullable(obj, ["a", "c"]);
|
|
137
|
+
const safeObj = obj;
|
|
138
|
+
// typeof safeObj === { a: string, b: string, c: number }
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
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, it will remain undefined/null after the assert.
|
|
142
|
+
|
|
143
|
+
### Asserting unreachable code
|
|
144
|
+
|
|
145
|
+
The unreachable assertion can both help you ensure that switch/if statements are exhaustive at compile time and throw at runtime if the TypeScript types were inaccurate somewhere.
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
const x: "a" | "b" = "a";
|
|
149
|
+
|
|
150
|
+
switch (x) {
|
|
151
|
+
case "a":
|
|
152
|
+
break;
|
|
153
|
+
case "b":
|
|
154
|
+
break;
|
|
155
|
+
default:
|
|
156
|
+
assertUnreachable(x);
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
If you were to extend `x`'s type to include `"c"`, TypeScript would not compile anymore because `assertUnreachable` only accepts values of type `never`, but the type in the default case would be `"c"`.
|
|
161
|
+
|
|
162
|
+
### Other utils
|
|
163
|
+
|
|
164
|
+
For now, there is only one:
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
assertFiniteNumber(123);
|
|
168
|
+
// Ensures passed value is typeof number and finite
|
|
169
|
+
// DO NOT USE FOR INPUT VALIDATION OF USER PROVIDED VALUES!
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Useful for string-to-number conversions when you expect valid number strings. Prevents accidental usage of strings or invalid numbers due to JavaScript's loose equality (`123 == "123"`).
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
type PrimitiveTypeStrings = "string" | "number" | "boolean" | "bigint" | "undefined" | "function" | "object" | "symbol";
|
|
2
|
+
type PrimitiveTypes = {
|
|
3
|
+
"string": string;
|
|
4
|
+
"number": number;
|
|
5
|
+
"boolean": boolean;
|
|
6
|
+
"bigint": bigint;
|
|
7
|
+
"undefined": undefined;
|
|
8
|
+
"function": Function;
|
|
9
|
+
"object": object;
|
|
10
|
+
"symbol": symbol;
|
|
11
|
+
};
|
|
12
|
+
type NullableKeys<T> = {
|
|
13
|
+
[K in keyof T]-?: undefined extends T[K] ? K : null extends T[K] ? K : never;
|
|
14
|
+
}[keyof T];
|
|
15
|
+
type PropsNonNullable<T, N extends NullableKeys<T>> = T & {
|
|
16
|
+
[K in N]-?: NonNullable<T[K]>;
|
|
17
|
+
};
|
|
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;
|
|
21
|
+
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 assertInstanceOf<T>(obj: unknown, constructable: Constructor<T>): asserts obj is T;
|
|
32
|
+
export declare function assertUnreachable(obj: never, msg?: string): asserts obj is never;
|
|
33
|
+
export declare function assertPropsNonNullable<T, N extends NullableKeys<T>>(obj: T, props: N[]): asserts obj is PropsNonNullable<T, N>;
|
|
34
|
+
export declare function assertNonNullable<T>(obj: T): asserts obj is NonNullable<T>;
|
|
35
|
+
export declare function assertFiniteNumber(obj: unknown): asserts obj is number;
|
|
36
|
+
export {};
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
class AssertionError extends Error {
|
|
2
|
+
constructor(msg) {
|
|
3
|
+
super(`Assertion failed: ${msg}`);
|
|
4
|
+
this.name = AssertionError.name;
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
export function assert(hasToBeTrue, msg = "No specific message provided.") {
|
|
8
|
+
if (!import.meta.env.DEV)
|
|
9
|
+
return;
|
|
10
|
+
if (!hasToBeTrue)
|
|
11
|
+
throw new AssertionError(msg);
|
|
12
|
+
}
|
|
13
|
+
export function assertType(obj, expectedType) {
|
|
14
|
+
if (!import.meta.env.DEV)
|
|
15
|
+
return;
|
|
16
|
+
if (typeof obj === expectedType)
|
|
17
|
+
return;
|
|
18
|
+
const reducedExpectedType = expectedType;
|
|
19
|
+
if (obj === reducedExpectedType)
|
|
20
|
+
return;
|
|
21
|
+
const remainingOption = expectedType;
|
|
22
|
+
if (obj instanceof remainingOption)
|
|
23
|
+
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}`);
|
|
25
|
+
}
|
|
26
|
+
export function assertTypeofString(obj) {
|
|
27
|
+
if (!import.meta.env.DEV)
|
|
28
|
+
return;
|
|
29
|
+
if (typeof obj !== "string")
|
|
30
|
+
throw new AssertionError(`Provided object was not of type string. Was: ${typeof obj}`);
|
|
31
|
+
}
|
|
32
|
+
export function assertTypeofNumber(obj) {
|
|
33
|
+
if (!import.meta.env.DEV)
|
|
34
|
+
return;
|
|
35
|
+
if (typeof obj !== "number")
|
|
36
|
+
throw new AssertionError(`Provided object was not of type number. Was: ${typeof obj}`);
|
|
37
|
+
}
|
|
38
|
+
export function assertTypeofBoolean(obj) {
|
|
39
|
+
if (!import.meta.env.DEV)
|
|
40
|
+
return;
|
|
41
|
+
if (typeof obj !== "boolean")
|
|
42
|
+
throw new AssertionError(`Provided object was not of type boolean. Was: ${typeof obj}`);
|
|
43
|
+
}
|
|
44
|
+
export function assertTypeofBigint(obj) {
|
|
45
|
+
if (!import.meta.env.DEV)
|
|
46
|
+
return;
|
|
47
|
+
if (typeof obj !== "bigint")
|
|
48
|
+
throw new AssertionError(`Provided object was not of type bigint. Was: ${typeof obj}`);
|
|
49
|
+
}
|
|
50
|
+
export function assertTypeofUndefined(obj) {
|
|
51
|
+
if (!import.meta.env.DEV)
|
|
52
|
+
return;
|
|
53
|
+
if (typeof obj !== "undefined")
|
|
54
|
+
throw new AssertionError(`Provided object was not of type undefined. Was: ${typeof obj}`);
|
|
55
|
+
}
|
|
56
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
57
|
+
export function assertTypeofFunction(obj) {
|
|
58
|
+
if (!import.meta.env.DEV)
|
|
59
|
+
return;
|
|
60
|
+
if (typeof obj !== "function")
|
|
61
|
+
throw new AssertionError(`Provided object was not of type function. Was: ${typeof obj}`);
|
|
62
|
+
}
|
|
63
|
+
export function assertTypeofObject(obj) {
|
|
64
|
+
if (!import.meta.env.DEV)
|
|
65
|
+
return;
|
|
66
|
+
if (typeof obj !== "object")
|
|
67
|
+
throw new AssertionError(`Provided object was not of type object. Was: ${typeof obj}`);
|
|
68
|
+
}
|
|
69
|
+
export function assertTypeofSymbol(obj) {
|
|
70
|
+
if (!import.meta.env.DEV)
|
|
71
|
+
return;
|
|
72
|
+
if (typeof obj !== "symbol")
|
|
73
|
+
throw new AssertionError(`Provided object was not of type symbol. Was: ${typeof obj}`);
|
|
74
|
+
}
|
|
75
|
+
export function assertInstanceOf(obj, constructable) {
|
|
76
|
+
if (!import.meta.env.DEV)
|
|
77
|
+
return;
|
|
78
|
+
if (!(obj instanceof constructable))
|
|
79
|
+
throw new AssertionError(`Provided object was not of type ${constructable.name} but was type: ${(obj === null) ? "null" : obj?.constructor?.name ?? typeof obj}, value: ${obj}`);
|
|
80
|
+
}
|
|
81
|
+
export function assertUnreachable(obj, msg = "Unreachable code of type never was reached. TypeScript types are inaccurate somewhere.") {
|
|
82
|
+
if (!import.meta.env.DEV)
|
|
83
|
+
return;
|
|
84
|
+
throw new AssertionError(msg);
|
|
85
|
+
}
|
|
86
|
+
export function assertPropsNonNullable(obj, props) {
|
|
87
|
+
if (!import.meta.env.DEV)
|
|
88
|
+
return;
|
|
89
|
+
for (const prop of props) {
|
|
90
|
+
if (obj[prop] === null || obj[prop] === undefined)
|
|
91
|
+
throw new AssertionError(`Provided object prop ${String(prop)} should've been non-null but was: ${obj[prop]}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
export function assertNonNullable(obj) {
|
|
95
|
+
if (!import.meta.env.DEV)
|
|
96
|
+
return;
|
|
97
|
+
if (obj === undefined || obj === null)
|
|
98
|
+
throw new AssertionError(`Provided object should've been non-null but was: ${obj}`);
|
|
99
|
+
}
|
|
100
|
+
export function assertFiniteNumber(obj) {
|
|
101
|
+
if (!import.meta.env.DEV)
|
|
102
|
+
return;
|
|
103
|
+
if (typeof obj !== "number")
|
|
104
|
+
throw new AssertionError(`Provided object was not of type number. Was: ${typeof obj}`);
|
|
105
|
+
if (!isFinite(obj))
|
|
106
|
+
throw new AssertionError(`Provided number was not finite. Was: ${obj}`);
|
|
107
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "assertie",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Debug assertions for TypeScript, auto tree-shaken by vite for production.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"TypeScript",
|
|
7
|
+
"vite",
|
|
8
|
+
"debug",
|
|
9
|
+
"assert"
|
|
10
|
+
],
|
|
11
|
+
"homepage": "https://github.com/OfficialHalfwayDead/assertie#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/OfficialHalfwayDead/assertie/issues"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/OfficialHalfwayDead/assertie.git"
|
|
18
|
+
},
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"author": "halfie",
|
|
21
|
+
"type": "module",
|
|
22
|
+
"types": "./lib/index.d.ts",
|
|
23
|
+
"main": "./lib/index.js",
|
|
24
|
+
"exports": {
|
|
25
|
+
"import": {
|
|
26
|
+
"types": "./lib/index.d.ts",
|
|
27
|
+
"default": "./lib/index.js"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"sideEffects": false,
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"typescript": ">=3.7.0",
|
|
33
|
+
"vite": ">=2.0.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"typescript": "^5.7.3",
|
|
37
|
+
"vite": "^6.1.0"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsc"
|
|
41
|
+
}
|
|
42
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
type PrimitiveTypeStrings = "string" | "number" | "boolean" | "bigint" | "undefined" | "function" | "object" | "symbol";
|
|
2
|
+
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
|
|
16
|
+
}[keyof T];
|
|
17
|
+
|
|
18
|
+
type PropsNonNullable<T, N extends NullableKeys<T>> = T & { [K in N]-?: NonNullable<T[K]> };
|
|
19
|
+
|
|
20
|
+
class AssertionError extends Error {
|
|
21
|
+
constructor(msg: string) {
|
|
22
|
+
super(`Assertion failed: ${msg}`);
|
|
23
|
+
this.name = AssertionError.name;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
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;
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
export function assert(hasToBeTrue: boolean, msg: string = "No specific message provided."): asserts hasToBeTrue is true {
|
|
35
|
+
if (!import.meta.env.DEV) return;
|
|
36
|
+
if (!hasToBeTrue) throw new AssertionError(msg);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function assertType<T extends AllJSTypes>(obj: unknown, expectedType: T): asserts obj is ResolveAnyJSType<T> {
|
|
40
|
+
if (!import.meta.env.DEV) return;
|
|
41
|
+
|
|
42
|
+
if (typeof obj === expectedType) return;
|
|
43
|
+
const reducedExpectedType = expectedType as Exclude<typeof expectedType, PrimitiveTypeStrings>;
|
|
44
|
+
|
|
45
|
+
if (obj === reducedExpectedType) return;
|
|
46
|
+
const remainingOption = expectedType as Exclude<typeof reducedExpectedType, null | undefined>;
|
|
47
|
+
|
|
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}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function assertTypeofString(obj: unknown): asserts obj is string {
|
|
54
|
+
if (!import.meta.env.DEV) return;
|
|
55
|
+
if (typeof obj !== "string") throw new AssertionError(`Provided object was not of type string. Was: ${typeof obj}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function assertTypeofNumber(obj: unknown): asserts obj is number {
|
|
59
|
+
if (!import.meta.env.DEV) return;
|
|
60
|
+
if (typeof obj !== "number") throw new AssertionError(`Provided object was not of type number. Was: ${typeof obj}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function assertTypeofBoolean(obj: unknown): asserts obj is boolean {
|
|
64
|
+
if (!import.meta.env.DEV) return;
|
|
65
|
+
if (typeof obj !== "boolean") throw new AssertionError(`Provided object was not of type boolean. Was: ${typeof obj}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function assertTypeofBigint(obj: unknown): asserts obj is bigint {
|
|
69
|
+
if (!import.meta.env.DEV) return;
|
|
70
|
+
if (typeof obj !== "bigint") throw new AssertionError(`Provided object was not of type bigint. Was: ${typeof obj}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function assertTypeofUndefined(obj: unknown): asserts obj is undefined {
|
|
74
|
+
if (!import.meta.env.DEV) return;
|
|
75
|
+
if (typeof obj !== "undefined") throw new AssertionError(`Provided object was not of type undefined. Was: ${typeof obj}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
79
|
+
export function assertTypeofFunction(obj: unknown): asserts obj is Function {
|
|
80
|
+
if (!import.meta.env.DEV) return;
|
|
81
|
+
if (typeof obj !== "function") throw new AssertionError(`Provided object was not of type function. Was: ${typeof obj}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function assertTypeofObject(obj: unknown): asserts obj is object {
|
|
85
|
+
if (!import.meta.env.DEV) return;
|
|
86
|
+
if (typeof obj !== "object") throw new AssertionError(`Provided object was not of type object. Was: ${typeof obj}`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function assertTypeofSymbol(obj: unknown): asserts obj is symbol {
|
|
90
|
+
if (!import.meta.env.DEV) return;
|
|
91
|
+
if (typeof obj !== "symbol") throw new AssertionError(`Provided object was not of type symbol. Was: ${typeof obj}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function assertInstanceOf<T>(obj: unknown, constructable: Constructor<T>): asserts obj is T {
|
|
95
|
+
if (!import.meta.env.DEV) return;
|
|
96
|
+
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}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function assertUnreachable(obj: never, msg: string = "Unreachable code of type never was reached. TypeScript types are inaccurate somewhere."): asserts obj is never {
|
|
100
|
+
if (!import.meta.env.DEV) return;
|
|
101
|
+
throw new AssertionError(msg);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function assertPropsNonNullable<T, N extends NullableKeys<T>>(obj: T, props: N[]): asserts obj is PropsNonNullable<T, N> {
|
|
105
|
+
if (!import.meta.env.DEV) return;
|
|
106
|
+
for (const prop of props) {
|
|
107
|
+
if (obj[prop] === null || obj[prop] === undefined)
|
|
108
|
+
throw new AssertionError(`Provided object prop ${String(prop)} should've been non-null but was: ${obj[prop]}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function assertNonNullable<T>(obj: T): asserts obj is NonNullable<T> {
|
|
113
|
+
if (!import.meta.env.DEV) return;
|
|
114
|
+
if (obj === undefined || obj === null) throw new AssertionError(`Provided object should've been non-null but was: ${obj}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function assertFiniteNumber(obj: unknown): asserts obj is number {
|
|
118
|
+
if (!import.meta.env.DEV) return;
|
|
119
|
+
if (typeof obj !== "number") throw new AssertionError(`Provided object was not of type number. Was: ${typeof obj}`);
|
|
120
|
+
if (!isFinite(obj)) throw new AssertionError(`Provided number was not finite. Was: ${obj}`);
|
|
121
|
+
}
|