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 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
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "types": ["vite/client"],
6
+ "outDir": "lib",
7
+ "declaration": true,
8
+ "declarationDir": "lib",
9
+ },
10
+ "include": ["src/index.ts"],
11
+ }