diff --git a/.gitignore b/.gitignore
index 9f563c04..d0a3f627 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,3 +24,4 @@ test/auth/*
 
 /chai.js
 /chai.cjs
+/lib
diff --git a/eslint.config.js b/eslint.config.js
index 22339fce..10fc53fc 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -1,4 +1,5 @@
 import jsdoc from "eslint-plugin-jsdoc";
+import {configs as tseslintConfigs} from 'typescript-eslint';
 import eslintjs from "@eslint/js";
 
 const {configs: eslintConfigs} = eslintjs;
@@ -6,6 +7,7 @@ const {configs: eslintConfigs} = eslintjs;
 export default [
   jsdoc.configs["flat/recommended"],
   eslintConfigs["recommended"],
+  ...tseslintConfigs.recommended,
   {
     languageOptions: {
       // if we ever use more globals than this, pull in the `globals` package
@@ -17,10 +19,16 @@ export default [
       "jsdoc/require-param-description": "off",
       "jsdoc/require-returns-description": "off",
       "jsdoc/tag-lines": ["error", "any", { startLines: 1 }],
-      "no-unused-vars": ["error", {
-        argsIgnorePattern: "^_",
-        caughtErrorsIgnorePattern: "^_"
-      }]
+
+      // temporary until we do a cleanup
+      "no-var": "off",
+      "@typescript-eslint/no-unsafe-function-type": "off",
+      "prefer-rest-params": "off",
+      "@typescript-eslint/no-unused-expressions": "off",
+      "@typescript-eslint/no-unused-vars": ["error", {
+        "argsIgnorePattern": "^_",
+        "caughtErrorsIgnorePattern": "^_"
+      }],
     },
   },
 ];
diff --git a/index.js b/index.js
deleted file mode 100644
index ea487486..00000000
--- a/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export * from './lib/chai.js';
diff --git a/lib/chai/assertion.js b/lib/chai/assertion.js
deleted file mode 100644
index 2969d3e9..00000000
--- a/lib/chai/assertion.js
+++ /dev/null
@@ -1,180 +0,0 @@
-/*!
- * chai
- * http://chaijs.com
- * Copyright(c) 2011-2014 Jake Luer <jake@alogicalparadox.com>
- * MIT Licensed
- */
-
-import {config} from './config.js';
-import {AssertionError} from 'assertion-error';
-import * as util from './utils/index.js';
-
-/**
- * Assertion Constructor
- *
- * Creates object for chaining.
- *
- * `Assertion` objects contain metadata in the form of flags. Three flags can
- * be assigned during instantiation by passing arguments to this constructor:
- *
- * - `object`: This flag contains the target of the assertion. For example, in
- * the assertion `expect(numKittens).to.equal(7);`, the `object` flag will
- * contain `numKittens` so that the `equal` assertion can reference it when
- * needed.
- *
- * - `message`: This flag contains an optional custom error message to be
- * prepended to the error message that's generated by the assertion when it
- * fails.
- *
- * - `ssfi`: This flag stands for "start stack function indicator". It
- * contains a function reference that serves as the starting point for
- * removing frames from the stack trace of the error that's created by the
- * assertion when it fails. The goal is to provide a cleaner stack trace to
- * end users by removing Chai's internal functions. Note that it only works
- * in environments that support `Error.captureStackTrace`, and only when
- * `Chai.config.includeStack` hasn't been set to `false`.
- *
- * - `lockSsfi`: This flag controls whether or not the given `ssfi` flag
- * should retain its current value, even as assertions are chained off of
- * this object. This is usually set to `true` when creating a new assertion
- * from within another assertion. It's also temporarily set to `true` before
- * an overwritten assertion gets called by the overwriting assertion.
- *
- * - `eql`: This flag contains the deepEqual function to be used by the assertion.
- *
- * @param {unknown} obj target of the assertion
- * @param {string} msg (optional) custom error message
- * @param {Function} ssfi (optional) starting point for removing stack frames
- * @param {boolean} lockSsfi (optional) whether or not the ssfi flag is locked
- * @returns {unknown}
- * @private
- */
-export function Assertion(obj, msg, ssfi, lockSsfi) {
-  util.flag(this, 'ssfi', ssfi || Assertion);
-  util.flag(this, 'lockSsfi', lockSsfi);
-  util.flag(this, 'object', obj);
-  util.flag(this, 'message', msg);
-  util.flag(this, 'eql', config.deepEqual || util.eql);
-
-  return util.proxify(this);
-}
-
-Object.defineProperty(Assertion, 'includeStack', {
-  get: function () {
-    console.warn(
-      'Assertion.includeStack is deprecated, use chai.config.includeStack instead.'
-    );
-    return config.includeStack;
-  },
-  set: function (value) {
-    console.warn(
-      'Assertion.includeStack is deprecated, use chai.config.includeStack instead.'
-    );
-    config.includeStack = value;
-  }
-});
-
-Object.defineProperty(Assertion, 'showDiff', {
-  get: function () {
-    console.warn(
-      'Assertion.showDiff is deprecated, use chai.config.showDiff instead.'
-    );
-    return config.showDiff;
-  },
-  set: function (value) {
-    console.warn(
-      'Assertion.showDiff is deprecated, use chai.config.showDiff instead.'
-    );
-    config.showDiff = value;
-  }
-});
-
-Assertion.addProperty = function (name, fn) {
-  util.addProperty(this.prototype, name, fn);
-};
-
-Assertion.addMethod = function (name, fn) {
-  util.addMethod(this.prototype, name, fn);
-};
-
-Assertion.addChainableMethod = function (name, fn, chainingBehavior) {
-  util.addChainableMethod(this.prototype, name, fn, chainingBehavior);
-};
-
-Assertion.overwriteProperty = function (name, fn) {
-  util.overwriteProperty(this.prototype, name, fn);
-};
-
-Assertion.overwriteMethod = function (name, fn) {
-  util.overwriteMethod(this.prototype, name, fn);
-};
-
-Assertion.overwriteChainableMethod = function (name, fn, chainingBehavior) {
-  util.overwriteChainableMethod(this.prototype, name, fn, chainingBehavior);
-};
-
-/**
- * ### .assert(expression, message, negateMessage, expected, actual, showDiff)
- *
- * Executes an expression and check expectations. Throws AssertionError for reporting if test doesn't pass.
- *
- * @name assert
- * @param {unknown} expression to be tested
- * @param {string | Function} message or function that returns message to display if expression fails
- * @param {string | Function} negatedMessage or function that returns negatedMessage to display if negated expression fails
- * @param {unknown} expected value (remember to check for negation)
- * @param {unknown} actual (optional) will default to `this.obj`
- * @param {boolean} showDiff (optional) when set to `true`, assert will display a diff in addition to the message if expression fails
- * @private
- */
-
-Assertion.prototype.assert = function (
-  expr,
-  msg,
-  negateMsg,
-  expected,
-  _actual,
-  showDiff
-) {
-  var ok = util.test(this, arguments);
-  if (false !== showDiff) showDiff = true;
-  if (undefined === expected && undefined === _actual) showDiff = false;
-  if (true !== config.showDiff) showDiff = false;
-
-  if (!ok) {
-    msg = util.getMessage(this, arguments);
-    var actual = util.getActual(this, arguments);
-    var assertionErrorObjectProperties = {
-      actual: actual,
-      expected: expected,
-      showDiff: showDiff
-    };
-
-    var operator = util.getOperator(this, arguments);
-    if (operator) {
-      assertionErrorObjectProperties.operator = operator;
-    }
-
-    throw new AssertionError(
-      msg,
-      assertionErrorObjectProperties,
-      config.includeStack ? this.assert : util.flag(this, 'ssfi')
-    );
-  }
-};
-
-/**
- * ### ._obj
- *
- * Quick reference to stored `actual` value for plugin developers.
- *
- * @private
- */
-Object.defineProperty(Assertion.prototype, '_obj', {
-  get: function () {
-    return util.flag(this, 'object');
-  },
-  set: function (val) {
-    util.flag(this, 'object', val);
-  }
-});
diff --git a/lib/chai/utils/flag.js b/lib/chai/utils/flag.js
deleted file mode 100644
index 89434b71..00000000
--- a/lib/chai/utils/flag.js
+++ /dev/null
@@ -1,33 +0,0 @@
-/*!
- * Chai - flag utility
- * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
- * MIT Licensed
- */
-
-/**
- * ### .flag(object, key, [value])
- *
- * Get or set a flag value on an object. If a
- * value is provided it will be set, else it will
- * return the currently set value or `undefined` if
- * the value is not set.
- *
- *     utils.flag(this, 'foo', 'bar'); // setter
- *     utils.flag(this, 'foo'); // getter, returns `bar`
- *
- * @param {object} obj object constructed Assertion
- * @param {string} key
- * @param {unknown} value (optional)
- * @namespace Utils
- * @name flag
- * @returns {unknown | undefined}
- * @private
- */
-export function flag(obj, key, value) {
-  var flags = obj.__flags || (obj.__flags = Object.create(null));
-  if (arguments.length === 3) {
-    flags[key] = value;
-  } else {
-    return flags[key];
-  }
-}
diff --git a/package-lock.json b/package-lock.json
index f44d3140..71f58804 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,9 @@
       "version": "0.0.0-development",
       "license": "MIT",
       "dependencies": {
+        "@types/check-error": "^1.0.3",
+        "@types/deep-eql": "^4.0.2",
+        "@types/pathval": "^1.1.2",
         "assertion-error": "^2.0.1",
         "check-error": "^2.1.1",
         "deep-eql": "^5.0.1",
@@ -25,7 +28,9 @@
         "eslint": "^8.56.0",
         "eslint-plugin-jsdoc": "^48.0.4",
         "mocha": "^10.2.0",
-        "prettier": "^3.4.2"
+        "prettier": "^3.4.2",
+        "typescript": "^5.3.3",
+        "typescript-eslint": "^8.20.0"
       },
       "engines": {
         "node": ">=12"
@@ -550,9 +555,9 @@
       }
     },
     "node_modules/@eslint/js": {
-      "version": "9.17.0",
-      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.17.0.tgz",
-      "integrity": "sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==",
+      "version": "9.18.0",
+      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.18.0.tgz",
+      "integrity": "sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA==",
       "dev": true,
       "license": "MIT",
       "engines": {
@@ -560,12 +565,14 @@
       }
     },
     "node_modules/@humanwhocodes/config-array": {
-      "version": "0.11.14",
-      "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
-      "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
+      "version": "0.13.0",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
+      "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==",
+      "deprecated": "Use @eslint/config-array instead",
       "dev": true,
+      "license": "Apache-2.0",
       "dependencies": {
-        "@humanwhocodes/object-schema": "^2.0.2",
+        "@humanwhocodes/object-schema": "^2.0.3",
         "debug": "^4.3.1",
         "minimatch": "^3.0.5"
       },
@@ -578,6 +585,7 @@
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
       "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
         "balanced-match": "^1.0.0",
         "concat-map": "0.0.1"
@@ -588,6 +596,7 @@
       "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
       "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
       "dev": true,
+      "license": "ISC",
       "dependencies": {
         "brace-expansion": "^1.1.7"
       },
@@ -609,10 +618,12 @@
       }
     },
     "node_modules/@humanwhocodes/object-schema": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz",
-      "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==",
-      "dev": true
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
+      "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
+      "deprecated": "Use @eslint/object-schema instead",
+      "dev": true,
+      "license": "BSD-3-Clause"
     },
     "node_modules/@jridgewell/resolve-uri": {
       "version": "3.1.1",
@@ -1132,6 +1143,12 @@
         "@types/node": "*"
       }
     },
+    "node_modules/@types/check-error": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/@types/check-error/-/check-error-1.0.3.tgz",
+      "integrity": "sha512-xq2qg/lG9b/73i4id0YJPILVK/5W2yZV/LkMzY5BKQ27n0GJqY6HFtaLyK2ff0qnxa03W7AerOhhnAM68DUAZw==",
+      "license": "MIT"
+    },
     "node_modules/@types/co-body": {
       "version": "6.1.3",
       "resolved": "https://registry.npmjs.org/@types/co-body/-/co-body-6.1.3.tgz",
@@ -1187,6 +1204,12 @@
       "integrity": "sha512-jBqiORIzKDOToaF63Fm//haOCHuwQuLa2202RK4MozpA6lh93eCBc+/8+wZn5OzjJt3ySdc+74SXWXB55Ewtyw==",
       "dev": true
     },
+    "node_modules/@types/deep-eql": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
+      "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
+      "license": "MIT"
+    },
     "node_modules/@types/estree": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
@@ -1305,6 +1328,12 @@
       "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==",
       "dev": true
     },
+    "node_modules/@types/pathval": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@types/pathval/-/pathval-1.1.2.tgz",
+      "integrity": "sha512-Lecyi0eLLv4ERUAXtmqvYfoy+VxtLwYqitXfAwCJk8IY0t0OWQu2ptJNEFTr6v8qUFhq41on7UFfdvSV+wB8RQ==",
+      "license": "MIT"
+    },
     "node_modules/@types/qs": {
       "version": "6.9.11",
       "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.11.tgz",
@@ -1364,6 +1393,215 @@
         "@types/node": "*"
       }
     },
+    "node_modules/@typescript-eslint/eslint-plugin": {
+      "version": "8.20.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.20.0.tgz",
+      "integrity": "sha512-naduuphVw5StFfqp4Gq4WhIBE2gN1GEmMUExpJYknZJdRnc+2gDzB8Z3+5+/Kv33hPQRDGzQO/0opHE72lZZ6A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@eslint-community/regexpp": "^4.10.0",
+        "@typescript-eslint/scope-manager": "8.20.0",
+        "@typescript-eslint/type-utils": "8.20.0",
+        "@typescript-eslint/utils": "8.20.0",
+        "@typescript-eslint/visitor-keys": "8.20.0",
+        "graphemer": "^1.4.0",
+        "ignore": "^5.3.1",
+        "natural-compare": "^1.4.0",
+        "ts-api-utils": "^2.0.0"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0",
+        "eslint": "^8.57.0 || ^9.0.0",
+        "typescript": ">=4.8.4 <5.8.0"
+      }
+    },
+    "node_modules/@typescript-eslint/parser": {
+      "version": "8.20.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.20.0.tgz",
+      "integrity": "sha512-gKXG7A5HMyjDIedBi6bUrDcun8GIjnI8qOwVLiY3rx6T/sHP/19XLJOnIq/FgQvWLHja5JN/LSE7eklNBr612g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@typescript-eslint/scope-manager": "8.20.0",
+        "@typescript-eslint/types": "8.20.0",
+        "@typescript-eslint/typescript-estree": "8.20.0",
+        "@typescript-eslint/visitor-keys": "8.20.0",
+        "debug": "^4.3.4"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "eslint": "^8.57.0 || ^9.0.0",
+        "typescript": ">=4.8.4 <5.8.0"
+      }
+    },
+    "node_modules/@typescript-eslint/scope-manager": {
+      "version": "8.20.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.20.0.tgz",
+      "integrity": "sha512-J7+VkpeGzhOt3FeG1+SzhiMj9NzGD/M6KoGn9f4dbz3YzK9hvbhVTmLj/HiTp9DazIzJ8B4XcM80LrR9Dm1rJw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@typescript-eslint/types": "8.20.0",
+        "@typescript-eslint/visitor-keys": "8.20.0"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
+    "node_modules/@typescript-eslint/type-utils": {
+      "version": "8.20.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.20.0.tgz",
+      "integrity": "sha512-bPC+j71GGvA7rVNAHAtOjbVXbLN5PkwqMvy1cwGeaxUoRQXVuKCebRoLzm+IPW/NtFFpstn1ummSIasD5t60GA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@typescript-eslint/typescript-estree": "8.20.0",
+        "@typescript-eslint/utils": "8.20.0",
+        "debug": "^4.3.4",
+        "ts-api-utils": "^2.0.0"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "eslint": "^8.57.0 || ^9.0.0",
+        "typescript": ">=4.8.4 <5.8.0"
+      }
+    },
+    "node_modules/@typescript-eslint/types": {
+      "version": "8.20.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.20.0.tgz",
+      "integrity": "sha512-cqaMiY72CkP+2xZRrFt3ExRBu0WmVitN/rYPZErA80mHjHx/Svgp8yfbzkJmDoQ/whcytOPO9/IZXnOc+wigRA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
+    "node_modules/@typescript-eslint/typescript-estree": {
+      "version": "8.20.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.20.0.tgz",
+      "integrity": "sha512-Y7ncuy78bJqHI35NwzWol8E0X7XkRVS4K4P4TCyzWkOJih5NDvtoRDW4Ba9YJJoB2igm9yXDdYI/+fkiiAxPzA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@typescript-eslint/types": "8.20.0",
+        "@typescript-eslint/visitor-keys": "8.20.0",
+        "debug": "^4.3.4",
+        "fast-glob": "^3.3.2",
+        "is-glob": "^4.0.3",
+        "minimatch": "^9.0.4",
+        "semver": "^7.6.0",
+        "ts-api-utils": "^2.0.0"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "typescript": ">=4.8.4 <5.8.0"
+      }
+    },
+    "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
+      "version": "9.0.5",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+      "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "brace-expansion": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=16 || 14 >=14.17"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/@typescript-eslint/utils": {
+      "version": "8.20.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.20.0.tgz",
+      "integrity": "sha512-dq70RUw6UK9ei7vxc4KQtBRk7qkHZv447OUZ6RPQMQl71I3NZxQJX/f32Smr+iqWrB02pHKn2yAdHBb0KNrRMA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@eslint-community/eslint-utils": "^4.4.0",
+        "@typescript-eslint/scope-manager": "8.20.0",
+        "@typescript-eslint/types": "8.20.0",
+        "@typescript-eslint/typescript-estree": "8.20.0"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "eslint": "^8.57.0 || ^9.0.0",
+        "typescript": ">=4.8.4 <5.8.0"
+      }
+    },
+    "node_modules/@typescript-eslint/visitor-keys": {
+      "version": "8.20.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.20.0.tgz",
+      "integrity": "sha512-v/BpkeeYAsPkKCkR8BDwcno0llhzWVqPOamQrAEMdpZav2Y9OVjd9dwJyBLJWwf335B5DmlifECIkZRJCaGaHA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@typescript-eslint/types": "8.20.0",
+        "eslint-visitor-keys": "^4.2.0"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
+    "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
+      "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
     "node_modules/@ungap/structured-clone": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
@@ -2884,16 +3122,18 @@
       }
     },
     "node_modules/eslint": {
-      "version": "8.56.0",
-      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz",
-      "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==",
+      "version": "8.57.1",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
+      "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
+      "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.2.0",
         "@eslint-community/regexpp": "^4.6.1",
         "@eslint/eslintrc": "^2.1.4",
-        "@eslint/js": "8.56.0",
-        "@humanwhocodes/config-array": "^0.11.13",
+        "@eslint/js": "8.57.1",
+        "@humanwhocodes/config-array": "^0.13.0",
         "@humanwhocodes/module-importer": "^1.0.1",
         "@nodelib/fs.walk": "^1.2.8",
         "@ungap/structured-clone": "^1.2.0",
@@ -3002,9 +3242,9 @@
       }
     },
     "node_modules/eslint/node_modules/@eslint/js": {
-      "version": "8.56.0",
-      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz",
-      "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==",
+      "version": "8.57.1",
+      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz",
+      "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==",
       "dev": true,
       "license": "MIT",
       "engines": {
@@ -3822,10 +4062,11 @@
       "license": "BSD-3-Clause"
     },
     "node_modules/ignore": {
-      "version": "5.3.0",
-      "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz",
-      "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==",
+      "version": "5.3.2",
+      "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+      "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
       "dev": true,
+      "license": "MIT",
       "engines": {
         "node": ">= 4"
       }
@@ -6273,6 +6514,19 @@
         "node": ">=12"
       }
     },
+    "node_modules/ts-api-utils": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz",
+      "integrity": "sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=18.12"
+      },
+      "peerDependencies": {
+        "typescript": ">=4.8.4"
+      }
+    },
     "node_modules/tslib": {
       "version": "2.7.0",
       "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
@@ -6326,6 +6580,43 @@
         "node": ">= 0.6"
       }
     },
+    "node_modules/typescript": {
+      "version": "5.7.3",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
+      "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "bin": {
+        "tsc": "bin/tsc",
+        "tsserver": "bin/tsserver"
+      },
+      "engines": {
+        "node": ">=14.17"
+      }
+    },
+    "node_modules/typescript-eslint": {
+      "version": "8.20.0",
+      "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.20.0.tgz",
+      "integrity": "sha512-Kxz2QRFsgbWj6Xcftlw3Dd154b3cEPFqQC+qMZrMypSijPd4UanKKvoKDrJ4o8AIfZFKAF+7sMaEIR8mTElozA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@typescript-eslint/eslint-plugin": "8.20.0",
+        "@typescript-eslint/parser": "8.20.0",
+        "@typescript-eslint/utils": "8.20.0"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "eslint": "^8.57.0 || ^9.0.0",
+        "typescript": ">=4.8.4 <5.8.0"
+      }
+    },
     "node_modules/typical": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz",
diff --git a/package.json b/package.json
index 4af256d8..bb371dd3 100644
--- a/package.json
+++ b/package.json
@@ -3,6 +3,11 @@
   "name": "chai",
   "type": "module",
   "description": "BDD/TDD assertion library for node.js and the browser. Test framework agnostic.",
+  "files": [
+    "lib",
+    "/chai.js",
+    "/register-*"
+  ],
   "keywords": [
     "test",
     "assertion",
@@ -30,21 +35,25 @@
   "scripts": {
     "prebuild": "npm run clean",
     "build": "npm run build:esm",
-    "build:esm": "esbuild --bundle --format=esm --keep-names --outfile=chai.js index.js",
-    "format": "prettier --write lib",
+    "build:esm": "esbuild --bundle --format=esm --keep-names --outfile=chai.js src/chai.ts",
+    "format": "prettier --write src",
     "pretest": "npm run lint && npm run build",
     "test": "npm run test-node && npm run test-chrome",
     "test-node": "mocha --require ./test/bootstrap/index.js --reporter dot test/*.js",
     "test-chrome": "web-test-runner --playwright",
-    "lint": "npm run lint:js && npm run lint:format",
-    "lint:js": "eslint lib/",
-    "lint:format": "prettier --check lib",
-    "clean": "rm -f chai.js coverage"
+    "lint": "npm run lint:js && npm run lint:format && npm run lint:types",
+    "lint:js": "eslint src",
+    "lint:format": "prettier --check src",
+    "lint:types": "tsc --noEmit",
+    "clean": "rm -rf chai.js coverage lib"
   },
   "engines": {
     "node": ">=12"
   },
   "dependencies": {
+    "@types/check-error": "^1.0.3",
+    "@types/deep-eql": "^4.0.2",
+    "@types/pathval": "^1.1.2",
     "assertion-error": "^2.0.1",
     "check-error": "^2.1.1",
     "deep-eql": "^5.0.1",
@@ -61,6 +70,8 @@
     "eslint": "^8.56.0",
     "eslint-plugin-jsdoc": "^48.0.4",
     "mocha": "^10.2.0",
+    "typescript": "^5.3.3",
+    "typescript-eslint": "^8.20.0",
     "prettier": "^3.4.2"
   }
 }
diff --git a/register-assert.js b/register-assert.js
index f593717e..6390068f 100644
--- a/register-assert.js
+++ b/register-assert.js
@@ -1,3 +1,3 @@
-import {assert} from './index.js';
+import {assert} from './chai.js';
 
 globalThis.assert = assert;
diff --git a/register-expect.js b/register-expect.js
index 2807b89b..90a87ff3 100644
--- a/register-expect.js
+++ b/register-expect.js
@@ -1,3 +1,3 @@
-import {expect} from './index.js';
+import {expect} from './chai.js';
 
 globalThis.expect = expect;
diff --git a/register-should.js b/register-should.js
index 1339ee4c..d4f48d67 100644
--- a/register-should.js
+++ b/register-should.js
@@ -1,3 +1,3 @@
-import {should} from './index.js';
+import {should} from './chai.js';
 
 globalThis.should = should();
diff --git a/lib/chai.js b/src/chai.ts
similarity index 88%
rename from lib/chai.js
rename to src/chai.ts
index 19186896..c6a1154d 100644
--- a/lib/chai.js
+++ b/src/chai.ts
@@ -13,7 +13,9 @@ import {Assertion} from './chai/assertion.js';
 import * as should from './chai/interface/should.js';
 import {assert} from './chai/interface/assert.js';
 
-const used = [];
+type UseFn = (exports: Record<PropertyKey, unknown>, u: typeof util) => void;
+
+const used: Array<UseFn> = [];
 
 // Assertion Error
 export {AssertionError};
@@ -27,7 +29,7 @@ export {AssertionError};
  * @returns {this} for chaining
  * @public
  */
-export function use(fn) {
+export function use(fn: UseFn): Record<string, unknown> {
   const exports = {
     use,
     AssertionError,
diff --git a/src/chai/assertion.ts b/src/chai/assertion.ts
new file mode 100644
index 00000000..4a13781f
--- /dev/null
+++ b/src/chai/assertion.ts
@@ -0,0 +1,250 @@
+/*!
+ * chai
+ * http://chaijs.com
+ * Copyright(c) 2011-2014 Jake Luer <jake@alogicalparadox.com>
+ * MIT Licensed
+ */
+
+import {config} from './config.js';
+import {AssertionError} from 'assertion-error';
+import * as util from './utils/index.js';
+import {ChainableBehavior} from './utils/chainableBehavior.js';
+
+export interface AssertionFlags<T> {
+  ssfi: Function;
+  lockSsfi?: boolean;
+  object: T;
+  message?: string | null;
+  eql: (a: unknown, b: unknown) => boolean;
+  deltaBehavior?: string;
+  nested?: boolean;
+  deep?: boolean;
+  [key: PropertyKey]: unknown;
+}
+
+type MethodNames<T> = {
+  [k in keyof T]: T[k] extends (...args: never) => void ? k : never;
+}[keyof T];
+
+const getDefaultValue = <T>(assertion: Assertion<T>): Assertion<T> => {
+  var newAssertion = Assertion.create<T>();
+  util.transferFlags(assertion, newAssertion);
+  return newAssertion;
+};
+
+/*!
+ * Assertion Constructor
+ *
+ * Creates object for chaining.
+ *
+ * `Assertion` objects contain metadata in the form of flags. Three flags can
+ * be assigned during instantiation by passing arguments to this constructor:
+ *
+ * - `object`: This flag contains the target of the assertion. For example, in
+ *   the assertion `expect(numKittens).to.equal(7);`, the `object` flag will
+ *   contain `numKittens` so that the `equal` assertion can reference it when
+ *   needed.
+ *
+ * - `message`: This flag contains an optional custom error message to be
+ *   prepended to the error message that's generated by the assertion when it
+ *   fails.
+ *
+ * - `ssfi`: This flag stands for "start stack function indicator". It
+ *   contains a function reference that serves as the starting point for
+ *   removing frames from the stack trace of the error that's created by the
+ *   assertion when it fails. The goal is to provide a cleaner stack trace to
+ *   end users by removing Chai's internal functions. Note that it only works
+ *   in environments that support `Error.captureStackTrace`, and only when
+ *   `Chai.config.includeStack` hasn't been set to `false`.
+ *
+ * - `lockSsfi`: This flag controls whether or not the given `ssfi` flag
+ *   should retain its current value, even as assertions are chained off of
+ *   this object. This is usually set to `true` when creating a new assertion
+ *   from within another assertion. It's also temporarily set to `true` before
+ *   an overwritten assertion gets called by the overwriting assertion.
+ *
+ * - `eql`: This flag contains the deepEqual function to be used by the assertion.
+ *
+ * @param {unknown} obj target of the assertion
+ * @param {string} msg (optional) custom error message
+ * @param {Function} ssfi (optional) starting point for removing stack frames
+ * @param {boolean} lockSsfi (optional) whether or not the ssfi flag is locked
+ * @private
+ */
+export class Assertion<
+  T,
+  TFlags extends AssertionFlags<T> = AssertionFlags<T>
+> {
+  declare public __flags: TFlags;
+  public __methods: Record<string, ChainableBehavior> = {};
+
+  public constructor(
+    obj?: unknown,
+    msg?: string | null,
+    ssfi?: Function,
+    lockSsfi?: boolean
+  ) {
+    util.flag(this, 'ssfi', ssfi || Assertion);
+    util.flag(this, 'lockSsfi', lockSsfi);
+    util.flag(this, 'object', obj);
+    util.flag(this, 'message', msg);
+    util.flag(this, 'eql', config.deepEqual ?? util.eql);
+  }
+
+  public static create<TObj>(
+    obj?: TObj,
+    msg?: string | null,
+    ssfi?: Function,
+    lockSsfi?: boolean
+  ): Assertion<TObj> {
+    return util.proxify(new Assertion<TObj>(obj, msg, ssfi, lockSsfi));
+  }
+
+  public static get includeStack(): boolean {
+    console.warn(
+      'Assertion.includeStack is deprecated, use chai.config.includeStack instead.'
+    );
+    return config.includeStack;
+  }
+
+  public static set includeStack(value: boolean) {
+    console.warn(
+      'Assertion.includeStack is deprecated, use chai.config.includeStack instead.'
+    );
+    config.includeStack = value;
+  }
+
+  public static get showDiff(): boolean {
+    console.warn(
+      'Assertion.showDiff is deprecated, use chai.config.showDiff instead.'
+    );
+    return config.showDiff;
+  }
+
+  public static set showDiff(value: boolean) {
+    console.warn(
+      'Assertion.showDiff is deprecated, use chai.config.showDiff instead.'
+    );
+    config.showDiff = value;
+  }
+
+  public static addProperty(
+    name: string,
+    fn?: (this: Assertion<unknown>) => unknown
+  ): void {
+    util.addProperty(this.prototype, name, fn, getDefaultValue);
+  }
+
+  public static addMethod<TKey extends PropertyKey>(
+    name: TKey,
+    fn: TKey extends MethodNames<Assertion<unknown>>
+      ? (
+          this: Assertion<unknown>,
+          ...args: Parameters<Assertion<unknown>[TKey]>
+        ) => ReturnType<Assertion<unknown>[TKey]> | void
+      : (this: Assertion<unknown>, ...args: never) => unknown
+  ): void {
+    util.addMethod(this.prototype, name, fn, getDefaultValue);
+  }
+
+  public static addChainableMethod<T extends unknown[]>(
+    name: string,
+    fn: (this: Assertion<unknown>, ...args: T) => unknown,
+    chainingBehavior?: () => void
+  ): void {
+    util.addChainableMethod(
+      this.prototype,
+      name,
+      fn,
+      chainingBehavior,
+      getDefaultValue
+    );
+  }
+
+  public static overwriteProperty(name: string, fn: Function): void {
+    util.overwriteProperty(this.prototype, name, fn, getDefaultValue);
+  }
+
+  public static overwriteMethod(name: string, fn: Function): void {
+    util.overwriteMethod(this.prototype, name, fn, getDefaultValue);
+  }
+
+  public static overwriteChainableMethod(
+    name: string,
+    fn: Function,
+    chainingBehavior: Function
+  ): void {
+    util.overwriteChainableMethod(
+      this.prototype,
+      name,
+      fn,
+      chainingBehavior,
+      getDefaultValue
+    );
+  }
+
+  /**
+   * ### .assert(expression, message, negateMessage, expected, actual, showDiff)
+   *
+   * Executes an expression and check expectations. Throws AssertionError for reporting if test doesn't pass.
+   *
+   * @name assert
+   * @param {unknown} _expr to be tested
+   * @param {string | Function} msg or function that returns message to display if expression fails
+   * @param {string | Function} _negateMsg or function that returns negatedMessage to display if negated expression fails
+   * @param {unknown} expected value (remember to check for negation)
+   * @param {unknown} _actual (optional) will default to `this.obj`
+   * @param {boolean} showDiff (optional) when set to `true`, assert will display a diff in addition to the message if expression fails
+   * @private
+   */
+  public assert(
+    _expr: unknown,
+    msg?: string | (() => string),
+    _negateMsg?: string | (() => string),
+    expected?: unknown,
+    _actual?: unknown,
+    showDiff?: boolean
+  ): void {
+    var ok = util.test(this, arguments);
+    if (false !== showDiff) showDiff = true;
+    if (undefined === expected && undefined === _actual) showDiff = false;
+    if (true !== config.showDiff) showDiff = false;
+
+    if (!ok) {
+      msg = util.getMessage(this, arguments);
+      var actual = util.getActual(this, arguments);
+      var assertionErrorObjectProperties: Record<string, unknown> = {
+        actual: actual,
+        expected: expected,
+        showDiff: showDiff,
+        operator: undefined
+      };
+
+      var operator = util.getOperator(this, arguments);
+      if (operator) {
+        assertionErrorObjectProperties.operator = operator;
+      }
+
+      throw new AssertionError(
+        msg,
+        assertionErrorObjectProperties,
+        config.includeStack ? this.assert : util.flag(this, 'ssfi')
+      );
+    }
+  }
+
+  /*!
+   * ### ._obj
+   *
+   * Quick reference to stored `actual` value for plugin developers.
+   *
+   * @private
+   */
+  public get _obj(): unknown {
+    return util.flag(this, 'object');
+  }
+
+  public set _obj(val: unknown) {
+    util.flag(this, 'object', val);
+  }
+}
diff --git a/lib/chai/config.js b/src/chai/config.ts
similarity index 100%
rename from lib/chai/config.js
rename to src/chai/config.ts
diff --git a/lib/chai/core/assertions.js b/src/chai/core/assertions.ts
similarity index 82%
rename from lib/chai/core/assertions.js
rename to src/chai/core/assertions.ts
index 80a1ce7e..74bf4a4c 100644
--- a/lib/chai/core/assertions.js
+++ b/src/chai/core/assertions.ts
@@ -5,12 +5,495 @@
  * MIT Licensed
  */
 
-import {Assertion} from '../assertion.js';
+import {Assertion, AssertionFlags} from '../assertion.js';
 import {AssertionError} from 'assertion-error';
 import * as _ from '../utils/index.js';
+import {
+  OnlyIf,
+  Constructor,
+  LengthLike,
+  CollectionLike
+} from '../utils/types.js';
 
 const {flag} = _;
 
+type ChainedMethod<
+  T,
+  TFlags extends AssertionFlags<T>,
+  TParams extends unknown[],
+  TReturn = Assertion<T>
+> = Assertion<T, TFlags> & {
+  (...args: TParams): TReturn;
+};
+
+declare module '../assertion.js' {
+  interface Assertion<T, TFlags extends AssertionFlags<T> = AssertionFlags<T>> {
+    Arguments: Assertion<T, TFlags>;
+    NaN: Assertion<T, TFlags>;
+    all: Assertion<T, TFlags>;
+    also: Assertion<T, TFlags>;
+    and: Assertion<T, TFlags>;
+    any: Assertion<T, TFlags>;
+    arguments: Assertion<T, TFlags>;
+    at: Assertion<T, TFlags>;
+    be: Assertion<T, TFlags>;
+    been: Assertion<T, TFlags>;
+    but: Assertion<T, TFlags>;
+    callable: Assertion<T, TFlags>;
+    deep: Assertion<T, TFlags & {deep: true}>;
+    does: Assertion<T, TFlags>;
+    empty: Assertion<T, TFlags>;
+    exist: Assertion<T, TFlags>;
+    exists: Assertion<T, TFlags>;
+    extensible: Assertion<T, TFlags>;
+    false: Assertion<T, TFlags>;
+    finite: Assertion<T, TFlags>;
+    frozen: Assertion<T, TFlags>;
+    has: Assertion<T, TFlags>;
+    have: Assertion<T, TFlags>;
+    is: Assertion<T, TFlags>;
+    iterable: Assertion<T, TFlags>;
+    itself: Assertion<T, TFlags>;
+    nested: Assertion<T, TFlags & {nested: true}>;
+    not: Assertion<T, TFlags>;
+    null: Assertion<T, TFlags>;
+    numeric: Assertion<T, TFlags>;
+    of: Assertion<T, TFlags>;
+    ok: Assertion<T, TFlags>;
+    ordered: Assertion<T, TFlags>;
+    own: Assertion<T, TFlags>;
+    same: Assertion<T, TFlags>;
+    sealed: Assertion<T, TFlags>;
+    still: Assertion<T, TFlags>;
+    that: Assertion<T, TFlags>;
+    to: Assertion<T, TFlags>;
+    true: Assertion<T, TFlags>;
+    undefined: Assertion<T, TFlags>;
+    which: Assertion<T, TFlags>;
+    with: Assertion<T, TFlags>;
+
+    a: ChainedMethod<T, TFlags, [type: string, msg?: string]>;
+    an: ChainedMethod<T, TFlags, [type: string, msg?: string]>;
+
+    above: OnlyIf<
+      T,
+      Date | number,
+      (val: T, msg?: string) => Assertion<T, TFlags>,
+      OnlyIf<
+        TFlags,
+        {doLength: true},
+        (val: number, msg?: string) => Assertion<T, TFlags>
+      >
+    >;
+    gt: OnlyIf<
+      T,
+      Date | number,
+      (val: T, msg?: string) => Assertion<T, TFlags>,
+      OnlyIf<
+        TFlags,
+        {doLength: true},
+        (val: number, msg?: string) => Assertion<T, TFlags>
+      >
+    >;
+    greaterThan: OnlyIf<
+      T,
+      Date | number,
+      (val: T, msg?: string) => Assertion<T, TFlags>,
+      OnlyIf<
+        TFlags,
+        {doLength: true},
+        (val: number, msg?: string) => Assertion<T, TFlags>
+      >
+    >;
+
+    approximately: OnlyIf<
+      T,
+      number,
+      (expected: number, delta: number, msg?: string) => Assertion<T, TFlags>
+    >;
+    closeTo: OnlyIf<
+      T,
+      number,
+      (expected: number, delta: number, msg?: string) => Assertion<T, TFlags>
+    >;
+
+    below: OnlyIf<
+      T,
+      Date | number,
+      (val: T, msg?: string) => Assertion<T, TFlags>,
+      OnlyIf<
+        TFlags,
+        {doLength: true},
+        (val: number, msg?: string) => Assertion<T, TFlags>
+      >
+    >;
+    lt: OnlyIf<
+      T,
+      Date | number,
+      (val: T, msg?: string) => Assertion<T, TFlags>,
+      OnlyIf<
+        TFlags,
+        {doLength: true},
+        (val: number, msg?: string) => Assertion<T, TFlags>
+      >
+    >;
+    lessThan: OnlyIf<
+      T,
+      Date | number,
+      (val: T, msg?: string) => Assertion<T, TFlags>,
+      OnlyIf<
+        TFlags,
+        {doLength: true},
+        (val: number, msg?: string) => Assertion<T, TFlags>
+      >
+    >;
+
+    by: OnlyIf<
+      TFlags,
+      {deltaBehavior: string},
+      (delta: number, msg?: string) => Assertion<T, TFlags>
+    >;
+
+    change: {
+      <TSubject>(
+        subject: TSubject,
+        prop: keyof TSubject,
+        msg?: string
+      ): Assertion<T, TFlags & {deltaBehavior: string}>;
+      (subject: Function): Assertion<T, TFlags & {deltaBehavior: string}>;
+    };
+    changes: {
+      <TSubject>(
+        subject: TSubject,
+        prop: keyof TSubject,
+        msg?: string
+      ): Assertion<T, TFlags & {deltaBehavior: string}>;
+      (subject: Function): Assertion<T, TFlags & {deltaBehavior: string}>;
+    };
+
+    contain: OnlyIf<
+      T,
+      CollectionLike<never> | string | object,
+      ChainedMethod<T, TFlags, [val: unknown, msg?: string]>
+    >;
+    contains: OnlyIf<
+      T,
+      CollectionLike<never> | string | object,
+      ChainedMethod<T, TFlags, [val: unknown, msg?: string]>
+    >;
+    include: OnlyIf<
+      T,
+      CollectionLike<never> | string | object,
+      ChainedMethod<T, TFlags, [val: unknown, msg?: string]>
+    >;
+    includes: OnlyIf<
+      T,
+      CollectionLike<never> | string | object,
+      ChainedMethod<T, TFlags, [val: unknown, msg?: string]>
+    >;
+
+    decrease: OnlyIf<
+      T,
+      Function,
+      {
+        (subject: Function): Assertion<T, TFlags & {deltaBehavior: string}>;
+        <TSubject>(
+          subject: TSubject,
+          prop: keyof TSubject,
+          msg?: string
+        ): Assertion<T, TFlags & {deltaBehavior: string}>;
+      }
+    >;
+    decreases: OnlyIf<
+      T,
+      Function,
+      {
+        (subject: Function): Assertion<T, TFlags & {deltaBehavior: string}>;
+        <TSubject>(
+          subject: TSubject,
+          prop: keyof TSubject,
+          msg?: string
+        ): Assertion<T, TFlags & {deltaBehavior: string}>;
+      }
+    >;
+
+    eq: (val: T, msg?: string) => Assertion<T, TFlags>;
+    equal: (val: T, msg?: string) => Assertion<T, TFlags>;
+    equals: (val: T, msg?: string) => Assertion<T, TFlags>;
+
+    eql: (val: T, msg?: string) => Assertion<T, TFlags>;
+    eqls: (val: T, msg?: string) => Assertion<T, TFlags>;
+
+    greaterThanOrEqual: OnlyIf<
+      T,
+      Date | number,
+      (val: T, msg?: string) => Assertion<T, TFlags>,
+      OnlyIf<
+        TFlags,
+        {doLength: true},
+        (val: number, msg?: string) => Assertion<T, TFlags>
+      >
+    >;
+    least: OnlyIf<
+      T,
+      Date | number,
+      (val: T, msg?: string) => Assertion<T, TFlags>,
+      OnlyIf<
+        TFlags,
+        {doLength: true},
+        (val: number, msg?: string) => Assertion<T, TFlags>
+      >
+    >;
+    gte: OnlyIf<
+      T,
+      Date | number,
+      (val: T, msg?: string) => Assertion<T, TFlags>,
+      OnlyIf<
+        TFlags,
+        {doLength: true},
+        (val: number, msg?: string) => Assertion<T, TFlags>
+      >
+    >;
+
+    haveOwnProperty: OnlyIf<
+      T,
+      object,
+      {
+        <TKey extends keyof T>(name: TKey): Assertion<T[TKey]>;
+        (name: PropertyKey): Assertion<unknown>;
+        <TKey extends keyof T>(
+          name: TKey,
+          val: T[TKey],
+          msg?: string
+        ): Assertion<T[TKey]>;
+      }
+    >;
+    ownProperty: OnlyIf<
+      T,
+      object,
+      {
+        <TKey extends keyof T>(name: TKey): Assertion<T[TKey]>;
+        (name: PropertyKey): Assertion<unknown>;
+        <TKey extends keyof T>(
+          name: TKey,
+          val: T[TKey],
+          msg?: string
+        ): Assertion<T[TKey]>;
+      }
+    >;
+
+    haveOwnPropertyDescriptor: (
+      name: PropertyKey,
+      descriptor: PropertyDescriptor,
+      msg?: string
+    ) => Assertion<PropertyDescriptor>;
+    ownPropertyDescriptor: (
+      name: PropertyKey,
+      descriptor: PropertyDescriptor,
+      msg?: string
+    ) => Assertion<PropertyDescriptor>;
+
+    increase: OnlyIf<
+      T,
+      Function,
+      {
+        (subject: Function): Assertion<T, TFlags & {deltaBehavior: string}>;
+        <TSubject>(
+          subject: TSubject,
+          prop: keyof TSubject,
+          msg?: string
+        ): Assertion<T, TFlags & {deltaBehavior: string}>;
+      }
+    >;
+    increases: OnlyIf<
+      T,
+      Function,
+      {
+        (subject: Function): Assertion<T, TFlags & {deltaBehavior: string}>;
+        <TSubject>(
+          subject: TSubject,
+          prop: keyof TSubject,
+          msg?: string
+        ): Assertion<T, TFlags & {deltaBehavior: string}>;
+      }
+    >;
+
+    instanceOf: (
+      ctor: Constructor<unknown>,
+      msg?: string
+    ) => Assertion<T, TFlags>;
+    instanceof: (
+      ctor: Constructor<unknown>,
+      msg?: string
+    ) => Assertion<T, TFlags>;
+
+    keys: T extends Map<unknown, unknown> | Set<unknown>
+      ? {
+          (keys: unknown[]): Assertion<T, TFlags>;
+          (...keys: unknown[]): Assertion<T, TFlags>;
+        }
+      : T extends object
+        ? {
+            (keys: Record<PropertyKey, unknown>): Assertion<T, TFlags>;
+            (keys: PropertyKey[]): Assertion<T, TFlags>;
+            (...keys: PropertyKey[]): Assertion<T, TFlags>;
+          }
+        : never;
+    key: T extends Map<unknown, unknown> | Set<unknown>
+      ? {
+          (keys: unknown[]): Assertion<T, TFlags>;
+          (...keys: unknown[]): Assertion<T, TFlags>;
+        }
+      : T extends object
+        ? {
+            (keys: PropertyKey[]): Assertion<T, TFlags>;
+            (...keys: PropertyKey[]): Assertion<T, TFlags>;
+          }
+        : never;
+
+    length: OnlyIf<
+      T,
+      LengthLike,
+      ChainedMethod<
+        T,
+        AssertionFlags<T> & {doLength: true},
+        [n?: number, msg?: string],
+        Assertion<T, AssertionFlags<T> & {doLength: true}>
+      >
+    >;
+    lengthOf: OnlyIf<
+      T,
+      LengthLike,
+      ChainedMethod<
+        T,
+        AssertionFlags<T> & {doLength: true},
+        [n?: number, msg?: string],
+        Assertion<T, AssertionFlags<T> & {doLength: true}>
+      >
+    >;
+
+    lessThanOrEqual: OnlyIf<
+      T,
+      Date | number,
+      (val: T, msg?: string) => Assertion<T, TFlags>,
+      OnlyIf<
+        TFlags,
+        {doLength: true},
+        (val: number, msg?: string) => Assertion<T, TFlags>
+      >
+    >;
+    lte: OnlyIf<
+      T,
+      Date | number,
+      (val: T, msg?: string) => Assertion<T, TFlags>,
+      OnlyIf<
+        TFlags,
+        {doLength: true},
+        (val: number, msg?: string) => Assertion<T, TFlags>
+      >
+    >;
+    most: OnlyIf<
+      T,
+      Date | number,
+      (val: T, msg?: string) => Assertion<T, TFlags>,
+      OnlyIf<
+        TFlags,
+        {doLength: true},
+        (val: number, msg?: string) => Assertion<T, TFlags>
+      >
+    >;
+
+    match: OnlyIf<
+      T,
+      string,
+      (re: RegExp, msg?: string) => Assertion<T, TFlags>
+    >;
+    matches: OnlyIf<
+      T,
+      string,
+      (re: RegExp, msg?: string) => Assertion<T, TFlags>
+    >;
+
+    members: OnlyIf<
+      T,
+      unknown[],
+      (subset: T, msg?: string) => Assertion<T, TFlags>
+    >;
+
+    oneOf: OnlyIf<
+      TFlags,
+      {deep: true},
+      (list: unknown[], msg?: string) => Assertion<T, TFlags>,
+      OnlyIf<
+        T,
+        string | unknown[],
+        (list: unknown[], msg?: string) => Assertion<T, TFlags>
+      >
+    >;
+
+    property: OnlyIf<
+      T,
+      object,
+      OnlyIf<
+        TFlags,
+        {nested: true},
+        (
+          name: string,
+          val?: unknown,
+          msg?: string
+        ) => Assertion<unknown, TFlags>,
+        {
+          <TKey extends keyof T>(name: TKey): Assertion<T[TKey]>;
+          (name: PropertyKey): Assertion<unknown>;
+          <TKey extends keyof T>(
+            name: TKey,
+            val: T[TKey],
+            msg?: string
+          ): Assertion<T[TKey]>;
+        }
+      >
+    >;
+
+    respondTo: (method: string, msg?: string) => Assertion<T, TFlags>;
+    respondsTo: (method: string, msg?: string) => Assertion<T, TFlags>;
+
+    satifies: (matcher: Function, msg?: string) => Assertion<T, TFlags>;
+    satisfy: (matcher: Function, msg?: string) => Assertion<T, TFlags>;
+
+    string: (str: string, msg?: string) => Assertion<T, TFlags>;
+
+    Throw: ((errMsgMatcher: string | RegExp) => Assertion<T, TFlags>) &
+      ((
+        errorLike: Error | Constructor<Error>,
+        errMsgMatcher: string | RegExp,
+        msg?: string
+      ) => Assertion<T, TFlags>);
+    throws: ((errMsgMatcher: string | RegExp) => Assertion<T, TFlags>) &
+      ((
+        errorLike: Error | Constructor<Error>,
+        errMsgMatcher: string | RegExp,
+        msg?: string
+      ) => Assertion<T, TFlags>);
+    throw: ((errMsgMatcher: string | RegExp) => Assertion<T, TFlags>) &
+      ((
+        errorLike: Error | Constructor<Error>,
+        errMsgMatcher: string | RegExp,
+        msg?: string
+      ) => Assertion<T, TFlags>);
+
+    within: OnlyIf<
+      T,
+      Date | number,
+      (start: T, finish: T, msg?: string) => Assertion<T, TFlags>,
+      OnlyIf<
+        TFlags,
+        {doLength: true},
+        (start: number, finish: number, msg?: string) => Assertion<T, TFlags>
+      >
+    >;
+  }
+}
+
 /**
  * ### Language Chains
  *
@@ -253,7 +736,7 @@ Assertion.addProperty('all', function () {
   flag(this, 'any', false);
 });
 
-const functionTypes = {
+const functionTypes: Record<string, string[]> = {
   function: [
     'function',
     'asyncfunction',
@@ -324,7 +807,7 @@ const functionTypes = {
  * @namespace BDD
  * @public
  */
-function an(type, msg) {
+function an(this: Assertion<unknown>, type: string, msg?: string) {
   if (msg) flag(this, 'message', msg);
   type = type.toLowerCase();
   var obj = flag(this, 'object'),
@@ -356,14 +839,14 @@ Assertion.addChainableMethod('a', an);
  * @param {unknown} b
  * @returns {boolean}
  */
-function SameValueZero(a, b) {
+function SameValueZero(a: unknown, b: unknown): boolean {
   return (_.isNaN(a) && _.isNaN(b)) || a === b;
 }
 
 /**
- *
+ * Flags that this assertion should operate using the contains logic
  */
-function includeChainingBehavior() {
+function includeChainingBehavior(this: Assertion<unknown>): void {
   flag(this, 'contains', true);
 }
 
@@ -513,7 +996,7 @@ function includeChainingBehavior() {
  * @namespace BDD
  * @public
  */
-function include(val, msg) {
+function include<T>(this: Assertion<unknown>, val: T, msg?: string) {
   if (msg) flag(this, 'message', msg);
 
   var obj = flag(this, 'object'),
@@ -531,7 +1014,7 @@ function include(val, msg) {
 
   switch (objType) {
     case 'string':
-      included = obj.indexOf(val) !== -1;
+      included = (obj as string).indexOf(val as string) !== -1;
       break;
 
     case 'weakset':
@@ -543,32 +1026,32 @@ function include(val, msg) {
         );
       }
 
-      included = obj.has(val);
+      included = (obj as WeakSet<object>).has(val as object);
       break;
 
     case 'map':
-      obj.forEach(function (item) {
+      (obj as Map<unknown, unknown>).forEach(function (item) {
         included = included || isEql(item, val);
       });
       break;
 
     case 'set':
       if (isDeep) {
-        obj.forEach(function (item) {
+        (obj as Set<unknown>).forEach(function (item) {
           included = included || isEql(item, val);
         });
       } else {
-        included = obj.has(val);
+        included = (obj as Set<unknown>).has(val);
       }
       break;
 
     case 'array':
       if (isDeep) {
-        included = obj.some(function (item) {
+        included = (obj as Array<unknown>).some(function (item) {
           return isEql(item, val);
         });
       } else {
-        included = obj.indexOf(val) !== -1;
+        included = (obj as Array<unknown>).indexOf(val) !== -1;
       }
       break;
 
@@ -593,24 +1076,34 @@ function include(val, msg) {
         );
       }
 
-      var props = Object.keys(val),
-        firstErr = null,
+      var props = Object.keys(val as object),
+        firstErr: unknown = null,
         numErrs = 0;
 
-      props.forEach(function (prop) {
-        var propAssertion = new Assertion(obj);
+      props.forEach(function (this: Assertion<unknown>, prop) {
+        var propAssertion = Assertion.create(
+          obj as Record<PropertyKey, unknown>
+        );
         _.transferFlags(this, propAssertion, true);
         flag(propAssertion, 'lockSsfi', true);
 
         if (!negate || props.length === 1) {
-          propAssertion.property(prop, val[prop]);
+          propAssertion.property(
+            prop,
+            (val as Record<PropertyKey, unknown>)[prop]
+          );
           return;
         }
 
         try {
-          propAssertion.property(prop, val[prop]);
+          propAssertion.property(
+            prop,
+            (val as Record<PropertyKey, unknown>)[prop]
+          );
         } catch (err) {
-          if (!_.checkError.compatibleConstructor(err, AssertionError)) {
+          if (
+            !_.checkError.compatibleConstructor(err as Error, AssertionError)
+          ) {
             throw err;
           }
           if (firstErr === null) firstErr = err;
@@ -923,7 +1416,7 @@ Assertion.addProperty('NaN', function () {
  * @namespace BDD
  * @public
  */
-function assertExist() {
+function assertExist(this: Assertion<unknown>) {
   var val = flag(this, 'object');
   this.assert(
     val !== null && val !== undefined,
@@ -994,11 +1487,11 @@ Assertion.addProperty('empty', function () {
   switch (_.type(val).toLowerCase()) {
     case 'array':
     case 'string':
-      itemsCount = val.length;
+      itemsCount = (val as Array<unknown> | string).length;
       break;
     case 'map':
     case 'set':
-      itemsCount = val.size;
+      itemsCount = (val as Map<unknown, unknown> | Set<unknown>).size;
       break;
     case 'weakmap':
     case 'weakset':
@@ -1008,7 +1501,8 @@ Assertion.addProperty('empty', function () {
         ssfi
       );
     case 'function':
-      var msg = flagMsg + '.empty was passed a function ' + _.getName(val);
+      var msg =
+        flagMsg + '.empty was passed a function ' + _.getName(val as Function);
       throw new AssertionError(msg.trim(), undefined, ssfi);
     default:
       if (val !== Object(val)) {
@@ -1018,7 +1512,7 @@ Assertion.addProperty('empty', function () {
           ssfi
         );
       }
-      itemsCount = Object.keys(val).length;
+      itemsCount = Object.keys(val as object).length;
   }
 
   this.assert(
@@ -1057,7 +1551,7 @@ Assertion.addProperty('empty', function () {
  * @namespace BDD
  * @public
  */
-function checkArguments() {
+function checkArguments(this: Assertion<unknown>) {
   var obj = flag(this, 'object'),
     type = _.type(obj);
   this.assert(
@@ -1114,7 +1608,7 @@ Assertion.addProperty('Arguments', checkArguments);
  * @namespace BDD
  * @public
  */
-function assertEqual(val, msg) {
+function assertEqual(this: Assertion<unknown>, val: unknown, msg?: string) {
   if (msg) flag(this, 'message', msg);
   var obj = flag(this, 'object');
   if (flag(this, 'deep')) {
@@ -1178,7 +1672,7 @@ Assertion.addMethod('eq', assertEqual);
  * @namespace BDD
  * @public
  */
-function assertEql(obj, msg) {
+function assertEql(this: Assertion<unknown>, obj: unknown, msg?: string) {
   if (msg) flag(this, 'message', msg);
   var eql = flag(this, 'eql');
   this.assert(
@@ -1236,7 +1730,7 @@ Assertion.addMethod('eqls', assertEql);
  * @namespace BDD
  * @public
  */
-function assertAbove(n, msg) {
+function assertAbove(this: Assertion<unknown>, n: Date | number, msg?: string) {
   if (msg) flag(this, 'message', msg);
   var obj = flag(this, 'object'),
     doLength = flag(this, 'doLength'),
@@ -1247,7 +1741,9 @@ function assertAbove(n, msg) {
     nType = _.type(n).toLowerCase();
 
   if (doLength && objType !== 'map' && objType !== 'set') {
-    new Assertion(obj, flagMsg, ssfi, true).to.have.property('length');
+    Assertion.create(obj as object, flagMsg, ssfi, true).to.have.property(
+      'length'
+    );
   }
 
   if (!doLength && objType === 'date' && nType !== 'date') {
@@ -1276,12 +1772,12 @@ function assertAbove(n, msg) {
       itemsCount;
     if (objType === 'map' || objType === 'set') {
       descriptor = 'size';
-      itemsCount = obj.size;
+      itemsCount = (obj as Set<unknown> | Map<unknown, unknown>).size;
     } else {
-      itemsCount = obj.length;
+      itemsCount = (obj as Array<unknown>).length;
     }
     this.assert(
-      itemsCount > n,
+      itemsCount > (n as number),
       'expected #{this} to have a ' +
         descriptor +
         ' above #{exp} but got #{act}',
@@ -1291,7 +1787,7 @@ function assertAbove(n, msg) {
     );
   } else {
     this.assert(
-      obj > n,
+      (obj as number) > (n as number),
       'expected #{this} to be above #{exp}',
       'expected #{this} to be at most #{exp}',
       n
@@ -1346,7 +1842,7 @@ Assertion.addMethod('greaterThan', assertAbove);
  * @namespace BDD
  * @public
  */
-function assertLeast(n, msg) {
+function assertLeast(this: Assertion<unknown>, n: unknown, msg?: string) {
   if (msg) flag(this, 'message', msg);
   var obj = flag(this, 'object'),
     doLength = flag(this, 'doLength'),
@@ -1355,11 +1851,13 @@ function assertLeast(n, msg) {
     ssfi = flag(this, 'ssfi'),
     objType = _.type(obj).toLowerCase(),
     nType = _.type(n).toLowerCase(),
-    errorMessage,
+    errorMessage = 'unknown error',
     shouldThrow = true;
 
   if (doLength && objType !== 'map' && objType !== 'set') {
-    new Assertion(obj, flagMsg, ssfi, true).to.have.property('length');
+    Assertion.create(obj as object, flagMsg, ssfi, true).to.have.property(
+      'length'
+    );
   }
 
   if (!doLength && objType === 'date' && nType !== 'date') {
@@ -1383,12 +1881,12 @@ function assertLeast(n, msg) {
       itemsCount;
     if (objType === 'map' || objType === 'set') {
       descriptor = 'size';
-      itemsCount = obj.size;
+      itemsCount = (obj as Set<unknown> | Map<unknown, unknown>).size;
     } else {
-      itemsCount = obj.length;
+      itemsCount = (obj as Array<unknown>).length;
     }
     this.assert(
-      itemsCount >= n,
+      itemsCount >= (n as number),
       'expected #{this} to have a ' +
         descriptor +
         ' at least #{exp} but got #{act}',
@@ -1398,7 +1896,7 @@ function assertLeast(n, msg) {
     );
   } else {
     this.assert(
-      obj >= n,
+      (obj as number) >= (n as number),
       'expected #{this} to be at least #{exp}',
       'expected #{this} to be below #{exp}',
       n
@@ -1452,7 +1950,7 @@ Assertion.addMethod('greaterThanOrEqual', assertLeast);
  * @namespace BDD
  * @public
  */
-function assertBelow(n, msg) {
+function assertBelow(this: Assertion<unknown>, n: unknown, msg?: string) {
   if (msg) flag(this, 'message', msg);
   var obj = flag(this, 'object'),
     doLength = flag(this, 'doLength'),
@@ -1461,11 +1959,13 @@ function assertBelow(n, msg) {
     ssfi = flag(this, 'ssfi'),
     objType = _.type(obj).toLowerCase(),
     nType = _.type(n).toLowerCase(),
-    errorMessage,
+    errorMessage = 'unknown error',
     shouldThrow = true;
 
   if (doLength && objType !== 'map' && objType !== 'set') {
-    new Assertion(obj, flagMsg, ssfi, true).to.have.property('length');
+    Assertion.create(obj as object, flagMsg, ssfi, true).to.have.property(
+      'length'
+    );
   }
 
   if (!doLength && objType === 'date' && nType !== 'date') {
@@ -1489,12 +1989,12 @@ function assertBelow(n, msg) {
       itemsCount;
     if (objType === 'map' || objType === 'set') {
       descriptor = 'size';
-      itemsCount = obj.size;
+      itemsCount = (obj as Set<unknown> | Map<unknown, unknown>).size;
     } else {
-      itemsCount = obj.length;
+      itemsCount = (obj as Array<unknown>).length;
     }
     this.assert(
-      itemsCount < n,
+      itemsCount < (n as number),
       'expected #{this} to have a ' +
         descriptor +
         ' below #{exp} but got #{act}',
@@ -1504,7 +2004,7 @@ function assertBelow(n, msg) {
     );
   } else {
     this.assert(
-      obj < n,
+      (obj as number) < (n as number),
       'expected #{this} to be below #{exp}',
       'expected #{this} to be at least #{exp}',
       n
@@ -1559,7 +2059,7 @@ Assertion.addMethod('lessThan', assertBelow);
  * @namespace BDD
  * @public
  */
-function assertMost(n, msg) {
+function assertMost(this: Assertion<unknown>, n: unknown, msg?: string) {
   if (msg) flag(this, 'message', msg);
   var obj = flag(this, 'object'),
     doLength = flag(this, 'doLength'),
@@ -1568,11 +2068,13 @@ function assertMost(n, msg) {
     ssfi = flag(this, 'ssfi'),
     objType = _.type(obj).toLowerCase(),
     nType = _.type(n).toLowerCase(),
-    errorMessage,
+    errorMessage = 'unknown error',
     shouldThrow = true;
 
   if (doLength && objType !== 'map' && objType !== 'set') {
-    new Assertion(obj, flagMsg, ssfi, true).to.have.property('length');
+    Assertion.create(obj as object, flagMsg, ssfi, true).to.have.property(
+      'length'
+    );
   }
 
   if (!doLength && objType === 'date' && nType !== 'date') {
@@ -1596,12 +2098,12 @@ function assertMost(n, msg) {
       itemsCount;
     if (objType === 'map' || objType === 'set') {
       descriptor = 'size';
-      itemsCount = obj.size;
+      itemsCount = (obj as Set<unknown> | Map<unknown, unknown>).size;
     } else {
-      itemsCount = obj.length;
+      itemsCount = (obj as Array<unknown>).length;
     }
     this.assert(
-      itemsCount <= n,
+      itemsCount <= (n as number),
       'expected #{this} to have a ' +
         descriptor +
         ' at most #{exp} but got #{act}',
@@ -1611,7 +2113,7 @@ function assertMost(n, msg) {
     );
   } else {
     this.assert(
-      obj <= n,
+      (obj as number) <= (n as number),
       'expected #{this} to be at most #{exp}',
       'expected #{this} to be above #{exp}',
       n
@@ -1665,7 +2167,14 @@ Assertion.addMethod('lessThanOrEqual', assertMost);
  * @namespace BDD
  * @public
  */
-Assertion.addMethod('within', function (start, finish, msg) {
+function assertWithin(start: number, finish: number, msg?: string): void;
+function assertWithin(start: Date, finish: Date, msg?: string): void;
+function assertWithin(
+  this: Assertion<unknown>,
+  start: number | Date,
+  finish: number | Date,
+  msg?: string
+): void {
   if (msg) flag(this, 'message', msg);
   var obj = flag(this, 'object'),
     doLength = flag(this, 'doLength'),
@@ -1675,15 +2184,17 @@ Assertion.addMethod('within', function (start, finish, msg) {
     objType = _.type(obj).toLowerCase(),
     startType = _.type(start).toLowerCase(),
     finishType = _.type(finish).toLowerCase(),
-    errorMessage,
+    errorMessage = 'unknown error',
     shouldThrow = true,
     range =
       startType === 'date' && finishType === 'date'
-        ? start.toISOString() + '..' + finish.toISOString()
+        ? (start as Date).toISOString() + '..' + (finish as Date).toISOString()
         : start + '..' + finish;
 
   if (doLength && objType !== 'map' && objType !== 'set') {
-    new Assertion(obj, flagMsg, ssfi, true).to.have.property('length');
+    Assertion.create(obj as object, flagMsg, ssfi, true).to.have.property(
+      'length'
+    );
   }
 
   if (
@@ -1714,23 +2225,26 @@ Assertion.addMethod('within', function (start, finish, msg) {
       itemsCount;
     if (objType === 'map' || objType === 'set') {
       descriptor = 'size';
-      itemsCount = obj.size;
+      itemsCount = (obj as Set<unknown> | Map<string, unknown>).size;
     } else {
-      itemsCount = obj.length;
+      itemsCount = (obj as Array<unknown>).length;
     }
     this.assert(
-      itemsCount >= start && itemsCount <= finish,
+      itemsCount >= (start as number) && itemsCount <= (finish as number),
       'expected #{this} to have a ' + descriptor + ' within ' + range,
       'expected #{this} to not have a ' + descriptor + ' within ' + range
     );
   } else {
     this.assert(
-      obj >= start && obj <= finish,
+      (obj as number) >= (start as number) &&
+        (obj as number) <= (finish as number),
       'expected #{this} to be within ' + range,
       'expected #{this} to not be within ' + range
     );
   }
-});
+}
+
+Assertion.addMethod('within', assertWithin);
 
 /**
  * ### .instanceof(constructor[, msg])
@@ -1770,7 +2284,11 @@ Assertion.addMethod('within', function (start, finish, msg) {
  * @namespace BDD
  * @public
  */
-function assertInstanceOf(constructor, msg) {
+function assertInstanceOf(
+  this: Assertion<unknown>,
+  constructor: {new (): unknown},
+  msg?: string
+) {
   if (msg) flag(this, 'message', msg);
 
   var target = flag(this, 'object');
@@ -1919,7 +2437,12 @@ Assertion.addMethod('instanceOf', assertInstanceOf);
  * @namespace BDD
  * @public
  */
-function assertProperty(name, val, msg) {
+function assertProperty(
+  this: Assertion<unknown>,
+  name: PropertyKey,
+  val?: unknown,
+  msg?: string
+) {
   if (msg) flag(this, 'message', msg);
 
   var isNested = flag(this, 'nested'),
@@ -1973,9 +2496,19 @@ function assertProperty(name, val, msg) {
 
   var isDeep = flag(this, 'deep'),
     negate = flag(this, 'negate'),
-    pathInfo = isNested ? _.getPathInfo(obj, name) : null,
-    value = isNested ? pathInfo.value : obj[name],
-    isEql = isDeep ? flag(this, 'eql') : (val1, val2) => val1 === val2;
+    isEql = isDeep
+      ? flag(this, 'eql')
+      : (val1: unknown, val2: unknown): boolean => val1 === val2;
+  let pathInfo;
+  let value;
+
+  if (isNested) {
+    pathInfo = _.getPathInfo(obj, name as string);
+    value = pathInfo.value;
+  } else {
+    pathInfo = null;
+    value = (obj as Record<PropertyKey, unknown>)[name];
+  }
 
   var descriptor = '';
   if (isDeep) descriptor += 'deep ';
@@ -1985,7 +2518,7 @@ function assertProperty(name, val, msg) {
 
   var hasProperty;
   if (isOwn) hasProperty = Object.prototype.hasOwnProperty.call(obj, name);
-  else if (isNested) hasProperty = pathInfo.exists;
+  else if (isNested && pathInfo) hasProperty = pathInfo.exists;
   else hasProperty = _.hasProperty(obj, name);
 
   // When performing a negated assertion for both name and val, merely having
@@ -2023,14 +2556,15 @@ function assertProperty(name, val, msg) {
 Assertion.addMethod('property', assertProperty);
 
 /**
- *
- * @param {unknown} _name
- * @param {unknown} _value
- * @param {string} _msg
+ * Asserts that an object has an own property
  */
-function assertOwnProperty(_name, _value, _msg) {
+function assertOwnProperty(this: Assertion<unknown>) {
   flag(this, 'own', true);
-  assertProperty.apply(this, arguments);
+  // TODO (43081j): remove this highly questionable cast
+  assertProperty.apply(
+    this,
+    arguments as unknown as Parameters<typeof assertProperty>
+  );
 }
 
 Assertion.addMethod('ownProperty', assertOwnProperty);
@@ -2154,7 +2688,12 @@ Assertion.addMethod('haveOwnProperty', assertOwnProperty);
  * @namespace BDD
  * @public
  */
-function assertOwnPropertyDescriptor(name, descriptor, msg) {
+function assertOwnPropertyDescriptor(
+  this: Assertion<unknown>,
+  name: PropertyKey,
+  descriptor?: unknown,
+  msg?: string
+) {
   if (typeof descriptor === 'string') {
     msg = descriptor;
     descriptor = null;
@@ -2196,9 +2735,9 @@ Assertion.addMethod('ownPropertyDescriptor', assertOwnPropertyDescriptor);
 Assertion.addMethod('haveOwnPropertyDescriptor', assertOwnPropertyDescriptor);
 
 /**
- *
+ * Flags that this assertion should operate on the object's length
  */
-function assertLengthChain() {
+function assertLengthChain(this: Assertion<unknown>) {
   flag(this, 'doLength', true);
 }
 
@@ -2259,7 +2798,7 @@ function assertLengthChain() {
  * @namespace BDD
  * @public
  */
-function assertLength(n, msg) {
+function assertLength(this: Assertion<unknown>, n: number, msg?: string) {
   if (msg) flag(this, 'message', msg);
   var obj = flag(this, 'object'),
     objType = _.type(obj).toLowerCase(),
@@ -2272,11 +2811,13 @@ function assertLength(n, msg) {
     case 'map':
     case 'set':
       descriptor = 'size';
-      itemsCount = obj.size;
+      itemsCount = (obj as Set<unknown> | Map<unknown, unknown>).size;
       break;
     default:
-      new Assertion(obj, flagMsg, ssfi, true).to.have.property('length');
-      itemsCount = obj.length;
+      Assertion.create(obj as object, flagMsg, ssfi, true).to.have.property(
+        'length'
+      );
+      itemsCount = (obj as Array<unknown>).length;
   }
 
   this.assert(
@@ -2318,11 +2859,11 @@ Assertion.addChainableMethod('lengthOf', assertLength, assertLengthChain);
  * @namespace BDD
  * @public
  */
-function assertMatch(re, msg) {
+function assertMatch(this: Assertion<unknown>, re: RegExp, msg?: string) {
   if (msg) flag(this, 'message', msg);
   var obj = flag(this, 'object');
   this.assert(
-    re.exec(obj),
+    re.exec(obj as string),
     'expected #{this} to match ' + re,
     'expected #{this} not to match ' + re
   );
@@ -2355,15 +2896,15 @@ Assertion.addMethod('matches', assertMatch);
  * @namespace BDD
  * @public
  */
-Assertion.addMethod('string', function (str, msg) {
+Assertion.addMethod('string', function (str: string, msg?: string) {
   if (msg) flag(this, 'message', msg);
   var obj = flag(this, 'object'),
     flagMsg = flag(this, 'message'),
     ssfi = flag(this, 'ssfi');
-  new Assertion(obj, flagMsg, ssfi, true).is.a('string');
+  Assertion.create(obj, flagMsg, ssfi, true).is.a('string');
 
   this.assert(
-    ~obj.indexOf(str),
+    ~(obj as string).indexOf(str),
     'expected #{this} to contain ' + _.inspect(str),
     'expected #{this} to not contain ' + _.inspect(str)
   );
@@ -2472,7 +3013,10 @@ Assertion.addMethod('string', function (str, msg) {
  * @namespace BDD
  * @public
  */
-function assertKeys(keys) {
+function assertKeys(
+  this: Assertion<unknown>,
+  keys: Array<PropertyKey> | Record<PropertyKey, unknown>
+) {
   var obj = flag(this, 'object'),
     objType = _.type(obj),
     keysType = _.type(keys),
@@ -2480,9 +3024,10 @@ function assertKeys(keys) {
     isDeep = flag(this, 'deep'),
     str,
     deepStr = '',
-    actual,
+    actual: PropertyKey[] = [],
     ok = true,
-    flagMsg = flag(this, 'message');
+    flagMsg = flag(this, 'message'),
+    normalisedKeys: PropertyKey[] = [];
 
   flagMsg = flagMsg ? flagMsg + ': ' : '';
   var mixedArgsMsg =
@@ -2494,47 +3039,52 @@ function assertKeys(keys) {
     actual = [];
 
     // Map and Set '.keys' aren't supported in IE 11. Therefore, use .forEach.
-    obj.forEach(function (val, key) {
-      actual.push(key);
+    (obj as Set<unknown> | Map<unknown, unknown>).forEach(function (_val, key) {
+      (actual as Array<unknown>).push(key);
     });
 
     if (keysType !== 'Array') {
-      keys = Array.prototype.slice.call(arguments);
+      normalisedKeys = Array.prototype.slice.call(arguments);
+    } else {
+      normalisedKeys = keys as PropertyKey[];
     }
   } else {
-    actual = _.getOwnEnumerableProperties(obj);
+    actual = _.getOwnEnumerableProperties(obj as object);
 
     switch (keysType) {
       case 'Array':
         if (arguments.length > 1) {
           throw new AssertionError(mixedArgsMsg, undefined, ssfi);
         }
+        normalisedKeys = keys as PropertyKey[];
         break;
       case 'Object':
         if (arguments.length > 1) {
           throw new AssertionError(mixedArgsMsg, undefined, ssfi);
         }
-        keys = Object.keys(keys);
+        normalisedKeys = Object.keys(keys);
         break;
       default:
-        keys = Array.prototype.slice.call(arguments);
+        normalisedKeys = Array.prototype.slice.call(arguments);
     }
 
     // Only stringify non-Symbols because Symbols would become "Symbol()"
-    keys = keys.map(function (val) {
+    normalisedKeys = normalisedKeys.map(function (val) {
       return typeof val === 'symbol' ? val : String(val);
     });
   }
 
-  if (!keys.length) {
+  if (!normalisedKeys.length) {
     throw new AssertionError(flagMsg + 'keys required', undefined, ssfi);
   }
 
-  var len = keys.length,
+  var len = normalisedKeys.length,
     any = flag(this, 'any'),
     all = flag(this, 'all'),
-    expected = keys,
-    isEql = isDeep ? flag(this, 'eql') : (val1, val2) => val1 === val2;
+    expected = normalisedKeys,
+    isEql = isDeep
+      ? flag(this, 'eql')
+      : (val1: unknown, val2: unknown): boolean => val1 === val2;
 
   if (!any && !all) {
     all = true;
@@ -2558,24 +3108,24 @@ function assertKeys(keys) {
     });
 
     if (!flag(this, 'contains')) {
-      ok = ok && keys.length == actual.length;
+      ok = ok && normalisedKeys.length == actual.length;
     }
   }
 
   // Key string
   if (len > 1) {
-    keys = keys.map(function (key) {
+    normalisedKeys = normalisedKeys.map(function (key) {
       return _.inspect(key);
     });
-    var last = keys.pop();
+    var last = normalisedKeys.pop();
     if (all) {
-      str = keys.join(', ') + ', and ' + last;
+      str = normalisedKeys.join(', ') + ', and ' + String(last);
     }
     if (any) {
-      str = keys.join(', ') + ', or ' + last;
+      str = normalisedKeys.join(', ') + ', or ' + String(last);
     }
   } else {
-    str = _.inspect(keys[0]);
+    str = _.inspect(normalisedKeys[0]);
   }
 
   // Form
@@ -2757,23 +3307,45 @@ Assertion.addMethod('key', assertKeys);
  * @namespace BDD
  * @public
  */
-function assertThrows(errorLike, errMsgMatcher, msg) {
+function assertThrows(
+  this: Assertion<unknown>,
+  errMsgMatcher: string | RegExp
+): void;
+function assertThrows(
+  this: Assertion<unknown>,
+  errorLike: Error | Constructor<Error>,
+  errMsgMatcher: string | RegExp,
+  msg?: string
+): void;
+function assertThrows(
+  this: Assertion<unknown>,
+  errorLikeOrMatcher: Error | Constructor<Error> | string | RegExp,
+  errMsgMatcher?: string | RegExp,
+  msg?: string
+): void {
   if (msg) flag(this, 'message', msg);
   var obj = flag(this, 'object'),
     ssfi = flag(this, 'ssfi'),
     flagMsg = flag(this, 'message'),
     negate = flag(this, 'negate') || false;
-  new Assertion(obj, flagMsg, ssfi, true).is.a('function');
+  Assertion.create(obj, flagMsg, ssfi, true).is.a('function');
+
+  let errorLike: Error | Constructor<Error> | null;
 
-  if (_.isRegExp(errorLike) || typeof errorLike === 'string') {
-    errMsgMatcher = errorLike;
+  if (
+    _.isRegExp(errorLikeOrMatcher) ||
+    typeof errorLikeOrMatcher === 'string'
+  ) {
+    errMsgMatcher = errorLikeOrMatcher;
     errorLike = null;
+  } else {
+    errorLike = errorLikeOrMatcher;
   }
 
   let caughtErr;
   let errorWasThrown = false;
   try {
-    obj();
+    (obj as () => void)();
   } catch (err) {
     errorWasThrown = true;
     caughtErr = err;
@@ -2810,7 +3382,7 @@ function assertThrows(errorLike, errMsgMatcher, msg) {
       (typeof caughtErr === 'object' || typeof caughtErr === 'function')
     ) {
       try {
-        actual = _.checkError.getConstructorName(caughtErr);
+        actual = _.checkError.getConstructorName(caughtErr as never);
       } catch (_err) {
         // somehow wasn't a constructor, maybe we got a function thrown
         // or similar
@@ -2830,7 +3402,7 @@ function assertThrows(errorLike, errMsgMatcher, msg) {
     // We should compare instances only if `errorLike` is an instance of `Error`
     if (errorLike instanceof Error) {
       var isCompatibleInstance = _.checkError.compatibleInstance(
-        caughtErr,
+        caughtErr as Error,
         errorLike
       );
 
@@ -2853,7 +3425,7 @@ function assertThrows(errorLike, errMsgMatcher, msg) {
     }
 
     var isCompatibleConstructor = _.checkError.compatibleConstructor(
-      caughtErr,
+      caughtErr as Error,
       errorLike
     );
     if (isCompatibleConstructor === negate) {
@@ -2870,7 +3442,7 @@ function assertThrows(errorLike, errMsgMatcher, msg) {
             : errorLike && _.checkError.getConstructorName(errorLike),
           caughtErr instanceof Error
             ? caughtErr.toString()
-            : caughtErr && _.checkError.getConstructorName(caughtErr)
+            : caughtErr && _.checkError.getConstructorName(caughtErr as Error)
         );
       }
     }
@@ -2884,7 +3456,7 @@ function assertThrows(errorLike, errMsgMatcher, msg) {
     }
 
     var isCompatibleMessage = _.checkError.compatibleMessage(
-      caughtErr,
+      caughtErr as Error,
       errMsgMatcher
     );
     if (isCompatibleMessage === negate) {
@@ -2898,7 +3470,7 @@ function assertThrows(errorLike, errMsgMatcher, msg) {
             ' #{exp} but got #{act}',
           'expected #{this} to throw error not ' + placeholder + ' #{exp}',
           errMsgMatcher,
-          _.checkError.getMessage(caughtErr)
+          _.checkError.getMessage(caughtErr as Error)
         );
       }
     }
@@ -2916,7 +3488,7 @@ function assertThrows(errorLike, errMsgMatcher, msg) {
         : errorLike && _.checkError.getConstructorName(errorLike),
       caughtErr instanceof Error
         ? caughtErr.toString()
-        : caughtErr && _.checkError.getConstructorName(caughtErr)
+        : caughtErr && _.checkError.getConstructorName(caughtErr as Error)
     );
   }
 
@@ -2991,14 +3563,18 @@ Assertion.addMethod('Throw', assertThrows);
  * @namespace BDD
  * @public
  */
-function respondTo(method, msg) {
+function respondTo(
+  this: Assertion<unknown>,
+  method: PropertyKey,
+  msg?: string
+) {
   if (msg) flag(this, 'message', msg);
   var obj = flag(this, 'object'),
     itself = flag(this, 'itself'),
     context =
       'function' === typeof obj && !itself
         ? obj.prototype[method]
-        : obj[method];
+        : (obj as Record<PropertyKey, unknown>)[method];
 
   this.assert(
     'function' === typeof context,
@@ -3070,7 +3646,7 @@ Assertion.addProperty('itself', function () {
  * @namespace BDD
  * @public
  */
-function satisfy(matcher, msg) {
+function satisfy(this: Assertion<unknown>, matcher: Function, msg?: string) {
   if (msg) flag(this, 'message', msg);
   var obj = flag(this, 'object');
   var result = matcher(obj);
@@ -3123,9 +3699,14 @@ Assertion.addMethod('satisfies', satisfy);
  * @namespace BDD
  * @public
  */
-function closeTo(expected, delta, msg) {
+function closeTo(
+  this: Assertion<unknown>,
+  expected: number,
+  delta: number,
+  msg?: string
+) {
   if (msg) flag(this, 'message', msg);
-  var obj = flag(this, 'object'),
+  var obj = flag(this, 'object') as number,
     flagMsg = flag(this, 'message'),
     ssfi = flag(this, 'ssfi');
 
@@ -3147,7 +3728,7 @@ function closeTo(expected, delta, msg) {
     );
   new Assertion(expected, flagMsg, ssfi, true).is.numeric;
 
-  const abs = (x) => (x < 0n ? -x : x);
+  const abs = (x: number): number => (x < 0n ? -x : x);
 
   this.assert(
     abs(obj - expected) <= delta,
@@ -3160,38 +3741,45 @@ Assertion.addMethod('closeTo', closeTo);
 Assertion.addMethod('approximately', closeTo);
 
 /**
- * @param {unknown} _subset
- * @param {unknown} _superset
+ * @param {unknown} subset
+ * @param {unknown} superset
  * @param {unknown} cmp
  * @param {unknown} contains
  * @param {unknown} ordered
  * @returns {boolean}
  */
-function isSubsetOf(_subset, _superset, cmp, contains, ordered) {
-  let superset = Array.from(_superset);
-  let subset = Array.from(_subset);
-  if (!contains) {
-    if (subset.length !== superset.length) return false;
-    superset = superset.slice();
+function isSubsetOf(
+  subset: Iterable<unknown>,
+  superset: Iterable<unknown>,
+  cmp: ((a: unknown, b: unknown) => boolean) | undefined,
+  contains: boolean,
+  ordered: boolean
+) {
+  const subsetArr = [...subset];
+  const supersetArr = [...superset];
+  // Note: Duplicates are ignored if testing for inclusion instead of sameness.
+  if (!contains && subsetArr.length !== supersetArr.length) {
+    return false;
   }
 
-  return subset.every(function (elem, idx) {
-    if (ordered) return cmp ? cmp(elem, superset[idx]) : elem === superset[idx];
+  return subsetArr.every(function (elem, idx) {
+    if (ordered)
+      return cmp ? cmp(elem, supersetArr[idx]) : elem === supersetArr[idx];
 
     if (!cmp) {
-      var matchIdx = superset.indexOf(elem);
+      var matchIdx = supersetArr.indexOf(elem);
       if (matchIdx === -1) return false;
 
-      // Remove match from superset so not counted twice if duplicate in subset.
-      if (!contains) superset.splice(matchIdx, 1);
+      // Remove match from supersetArr so not counted twice if duplicate in subsetArr.
+      if (!contains) supersetArr.splice(matchIdx, 1);
       return true;
     }
 
-    return superset.some(function (elem2, matchIdx) {
+    return supersetArr.some(function (elem2, matchIdx) {
       if (!cmp(elem, elem2)) return false;
 
-      // Remove match from superset so not counted twice if duplicate in subset.
-      if (!contains) superset.splice(matchIdx, 1);
+      // Remove match from supersetArr so not counted twice if duplicate in subsetArr.
+      if (!contains) supersetArr.splice(matchIdx, 1);
       return true;
     });
   });
@@ -3265,7 +3853,7 @@ function isSubsetOf(_subset, _superset, cmp, contains, ordered) {
  * @namespace BDD
  * @public
  */
-Assertion.addMethod('members', function (subset, msg) {
+Assertion.addMethod('members', function (subset: unknown[], msg?: string) {
   if (msg) flag(this, 'message', msg);
   var obj = flag(this, 'object'),
     flagMsg = flag(this, 'message'),
@@ -3274,8 +3862,8 @@ Assertion.addMethod('members', function (subset, msg) {
   new Assertion(obj, flagMsg, ssfi, true).to.be.iterable;
   new Assertion(subset, flagMsg, ssfi, true).to.be.iterable;
 
-  var contains = flag(this, 'contains');
-  var ordered = flag(this, 'ordered');
+  var contains = flag(this, 'contains') === true;
+  var ordered = flag(this, 'ordered') === true;
 
   var subject, failMsg, failNegateMsg;
 
@@ -3293,7 +3881,7 @@ Assertion.addMethod('members', function (subset, msg) {
   var cmp = flag(this, 'deep') ? flag(this, 'eql') : undefined;
 
   this.assert(
-    isSubsetOf(subset, obj, cmp, contains, ordered),
+    isSubsetOf(subset, obj as Iterable<unknown>, cmp, contains, ordered),
     failMsg,
     failNegateMsg,
     subset,
@@ -3323,17 +3911,22 @@ Assertion.addMethod('members', function (subset, msg) {
  * @namespace BDD
  * @public
  */
-Assertion.addProperty('iterable', function (msg) {
-  if (msg) flag(this, 'message', msg);
-  var obj = flag(this, 'object');
+Assertion.addProperty(
+  'iterable',
+  function (this: Assertion<unknown>, msg?: string) {
+    if (msg) flag(this, 'message', msg);
+    var obj = flag(this, 'object');
 
-  this.assert(
-    obj != undefined && obj[Symbol.iterator],
-    'expected #{this} to be an iterable',
-    'expected #{this} to not be an iterable',
-    obj
-  );
-});
+    this.assert(
+      obj !== null &&
+        obj !== undefined &&
+        (obj as Record<PropertyKey, unknown>)[Symbol.iterator],
+      'expected #{this} to be an iterable',
+      'expected #{this} to not be an iterable',
+      obj
+    );
+  }
+);
 
 /**
  * ### .oneOf(list[, msg])
@@ -3372,7 +3965,7 @@ Assertion.addProperty('iterable', function (msg) {
  * @namespace BDD
  * @public
  */
-function oneOf(list, msg) {
+function oneOf(this: Assertion<unknown>, list: unknown[], msg?: string) {
   if (msg) flag(this, 'message', msg);
   var expected = flag(this, 'object'),
     flagMsg = flag(this, 'message'),
@@ -3380,12 +3973,12 @@ function oneOf(list, msg) {
     contains = flag(this, 'contains'),
     isDeep = flag(this, 'deep'),
     eql = flag(this, 'eql');
-  new Assertion(list, flagMsg, ssfi, true).to.be.an('array');
+  Assertion.create(list, flagMsg, ssfi, true).to.be.an('array');
 
   if (contains) {
     this.assert(
       list.some(function (possibility) {
-        return expected.indexOf(possibility) > -1;
+        return (expected as Array<unknown>).indexOf(possibility) > -1;
       }),
       'expected #{this} to contain one of #{exp}',
       'expected #{this} to not contain one of #{exp}',
@@ -3511,26 +4104,42 @@ Assertion.addMethod('oneOf', oneOf);
  * @namespace BDD
  * @public
  */
-function assertChanges(subject, prop, msg) {
+function assertChanges(
+  this: Assertion<unknown>,
+  subject: object,
+  prop: PropertyKey,
+  msg?: string
+): void;
+function assertChanges(this: Assertion<unknown>, subject: Function): void;
+function assertChanges(
+  this: Assertion<unknown>,
+  subject: Function | object,
+  prop?: PropertyKey,
+  msg?: string
+) {
   if (msg) flag(this, 'message', msg);
-  var fn = flag(this, 'object'),
+  var fn = flag(this, 'object') as () => void,
     flagMsg = flag(this, 'message'),
     ssfi = flag(this, 'ssfi');
-  new Assertion(fn, flagMsg, ssfi, true).is.a('function');
+  Assertion.create(fn, flagMsg, ssfi, true).is.a('function');
 
   var initial;
   if (!prop) {
-    new Assertion(subject, flagMsg, ssfi, true).is.a('function');
-    initial = subject();
+    Assertion.create(subject, flagMsg, ssfi, true).is.a('function');
+    initial = (subject as () => void)();
   } else {
-    new Assertion(subject, flagMsg, ssfi, true).to.have.property(prop);
-    initial = subject[prop];
+    Assertion.create(subject, flagMsg, ssfi, true).to.have.property(prop);
+    initial = (subject as Record<PropertyKey, unknown>)[prop];
   }
 
   fn();
 
-  var final = prop === undefined || prop === null ? subject() : subject[prop];
-  var msgObj = prop === undefined || prop === null ? initial : '.' + prop;
+  var final =
+    prop === undefined || prop === null
+      ? (subject as () => void)()
+      : (subject as Record<PropertyKey, unknown>)[prop];
+  var msgObj =
+    prop === undefined || prop === null ? initial : '.' + String(prop);
 
   // This gets flagged because of the .by(delta) assertion
   flag(this, 'deltaMsgObj', msgObj);
@@ -3627,29 +4236,45 @@ Assertion.addMethod('changes', assertChanges);
  * @namespace BDD
  * @public
  */
-function assertIncreases(subject, prop, msg) {
+function assertIncreases(this: Assertion<unknown>, subject: () => void): void;
+function assertIncreases(
+  this: Assertion<unknown>,
+  subject: object,
+  prop: PropertyKey,
+  msg?: string
+): void;
+function assertIncreases(
+  this: Assertion<unknown>,
+  subject: object | (() => number),
+  prop?: PropertyKey,
+  msg?: string
+): void {
   if (msg) flag(this, 'message', msg);
-  var fn = flag(this, 'object'),
+  var fn = flag(this, 'object') as () => void,
     flagMsg = flag(this, 'message'),
     ssfi = flag(this, 'ssfi');
-  new Assertion(fn, flagMsg, ssfi, true).is.a('function');
+  Assertion.create(fn, flagMsg, ssfi, true).is.a('function');
 
   var initial;
   if (!prop) {
-    new Assertion(subject, flagMsg, ssfi, true).is.a('function');
-    initial = subject();
+    Assertion.create(subject, flagMsg, ssfi, true).is.a('function');
+    initial = (subject as () => number)();
   } else {
-    new Assertion(subject, flagMsg, ssfi, true).to.have.property(prop);
-    initial = subject[prop];
+    Assertion.create(subject, flagMsg, ssfi, true).to.have.property(prop);
+    initial = (subject as Record<PropertyKey, number>)[prop];
   }
 
   // Make sure that the target is a number
-  new Assertion(initial, flagMsg, ssfi, true).is.a('number');
+  Assertion.create(initial, flagMsg, ssfi, true).is.a('number');
 
   fn();
 
-  var final = prop === undefined || prop === null ? subject() : subject[prop];
-  var msgObj = prop === undefined || prop === null ? initial : '.' + prop;
+  var final =
+    prop === undefined || prop === null
+      ? (subject as () => number)()
+      : (subject as Record<PropertyKey, number>)[prop];
+  var msgObj =
+    prop === undefined || prop === null ? initial : '.' + String(prop);
 
   flag(this, 'deltaMsgObj', msgObj);
   flag(this, 'initialDeltaValue', initial);
@@ -3745,29 +4370,45 @@ Assertion.addMethod('increases', assertIncreases);
  * @namespace BDD
  * @public
  */
-function assertDecreases(subject, prop, msg) {
+function assertDecreases(this: Assertion<unknown>, subject: () => number): void;
+function assertDecreases(
+  this: Assertion<unknown>,
+  subject: object,
+  prop: PropertyKey,
+  msg?: string
+): void;
+function assertDecreases(
+  this: Assertion<unknown>,
+  subject: object | (() => number),
+  prop?: PropertyKey,
+  msg?: string
+): void {
   if (msg) flag(this, 'message', msg);
-  var fn = flag(this, 'object'),
+  var fn = flag(this, 'object') as () => void,
     flagMsg = flag(this, 'message'),
     ssfi = flag(this, 'ssfi');
-  new Assertion(fn, flagMsg, ssfi, true).is.a('function');
+  Assertion.create(fn, flagMsg, ssfi, true).is.a('function');
 
   var initial;
   if (!prop) {
-    new Assertion(subject, flagMsg, ssfi, true).is.a('function');
-    initial = subject();
+    Assertion.create(subject, flagMsg, ssfi, true).is.a('function');
+    initial = (subject as () => number)();
   } else {
-    new Assertion(subject, flagMsg, ssfi, true).to.have.property(prop);
-    initial = subject[prop];
+    Assertion.create(subject, flagMsg, ssfi, true).to.have.property(prop);
+    initial = (subject as Record<PropertyKey, number>)[prop];
   }
 
   // Make sure that the target is a number
-  new Assertion(initial, flagMsg, ssfi, true).is.a('number');
+  Assertion.create(initial, flagMsg, ssfi, true).is.a('number');
 
   fn();
 
-  var final = prop === undefined || prop === null ? subject() : subject[prop];
-  var msgObj = prop === undefined || prop === null ? initial : '.' + prop;
+  var final =
+    prop === undefined || prop === null
+      ? (subject as () => number)()
+      : (subject as Record<PropertyKey, number>)[prop];
+  var msgObj =
+    prop === undefined || prop === null ? initial : '.' + String(prop);
 
   flag(this, 'deltaMsgObj', msgObj);
   flag(this, 'initialDeltaValue', initial);
@@ -3850,12 +4491,12 @@ Assertion.addMethod('decreases', assertDecreases);
  * @namespace BDD
  * @public
  */
-function assertDelta(delta, msg) {
+function assertDelta(this: Assertion<unknown>, delta: number, msg?: string) {
   if (msg) flag(this, 'message', msg);
 
   var msgObj = flag(this, 'deltaMsgObj');
-  var initial = flag(this, 'initialDeltaValue');
-  var final = flag(this, 'finalDeltaValue');
+  var initial = flag(this, 'initialDeltaValue') as number;
+  var final = flag(this, 'finalDeltaValue') as number;
   var behavior = flag(this, 'deltaBehavior');
   var realDelta = flag(this, 'realDelta');
 
@@ -4052,7 +4693,7 @@ Assertion.addProperty('frozen', function () {
  * @namespace BDD
  * @public
  */
-Assertion.addProperty('finite', function (_msg) {
+Assertion.addProperty('finite', function () {
   var obj = flag(this, 'object');
 
   this.assert(
diff --git a/lib/chai/interface/assert.js b/src/chai/interface/assert.ts
similarity index 60%
rename from lib/chai/interface/assert.js
rename to src/chai/interface/assert.ts
index 07a6767f..24c2ee8f 100644
--- a/lib/chai/interface/assert.js
+++ b/src/chai/interface/assert.ts
@@ -4,10 +4,576 @@
  * MIT Licensed
  */
 
-import * as chai from '../../../index.js';
 import {Assertion} from '../assertion.js';
 import {flag, inspect} from '../utils/index.js';
 import {AssertionError} from 'assertion-error';
+import {
+  Constructor,
+  LengthLike,
+  CollectionLike,
+  KeyedObject
+} from '../utils/types.js';
+
+export interface AssertInterface {
+  (expr: unknown, msg?: string): asserts expr;
+  fail(msg?: string): never;
+  fail<T>(actual: T, expected: T, message: string, operator: string): never;
+  isOk(val: unknown, msg?: string): void;
+  ok(val: unknown, msg?: string): void;
+  isNotOk(val: unknown, msg?: string): void;
+  notOk(val: unknown, msg?: string): void;
+  equal<TActual, TExpected extends TActual>(
+    actual: TActual,
+    expected: TExpected,
+    msg?: string
+  ): asserts actual is TExpected;
+  notEqual<T>(actual: T, expected: T, msg?: string): void;
+  strictEqual<TActual, TExpected extends TActual>(
+    actual: TActual,
+    expected: TExpected,
+    msg?: string
+  ): asserts actual is TExpected;
+  notStrictEqual<T>(actual: T, expected: T, msg?: string): void;
+  deepEqual<TActual, TExpected extends TActual>(
+    actual: TActual,
+    expected: TExpected,
+    msg?: string
+  ): asserts actual is TExpected;
+  deepStrictEqual<TActual, TExpected extends TActual>(
+    actual: TActual,
+    expected: TExpected,
+    msg?: string
+  ): asserts actual is TExpected;
+  notDeepEqual<T>(actual: T, expected: T, msg?: string): void;
+  isAbove<T extends Date | number>(val: T, abv: T, msg?: string): void;
+  isAtLeast<T extends Date | number>(val: T, atlst: T, msg?: string): void;
+  isBelow<T extends Date | number>(val: T, blw: T, msg?: string): void;
+  isAtMost<T extends Date | number>(val: T, atmst: T, msg?: string): void;
+  isTrue(val: unknown, msg?: string): asserts val is true;
+  isNotTrue<T>(val: T, msg?: string): asserts val is Exclude<T, true>;
+  isFalse(val: unknown, msg?: string): asserts val is false;
+  isNotFalse<T>(val: T, msg?: string): asserts val is Exclude<T, false>;
+  isNull(val: unknown, msg?: string): asserts val is null;
+  isNotNull<T>(val: T, msg?: string): asserts val is Exclude<T, null>;
+  isNaN(val: unknown, msg?: string): asserts val is number;
+  isNotNaN(val: unknown, msg?: string): void;
+  exists<T>(val: T, msg?: string): asserts val is NonNullable<T>;
+  notExists(val: unknown, msg?: string): void;
+  isUndefined(val: unknown, msg?: string): asserts val is undefined;
+  isDefined(val: unknown, msg?: string): void;
+  isFunction(val: unknown, msg?: string): asserts val is Function;
+  isNotFunction(val: unknown, msg?: string): void;
+  isObject(val: unknown, msg?: string): asserts val is object;
+  isNotObject(val: unknown, msg?: string): void;
+  isArray(val: unknown, msg?: string): asserts val is Array<unknown>;
+  isNotArray(val: unknown, msg?: string): void;
+  isString(val: unknown, msg?: string): asserts val is string;
+  isNotString<T>(val: T, msg?: string): asserts val is Exclude<T, string>;
+  isNumber(val: unknown, msg?: string): asserts val is number;
+  isNotNumber<T>(val: T, msg?: string): asserts val is Exclude<T, number>;
+  isNumeric(val: unknown, msg?: string): asserts val is number | bigint;
+  isNotNumeric<T>(
+    val: T,
+    msg?: string
+  ): asserts val is Exclude<T, number | bigint>;
+  isFinite(val: number, msg?: string): void;
+  isBoolean(val: unknown, msg?: string): asserts val is boolean;
+  isNotBoolean<T>(val: T, msg?: string): asserts val is Exclude<T, boolean>;
+
+  // typeof
+  typeOf(val: unknown, type: 'undefined'): asserts val is undefined;
+  typeOf(val: unknown, type: 'null'): asserts val is null;
+  typeOf(val: unknown, type: 'map'): asserts val is Map<unknown, unknown>;
+  typeOf(val: unknown, type: 'set'): asserts val is Set<unknown>;
+  typeOf(val: unknown, type: 'promise'): asserts val is Promise<unknown>;
+  typeOf(val: unknown, type: 'string'): asserts val is string;
+  typeOf(val: unknown, type: 'number'): asserts val is number;
+  typeOf(val: unknown, type: 'array'): asserts val is Array<unknown>;
+  typeOf(val: unknown, type: 'boolean'): asserts val is boolean;
+  typeOf(val: unknown, type: string, msg?: string): void;
+  notTypeOf(val: unknown, type: string, msg?: string): void;
+
+  // instanceof
+  instanceOf<T extends Constructor<unknown>>(
+    val: unknown,
+    type: T,
+    msg?: string
+  ): asserts val is InstanceType<T>;
+  notInstanceOf(val: object, type: Constructor<unknown>, msg?: string): void;
+
+  // includes
+  include(
+    expr: CollectionLike<unknown> | string | object,
+    inc: unknown,
+    msg?: string
+  ): void;
+  notInclude(
+    expr: CollectionLike<unknown> | string | object,
+    inc: unknown,
+    msg?: string
+  ): void;
+  deepInclude(
+    expr: CollectionLike<unknown> | string | object,
+    inc: unknown,
+    msg?: string
+  ): void;
+  notDeepInclude(
+    expr: CollectionLike<unknown> | string | object,
+    inc: unknown,
+    msg?: string
+  ): void;
+  nestedInclude(
+    expr: CollectionLike<unknown> | object,
+    inc: unknown,
+    msg?: string
+  ): void;
+  notNestedInclude(
+    expr: CollectionLike<unknown> | object,
+    inc: unknown,
+    msg?: string
+  ): void;
+  deepNestedInclude(
+    expr: CollectionLike<unknown> | object,
+    inc: unknown,
+    msg?: string
+  ): void;
+  notDeepNestedInclude(
+    expr: CollectionLike<unknown> | object,
+    inc: unknown,
+    msg?: string
+  ): void;
+  ownInclude(expr: object, inc: unknown, msg?: string): void;
+  notOwnInclude(expr: object, inc: unknown, msg?: string): void;
+  deepOwnInclude(expr: object, inc: unknown, msg?: string): void;
+  notDeepOwnInclude(expr: object, inc: unknown, msg?: string): void;
+
+  // match
+  match(expr: string, re: RegExp, msg?: string): void;
+  notMatch(expr: string, re: RegExp, msg?: string): void;
+
+  // properties
+  property<T extends object, TKey extends PropertyKey>(
+    obj: T,
+    prop: TKey,
+    msg?: string
+  ): asserts obj is TKey extends keyof T ? T : T & {[k in TKey]: unknown};
+  notProperty(obj: object, prop: PropertyKey, msg?: string): void;
+  propertyVal<T extends object, TKey extends keyof T>(
+    obj: T,
+    prop: TKey,
+    val: T[TKey],
+    msg?: string
+  ): void;
+  notPropertyVal<T extends object, TKey extends keyof T>(
+    obj: T,
+    prop: TKey,
+    val: T[TKey],
+    msg?: string
+  ): void;
+  deepPropertyVal<T extends object, TKey extends keyof T>(
+    obj: T,
+    prop: TKey,
+    val: T[TKey],
+    msg?: string
+  ): void;
+  notDeepPropertyVal<T extends object, TKey extends keyof T>(
+    obj: T,
+    prop: TKey,
+    val: T[TKey],
+    msg?: string
+  ): void;
+  ownProperty<T extends object, TKey extends PropertyKey>(
+    obj: T,
+    prop: TKey,
+    msg?: string
+  ): asserts obj is TKey extends keyof T ? T : T & {[k in TKey]: unknown};
+  notOwnProperty(obj: unknown, prop: PropertyKey, msg?: string): void;
+  ownPropertyVal<T extends object, TKey extends keyof T>(
+    obj: T,
+    prop: TKey,
+    val: T[TKey],
+    msg?: string
+  ): void;
+  notOwnPropertyVal<T extends object, TKey extends keyof T>(
+    obj: T,
+    prop: TKey,
+    val: T[TKey],
+    msg?: string
+  ): void;
+  deepOwnPropertyVal<T extends object, TKey extends keyof T>(
+    obj: T,
+    prop: TKey,
+    val: T[TKey],
+    msg?: string
+  ): void;
+  notDeepOwnPropertyVal<T extends object, TKey extends keyof T>(
+    obj: T,
+    prop: TKey,
+    val: T[TKey],
+    msg?: string
+  ): void;
+  nestedProperty(obj: unknown, prop: string, msg?: string): void;
+  notNestedProperty(obj: unknown, prop: string, msg?: string): void;
+  nestedPropertyVal(
+    obj: unknown,
+    prop: string,
+    val: unknown,
+    msg?: string
+  ): void;
+  notNestedPropertyVal(
+    obj: unknown,
+    prop: string,
+    val: unknown,
+    msg?: string
+  ): void;
+  deepNestedPropertyVal(
+    obj: unknown,
+    prop: string,
+    val: unknown,
+    msg?: string
+  ): void;
+  notDeepNestedPropertyVal(
+    obj: unknown,
+    prop: string,
+    val: unknown,
+    msg?: string
+  ): void;
+  lengthOf(expr: LengthLike, len: number, msg?: string): void;
+
+  // keys
+  hasAnyKeys(
+    obj: Set<unknown> | Map<unknown, unknown>,
+    keys: unknown[],
+    msg?: string
+  ): void;
+  hasAnyKeys(
+    obj: object,
+    keys: PropertyKey[] | Record<PropertyKey, unknown>,
+    msg?: string
+  ): void;
+
+  hasAllKeys(
+    obj: Set<unknown> | Map<unknown, unknown>,
+    keys: unknown[],
+    msg?: string
+  ): void;
+  hasAllKeys(
+    obj: object,
+    keys: PropertyKey[] | Record<PropertyKey, unknown>,
+    msg?: string
+  ): void;
+
+  containsAllKeys(
+    obj: Set<unknown> | Map<unknown, unknown>,
+    keys: unknown[],
+    msg?: string
+  ): void;
+  containsAllKeys(
+    obj: object,
+    keys: PropertyKey[] | Record<PropertyKey, unknown>,
+    msg?: string
+  ): void;
+
+  doesNotHaveAnyKeys(
+    obj: Set<unknown> | Map<unknown, unknown>,
+    keys: unknown[],
+    msg?: string
+  ): void;
+  doesNotHaveAnyKeys(
+    obj: object,
+    keys: PropertyKey[] | Record<PropertyKey, unknown>,
+    msg?: string
+  ): void;
+
+  doesNotHaveAllKeys(
+    obj: Set<unknown> | Map<unknown, unknown>,
+    keys: unknown[],
+    msg?: string
+  ): void;
+  doesNotHaveAllKeys(
+    obj: object,
+    keys: PropertyKey[] | Record<PropertyKey, unknown>,
+    msg?: string
+  ): void;
+
+  hasAnyDeepKeys(
+    obj: Set<unknown> | Map<unknown, unknown>,
+    keys: unknown[],
+    msg?: string
+  ): void;
+  hasAnyDeepKeys(
+    obj: object,
+    keys: PropertyKey[] | Record<PropertyKey, unknown>,
+    msg?: string
+  ): void;
+
+  hasAllDeepKeys(
+    obj: Set<unknown> | Map<unknown, unknown>,
+    keys: unknown[],
+    msg?: string
+  ): void;
+  hasAllDeepKeys(
+    obj: object,
+    keys: PropertyKey[] | Record<PropertyKey, unknown>,
+    msg?: string
+  ): void;
+
+  containsAllDeepKeys(
+    obj: Set<unknown> | Map<unknown, unknown>,
+    keys: unknown[],
+    msg?: string
+  ): void;
+  containsAllDeepKeys(
+    obj: object,
+    keys: PropertyKey[] | Record<PropertyKey, unknown>,
+    msg?: string
+  ): void;
+
+  doesNotHaveAnyDeepKeys(
+    obj: Set<unknown> | Map<unknown, unknown>,
+    keys: unknown[],
+    msg?: string
+  ): void;
+  doesNotHaveAnyDeepKeys(
+    obj: object,
+    keys: PropertyKey[] | Record<PropertyKey, unknown>,
+    msg?: string
+  ): void;
+
+  doesNotHaveAllDeepKeys(
+    obj: Set<unknown> | Map<unknown, unknown>,
+    keys: unknown[],
+    msg?: string
+  ): void;
+  doesNotHaveAllDeepKeys(
+    obj: object,
+    keys: PropertyKey[] | Record<PropertyKey, unknown>,
+    msg?: string
+  ): void;
+
+  Throw(
+    fn: Function,
+    errorLike: Error | Constructor<Error>,
+    errMsgMatcher: RegExp | string,
+    msg?: string
+  ): void;
+  Throw(fn: Function, errMsgMatcher: RegExp | string): void;
+  throw(
+    fn: Function,
+    errorLike: Error | Constructor<Error>,
+    errMsgMatcher: RegExp | string,
+    msg?: string
+  ): void;
+  throw(fn: Function, errMsgMatcher: RegExp | string): void;
+  throws(
+    fn: Function,
+    errorLike: Error | Constructor<Error>,
+    errMsgMatcher: RegExp | string,
+    msg?: string
+  ): void;
+  throws(fn: Function, errMsgMatcher: RegExp | string): void;
+
+  doesNotThrow(fn: Function, errMsgMatcher: RegExp | string): void;
+  doesNotThrow(
+    fn: Function,
+    errorLike: Error | Constructor<Error>,
+    errMsgMatcher: RegExp | string,
+    msg?: string
+  ): void;
+
+  operator<T>(val: T, operator: string, val2: T, msg?: string): void;
+  closeTo(actual: number, expected: number, delta: number, msg?: string): void;
+  approximately(
+    actual: number,
+    expected: number,
+    delta: number,
+    msg?: string
+  ): void;
+
+  // members
+  sameMembers<T>(set1: T[], set2: T[], msg?: string): void;
+  notSameMembers<T>(set1: T[], set2: T[], msg?: string): void;
+  sameDeepMembers<T>(set1: T[], set2: T[], msg?: string): void;
+  notSameDeepMembers<T>(set1: T[], set2: T[], msg?: string): void;
+  sameOrderedMembers<T>(set1: T[], set2: T[], msg?: string): void;
+  notSameOrderedMembers<T>(set1: T[], set2: T[], msg?: string): void;
+  sameDeepOrderedMembers<T>(set1: T[], set2: T[], msg?: string): void;
+  notSameDeepOrderedMembers<T>(set1: T[], set2: T[], msg?: string): void;
+  includeMembers<T>(superset: T[], subset: T[], msg?: string): void;
+  notIncludeMembers<T>(superset: T[], subset: T[], msg?: string): void;
+  includeDeepMembers<T>(superset: T[], subset: T[], msg?: string): void;
+  notIncludeDeepMembers<T>(superset: T[], subset: T[], msg?: string): void;
+  includeOrderedMembers<T>(superset: T[], subset: T[], msg?: string): void;
+  notIncludeOrderedMembers<T>(superset: T[], subset: T[], msg?: string): void;
+  includeDeepOrderedMembers<T>(superset: T[], subset: T[], msg?: string): void;
+  notIncludeDeepOrderedMembers<T>(
+    superset: T[],
+    subset: T[],
+    msg?: string
+  ): void;
+
+  oneOf<T extends string | unknown[]>(inList: T, list: T[], msg?: string): void;
+
+  changes<T>(fn: Function, obj: T, prop: keyof T, msg?: string): void;
+  changes(fn: Function, obj: () => void, msg?: string): void;
+
+  changesBy<T>(
+    fn: Function,
+    obj: T,
+    prop: keyof T,
+    delta: number,
+    msg?: string
+  ): void;
+  changesBy(fn: Function, obj: () => void, delta: number, msg?: string): void;
+
+  doesNotChange<T>(
+    fn: Function,
+    obj: T,
+    prop: keyof T,
+    msg?: string
+  ): Assertion<Function>;
+  doesNotChange(
+    fn: Function,
+    obj: () => void,
+    msg?: string
+  ): Assertion<Function>;
+
+  changesButNotBy<T>(
+    fn: Function,
+    obj: T,
+    prop: keyof T,
+    delta: number,
+    msg?: string
+  ): void;
+  changesButNotBy(
+    fn: Function,
+    obj: () => void,
+    delta: number,
+    msg?: string
+  ): void;
+
+  increases<T>(
+    fn: Function,
+    obj: T,
+    prop: keyof T,
+    msg?: string
+  ): Assertion<Function>;
+  increases(fn: Function, obj: () => void, msg?: string): Assertion<Function>;
+
+  increasesBy<T>(
+    fn: Function,
+    obj: T,
+    prop: keyof T,
+    delta: number,
+    msg?: string
+  ): void;
+  increasesBy(fn: Function, obj: () => void, delta: number, msg?: string): void;
+
+  doesNotIncrease<T>(
+    fn: Function,
+    obj: T,
+    prop: keyof T,
+    msg?: string
+  ): Assertion<Function>;
+  doesNotIncrease(
+    fn: Function,
+    obj: () => void,
+    msg?: string
+  ): Assertion<Function>;
+
+  increasesButNotBy<T>(
+    fn: Function,
+    obj: T,
+    prop: keyof T,
+    delta: number,
+    msg?: string
+  ): void;
+  increasesButNotBy(
+    fn: Function,
+    obj: () => void,
+    delta: number,
+    msg?: string
+  ): void;
+
+  decreases<T>(
+    fn: Function,
+    obj: T,
+    prop: keyof T,
+    msg?: string
+  ): Assertion<Function>;
+  decreases(fn: Function, obj: () => void, msg?: string): Assertion<Function>;
+
+  decreasesBy<T>(
+    fn: Function,
+    obj: T,
+    prop: keyof T,
+    delta: number,
+    msg?: string
+  ): void;
+  decreasesBy(fn: Function, obj: () => void, delta: number, msg?: string): void;
+
+  doesNotDecrease<T>(
+    fn: Function,
+    obj: T,
+    prop: keyof T,
+    msg?: string
+  ): Assertion<Function>;
+  doesNotDecrease(
+    fn: Function,
+    obj: () => void,
+    msg?: string
+  ): Assertion<Function>;
+
+  doesNotDecreaseBy<T>(
+    fn: Function,
+    obj: T,
+    prop: keyof T,
+    delta: number,
+    msg?: string
+  ): Assertion<Function>;
+  doesNotDecreaseBy(
+    fn: Function,
+    obj: () => void,
+    delta: number,
+    msg?: string
+  ): Assertion<Function>;
+
+  decreasesButNotBy<T>(
+    fn: Function,
+    obj: T,
+    prop: keyof T,
+    delta: number,
+    msg?: string
+  ): void;
+  decreasesButNotBy(
+    fn: Function,
+    obj: () => void,
+    delta: number,
+    msg?: string
+  ): void;
+
+  ifError(val: unknown): void;
+  isExtensible(obj: object, msg?: string): void;
+  extensible(obj: object, msg?: string): void;
+  isNotExtensible(obj: object, msg?: string): void;
+  notExtensible(obj: object, msg?: string): void;
+  isSealed(obj: object, msg?: string): void;
+  sealed(obj: object, msg?: string): void;
+  isNotSealed(obj: object, msg?: string): void;
+  notSealed(obj: object, msg?: string): void;
+  isFrozen(obj: object, msg?: string): void;
+  frozen(obj: object, msg?: string): void;
+  isNotFrozen(obj: object, msg?: string): void;
+  notFrozen(obj: object, msg?: string): void;
+  isEmpty(val: unknown, msg?: string): void;
+  empty(val: unknown, msg?: string): void;
+  isNotEmpty(val: unknown, msg?: string): void;
+  notEmpty(val: unknown, msg?: string): void;
+
+  isCallable(val: unknown, msg?: string): void;
+  isNotCallable(val: unknown, msg?: string): void;
+  isIterable(val: unknown, msg?: string): asserts val is Iterable<unknown>;
+}
 
 /**
  * ### assert(expression, message)
@@ -23,10 +589,15 @@ import {AssertionError} from 'assertion-error';
  * @namespace Assert
  * @public
  */
-export function assert(express, errmsg) {
-  var test = new Assertion(null, null, chai.assert, true);
+const assert: AssertInterface = function assert(
+  express: unknown,
+  errmsg?: string
+) {
+  var test = Assertion.create(null, null, assert, true);
   test.assert(express, errmsg, '[ negation message unavailable ]');
-}
+} as AssertInterface;
+
+export {assert};
 
 /**
  * ### .fail([message])
@@ -49,17 +620,25 @@ export function assert(express, errmsg) {
  * @namespace Assert
  * @public
  */
-assert.fail = function (actual, expected, message, operator) {
+assert.fail = function fail(
+  actualOrMsg?: unknown,
+  expected?: unknown,
+  message?: string,
+  operator?: string
+) {
+  let msg = message;
+  let actual = actualOrMsg;
+
   if (arguments.length < 2) {
     // Comply with Node's fail([message]) interface
 
-    message = actual;
+    msg = actualOrMsg as string;
     actual = undefined;
   }
 
-  message = message || 'assert.fail()';
+  msg = msg || 'assert.fail()';
   throw new AssertionError(
-    message,
+    msg,
     {
       actual: actual,
       expected: expected,
@@ -84,8 +663,8 @@ assert.fail = function (actual, expected, message, operator) {
  * @namespace Assert
  * @public
  */
-assert.isOk = function (val, msg) {
-  new Assertion(val, msg, assert.isOk, true).is.ok;
+assert.isOk = assert.ok = function (val: unknown, msg?: string) {
+  Assertion.create(val, msg, assert.isOk, true).is.ok;
 };
 
 /**
@@ -103,8 +682,8 @@ assert.isOk = function (val, msg) {
  * @namespace Assert
  * @public
  */
-assert.isNotOk = function (val, msg) {
-  new Assertion(val, msg, assert.isNotOk, true).is.not.ok;
+assert.isNotOk = assert.notOk = function (val: unknown, msg?: string) {
+  Assertion.create(val, msg, assert.isNotOk, true).is.not.ok;
 };
 
 /**
@@ -121,8 +700,8 @@ assert.isNotOk = function (val, msg) {
  * @namespace Assert
  * @public
  */
-assert.equal = function (act, exp, msg) {
-  var test = new Assertion(act, msg, assert.equal, true);
+assert.equal = function (act: unknown, exp: unknown, msg?: string) {
+  var test = Assertion.create(act, msg, assert.equal, true);
 
   test.assert(
     exp == flag(test, 'object'),
@@ -148,8 +727,8 @@ assert.equal = function (act, exp, msg) {
  * @namespace Assert
  * @public
  */
-assert.notEqual = function (act, exp, msg) {
-  var test = new Assertion(act, msg, assert.notEqual, true);
+assert.notEqual = function (act: unknown, exp: unknown, msg?: string) {
+  var test = Assertion.create(act, msg, assert.notEqual, true);
 
   test.assert(
     exp != flag(test, 'object'),
@@ -175,8 +754,8 @@ assert.notEqual = function (act, exp, msg) {
  * @namespace Assert
  * @public
  */
-assert.strictEqual = function (act, exp, msg) {
-  new Assertion(act, msg, assert.strictEqual, true).to.equal(exp);
+assert.strictEqual = function (act: unknown, exp: unknown, msg?: string) {
+  Assertion.create(act, msg, assert.strictEqual, true).to.equal(exp);
 };
 
 /**
@@ -193,8 +772,8 @@ assert.strictEqual = function (act, exp, msg) {
  * @namespace Assert
  * @public
  */
-assert.notStrictEqual = function (act, exp, msg) {
-  new Assertion(act, msg, assert.notStrictEqual, true).to.not.equal(exp);
+assert.notStrictEqual = function (act: unknown, exp: unknown, msg?: string) {
+  Assertion.create(act, msg, assert.notStrictEqual, true).to.not.equal(exp);
 };
 
 /**
@@ -212,8 +791,12 @@ assert.notStrictEqual = function (act, exp, msg) {
  * @namespace Assert
  * @public
  */
-assert.deepEqual = assert.deepStrictEqual = function (act, exp, msg) {
-  new Assertion(act, msg, assert.deepEqual, true).to.eql(exp);
+assert.deepEqual = assert.deepStrictEqual = function (
+  act: unknown,
+  exp: unknown,
+  msg?: string
+) {
+  Assertion.create(act, msg, assert.deepEqual, true).to.eql(exp);
 };
 
 /**
@@ -230,8 +813,8 @@ assert.deepEqual = assert.deepStrictEqual = function (act, exp, msg) {
  * @namespace Assert
  * @public
  */
-assert.notDeepEqual = function (act, exp, msg) {
-  new Assertion(act, msg, assert.notDeepEqual, true).to.not.eql(exp);
+assert.notDeepEqual = function (act: unknown, exp: unknown, msg?: string) {
+  Assertion.create(act, msg, assert.notDeepEqual, true).to.not.eql(exp);
 };
 
 /**
@@ -248,8 +831,12 @@ assert.notDeepEqual = function (act, exp, msg) {
  * @namespace Assert
  * @public
  */
-assert.isAbove = function (val, abv, msg) {
-  new Assertion(val, msg, assert.isAbove, true).to.be.above(abv);
+assert.isAbove = function isAbove<T extends Date | number>(
+  val: T,
+  abv: T,
+  msg?: string
+): void {
+  Assertion.create(val, msg, assert.isAbove, true).to.be.above(abv);
 };
 
 /**
@@ -267,8 +854,12 @@ assert.isAbove = function (val, abv, msg) {
  * @namespace Assert
  * @public
  */
-assert.isAtLeast = function (val, atlst, msg) {
-  new Assertion(val, msg, assert.isAtLeast, true).to.be.least(atlst);
+assert.isAtLeast = function isAtLeast<T extends Date | number>(
+  val: T,
+  atlst: T,
+  msg?: string
+) {
+  Assertion.create(val, msg, assert.isAtLeast, true).to.be.least(atlst);
 };
 
 /**
@@ -285,8 +876,12 @@ assert.isAtLeast = function (val, atlst, msg) {
  * @namespace Assert
  * @public
  */
-assert.isBelow = function (val, blw, msg) {
-  new Assertion(val, msg, assert.isBelow, true).to.be.below(blw);
+assert.isBelow = function isBelow<T extends Date | number>(
+  val: T,
+  blw: T,
+  msg?: string
+) {
+  Assertion.create(val, msg, assert.isBelow, true).to.be.below(blw);
 };
 
 /**
@@ -304,8 +899,12 @@ assert.isBelow = function (val, blw, msg) {
  * @namespace Assert
  * @public
  */
-assert.isAtMost = function (val, atmst, msg) {
-  new Assertion(val, msg, assert.isAtMost, true).to.be.most(atmst);
+assert.isAtMost = function isAtMost<T extends Date | number>(
+  val: T,
+  atmst: T,
+  msg?: string
+) {
+  Assertion.create(val, msg, assert.isAtMost, true).to.be.most(atmst);
 };
 
 /**
@@ -322,8 +921,8 @@ assert.isAtMost = function (val, atmst, msg) {
  * @namespace Assert
  * @public
  */
-assert.isTrue = function (val, msg) {
-  new Assertion(val, msg, assert.isTrue, true).is['true'];
+assert.isTrue = function (val: unknown, msg?: string) {
+  Assertion.create(val, msg, assert.isTrue, true).is['true'];
 };
 
 /**
@@ -340,8 +939,8 @@ assert.isTrue = function (val, msg) {
  * @namespace Assert
  * @public
  */
-assert.isNotTrue = function (val, msg) {
-  new Assertion(val, msg, assert.isNotTrue, true).to.not.equal(true);
+assert.isNotTrue = function (val: unknown, msg?: string) {
+  Assertion.create(val, msg, assert.isNotTrue, true).to.not.equal(true);
 };
 
 /**
@@ -358,8 +957,8 @@ assert.isNotTrue = function (val, msg) {
  * @namespace Assert
  * @public
  */
-assert.isFalse = function (val, msg) {
-  new Assertion(val, msg, assert.isFalse, true).is['false'];
+assert.isFalse = function (val: unknown, msg?: string) {
+  Assertion.create(val, msg, assert.isFalse, true).is['false'];
 };
 
 /**
@@ -376,8 +975,8 @@ assert.isFalse = function (val, msg) {
  * @namespace Assert
  * @public
  */
-assert.isNotFalse = function (val, msg) {
-  new Assertion(val, msg, assert.isNotFalse, true).to.not.equal(false);
+assert.isNotFalse = function (val: unknown, msg?: string) {
+  Assertion.create(val, msg, assert.isNotFalse, true).to.not.equal(false);
 };
 
 /**
@@ -393,8 +992,8 @@ assert.isNotFalse = function (val, msg) {
  * @namespace Assert
  * @public
  */
-assert.isNull = function (val, msg) {
-  new Assertion(val, msg, assert.isNull, true).to.equal(null);
+assert.isNull = function (val: unknown, msg?: string) {
+  Assertion.create(val, msg, assert.isNull, true).to.equal(null);
 };
 
 /**
@@ -411,8 +1010,8 @@ assert.isNull = function (val, msg) {
  * @namespace Assert
  * @public
  */
-assert.isNotNull = function (val, msg) {
-  new Assertion(val, msg, assert.isNotNull, true).to.not.equal(null);
+assert.isNotNull = function (val: unknown, msg?: string) {
+  Assertion.create(val, msg, assert.isNotNull, true).to.not.equal(null);
 };
 
 /**
@@ -428,8 +1027,8 @@ assert.isNotNull = function (val, msg) {
  * @namespace Assert
  * @public
  */
-assert.isNaN = function (val, msg) {
-  new Assertion(val, msg, assert.isNaN, true).to.be.NaN;
+assert.isNaN = function (val: unknown, msg?: string) {
+  Assertion.create(val, msg, assert.isNaN, true).to.be.NaN;
 };
 
 /**
@@ -440,13 +1039,13 @@ assert.isNaN = function (val, msg) {
  *     assert.isNotNaN(4, '4 is not NaN');
  *
  * @name isNotNaN
- * @param {unknown} value
- * @param {string} message
+ * @param {unknown} val
+ * @param {string} msg
  * @namespace Assert
  * @public
  */
-assert.isNotNaN = function (value, message) {
-  new Assertion(value, message, assert.isNotNaN, true).not.to.be.NaN;
+assert.isNotNaN = function (val: unknown, msg?: string) {
+  Assertion.create(val, msg, assert.isNotNaN, true).not.to.be.NaN;
 };
 
 /**
@@ -463,8 +1062,8 @@ assert.isNotNaN = function (value, message) {
  * @namespace Assert
  * @public
  */
-assert.exists = function (val, msg) {
-  new Assertion(val, msg, assert.exists, true).to.exist;
+assert.exists = function (val: unknown, msg?: string) {
+  Assertion.create(val, msg, assert.exists, true).to.exist;
 };
 
 /**
@@ -484,8 +1083,8 @@ assert.exists = function (val, msg) {
  * @namespace Assert
  * @public
  */
-assert.notExists = function (val, msg) {
-  new Assertion(val, msg, assert.notExists, true).to.not.exist;
+assert.notExists = function (val: unknown, msg?: string) {
+  Assertion.create(val, msg, assert.notExists, true).to.not.exist;
 };
 
 /**
@@ -502,8 +1101,8 @@ assert.notExists = function (val, msg) {
  * @namespace Assert
  * @public
  */
-assert.isUndefined = function (val, msg) {
-  new Assertion(val, msg, assert.isUndefined, true).to.equal(undefined);
+assert.isUndefined = function (val: unknown, msg?: string) {
+  Assertion.create(val, msg, assert.isUndefined, true).to.equal(undefined);
 };
 
 /**
@@ -520,8 +1119,8 @@ assert.isUndefined = function (val, msg) {
  * @namespace Assert
  * @public
  */
-assert.isDefined = function (val, msg) {
-  new Assertion(val, msg, assert.isDefined, true).to.not.equal(undefined);
+assert.isDefined = function (val: unknown, msg?: string) {
+  Assertion.create(val, msg, assert.isDefined, true).to.not.equal(undefined);
 };
 
 /**
@@ -538,10 +1137,12 @@ assert.isDefined = function (val, msg) {
  * @namespace Assert
  * @public
  */
-assert.isCallable = function (value, message) {
+assert.isCallable = function (value: unknown, message?: string) {
   new Assertion(value, message, assert.isCallable, true).is.callable;
 };
 
+assert.isFunction = assert.isCallable;
+
 /**
  * ### .isNotCallable(value, [message])
  *
@@ -556,10 +1157,12 @@ assert.isCallable = function (value, message) {
  * @namespace Assert
  * @public
  */
-assert.isNotCallable = function (value, message) {
+assert.isNotCallable = function (value: unknown, message?: string) {
   new Assertion(value, message, assert.isNotCallable, true).is.not.callable;
 };
 
+assert.isNotFunction = assert.isNotCallable;
+
 /**
  * ### .isObject(value, [message])
  *
@@ -575,8 +1178,8 @@ assert.isNotCallable = function (value, message) {
  * @namespace Assert
  * @public
  */
-assert.isObject = function (val, msg) {
-  new Assertion(val, msg, assert.isObject, true).to.be.a('object');
+assert.isObject = function (val: unknown, msg?: string) {
+  Assertion.create(val, msg, assert.isObject, true).to.be.a('object');
 };
 
 /**
@@ -594,8 +1197,8 @@ assert.isObject = function (val, msg) {
  * @namespace Assert
  * @public
  */
-assert.isNotObject = function (val, msg) {
-  new Assertion(val, msg, assert.isNotObject, true).to.not.be.a('object');
+assert.isNotObject = function (val: unknown, msg?: string) {
+  Assertion.create(val, msg, assert.isNotObject, true).to.not.be.a('object');
 };
 
 /**
@@ -612,8 +1215,8 @@ assert.isNotObject = function (val, msg) {
  * @namespace Assert
  * @public
  */
-assert.isArray = function (val, msg) {
-  new Assertion(val, msg, assert.isArray, true).to.be.an('array');
+assert.isArray = function (val: unknown, msg?: string) {
+  Assertion.create(val, msg, assert.isArray, true).to.be.an('array');
 };
 
 /**
@@ -630,8 +1233,8 @@ assert.isArray = function (val, msg) {
  * @namespace Assert
  * @public
  */
-assert.isNotArray = function (val, msg) {
-  new Assertion(val, msg, assert.isNotArray, true).to.not.be.an('array');
+assert.isNotArray = function (val: unknown, msg?: string) {
+  Assertion.create(val, msg, assert.isNotArray, true).to.not.be.an('array');
 };
 
 /**
@@ -648,8 +1251,8 @@ assert.isNotArray = function (val, msg) {
  * @namespace Assert
  * @public
  */
-assert.isString = function (val, msg) {
-  new Assertion(val, msg, assert.isString, true).to.be.a('string');
+assert.isString = function (val: unknown, msg?: string) {
+  Assertion.create(val, msg, assert.isString, true).to.be.a('string');
 };
 
 /**
@@ -666,8 +1269,8 @@ assert.isString = function (val, msg) {
  * @namespace Assert
  * @public
  */
-assert.isNotString = function (val, msg) {
-  new Assertion(val, msg, assert.isNotString, true).to.not.be.a('string');
+assert.isNotString = function (val: unknown, msg?: string) {
+  Assertion.create(val, msg, assert.isNotString, true).to.not.be.a('string');
 };
 
 /**
@@ -684,8 +1287,8 @@ assert.isNotString = function (val, msg) {
  * @namespace Assert
  * @public
  */
-assert.isNumber = function (val, msg) {
-  new Assertion(val, msg, assert.isNumber, true).to.be.a('number');
+assert.isNumber = function (val: number, msg?: string) {
+  Assertion.create(val, msg, assert.isNumber, true).to.be.a('number');
 };
 
 /**
@@ -702,7 +1305,7 @@ assert.isNumber = function (val, msg) {
  * @namespace Assert
  * @public
  */
-assert.isNotNumber = function (val, msg) {
+assert.isNotNumber = function (val: unknown, msg?: string) {
   new Assertion(val, msg, assert.isNotNumber, true).to.not.be.a('number');
 };
 
@@ -723,7 +1326,7 @@ assert.isNotNumber = function (val, msg) {
  * @namespace Assert
  * @public
  */
-assert.isNumeric = function (val, msg) {
+assert.isNumeric = function (val: unknown, msg?: string) {
   new Assertion(val, msg, assert.isNumeric, true).is.numeric;
 };
 
@@ -741,7 +1344,7 @@ assert.isNumeric = function (val, msg) {
  * @namespace Assert
  * @public
  */
-assert.isNotNumeric = function (val, msg) {
+assert.isNotNumeric = function (val: unknown, msg?: string) {
   new Assertion(val, msg, assert.isNotNumeric, true).is.not.numeric;
 };
 
@@ -760,7 +1363,7 @@ assert.isNotNumeric = function (val, msg) {
  * @namespace Assert
  * @public
  */
-assert.isFinite = function (val, msg) {
+assert.isFinite = function (val: unknown, msg?: string) {
   new Assertion(val, msg, assert.isFinite, true).to.be.finite;
 };
 
@@ -781,8 +1384,8 @@ assert.isFinite = function (val, msg) {
  * @namespace Assert
  * @public
  */
-assert.isBoolean = function (val, msg) {
-  new Assertion(val, msg, assert.isBoolean, true).to.be.a('boolean');
+assert.isBoolean = function (val: unknown, msg?: string) {
+  Assertion.create(val, msg, assert.isBoolean, true).to.be.a('boolean');
 };
 
 /**
@@ -802,8 +1405,8 @@ assert.isBoolean = function (val, msg) {
  * @namespace Assert
  * @public
  */
-assert.isNotBoolean = function (val, msg) {
-  new Assertion(val, msg, assert.isNotBoolean, true).to.not.be.a('boolean');
+assert.isNotBoolean = function (val: unknown, msg?: string) {
+  Assertion.create(val, msg, assert.isNotBoolean, true).to.not.be.a('boolean');
 };
 
 /**
@@ -826,8 +1429,8 @@ assert.isNotBoolean = function (val, msg) {
  * @namespace Assert
  * @public
  */
-assert.typeOf = function (val, type, msg) {
-  new Assertion(val, msg, assert.typeOf, true).to.be.a(type);
+assert.typeOf = function (val: unknown, type: string, msg?: string) {
+  Assertion.create(val, msg, assert.typeOf, true).to.be.a(type);
 };
 
 /**
@@ -839,14 +1442,14 @@ assert.typeOf = function (val, type, msg) {
  *     assert.notTypeOf('tea', 'number', 'strings are not numbers');
  *
  * @name notTypeOf
- * @param {unknown} value
+ * @param {unknown} val
  * @param {string} type
- * @param {string} message
+ * @param {string} msg
  * @namespace Assert
  * @public
  */
-assert.notTypeOf = function (value, type, message) {
-  new Assertion(value, message, assert.notTypeOf, true).to.not.be.a(type);
+assert.notTypeOf = function (val: unknown, type: string, msg?: string) {
+  Assertion.create(val, msg, assert.notTypeOf, true).to.not.be.a(type);
 };
 
 /**
@@ -866,8 +1469,12 @@ assert.notTypeOf = function (value, type, message) {
  * @namespace Assert
  * @public
  */
-assert.instanceOf = function (val, type, msg) {
-  new Assertion(val, msg, assert.instanceOf, true).to.be.instanceOf(type);
+assert.instanceOf = function instanceOf<T>(
+  val: T,
+  type: Constructor<T>,
+  msg?: string
+) {
+  Assertion.create(val, msg, assert.instanceOf, true).to.be.instanceOf(type);
 };
 
 /**
@@ -887,8 +1494,12 @@ assert.instanceOf = function (val, type, msg) {
  * @namespace Assert
  * @public
  */
-assert.notInstanceOf = function (val, type, msg) {
-  new Assertion(val, msg, assert.notInstanceOf, true).to.not.be.instanceOf(
+assert.notInstanceOf = function (
+  val: object,
+  type: Constructor<unknown>,
+  msg?: string
+) {
+  Assertion.create(val, msg, assert.notInstanceOf, true).to.not.be.instanceOf(
     type
   );
 };
@@ -923,8 +1534,12 @@ assert.notInstanceOf = function (val, type, msg) {
  * @namespace Assert
  * @public
  */
-assert.include = function (exp, inc, msg) {
-  new Assertion(exp, msg, assert.include, true).include(inc);
+assert.include = function include(
+  exp: CollectionLike<never> | string | object,
+  inc: unknown,
+  msg?: string
+) {
+  Assertion.create(exp, msg, assert.include, true).include(inc);
 };
 
 /**
@@ -958,8 +1573,12 @@ assert.include = function (exp, inc, msg) {
  * @namespace Assert
  * @public
  */
-assert.notInclude = function (exp, inc, msg) {
-  new Assertion(exp, msg, assert.notInclude, true).not.include(inc);
+assert.notInclude = function notInclude(
+  exp: CollectionLike<never> | string | object,
+  inc: unknown,
+  msg?: string
+) {
+  Assertion.create(exp, msg, assert.notInclude, true).not.include(inc);
 };
 
 /**
@@ -982,8 +1601,12 @@ assert.notInclude = function (exp, inc, msg) {
  * @namespace Assert
  * @public
  */
-assert.deepInclude = function (exp, inc, msg) {
-  new Assertion(exp, msg, assert.deepInclude, true).deep.include(inc);
+assert.deepInclude = function deepInclude(
+  exp: CollectionLike<never> | object,
+  inc: unknown,
+  msg?: string
+) {
+  Assertion.create(exp, msg, assert.deepInclude, true).deep.include(inc);
 };
 
 /**
@@ -1006,8 +1629,12 @@ assert.deepInclude = function (exp, inc, msg) {
  * @namespace Assert
  * @public
  */
-assert.notDeepInclude = function (exp, inc, msg) {
-  new Assertion(exp, msg, assert.notDeepInclude, true).not.deep.include(inc);
+assert.notDeepInclude = function notDeepInclude(
+  exp: CollectionLike<never> | object,
+  inc: unknown,
+  msg?: string
+) {
+  Assertion.create(exp, msg, assert.notDeepInclude, true).not.deep.include(inc);
 };
 
 /**
@@ -1030,8 +1657,12 @@ assert.notDeepInclude = function (exp, inc, msg) {
  * @namespace Assert
  * @public
  */
-assert.nestedInclude = function (exp, inc, msg) {
-  new Assertion(exp, msg, assert.nestedInclude, true).nested.include(inc);
+assert.nestedInclude = function (
+  exp: CollectionLike<never> | object,
+  inc: unknown,
+  msg?: string
+) {
+  Assertion.create(exp, msg, assert.nestedInclude, true).nested.include(inc);
 };
 
 /**
@@ -1054,8 +1685,12 @@ assert.nestedInclude = function (exp, inc, msg) {
  * @namespace Assert
  * @public
  */
-assert.notNestedInclude = function (exp, inc, msg) {
-  new Assertion(exp, msg, assert.notNestedInclude, true).not.nested.include(
+assert.notNestedInclude = function (
+  exp: CollectionLike<never> | object,
+  inc: unknown,
+  msg?: string
+) {
+  Assertion.create(exp, msg, assert.notNestedInclude, true).not.nested.include(
     inc
   );
 };
@@ -1080,10 +1715,17 @@ assert.notNestedInclude = function (exp, inc, msg) {
  * @namespace Assert
  * @public
  */
-assert.deepNestedInclude = function (exp, inc, msg) {
-  new Assertion(exp, msg, assert.deepNestedInclude, true).deep.nested.include(
-    inc
-  );
+assert.deepNestedInclude = function (
+  exp: CollectionLike<never> | object,
+  inc: unknown,
+  msg?: string
+) {
+  Assertion.create(
+    exp,
+    msg,
+    assert.deepNestedInclude,
+    true
+  ).deep.nested.include(inc);
 };
 
 /**
@@ -1106,8 +1748,12 @@ assert.deepNestedInclude = function (exp, inc, msg) {
  * @namespace Assert
  * @public
  */
-assert.notDeepNestedInclude = function (exp, inc, msg) {
-  new Assertion(
+assert.notDeepNestedInclude = function (
+  exp: CollectionLike<never> | object,
+  inc: unknown,
+  msg?: string
+) {
+  Assertion.create(
     exp,
     msg,
     assert.notDeepNestedInclude,
@@ -1131,8 +1777,8 @@ assert.notDeepNestedInclude = function (exp, inc, msg) {
  * @namespace Assert
  * @public
  */
-assert.ownInclude = function (exp, inc, msg) {
-  new Assertion(exp, msg, assert.ownInclude, true).own.include(inc);
+assert.ownInclude = function (exp: object, inc: unknown, msg?: string) {
+  Assertion.create(exp, msg, assert.ownInclude, true).own.include(inc);
 };
 
 /**
@@ -1152,8 +1798,8 @@ assert.ownInclude = function (exp, inc, msg) {
  * @namespace Assert
  * @public
  */
-assert.notOwnInclude = function (exp, inc, msg) {
-  new Assertion(exp, msg, assert.notOwnInclude, true).not.own.include(inc);
+assert.notOwnInclude = function (exp: object, inc: unknown, msg?: string) {
+  Assertion.create(exp, msg, assert.notOwnInclude, true).not.own.include(inc);
 };
 
 /**
@@ -1172,8 +1818,8 @@ assert.notOwnInclude = function (exp, inc, msg) {
  * @namespace Assert
  * @public
  */
-assert.deepOwnInclude = function (exp, inc, msg) {
-  new Assertion(exp, msg, assert.deepOwnInclude, true).deep.own.include(inc);
+assert.deepOwnInclude = function (exp: object, inc: unknown, msg?: string) {
+  Assertion.create(exp, msg, assert.deepOwnInclude, true).deep.own.include(inc);
 };
 
 /**
@@ -1192,10 +1838,13 @@ assert.deepOwnInclude = function (exp, inc, msg) {
  * @namespace Assert
  * @public
  */
-assert.notDeepOwnInclude = function (exp, inc, msg) {
-  new Assertion(exp, msg, assert.notDeepOwnInclude, true).not.deep.own.include(
-    inc
-  );
+assert.notDeepOwnInclude = function (exp: object, inc: unknown, msg?: string) {
+  Assertion.create(
+    exp,
+    msg,
+    assert.notDeepOwnInclude,
+    true
+  ).not.deep.own.include(inc);
 };
 
 /**
@@ -1212,8 +1861,8 @@ assert.notDeepOwnInclude = function (exp, inc, msg) {
  * @namespace Assert
  * @public
  */
-assert.match = function (exp, re, msg) {
-  new Assertion(exp, msg, assert.match, true).to.match(re);
+assert.match = function (exp: string, re: RegExp, msg?: string) {
+  Assertion.create(exp, msg, assert.match, true).to.match(re);
 };
 
 /**
@@ -1230,8 +1879,8 @@ assert.match = function (exp, re, msg) {
  * @namespace Assert
  * @public
  */
-assert.notMatch = function (exp, re, msg) {
-  new Assertion(exp, msg, assert.notMatch, true).to.not.match(re);
+assert.notMatch = function (exp: string, re: RegExp, msg?: string) {
+  Assertion.create(exp, msg, assert.notMatch, true).to.not.match(re);
 };
 
 /**
@@ -1250,8 +1899,12 @@ assert.notMatch = function (exp, re, msg) {
  * @namespace Assert
  * @public
  */
-assert.property = function (obj, prop, msg) {
-  new Assertion(obj, msg, assert.property, true).to.have.property(prop);
+assert.property = function property(
+  obj: object,
+  prop: PropertyKey,
+  msg?: string
+) {
+  Assertion.create(obj, msg, assert.property, true).to.have.property(prop);
 };
 
 /**
@@ -1269,8 +1922,10 @@ assert.property = function (obj, prop, msg) {
  * @namespace Assert
  * @public
  */
-assert.notProperty = function (obj, prop, msg) {
-  new Assertion(obj, msg, assert.notProperty, true).to.not.have.property(prop);
+assert.notProperty = function (obj: object, prop: string, msg?: string) {
+  Assertion.create(obj, msg, assert.notProperty, true).to.not.have.property(
+    prop
+  );
 };
 
 /**
@@ -1290,8 +1945,14 @@ assert.notProperty = function (obj, prop, msg) {
  * @namespace Assert
  * @public
  */
-assert.propertyVal = function (obj, prop, val, msg) {
-  new Assertion(obj, msg, assert.propertyVal, true).to.have.property(prop, val);
+assert.propertyVal = function propertyVal<
+  T extends object,
+  TKey extends keyof T
+>(obj: T, prop: TKey, val: T[TKey], msg?: string) {
+  Assertion.create(obj, msg, assert.propertyVal, true).to.have.property(
+    prop,
+    val
+  );
 };
 
 /**
@@ -1312,8 +1973,13 @@ assert.propertyVal = function (obj, prop, val, msg) {
  * @namespace Assert
  * @public
  */
-assert.notPropertyVal = function (obj, prop, val, msg) {
-  new Assertion(obj, msg, assert.notPropertyVal, true).to.not.have.property(
+assert.notPropertyVal = function notPropertyVal<T, TKey extends keyof T>(
+  obj: T,
+  prop: TKey,
+  val: T[TKey],
+  msg?: string
+) {
+  Assertion.create(obj, msg, assert.notPropertyVal, true).to.not.have.property(
     prop,
     val
   );
@@ -1335,11 +2001,18 @@ assert.notPropertyVal = function (obj, prop, val, msg) {
  * @namespace Assert
  * @public
  */
-assert.deepPropertyVal = function (obj, prop, val, msg) {
-  new Assertion(obj, msg, assert.deepPropertyVal, true).to.have.deep.property(
-    prop,
-    val
-  );
+assert.deepPropertyVal = function deepPropertyVal<T, TKey extends keyof T>(
+  obj: T,
+  prop: TKey,
+  val: T[TKey],
+  msg?: string
+) {
+  Assertion.create(
+    obj,
+    msg,
+    assert.deepPropertyVal,
+    true
+  ).to.have.deep.property(prop, val);
 };
 
 /**
@@ -1360,8 +2033,11 @@ assert.deepPropertyVal = function (obj, prop, val, msg) {
  * @namespace Assert
  * @public
  */
-assert.notDeepPropertyVal = function (obj, prop, val, msg) {
-  new Assertion(
+assert.notDeepPropertyVal = function notDeepPropertyVal<
+  T,
+  TKey extends keyof T
+>(obj: T, prop: TKey, val: T[TKey], msg?: string) {
+  Assertion.create(
     obj,
     msg,
     assert.notDeepPropertyVal,
@@ -1383,8 +2059,13 @@ assert.notDeepPropertyVal = function (obj, prop, val, msg) {
  * @param {string} msg
  * @public
  */
-assert.ownProperty = function (obj, prop, msg) {
-  new Assertion(obj, msg, assert.ownProperty, true).to.have.own.property(prop);
+assert.ownProperty = function ownProperty<
+  T extends object,
+  TKey extends PropertyKey
+>(obj: T, prop: TKey, msg?: string) {
+  Assertion.create(obj, msg, assert.ownProperty, true).to.have.own.property(
+    prop
+  );
 };
 
 /**
@@ -1402,10 +2083,13 @@ assert.ownProperty = function (obj, prop, msg) {
  * @param {string} msg
  * @public
  */
-assert.notOwnProperty = function (obj, prop, msg) {
-  new Assertion(obj, msg, assert.notOwnProperty, true).to.not.have.own.property(
-    prop
-  );
+assert.notOwnProperty = function (obj: object, prop: string, msg?: string) {
+  Assertion.create(
+    obj,
+    msg,
+    assert.notOwnProperty,
+    true
+  ).to.not.have.own.property(prop);
 };
 
 /**
@@ -1424,8 +2108,13 @@ assert.notOwnProperty = function (obj, prop, msg) {
  * @param {string} msg
  * @public
  */
-assert.ownPropertyVal = function (obj, prop, value, msg) {
-  new Assertion(obj, msg, assert.ownPropertyVal, true).to.have.own.property(
+assert.ownPropertyVal = function ownPropertyVal<T, TKey extends keyof T>(
+  obj: T,
+  prop: TKey,
+  value: T[TKey],
+  msg?: string
+) {
+  Assertion.create(obj, msg, assert.ownPropertyVal, true).to.have.own.property(
     prop,
     value
   );
@@ -1448,8 +2137,13 @@ assert.ownPropertyVal = function (obj, prop, value, msg) {
  * @param {string} msg
  * @public
  */
-assert.notOwnPropertyVal = function (obj, prop, value, msg) {
-  new Assertion(
+assert.notOwnPropertyVal = function notOwnPropertyVal<T, TKey extends keyof T>(
+  obj: T,
+  prop: TKey,
+  value: T[TKey],
+  msg?: string
+) {
+  Assertion.create(
     obj,
     msg,
     assert.notOwnPropertyVal,
@@ -1473,8 +2167,11 @@ assert.notOwnPropertyVal = function (obj, prop, value, msg) {
  * @param {string} msg
  * @public
  */
-assert.deepOwnPropertyVal = function (obj, prop, value, msg) {
-  new Assertion(
+assert.deepOwnPropertyVal = function deepOwnPropertyVal<
+  T,
+  TKey extends keyof T
+>(obj: T, prop: TKey, value: T[TKey], msg?: string) {
+  Assertion.create(
     obj,
     msg,
     assert.deepOwnPropertyVal,
@@ -1501,8 +2198,11 @@ assert.deepOwnPropertyVal = function (obj, prop, value, msg) {
  * @param {string} msg
  * @public
  */
-assert.notDeepOwnPropertyVal = function (obj, prop, value, msg) {
-  new Assertion(
+assert.notDeepOwnPropertyVal = function notDeepOwnPropertyVal<
+  T,
+  TKey extends keyof T
+>(obj: T, prop: TKey, value: T[TKey], msg?: string) {
+  Assertion.create(
     obj,
     msg,
     assert.notDeepOwnPropertyVal,
@@ -1526,10 +2226,13 @@ assert.notDeepOwnPropertyVal = function (obj, prop, value, msg) {
  * @namespace Assert
  * @public
  */
-assert.nestedProperty = function (obj, prop, msg) {
-  new Assertion(obj, msg, assert.nestedProperty, true).to.have.nested.property(
-    prop
-  );
+assert.nestedProperty = function (obj: object, prop: string, msg?: string) {
+  Assertion.create(
+    obj,
+    msg,
+    assert.nestedProperty,
+    true
+  ).to.have.nested.property(prop);
 };
 
 /**
@@ -1548,8 +2251,8 @@ assert.nestedProperty = function (obj, prop, msg) {
  * @namespace Assert
  * @public
  */
-assert.notNestedProperty = function (obj, prop, msg) {
-  new Assertion(
+assert.notNestedProperty = function (obj: object, prop: string, msg?: string) {
+  Assertion.create(
     obj,
     msg,
     assert.notNestedProperty,
@@ -1574,8 +2277,13 @@ assert.notNestedProperty = function (obj, prop, msg) {
  * @namespace Assert
  * @public
  */
-assert.nestedPropertyVal = function (obj, prop, val, msg) {
-  new Assertion(
+assert.nestedPropertyVal = function (
+  obj: object,
+  prop: string,
+  val: unknown,
+  msg?: string
+) {
+  Assertion.create(
     obj,
     msg,
     assert.nestedPropertyVal,
@@ -1601,8 +2309,13 @@ assert.nestedPropertyVal = function (obj, prop, val, msg) {
  * @namespace Assert
  * @public
  */
-assert.notNestedPropertyVal = function (obj, prop, val, msg) {
-  new Assertion(
+assert.notNestedPropertyVal = function (
+  obj: object,
+  prop: string,
+  val: unknown,
+  msg?: string
+) {
+  Assertion.create(
     obj,
     msg,
     assert.notNestedPropertyVal,
@@ -1627,8 +2340,13 @@ assert.notNestedPropertyVal = function (obj, prop, val, msg) {
  * @namespace Assert
  * @public
  */
-assert.deepNestedPropertyVal = function (obj, prop, val, msg) {
-  new Assertion(
+assert.deepNestedPropertyVal = function (
+  obj: object,
+  prop: string,
+  val: unknown,
+  msg?: string
+) {
+  Assertion.create(
     obj,
     msg,
     assert.deepNestedPropertyVal,
@@ -1655,8 +2373,13 @@ assert.deepNestedPropertyVal = function (obj, prop, val, msg) {
  * @namespace Assert
  * @public
  */
-assert.notDeepNestedPropertyVal = function (obj, prop, val, msg) {
-  new Assertion(
+assert.notDeepNestedPropertyVal = function (
+  obj: object,
+  prop: string,
+  val: unknown,
+  msg?: string
+) {
+  Assertion.create(
     obj,
     msg,
     assert.notDeepNestedPropertyVal,
@@ -1681,8 +2404,8 @@ assert.notDeepNestedPropertyVal = function (obj, prop, val, msg) {
  * @namespace Assert
  * @public
  */
-assert.lengthOf = function (exp, len, msg) {
-  new Assertion(exp, msg, assert.lengthOf, true).to.have.lengthOf(len);
+assert.lengthOf = function (exp: LengthLike, len: number, msg?: string) {
+  Assertion.create(exp, msg, assert.lengthOf, true).to.have.lengthOf(len);
 };
 
 /**
@@ -1704,8 +2427,14 @@ assert.lengthOf = function (exp, len, msg) {
  * @namespace Assert
  * @public
  */
-assert.hasAnyKeys = function (obj, keys, msg) {
-  new Assertion(obj, msg, assert.hasAnyKeys, true).to.have.any.keys(keys);
+assert.hasAnyKeys = function (
+  obj: KeyedObject,
+  keys: Array<unknown> | Record<PropertyKey, unknown>,
+  msg?: string
+) {
+  Assertion.create(obj, msg, assert.hasAnyKeys, true).to.have.any.keys(
+    keys as PropertyKey[]
+  );
 };
 
 /**
@@ -1727,8 +2456,8 @@ assert.hasAnyKeys = function (obj, keys, msg) {
  * @namespace Assert
  * @public
  */
-assert.hasAllKeys = function (obj, keys, msg) {
-  new Assertion(obj, msg, assert.hasAllKeys, true).to.have.all.keys(keys);
+assert.hasAllKeys = function (obj: KeyedObject, keys: string[], msg?: string) {
+  Assertion.create(obj, msg, assert.hasAllKeys, true).to.have.all.keys(keys);
 };
 
 /**
@@ -1754,8 +2483,12 @@ assert.hasAllKeys = function (obj, keys, msg) {
  * @namespace Assert
  * @public
  */
-assert.containsAllKeys = function (obj, keys, msg) {
-  new Assertion(obj, msg, assert.containsAllKeys, true).to.contain.all.keys(
+assert.containsAllKeys = function (
+  obj: KeyedObject,
+  keys: string[],
+  msg?: string
+) {
+  Assertion.create(obj, msg, assert.containsAllKeys, true).to.contain.all.keys(
     keys
   );
 };
@@ -1779,10 +2512,17 @@ assert.containsAllKeys = function (obj, keys, msg) {
  * @namespace Assert
  * @public
  */
-assert.doesNotHaveAnyKeys = function (obj, keys, msg) {
-  new Assertion(obj, msg, assert.doesNotHaveAnyKeys, true).to.not.have.any.keys(
-    keys
-  );
+assert.doesNotHaveAnyKeys = function (
+  obj: KeyedObject,
+  keys: string[],
+  msg?: string
+) {
+  Assertion.create(
+    obj,
+    msg,
+    assert.doesNotHaveAnyKeys,
+    true
+  ).to.not.have.any.keys(keys);
 };
 
 /**
@@ -1804,10 +2544,17 @@ assert.doesNotHaveAnyKeys = function (obj, keys, msg) {
  * @namespace Assert
  * @public
  */
-assert.doesNotHaveAllKeys = function (obj, keys, msg) {
-  new Assertion(obj, msg, assert.doesNotHaveAllKeys, true).to.not.have.all.keys(
-    keys
-  );
+assert.doesNotHaveAllKeys = function (
+  obj: KeyedObject,
+  keys: string[],
+  msg?: string
+) {
+  Assertion.create(
+    obj,
+    msg,
+    assert.doesNotHaveAllKeys,
+    true
+  ).to.not.have.all.keys(keys);
 };
 
 /**
@@ -1833,9 +2580,13 @@ assert.doesNotHaveAllKeys = function (obj, keys, msg) {
  * @namespace Assert
  * @public
  */
-assert.hasAnyDeepKeys = function (obj, keys, msg) {
-  new Assertion(obj, msg, assert.hasAnyDeepKeys, true).to.have.any.deep.keys(
-    keys
+assert.hasAnyDeepKeys = function (
+  obj: KeyedObject,
+  keys: Array<unknown> | Record<PropertyKey, unknown>,
+  msg?: string
+) {
+  Assertion.create(obj, msg, assert.hasAnyDeepKeys, true).to.have.any.deep.keys(
+    keys as PropertyKey[]
   );
 };
 
@@ -1860,9 +2611,13 @@ assert.hasAnyDeepKeys = function (obj, keys, msg) {
  * @namespace Assert
  * @public
  */
-assert.hasAllDeepKeys = function (obj, keys, msg) {
-  new Assertion(obj, msg, assert.hasAllDeepKeys, true).to.have.all.deep.keys(
-    keys
+assert.hasAllDeepKeys = function (
+  obj: KeyedObject,
+  keys: Array<unknown> | Record<PropertyKey, unknown>,
+  msg?: string
+) {
+  Assertion.create(obj, msg, assert.hasAllDeepKeys, true).to.have.all.deep.keys(
+    keys as PropertyKey[]
   );
 };
 
@@ -1887,13 +2642,17 @@ assert.hasAllDeepKeys = function (obj, keys, msg) {
  * @namespace Assert
  * @public
  */
-assert.containsAllDeepKeys = function (obj, keys, msg) {
-  new Assertion(
+assert.containsAllDeepKeys = function (
+  obj: KeyedObject,
+  keys: Array<unknown> | Record<PropertyKey, unknown>,
+  msg?: string
+) {
+  Assertion.create(
     obj,
     msg,
     assert.containsAllDeepKeys,
     true
-  ).to.contain.all.deep.keys(keys);
+  ).to.contain.all.deep.keys(keys as PropertyKey[]);
 };
 
 /**
@@ -1917,13 +2676,17 @@ assert.containsAllDeepKeys = function (obj, keys, msg) {
  * @namespace Assert
  * @public
  */
-assert.doesNotHaveAnyDeepKeys = function (obj, keys, msg) {
-  new Assertion(
+assert.doesNotHaveAnyDeepKeys = function (
+  obj: KeyedObject,
+  keys: Array<unknown> | Record<PropertyKey, unknown>,
+  msg?: string
+) {
+  Assertion.create(
     obj,
     msg,
     assert.doesNotHaveAnyDeepKeys,
     true
-  ).to.not.have.any.deep.keys(keys);
+  ).to.not.have.any.deep.keys(keys as PropertyKey[]);
 };
 
 /**
@@ -1947,13 +2710,17 @@ assert.doesNotHaveAnyDeepKeys = function (obj, keys, msg) {
  * @namespace Assert
  * @public
  */
-assert.doesNotHaveAllDeepKeys = function (obj, keys, msg) {
-  new Assertion(
+assert.doesNotHaveAllDeepKeys = function (
+  obj: KeyedObject,
+  keys: Array<unknown> | Record<PropertyKey, unknown>,
+  msg?: string
+) {
+  Assertion.create(
     obj,
     msg,
     assert.doesNotHaveAllDeepKeys,
     true
-  ).to.not.have.all.deep.keys(keys);
+  ).to.not.have.all.deep.keys(keys as PropertyKey[]);
 };
 
 /**
@@ -1987,18 +2754,36 @@ assert.doesNotHaveAllDeepKeys = function (obj, keys, msg) {
  * @namespace Assert
  * @public
  */
-assert.throws = function (fn, errorLike, errMsgMatcher, msg) {
-  if ('string' === typeof errorLike || errorLike instanceof RegExp) {
-    errMsgMatcher = errorLike;
-    errorLike = null;
+function assertThrows(
+  fn: Function,
+  errorLike: Error | Constructor<Error>,
+  errMsgMatcher: RegExp | string,
+  msg?: string
+): unknown;
+function assertThrows(fn: Function, errMsgMatcher: RegExp | string): unknown;
+function assertThrows(
+  fn: Function,
+  errorOrMatcher: Error | Constructor<Error> | RegExp | string,
+  errMsgMatcher?: RegExp | string,
+  msg?: string
+): unknown {
+  let assertErr: Assertion<Function>;
+
+  if ('string' === typeof errorOrMatcher || errorOrMatcher instanceof RegExp) {
+    assertErr = Assertion.create(fn, msg, assertThrows, true).to.throw(
+      errorOrMatcher
+    );
+  } else {
+    assertErr = Assertion.create(fn, msg, assertThrows, true).to.throw(
+      errorOrMatcher,
+      errMsgMatcher as RegExp | string
+    );
   }
 
-  var assertErr = new Assertion(fn, msg, assert.throws, true).to.throw(
-    errorLike,
-    errMsgMatcher
-  );
   return flag(assertErr, 'object');
-};
+}
+
+assert.throws = assert.Throw = assert.throw = assertThrows;
 
 /**
  * ### .doesNotThrow(fn, [errorLike/string/regexp], [string/regexp], [message])
@@ -2021,24 +2806,39 @@ assert.throws = function (fn, errorLike, errMsgMatcher, msg) {
  *
  * @name doesNotThrow
  * @param {Function} fn
- * @param {Error} errorLike
+ * @param {Error|Constructor<Error>} errorOrMatcher
  * @param {RegExp | string} errMsgMatcher
- * @param {string} message
+ * @param {string} msg
  * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types
  * @namespace Assert
  * @public
  */
-assert.doesNotThrow = function (fn, errorLike, errMsgMatcher, message) {
-  if ('string' === typeof errorLike || errorLike instanceof RegExp) {
-    errMsgMatcher = errorLike;
-    errorLike = null;
+function assertDoesNotThrow(fn: Function, errMsgMatcher: RegExp | string): void;
+function assertDoesNotThrow(
+  fn: Function,
+  errorLike: Error | Constructor<Error>,
+  errMsgMatcher: RegExp | string,
+  msg?: string
+): void;
+function assertDoesNotThrow(
+  fn: Function,
+  errorOrMatcher: Error | Constructor<Error> | RegExp | string,
+  errMsgMatcher?: RegExp | string,
+  msg?: string
+): void {
+  if ('string' === typeof errorOrMatcher || errorOrMatcher instanceof RegExp) {
+    Assertion.create(fn, msg, assertDoesNotThrow, true).to.not.throw(
+      errorOrMatcher
+    );
+  } else {
+    Assertion.create(fn, msg, assertDoesNotThrow, true).to.not.throw(
+      errorOrMatcher,
+      errMsgMatcher as RegExp | string
+    );
   }
+}
 
-  new Assertion(fn, message, assert.doesNotThrow, true).to.not.throw(
-    errorLike,
-    errMsgMatcher
-  );
-};
+assert.doesNotThrow = assertDoesNotThrow;
 
 /**
  * ### .operator(val1, operator, val2, [message])
@@ -2056,7 +2856,12 @@ assert.doesNotThrow = function (fn, errorLike, errMsgMatcher, message) {
  * @namespace Assert
  * @public
  */
-assert.operator = function (val, operator, val2, msg) {
+assert.operator = function (
+  val: unknown,
+  operator: string,
+  val2: unknown,
+  msg?: string
+) {
   var ok;
   switch (operator) {
     case '==':
@@ -2066,16 +2871,16 @@ assert.operator = function (val, operator, val2, msg) {
       ok = val === val2;
       break;
     case '>':
-      ok = val > val2;
+      ok = (val as number) > (val2 as number);
       break;
     case '>=':
-      ok = val >= val2;
+      ok = (val as number) >= (val2 as number);
       break;
     case '<':
-      ok = val < val2;
+      ok = (val as number) < (val2 as number);
       break;
     case '<=':
-      ok = val <= val2;
+      ok = (val as number) <= (val2 as number);
       break;
     case '!=':
       ok = val != val2;
@@ -2091,7 +2896,7 @@ assert.operator = function (val, operator, val2, msg) {
         assert.operator
       );
   }
-  var test = new Assertion(ok, msg, assert.operator, true);
+  var test = Assertion.create(ok, msg, assert.operator, true);
   test.assert(
     true === flag(test, 'object'),
     'expected ' + inspect(val) + ' to be ' + operator + ' ' + inspect(val2),
@@ -2114,8 +2919,13 @@ assert.operator = function (val, operator, val2, msg) {
  * @namespace Assert
  * @public
  */
-assert.closeTo = function (act, exp, delta, msg) {
-  new Assertion(act, msg, assert.closeTo, true).to.be.closeTo(exp, delta);
+assert.closeTo = function (
+  act: number,
+  exp: number,
+  delta: number,
+  msg?: string
+) {
+  Assertion.create(act, msg, assert.closeTo, true).to.be.closeTo(exp, delta);
 };
 
 /**
@@ -2133,8 +2943,13 @@ assert.closeTo = function (act, exp, delta, msg) {
  * @namespace Assert
  * @public
  */
-assert.approximately = function (act, exp, delta, msg) {
-  new Assertion(act, msg, assert.approximately, true).to.be.approximately(
+assert.approximately = function (
+  act: number,
+  exp: number,
+  delta: number,
+  msg?: string
+) {
+  Assertion.create(act, msg, assert.approximately, true).to.be.approximately(
     exp,
     delta
   );
@@ -2155,8 +2970,10 @@ assert.approximately = function (act, exp, delta, msg) {
  * @namespace Assert
  * @public
  */
-assert.sameMembers = function (set1, set2, msg) {
-  new Assertion(set1, msg, assert.sameMembers, true).to.have.same.members(set2);
+assert.sameMembers = function (set1: unknown[], set2: unknown[], msg?: string) {
+  Assertion.create(set1, msg, assert.sameMembers, true).to.have.same.members(
+    set2
+  );
 };
 
 /**
@@ -2174,8 +2991,12 @@ assert.sameMembers = function (set1, set2, msg) {
  * @namespace Assert
  * @public
  */
-assert.notSameMembers = function (set1, set2, msg) {
-  new Assertion(
+assert.notSameMembers = function (
+  set1: unknown[],
+  set2: unknown[],
+  msg?: string
+) {
+  Assertion.create(
     set1,
     msg,
     assert.notSameMembers,
@@ -2198,8 +3019,12 @@ assert.notSameMembers = function (set1, set2, msg) {
  * @namespace Assert
  * @public
  */
-assert.sameDeepMembers = function (set1, set2, msg) {
-  new Assertion(
+assert.sameDeepMembers = function (
+  set1: unknown[],
+  set2: unknown[],
+  msg?: string
+) {
+  Assertion.create(
     set1,
     msg,
     assert.sameDeepMembers,
@@ -2222,8 +3047,12 @@ assert.sameDeepMembers = function (set1, set2, msg) {
  * @namespace Assert
  * @public
  */
-assert.notSameDeepMembers = function (set1, set2, msg) {
-  new Assertion(
+assert.notSameDeepMembers = function (
+  set1: unknown[],
+  set2: unknown[],
+  msg?: string
+) {
+  Assertion.create(
     set1,
     msg,
     assert.notSameDeepMembers,
@@ -2246,8 +3075,12 @@ assert.notSameDeepMembers = function (set1, set2, msg) {
  * @namespace Assert
  * @public
  */
-assert.sameOrderedMembers = function (set1, set2, msg) {
-  new Assertion(
+assert.sameOrderedMembers = function (
+  set1: unknown[],
+  set2: unknown[],
+  msg?: string
+) {
+  Assertion.create(
     set1,
     msg,
     assert.sameOrderedMembers,
@@ -2270,8 +3103,12 @@ assert.sameOrderedMembers = function (set1, set2, msg) {
  * @namespace Assert
  * @public
  */
-assert.notSameOrderedMembers = function (set1, set2, msg) {
-  new Assertion(
+assert.notSameOrderedMembers = function (
+  set1: unknown[],
+  set2: unknown[],
+  msg?: string
+) {
+  Assertion.create(
     set1,
     msg,
     assert.notSameOrderedMembers,
@@ -2294,8 +3131,12 @@ assert.notSameOrderedMembers = function (set1, set2, msg) {
  * @namespace Assert
  * @public
  */
-assert.sameDeepOrderedMembers = function (set1, set2, msg) {
-  new Assertion(
+assert.sameDeepOrderedMembers = function (
+  set1: unknown[],
+  set2: unknown[],
+  msg?: string
+) {
+  Assertion.create(
     set1,
     msg,
     assert.sameDeepOrderedMembers,
@@ -2319,8 +3160,12 @@ assert.sameDeepOrderedMembers = function (set1, set2, msg) {
  * @namespace Assert
  * @public
  */
-assert.notSameDeepOrderedMembers = function (set1, set2, msg) {
-  new Assertion(
+assert.notSameDeepOrderedMembers = function (
+  set1: unknown[],
+  set2: unknown[],
+  msg?: string
+) {
+  Assertion.create(
     set1,
     msg,
     assert.notSameDeepOrderedMembers,
@@ -2343,10 +3188,17 @@ assert.notSameDeepOrderedMembers = function (set1, set2, msg) {
  * @namespace Assert
  * @public
  */
-assert.includeMembers = function (superset, subset, msg) {
-  new Assertion(superset, msg, assert.includeMembers, true).to.include.members(
-    subset
-  );
+assert.includeMembers = function (
+  superset: unknown[],
+  subset: unknown[],
+  msg?: string
+) {
+  Assertion.create(
+    superset,
+    msg,
+    assert.includeMembers,
+    true
+  ).to.include.members(subset);
 };
 
 /**
@@ -2364,8 +3216,12 @@ assert.includeMembers = function (superset, subset, msg) {
  * @namespace Assert
  * @public
  */
-assert.notIncludeMembers = function (superset, subset, msg) {
-  new Assertion(
+assert.notIncludeMembers = function (
+  superset: unknown[],
+  subset: unknown[],
+  msg?: string
+) {
+  Assertion.create(
     superset,
     msg,
     assert.notIncludeMembers,
@@ -2388,8 +3244,12 @@ assert.notIncludeMembers = function (superset, subset, msg) {
  * @namespace Assert
  * @public
  */
-assert.includeDeepMembers = function (superset, subset, msg) {
-  new Assertion(
+assert.includeDeepMembers = function (
+  superset: unknown[],
+  subset: unknown[],
+  msg?: string
+) {
+  Assertion.create(
     superset,
     msg,
     assert.includeDeepMembers,
@@ -2412,8 +3272,12 @@ assert.includeDeepMembers = function (superset, subset, msg) {
  * @namespace Assert
  * @public
  */
-assert.notIncludeDeepMembers = function (superset, subset, msg) {
-  new Assertion(
+assert.notIncludeDeepMembers = function (
+  superset: unknown[],
+  subset: unknown[],
+  msg?: string
+) {
+  Assertion.create(
     superset,
     msg,
     assert.notIncludeDeepMembers,
@@ -2437,8 +3301,12 @@ assert.notIncludeDeepMembers = function (superset, subset, msg) {
  * @namespace Assert
  * @public
  */
-assert.includeOrderedMembers = function (superset, subset, msg) {
-  new Assertion(
+assert.includeOrderedMembers = function (
+  superset: unknown[],
+  subset: unknown[],
+  msg?: string
+) {
+  Assertion.create(
     superset,
     msg,
     assert.includeOrderedMembers,
@@ -2463,8 +3331,12 @@ assert.includeOrderedMembers = function (superset, subset, msg) {
  * @namespace Assert
  * @public
  */
-assert.notIncludeOrderedMembers = function (superset, subset, msg) {
-  new Assertion(
+assert.notIncludeOrderedMembers = function (
+  superset: unknown[],
+  subset: unknown[],
+  msg?: string
+) {
+  Assertion.create(
     superset,
     msg,
     assert.notIncludeOrderedMembers,
@@ -2488,8 +3360,12 @@ assert.notIncludeOrderedMembers = function (superset, subset, msg) {
  * @namespace Assert
  * @public
  */
-assert.includeDeepOrderedMembers = function (superset, subset, msg) {
-  new Assertion(
+assert.includeDeepOrderedMembers = function (
+  superset: unknown[],
+  subset: unknown[],
+  msg?: string
+) {
+  Assertion.create(
     superset,
     msg,
     assert.includeDeepOrderedMembers,
@@ -2515,8 +3391,12 @@ assert.includeDeepOrderedMembers = function (superset, subset, msg) {
  * @namespace Assert
  * @public
  */
-assert.notIncludeDeepOrderedMembers = function (superset, subset, msg) {
-  new Assertion(
+assert.notIncludeDeepOrderedMembers = function (
+  superset: unknown[],
+  subset: unknown[],
+  msg?: string
+) {
+  Assertion.create(
     superset,
     msg,
     assert.notIncludeDeepOrderedMembers,
@@ -2538,8 +3418,12 @@ assert.notIncludeDeepOrderedMembers = function (superset, subset, msg) {
  * @namespace Assert
  * @public
  */
-assert.oneOf = function (inList, list, msg) {
-  new Assertion(inList, msg, assert.oneOf, true).to.be.oneOf(list);
+assert.oneOf = function (
+  inList: string | unknown[],
+  list: unknown[],
+  msg?: string
+) {
+  Assertion.create(inList, msg, assert.oneOf, true).to.be.oneOf(list);
 };
 
 /**
@@ -2555,8 +3439,12 @@ assert.oneOf = function (inList, list, msg) {
  * @namespace Assert
  * @public
  */
-assert.isIterable = function (obj, msg) {
-  if (obj == undefined || !obj[Symbol.iterator]) {
+assert.isIterable = function (obj: unknown, msg?: string) {
+  if (
+    obj === null ||
+    obj === undefined ||
+    !(obj as Record<PropertyKey, unknown>)[Symbol.iterator]
+  ) {
     msg = msg
       ? `${msg} expected ${inspect(obj)} to be an iterable`
       : `expected ${inspect(obj)} to be an iterable`;
@@ -2582,14 +3470,32 @@ assert.isIterable = function (obj, msg) {
  * @namespace Assert
  * @public
  */
-assert.changes = function (fn, obj, prop, msg) {
+function assertChanges(
+  fn: Function,
+  obj: object,
+  prop: PropertyKey,
+  msg?: string
+): void;
+function assertChanges(fn: Function, obj: () => void, msg?: string): void;
+function assertChanges(
+  fn: Function,
+  obj: object | (() => void),
+  propOrMsg?: PropertyKey | string,
+  msg?: string
+): void {
   if (arguments.length === 3 && typeof obj === 'function') {
-    msg = prop;
-    prop = null;
+    Assertion.create(fn, propOrMsg as string, assertChanges, true).to.change(
+      obj
+    );
+  } else {
+    Assertion.create(fn, msg, assertChanges, true).to.change(
+      obj as Record<PropertyKey, unknown>,
+      propOrMsg as PropertyKey
+    );
   }
+}
 
-  new Assertion(fn, msg, assert.changes, true).to.change(obj, prop);
-};
+assert.changes = assertChanges;
 
 /**
  * ### .changesBy(function, object, property, delta, [message])
@@ -2609,18 +3515,45 @@ assert.changes = function (fn, obj, prop, msg) {
  * @namespace Assert
  * @public
  */
-assert.changesBy = function (fn, obj, prop, delta, msg) {
+function assertChangesBy(
+  fn: Function,
+  obj: object,
+  prop: PropertyKey,
+  delta: number,
+  msg?: string
+): void;
+function assertChangesBy(
+  fn: Function,
+  obj: () => void,
+  delta: number,
+  msg?: string
+): void;
+function assertChangesBy(
+  fn: Function,
+  obj: object | (() => void),
+  deltaOrProp: number | PropertyKey,
+  msgOrDelta?: string | number,
+  msg?: string
+): void {
   if (arguments.length === 4 && typeof obj === 'function') {
-    var tmpMsg = delta;
-    delta = prop;
-    msg = tmpMsg;
+    Assertion.create(fn, msgOrDelta as string, assertChangesBy, true)
+      .to.change(obj)
+      .by(deltaOrProp as number);
   } else if (arguments.length === 3) {
-    delta = prop;
-    prop = null;
+    Assertion.create(fn, msg, assertChangesBy, true)
+      .to.change(obj as Function)
+      .by(deltaOrProp as number);
+  } else {
+    Assertion.create(fn, msg, assertChangesBy, true)
+      .to.change(
+        obj as Record<PropertyKey, unknown>,
+        deltaOrProp as PropertyKey
+      )
+      .by(msgOrDelta as number);
   }
+}
 
-  new Assertion(fn, msg, assert.changesBy, true).to.change(obj, prop).by(delta);
-};
+assert.changesBy = assertChangesBy;
 
 /**
  * ### .doesNotChange(function, object, property, [message])
@@ -2640,17 +3573,39 @@ assert.changesBy = function (fn, obj, prop, delta, msg) {
  * @namespace Assert
  * @public
  */
-assert.doesNotChange = function (fn, obj, prop, msg) {
+function assertDoesNotChange(
+  fn: Function,
+  obj: object,
+  prop: PropertyKey,
+  msg?: string
+): Assertion<Function>;
+function assertDoesNotChange(
+  fn: Function,
+  obj: () => void,
+  msg?: string
+): Assertion<Function>;
+function assertDoesNotChange(
+  fn: Function,
+  obj: object | (() => void),
+  propOrMsg?: PropertyKey | string,
+  msg?: string
+): Assertion<Function> {
   if (arguments.length === 3 && typeof obj === 'function') {
-    msg = prop;
-    prop = null;
+    return Assertion.create(
+      fn,
+      propOrMsg as string,
+      assertDoesNotChange,
+      true
+    ).to.not.change(obj);
+  } else {
+    return Assertion.create(fn, msg, assertDoesNotChange, true).to.not.change(
+      obj as Record<PropertyKey, unknown>,
+      propOrMsg as PropertyKey
+    );
   }
+}
 
-  return new Assertion(fn, msg, assert.doesNotChange, true).to.not.change(
-    obj,
-    prop
-  );
-};
+assert.doesNotChange = assertDoesNotChange;
 
 /**
  * ### .changesButNotBy(function, object, property, delta, [message])
@@ -2670,20 +3625,45 @@ assert.doesNotChange = function (fn, obj, prop, msg) {
  * @namespace Assert
  * @public
  */
-assert.changesButNotBy = function (fn, obj, prop, delta, msg) {
+function assertChangesButNotBy(
+  fn: Function,
+  obj: object,
+  prop: PropertyKey,
+  delta: number,
+  msg?: string
+): void;
+function assertChangesButNotBy(
+  fn: Function,
+  obj: () => void,
+  delta: number,
+  msg?: string
+): void;
+function assertChangesButNotBy(
+  fn: Function,
+  obj: object | (() => void),
+  deltaOrProp: number | PropertyKey,
+  msgOrDelta?: string | number,
+  msg?: string
+): void {
   if (arguments.length === 4 && typeof obj === 'function') {
-    var tmpMsg = delta;
-    delta = prop;
-    msg = tmpMsg;
+    Assertion.create(fn, msgOrDelta as string, assertChangesButNotBy, true)
+      .to.change(obj)
+      .but.not.by(deltaOrProp as number);
   } else if (arguments.length === 3) {
-    delta = prop;
-    prop = null;
+    Assertion.create(fn, msg, assertChangesButNotBy, true)
+      .to.change(obj as Function)
+      .but.not.by(deltaOrProp as number);
+  } else {
+    Assertion.create(fn, msg, assertChangesButNotBy, true)
+      .to.change(
+        obj as Record<PropertyKey, unknown>,
+        deltaOrProp as PropertyKey
+      )
+      .but.not.by(msgOrDelta as number);
   }
+}
 
-  new Assertion(fn, msg, assert.changesButNotBy, true).to
-    .change(obj, prop)
-    .but.not.by(delta);
-};
+assert.changesButNotBy = assertChangesButNotBy;
 
 /**
  * ### .increases(function, object, property, [message])
@@ -2703,14 +3683,39 @@ assert.changesButNotBy = function (fn, obj, prop, delta, msg) {
  * @param {string} msg - message _optional_
  * @returns {unknown}
  */
-assert.increases = function (fn, obj, prop, msg) {
+function assertIncreases(
+  fn: Function,
+  obj: object,
+  prop: PropertyKey,
+  msg?: string
+): Assertion<Function>;
+function assertIncreases(
+  fn: Function,
+  obj: () => void,
+  msg?: string
+): Assertion<Function>;
+function assertIncreases(
+  fn: Function,
+  obj: object | (() => void),
+  propOrMsg?: PropertyKey | string,
+  msg?: string
+): Assertion<Function> {
   if (arguments.length === 3 && typeof obj === 'function') {
-    msg = prop;
-    prop = null;
+    return Assertion.create(
+      fn,
+      propOrMsg as string,
+      assertIncreases,
+      true
+    ).to.increase(obj);
+  } else {
+    return Assertion.create(fn, msg, assertIncreases, true).to.increase(
+      obj as Record<PropertyKey, unknown>,
+      propOrMsg as PropertyKey
+    );
   }
+}
 
-  return new Assertion(fn, msg, assert.increases, true).to.increase(obj, prop);
-};
+assert.increases = assertIncreases;
 
 /**
  * ### .increasesBy(function, object, property, delta, [message])
@@ -2730,20 +3735,45 @@ assert.increases = function (fn, obj, prop, msg) {
  * @param {number} delta - change amount (delta)
  * @param {string} msg - message _optional_
  */
-assert.increasesBy = function (fn, obj, prop, delta, msg) {
+function assertIncreasesBy(
+  fn: Function,
+  obj: object,
+  prop: PropertyKey,
+  delta: number,
+  msg?: string
+): void;
+function assertIncreasesBy(
+  fn: Function,
+  obj: () => void,
+  delta: number,
+  msg?: string
+): void;
+function assertIncreasesBy(
+  fn: Function,
+  obj: object | (() => void),
+  deltaOrProp: number | PropertyKey,
+  msgOrDelta?: string | number,
+  msg?: string
+): void {
   if (arguments.length === 4 && typeof obj === 'function') {
-    var tmpMsg = delta;
-    delta = prop;
-    msg = tmpMsg;
+    Assertion.create(fn, msgOrDelta as string, assertIncreasesBy, true)
+      .to.increase(obj)
+      .by(deltaOrProp as number);
   } else if (arguments.length === 3) {
-    delta = prop;
-    prop = null;
+    Assertion.create(fn, msg, assertIncreasesBy, true)
+      .to.increase(obj as Function)
+      .by(deltaOrProp as number);
+  } else {
+    Assertion.create(fn, msg, assertIncreasesBy, true)
+      .to.increase(
+        obj as Record<PropertyKey, unknown>,
+        deltaOrProp as PropertyKey
+      )
+      .by(msgOrDelta as number);
   }
+}
 
-  new Assertion(fn, msg, assert.increasesBy, true).to
-    .increase(obj, prop)
-    .by(delta);
-};
+assert.increasesBy = assertIncreasesBy;
 
 /**
  * ### .doesNotIncrease(function, object, property, [message])
@@ -2763,17 +3793,44 @@ assert.increasesBy = function (fn, obj, prop, delta, msg) {
  * @namespace Assert
  * @public
  */
-assert.doesNotIncrease = function (fn, obj, prop, msg) {
+function assertDoesNotIncrease(
+  fn: Function,
+  obj: object,
+  prop: PropertyKey,
+  msg?: string
+): Assertion<Function>;
+function assertDoesNotIncrease(
+  fn: Function,
+  obj: () => void,
+  msg?: string
+): Assertion<Function>;
+function assertDoesNotIncrease(
+  fn: Function,
+  obj: object | (() => void),
+  propOrMsg?: PropertyKey | string,
+  msg?: string
+): Assertion<Function> {
   if (arguments.length === 3 && typeof obj === 'function') {
-    msg = prop;
-    prop = null;
+    return Assertion.create(
+      fn,
+      propOrMsg as string,
+      assertDoesNotIncrease,
+      true
+    ).to.not.increase(obj);
+  } else {
+    return Assertion.create(
+      fn,
+      msg,
+      assertDoesNotIncrease,
+      true
+    ).to.not.increase(
+      obj as Record<PropertyKey, unknown>,
+      propOrMsg as PropertyKey
+    );
   }
+}
 
-  return new Assertion(fn, msg, assert.doesNotIncrease, true).to.not.increase(
-    obj,
-    prop
-  );
-};
+assert.doesNotIncrease = assertDoesNotIncrease;
 
 /**
  * ### .increasesButNotBy(function, object, property, delta, [message])
@@ -2793,20 +3850,45 @@ assert.doesNotIncrease = function (fn, obj, prop, msg) {
  * @namespace Assert
  * @public
  */
-assert.increasesButNotBy = function (fn, obj, prop, delta, msg) {
+function assertIncreasesButNotBy(
+  fn: Function,
+  obj: object,
+  prop: PropertyKey,
+  delta: number,
+  msg?: string
+): void;
+function assertIncreasesButNotBy(
+  fn: Function,
+  obj: () => void,
+  delta: number,
+  msg?: string
+): void;
+function assertIncreasesButNotBy(
+  fn: Function,
+  obj: object | (() => void),
+  deltaOrProp: number | PropertyKey,
+  msgOrDelta?: string | number,
+  msg?: string
+): void {
   if (arguments.length === 4 && typeof obj === 'function') {
-    var tmpMsg = delta;
-    delta = prop;
-    msg = tmpMsg;
+    Assertion.create(fn, msgOrDelta as string, assertIncreasesButNotBy, true)
+      .to.increase(obj)
+      .but.not.by(deltaOrProp as number);
   } else if (arguments.length === 3) {
-    delta = prop;
-    prop = null;
+    Assertion.create(fn, msg, assertIncreasesButNotBy, true)
+      .to.increase(obj as Function)
+      .but.not.by(deltaOrProp as number);
+  } else {
+    Assertion.create(fn, msg, assertIncreasesButNotBy, true)
+      .to.increase(
+        obj as Record<PropertyKey, unknown>,
+        deltaOrProp as PropertyKey
+      )
+      .but.not.by(msgOrDelta as number);
   }
+}
 
-  new Assertion(fn, msg, assert.increasesButNotBy, true).to
-    .increase(obj, prop)
-    .but.not.by(delta);
-};
+assert.increasesButNotBy = assertIncreasesButNotBy;
 
 /**
  * ### .decreases(function, object, property, [message])
@@ -2826,14 +3908,39 @@ assert.increasesButNotBy = function (fn, obj, prop, delta, msg) {
  * @namespace Assert
  * @public
  */
-assert.decreases = function (fn, obj, prop, msg) {
+function assertDecreases(
+  fn: Function,
+  obj: object,
+  prop: PropertyKey,
+  msg?: string
+): Assertion<Function>;
+function assertDecreases(
+  fn: Function,
+  obj: () => void,
+  msg?: string
+): Assertion<Function>;
+function assertDecreases(
+  fn: Function,
+  obj: object | (() => void),
+  propOrMsg?: PropertyKey | string,
+  msg?: string
+): Assertion<Function> {
   if (arguments.length === 3 && typeof obj === 'function') {
-    msg = prop;
-    prop = null;
+    return Assertion.create(
+      fn,
+      propOrMsg as string,
+      assertDecreases,
+      true
+    ).to.decrease(obj);
+  } else {
+    return Assertion.create(fn, msg, assertDecreases, true).to.decrease(
+      obj as Record<PropertyKey, unknown>,
+      propOrMsg as PropertyKey
+    );
   }
+}
 
-  return new Assertion(fn, msg, assert.decreases, true).to.decrease(obj, prop);
-};
+assert.decreases = assertDecreases;
 
 /**
  * ### .decreasesBy(function, object, property, delta, [message])
@@ -2853,20 +3960,45 @@ assert.decreases = function (fn, obj, prop, msg) {
  * @namespace Assert
  * @public
  */
-assert.decreasesBy = function (fn, obj, prop, delta, msg) {
+function assertDecreasesBy(
+  fn: Function,
+  obj: object,
+  prop: PropertyKey,
+  delta: number,
+  msg?: string
+): void;
+function assertDecreasesBy(
+  fn: Function,
+  obj: () => void,
+  delta: number,
+  msg?: string
+): void;
+function assertDecreasesBy(
+  fn: Function,
+  obj: object | (() => void),
+  deltaOrProp: number | PropertyKey,
+  msgOrDelta?: string | number,
+  msg?: string
+): void {
   if (arguments.length === 4 && typeof obj === 'function') {
-    var tmpMsg = delta;
-    delta = prop;
-    msg = tmpMsg;
+    Assertion.create(fn, msgOrDelta as string, assertDecreasesBy, true)
+      .to.decrease(obj)
+      .by(deltaOrProp as number);
   } else if (arguments.length === 3) {
-    delta = prop;
-    prop = null;
+    Assertion.create(fn, msg, assertDecreasesBy, true)
+      .to.decrease(obj as Function)
+      .by(deltaOrProp as number);
+  } else {
+    Assertion.create(fn, msg, assertDecreasesBy, true)
+      .to.decrease(
+        obj as Record<PropertyKey, unknown>,
+        deltaOrProp as PropertyKey
+      )
+      .by(msgOrDelta as number);
   }
+}
 
-  new Assertion(fn, msg, assert.decreasesBy, true).to
-    .decrease(obj, prop)
-    .by(delta);
-};
+assert.decreasesBy = assertDecreasesBy;
 
 /**
  * ### .doesNotDecrease(function, object, property, [message])
@@ -2886,17 +4018,44 @@ assert.decreasesBy = function (fn, obj, prop, delta, msg) {
  * @namespace Assert
  * @public
  */
-assert.doesNotDecrease = function (fn, obj, prop, msg) {
+function assertDoesNotDecrease(
+  fn: Function,
+  obj: object,
+  prop: PropertyKey,
+  msg?: string
+): Assertion<Function>;
+function assertDoesNotDecrease(
+  fn: Function,
+  obj: () => void,
+  msg?: string
+): Assertion<Function>;
+function assertDoesNotDecrease(
+  fn: Function,
+  obj: object | (() => void),
+  propOrMsg?: PropertyKey | string,
+  msg?: string
+): Assertion<Function> {
   if (arguments.length === 3 && typeof obj === 'function') {
-    msg = prop;
-    prop = null;
+    return Assertion.create(
+      fn,
+      propOrMsg as string,
+      assertDoesNotDecrease,
+      true
+    ).to.not.decrease(obj);
+  } else {
+    return Assertion.create(
+      fn,
+      msg,
+      assertDoesNotDecrease,
+      true
+    ).to.not.decrease(
+      obj as Record<PropertyKey, unknown>,
+      propOrMsg as PropertyKey
+    );
   }
+}
 
-  return new Assertion(fn, msg, assert.doesNotDecrease, true).to.not.decrease(
-    obj,
-    prop
-  );
-};
+assert.doesNotDecrease = assertDoesNotDecrease;
 
 /**
  * ### .doesNotDecreaseBy(function, object, property, delta, [message])
@@ -2917,20 +4076,50 @@ assert.doesNotDecrease = function (fn, obj, prop, msg) {
  * @namespace Assert
  * @public
  */
-assert.doesNotDecreaseBy = function (fn, obj, prop, delta, msg) {
+function assertDoesNotDecreaseBy(
+  fn: Function,
+  obj: object,
+  prop: PropertyKey,
+  delta: number,
+  msg?: string
+): Assertion<Function>;
+function assertDoesNotDecreaseBy(
+  fn: Function,
+  obj: () => void,
+  delta: number,
+  msg?: string
+): Assertion<Function>;
+function assertDoesNotDecreaseBy(
+  fn: Function,
+  obj: object | (() => void),
+  deltaOrProp: number | PropertyKey,
+  msgOrDelta?: string | number,
+  msg?: string
+): Assertion<Function> {
   if (arguments.length === 4 && typeof obj === 'function') {
-    var tmpMsg = delta;
-    delta = prop;
-    msg = tmpMsg;
+    return Assertion.create(
+      fn,
+      msgOrDelta as string,
+      assertDoesNotDecreaseBy,
+      true
+    )
+      .to.not.decrease(obj as Function)
+      .by(deltaOrProp as number);
   } else if (arguments.length === 3) {
-    delta = prop;
-    prop = null;
+    return Assertion.create(fn, msg, assertDoesNotDecreaseBy, true)
+      .to.not.decrease(obj as Function)
+      .by(deltaOrProp as number);
+  } else {
+    return Assertion.create(fn, msg, assertDoesNotDecreaseBy, true)
+      .to.not.decrease(
+        obj as Record<PropertyKey, unknown>,
+        deltaOrProp as PropertyKey
+      )
+      .by(msgOrDelta as number);
   }
+}
 
-  return new Assertion(fn, msg, assert.doesNotDecreaseBy, true).to.not
-    .decrease(obj, prop)
-    .by(delta);
-};
+assert.doesNotDecreaseBy = assertDoesNotDecreaseBy;
 
 /**
  * ### .decreasesButNotBy(function, object, property, delta, [message])
@@ -2950,20 +4139,45 @@ assert.doesNotDecreaseBy = function (fn, obj, prop, delta, msg) {
  * @namespace Assert
  * @public
  */
-assert.decreasesButNotBy = function (fn, obj, prop, delta, msg) {
+function assertDecreasesButNotBy(
+  fn: Function,
+  obj: object,
+  prop: PropertyKey,
+  delta: number,
+  msg?: string
+): void;
+function assertDecreasesButNotBy(
+  fn: Function,
+  obj: () => void,
+  delta: number,
+  msg?: string
+): void;
+function assertDecreasesButNotBy(
+  fn: Function,
+  obj: object | (() => void),
+  deltaOrProp: number | PropertyKey,
+  msgOrDelta?: string | number,
+  msg?: string
+): void {
   if (arguments.length === 4 && typeof obj === 'function') {
-    var tmpMsg = delta;
-    delta = prop;
-    msg = tmpMsg;
+    Assertion.create(fn, msgOrDelta as string, assertDecreasesButNotBy, true)
+      .to.decrease(obj)
+      .but.not.by(deltaOrProp as number);
   } else if (arguments.length === 3) {
-    delta = prop;
-    prop = null;
+    Assertion.create(fn, msg, assertDecreasesButNotBy, true)
+      .to.decrease(obj as Function)
+      .but.not.by(deltaOrProp as number);
+  } else {
+    Assertion.create(fn, msg, assertDecreasesButNotBy, true)
+      .to.decrease(
+        obj as Record<PropertyKey, unknown>,
+        deltaOrProp as PropertyKey
+      )
+      .but.not.by(msgOrDelta as number);
   }
+}
 
-  new Assertion(fn, msg, assert.decreasesButNotBy, true).to
-    .decrease(obj, prop)
-    .but.not.by(delta);
-};
+assert.decreasesButNotBy = assertDecreasesButNotBy;
 
 /**
  * ### .ifError(object)
@@ -2980,7 +4194,7 @@ assert.decreasesButNotBy = function (fn, obj, prop, delta, msg) {
  * @namespace Assert
  * @public
  */
-assert.ifError = function (val) {
+assert.ifError = function (val: unknown) {
   if (val) {
     throw val;
   }
@@ -3000,8 +4214,8 @@ assert.ifError = function (val) {
  * @namespace Assert
  * @public
  */
-assert.isExtensible = function (obj, msg) {
-  new Assertion(obj, msg, assert.isExtensible, true).to.be.extensible;
+assert.isExtensible = assert.extensible = function (obj: object, msg?: string) {
+  Assertion.create(obj, msg, assert.isExtensible, true).to.be.extensible;
 };
 
 /**
@@ -3024,8 +4238,11 @@ assert.isExtensible = function (obj, msg) {
  * @namespace Assert
  * @public
  */
-assert.isNotExtensible = function (obj, msg) {
-  new Assertion(obj, msg, assert.isNotExtensible, true).to.not.be.extensible;
+assert.isNotExtensible = assert.notExtensible = function (
+  obj: object,
+  msg?: string
+) {
+  Assertion.create(obj, msg, assert.isNotExtensible, true).to.not.be.extensible;
 };
 
 /**
@@ -3047,8 +4264,8 @@ assert.isNotExtensible = function (obj, msg) {
  * @namespace Assert
  * @public
  */
-assert.isSealed = function (obj, msg) {
-  new Assertion(obj, msg, assert.isSealed, true).to.be.sealed;
+assert.isSealed = assert.sealed = function (obj: object, msg?: string) {
+  Assertion.create(obj, msg, assert.isSealed, true).to.be.sealed;
 };
 
 /**
@@ -3065,8 +4282,8 @@ assert.isSealed = function (obj, msg) {
  * @namespace Assert
  * @public
  */
-assert.isNotSealed = function (obj, msg) {
-  new Assertion(obj, msg, assert.isNotSealed, true).to.not.be.sealed;
+assert.isNotSealed = assert.notSealed = function (obj: object, msg?: string) {
+  Assertion.create(obj, msg, assert.isNotSealed, true).to.not.be.sealed;
 };
 
 /**
@@ -3085,8 +4302,8 @@ assert.isNotSealed = function (obj, msg) {
  * @namespace Assert
  * @public
  */
-assert.isFrozen = function (obj, msg) {
-  new Assertion(obj, msg, assert.isFrozen, true).to.be.frozen;
+assert.isFrozen = assert.frozen = function (obj: object, msg?: string) {
+  Assertion.create(obj, msg, assert.isFrozen, true).to.be.frozen;
 };
 
 /**
@@ -3103,8 +4320,8 @@ assert.isFrozen = function (obj, msg) {
  * @namespace Assert
  * @public
  */
-assert.isNotFrozen = function (obj, msg) {
-  new Assertion(obj, msg, assert.isNotFrozen, true).to.not.be.frozen;
+assert.isNotFrozen = assert.notFrozen = function (obj: object, msg?: string) {
+  Assertion.create(obj, msg, assert.isNotFrozen, true).to.not.be.frozen;
 };
 
 /**
@@ -3128,8 +4345,8 @@ assert.isNotFrozen = function (obj, msg) {
  * @namespace Assert
  * @public
  */
-assert.isEmpty = function (val, msg) {
-  new Assertion(val, msg, assert.isEmpty, true).to.be.empty;
+assert.isEmpty = assert.empty = function (val: unknown, msg?: string) {
+  Assertion.create(val, msg, assert.isEmpty, true).to.be.empty;
 };
 
 /**
@@ -3153,33 +4370,6 @@ assert.isEmpty = function (val, msg) {
  * @namespace Assert
  * @public
  */
-assert.isNotEmpty = function (val, msg) {
-  new Assertion(val, msg, assert.isNotEmpty, true).to.not.be.empty;
+assert.isNotEmpty = assert.notEmpty = function (val: unknown, msg?: string) {
+  Assertion.create(val, msg, assert.isNotEmpty, true).to.not.be.empty;
 };
-
-/**
- * Aliases.
- *
- * @param {unknown} name
- * @param {unknown} as
- * @returns {unknown}
- */
-const aliases = [
-  ['isOk', 'ok'],
-  ['isNotOk', 'notOk'],
-  ['throws', 'throw'],
-  ['throws', 'Throw'],
-  ['isExtensible', 'extensible'],
-  ['isNotExtensible', 'notExtensible'],
-  ['isSealed', 'sealed'],
-  ['isNotSealed', 'notSealed'],
-  ['isFrozen', 'frozen'],
-  ['isNotFrozen', 'notFrozen'],
-  ['isEmpty', 'empty'],
-  ['isNotEmpty', 'notEmpty'],
-  ['isCallable', 'isFunction'],
-  ['isNotCallable', 'isNotFunction']
-];
-for (const [name, as] of aliases) {
-  assert[as] = assert[name];
-}
diff --git a/lib/chai/interface/expect.js b/src/chai/interface/expect.ts
similarity index 50%
rename from lib/chai/interface/expect.js
rename to src/chai/interface/expect.ts
index b7c76930..ef15f195 100644
--- a/lib/chai/interface/expect.js
+++ b/src/chai/interface/expect.ts
@@ -4,19 +4,23 @@
  * MIT Licensed
  */
 
-import * as chai from '../../../index.js';
+import * as chai from '../../chai.js';
 import {Assertion} from '../assertion.js';
 import {AssertionError} from 'assertion-error';
 
-/**
- * @param {unknown} val
- * @param {string} message
- * @returns {Assertion}
- */
-function expect(val, message) {
-  return new Assertion(val, message);
+export interface ExpectInterface {
+  <T>(val: T, message?: string): Assertion<T>;
+  fail(message?: string): void;
+  fail<T>(actual: T, expected: T, message: string, operator: string): void;
 }
 
+const expect: ExpectInterface = function expect<T>(
+  val: T,
+  message?: string
+): Assertion<T> {
+  return Assertion.create(val, message);
+} as ExpectInterface;
+
 export {expect};
 
 /**
@@ -40,15 +44,33 @@ export {expect};
  * @namespace expect
  * @public
  */
-expect.fail = function (actual, expected, message, operator) {
+function expectFail(message?: string): void;
+function expectFail(
+  actual: unknown,
+  expected: unknown,
+  message?: string,
+  operator?: string
+): void;
+function expectFail(
+  actualOrMessage?: unknown,
+  expected?: unknown,
+  message?: string,
+  operator?: string
+): void {
+  let msg: string | undefined;
+  let actual;
+
   if (arguments.length < 2) {
-    message = actual;
+    msg = actualOrMessage as string | undefined;
     actual = undefined;
+  } else {
+    msg = message;
+    actual = actualOrMessage;
   }
 
-  message = message || 'expect.fail()';
+  msg = msg || 'expect.fail()';
   throw new AssertionError(
-    message,
+    msg,
     {
       actual: actual,
       expected: expected,
@@ -56,4 +78,6 @@ expect.fail = function (actual, expected, message, operator) {
     },
     chai.expect.fail
   );
-};
+}
+
+expect.fail = expectFail;
diff --git a/lib/chai/interface/should.js b/src/chai/interface/should.ts
similarity index 59%
rename from lib/chai/interface/should.js
rename to src/chai/interface/should.ts
index 2500fda5..f3d77a92 100644
--- a/lib/chai/interface/should.js
+++ b/src/chai/interface/should.ts
@@ -6,16 +6,51 @@
 
 import {Assertion} from '../assertion.js';
 import {AssertionError} from 'assertion-error';
+import {Constructor} from '../utils/types.js';
+
+export interface ShouldAssertions {
+  fail(message?: string): void;
+  fail<T>(actual: T, expected: T, message: string, operator: string): void;
+  equal<T>(val1: T, val2: T, msg: string): void;
+  Throw(fn: Function, errs: RegExp | string): void;
+  Throw(
+    fn: Function,
+    errt: Error | Constructor<Error>,
+    errs: RegExp,
+    msg?: string
+  ): void;
+  throw(fn: Function, errs: RegExp | string): void;
+  throw(
+    fn: Function,
+    errt: Error | Constructor<Error>,
+    errs: RegExp,
+    msg?: string
+  ): void;
+  exist(val: unknown, msg: string): void;
+}
+
+export interface ShouldInterface extends ShouldAssertions {
+  not: ShouldAssertions;
+}
+
+declare global {
+  interface Object {
+    // TODO (43081j): can this ever be strongly typed somehow?
+    should: Assertion<unknown>;
+  }
+}
 
 /**
- * @returns {void}
+ * Loads the `should` interface
+ *
+ * @returns {ShouldInterface}
  */
-function loadShould() {
+function loadShould(): ShouldInterface {
   // explicitly define this method as function as to have it's name to include as `ssfi`
   /**
    * @returns {Assertion}
    */
-  function shouldGetter() {
+  function shouldGetter(this: unknown) {
     if (
       this instanceof String ||
       this instanceof Number ||
@@ -23,14 +58,14 @@ function loadShould() {
       (typeof Symbol === 'function' && this instanceof Symbol) ||
       (typeof BigInt === 'function' && this instanceof BigInt)
     ) {
-      return new Assertion(this.valueOf(), null, shouldGetter);
+      return Assertion.create(this.valueOf(), null, shouldGetter);
     }
-    return new Assertion(this, null, shouldGetter);
+    return Assertion.create(this, null, shouldGetter);
   }
   /**
    * @param {unknown} value
    */
-  function shouldSetter(value) {
+  function shouldSetter(this: unknown, value: unknown) {
     // See https://github.com/chaijs/chai/issues/86: this makes
     // `whatever.should = someValue` actually set `someValue`, which is
     // especially useful for `global.should = require('chai').should()`.
@@ -51,8 +86,6 @@ function loadShould() {
     configurable: true
   });
 
-  var should = {};
-
   /**
    * ### .fail([message])
    * ### .fail(actual, expected, [message], [operator])
@@ -74,21 +107,33 @@ function loadShould() {
    * @namespace BDD
    * @public
    */
-  should.fail = function (actual, expected, message, operator) {
+  const shouldFail: ShouldAssertions['fail'] = function shouldFail(
+    actualOrMessage?: unknown,
+    expected?: unknown,
+    message?: string,
+    operator?: string
+  ): void {
+    let actual;
+    let msg: string | undefined;
+
     if (arguments.length < 2) {
-      message = actual;
+      msg = actualOrMessage as string | undefined;
       actual = undefined;
+    } else {
+      msg = message;
+      actual = actualOrMessage;
     }
 
-    message = message || 'should.fail()';
+    msg = msg || 'should.fail()';
+
     throw new AssertionError(
-      message,
+      msg,
       {
         actual: actual,
         expected: expected,
         operator: operator
       },
-      should.fail
+      shouldFail
     );
   };
 
@@ -106,8 +151,12 @@ function loadShould() {
    * @namespace Should
    * @public
    */
-  should.equal = function (actual, expected, message) {
-    new Assertion(actual, message).to.equal(expected);
+  const shouldEqual: ShouldAssertions['equal'] = function shouldEqual(
+    val1: unknown,
+    val2: unknown,
+    msg: string
+  ) {
+    Assertion.create(val1, msg).to.equal(val2);
   };
 
   /**
@@ -133,8 +182,17 @@ function loadShould() {
    * @namespace Should
    * @public
    */
-  should.Throw = function (fn, errt, errs, msg) {
-    new Assertion(fn, msg).to.Throw(errt, errs);
+  const shouldThrow: ShouldAssertions['throw'] = function shouldThrow(
+    fn: Function,
+    errt: Error | Constructor<Error> | RegExp | string,
+    errs?: RegExp | string,
+    msg?: string
+  ) {
+    if (errt instanceof RegExp || typeof errt === 'string') {
+      Assertion.create(fn, msg).to.Throw(errt);
+    } else {
+      Assertion.create(fn, msg).to.Throw(errt, errs as RegExp | string);
+    }
   };
 
   /**
@@ -151,13 +209,13 @@ function loadShould() {
    * @namespace Should
    * @public
    */
-  should.exist = function (val, msg) {
-    new Assertion(val, msg).to.exist;
+  const shouldExist: ShouldAssertions['exist'] = function shouldExist(
+    val: unknown,
+    msg: string
+  ) {
+    Assertion.create(val, msg).to.exist;
   };
 
-  // negation
-  should.not = {};
-
   /**
    * ### .not.equal(actual, expected, [message])
    *
@@ -172,8 +230,12 @@ function loadShould() {
    * @namespace Should
    * @public
    */
-  should.not.equal = function (actual, expected, msg) {
-    new Assertion(actual, msg).to.not.equal(expected);
+  const shouldNotEqual: ShouldAssertions['equal'] = function shouldNotEqual(
+    val1: unknown,
+    val2: unknown,
+    msg: string
+  ) {
+    Assertion.create(val1, msg).to.not.equal(val2);
   };
 
   /**
@@ -195,8 +257,17 @@ function loadShould() {
    * @namespace Should
    * @public
    */
-  should.not.Throw = function (fn, errt, errs, msg) {
-    new Assertion(fn, msg).to.not.Throw(errt, errs);
+  const shouldNotThrow: ShouldAssertions['throw'] = function shouldNotThrow(
+    fn: Function,
+    errt: Error | Constructor<Error> | RegExp | string,
+    errs?: RegExp | string,
+    msg?: string
+  ) {
+    if (errt instanceof RegExp || typeof errt === 'string') {
+      Assertion.create(fn, msg).to.not.Throw(errt);
+    } else {
+      Assertion.create(fn, msg).to.not.Throw(errt, errs as RegExp | string);
+    }
   };
 
   /**
@@ -213,14 +284,27 @@ function loadShould() {
    * @param {string} msg
    * @public
    */
-  should.not.exist = function (val, msg) {
-    new Assertion(val, msg).to.not.exist;
+  const shouldNotExist: ShouldAssertions['exist'] = function shouldNotExist(
+    val: unknown,
+    msg: string
+  ) {
+    Assertion.create(val, msg).to.not.exist;
   };
 
-  should['throw'] = should['Throw'];
-  should.not['throw'] = should.not['Throw'];
-
-  return should;
+  return {
+    equal: shouldEqual,
+    exist: shouldExist,
+    fail: shouldFail,
+    throw: shouldThrow,
+    Throw: shouldThrow,
+    not: {
+      equal: shouldNotEqual,
+      exist: shouldNotExist,
+      fail: () => {}, // Nonsensical, so make it noop
+      throw: shouldNotThrow,
+      Throw: shouldNotThrow
+    }
+  };
 }
 
 export const should = loadShould;
diff --git a/lib/chai/utils/addChainableMethod.js b/src/chai/utils/addChainableMethod.ts
similarity index 83%
rename from lib/chai/utils/addChainableMethod.js
rename to src/chai/utils/addChainableMethod.ts
index 1a000c6b..ce51fe91 100644
--- a/lib/chai/utils/addChainableMethod.js
+++ b/src/chai/utils/addChainableMethod.ts
@@ -4,11 +4,11 @@
  * MIT Licensed
  */
 
-import {Assertion} from '../assertion.js';
 import {addLengthGuard} from './addLengthGuard.js';
 import {flag} from './flag.js';
 import {proxify} from './proxify.js';
 import {transferFlags} from './transferFlags.js';
+import {ChainableBehavior} from './chainableBehavior.js';
 
 /**
  * Module variables
@@ -60,31 +60,42 @@ var call = Function.prototype.call,
  * @param {string} name of method to add
  * @param {Function} method function to be used for `name`, when called
  * @param {Function} chainingBehavior function to be called every time the property is accessed
+ * @param {Function=} createDefaultValue
  * @namespace Utils
  * @name addChainableMethod
  * @public
  */
-export function addChainableMethod(ctx, name, method, chainingBehavior) {
+export function addChainableMethod<T extends object>(
+  ctx: T,
+  name: string,
+  method: (...args: never) => unknown,
+  chainingBehavior?: () => void,
+  createDefaultValue?: (ctx: T) => unknown
+) {
+  const ctxChainable = ctx as {
+    __methods: Record<PropertyKey, ChainableBehavior>;
+  };
+
   if (typeof chainingBehavior !== 'function') {
     chainingBehavior = function () {};
   }
 
-  var chainableBehavior = {
+  var chainableBehavior: ChainableBehavior = {
     method: method,
     chainingBehavior: chainingBehavior
   };
 
   // save the methods so we can overwrite them later, if we need to.
-  if (!ctx.__methods) {
-    ctx.__methods = {};
+  if (!ctxChainable.__methods) {
+    ctxChainable.__methods = {};
   }
-  ctx.__methods[name] = chainableBehavior;
+  ctxChainable.__methods[name] = chainableBehavior;
 
   Object.defineProperty(ctx, name, {
     get: function chainableMethodGetter() {
       chainableBehavior.chainingBehavior.call(this);
 
-      var chainableMethodWrapper = function () {
+      var chainableMethodWrapper = function (this: T) {
         // Setting the `ssfi` flag to `chainableMethodWrapper` causes this
         // function to be the starting point for removing implementation
         // frames from the stack trace of a failed assertion.
@@ -104,14 +115,18 @@ export function addChainableMethod(ctx, name, method, chainingBehavior) {
           flag(this, 'ssfi', chainableMethodWrapper);
         }
 
-        var result = chainableBehavior.method.apply(this, arguments);
+        var result = (
+          chainableBehavior.method as (...args: unknown[]) => unknown
+        ).call(this, ...arguments);
         if (result !== undefined) {
           return result;
         }
 
-        var newAssertion = new Assertion();
-        transferFlags(this, newAssertion);
-        return newAssertion;
+        if (createDefaultValue) {
+          return createDefaultValue(this);
+        }
+
+        return undefined;
       };
 
       addLengthGuard(chainableMethodWrapper, name, true);
@@ -134,7 +149,9 @@ export function addChainableMethod(ctx, name, method, chainingBehavior) {
           }
 
           var pd = Object.getOwnPropertyDescriptor(ctx, asserterName);
-          Object.defineProperty(chainableMethodWrapper, asserterName, pd);
+          if (pd) {
+            Object.defineProperty(chainableMethodWrapper, asserterName, pd);
+          }
         });
       }
 
diff --git a/lib/chai/utils/addLengthGuard.js b/src/chai/utils/addLengthGuard.ts
similarity index 86%
rename from lib/chai/utils/addLengthGuard.js
rename to src/chai/utils/addLengthGuard.ts
index d08aad61..71f19cbc 100644
--- a/lib/chai/utils/addLengthGuard.js
+++ b/src/chai/utils/addLengthGuard.ts
@@ -40,30 +40,35 @@ const fnLengthDesc = Object.getOwnPropertyDescriptor(function () {}, 'length');
  * @namespace Utils
  * @name addLengthGuard
  */
-export function addLengthGuard(fn, assertionName, isChainable) {
-  if (!fnLengthDesc.configurable) return fn;
+export function addLengthGuard(
+  fn: Function,
+  assertionName: PropertyKey,
+  isChainable: boolean
+) {
+  if (!fnLengthDesc?.configurable) return fn;
 
+  const assertionNameStr = String(assertionName);
   Object.defineProperty(fn, 'length', {
     get: function () {
       if (isChainable) {
         throw Error(
           'Invalid Chai property: ' +
-            assertionName +
+            assertionNameStr +
             '.length. Due' +
             ' to a compatibility issue, "length" cannot directly follow "' +
-            assertionName +
+            assertionNameStr +
             '". Use "' +
-            assertionName +
+            assertionNameStr +
             '.lengthOf" instead.'
         );
       }
 
       throw Error(
         'Invalid Chai property: ' +
-          assertionName +
+          assertionNameStr +
           '.length. See' +
           ' docs for proper usage of "' +
-          assertionName +
+          assertionNameStr +
           '".'
       );
     }
diff --git a/lib/chai/utils/addMethod.js b/src/chai/utils/addMethod.ts
similarity index 82%
rename from lib/chai/utils/addMethod.js
rename to src/chai/utils/addMethod.ts
index de5b04fc..52ab546b 100644
--- a/lib/chai/utils/addMethod.js
+++ b/src/chai/utils/addMethod.ts
@@ -7,8 +7,6 @@
 import {addLengthGuard} from './addLengthGuard.js';
 import {flag} from './flag.js';
 import {proxify} from './proxify.js';
-import {transferFlags} from './transferFlags.js';
-import {Assertion} from '../assertion.js';
 
 /**
  * ### .addMethod(ctx, name, method)
@@ -31,12 +29,18 @@ import {Assertion} from '../assertion.js';
  * @param {object} ctx object to which the method is added
  * @param {string} name of method to add
  * @param {Function} method function to be used for name
+ * @param {Function=} createDefaultValue
  * @namespace Utils
  * @name addMethod
  * @public
  */
-export function addMethod(ctx, name, method) {
-  var methodWrapper = function () {
+export function addMethod<T extends object>(
+  ctx: T,
+  name: PropertyKey,
+  method: Function,
+  createDefaultValue?: (ctx: T) => unknown
+) {
+  var methodWrapper = function (this: T) {
     // Setting the `ssfi` flag to `methodWrapper` causes this function to be the
     // starting point for removing implementation frames from the stack trace of
     // a failed assertion.
@@ -56,11 +60,13 @@ export function addMethod(ctx, name, method) {
     var result = method.apply(this, arguments);
     if (result !== undefined) return result;
 
-    var newAssertion = new Assertion();
-    transferFlags(this, newAssertion);
-    return newAssertion;
+    if (createDefaultValue) {
+      return createDefaultValue(this);
+    }
+
+    return undefined;
   };
 
   addLengthGuard(methodWrapper, name, false);
-  ctx[name] = proxify(methodWrapper, name);
+  (ctx as Record<PropertyKey, unknown>)[name] = proxify(methodWrapper, name);
 }
diff --git a/lib/chai/utils/addProperty.js b/src/chai/utils/addProperty.ts
similarity index 84%
rename from lib/chai/utils/addProperty.js
rename to src/chai/utils/addProperty.ts
index 4375d61a..55d9bd16 100644
--- a/lib/chai/utils/addProperty.js
+++ b/src/chai/utils/addProperty.ts
@@ -4,10 +4,8 @@
  * MIT Licensed
  */
 
-import {Assertion} from '../assertion.js';
 import {flag} from './flag.js';
 import {isProxyEnabled} from './isProxyEnabled.js';
-import {transferFlags} from './transferFlags.js';
 
 /**
  * ### .addProperty(ctx, name, getter)
@@ -30,12 +28,18 @@ import {transferFlags} from './transferFlags.js';
  * @param {object} ctx object to which the property is added
  * @param {string} name of property to add
  * @param {Function} getter function to be used for name
+ * @param {Function=} getDefaultValue
  * @namespace Utils
  * @name addProperty
  * @public
  */
-export function addProperty(ctx, name, getter) {
-  getter = getter === undefined ? function () {} : getter;
+export function addProperty<T extends object>(
+  ctx: T,
+  name: PropertyKey,
+  getter?: Function,
+  getDefaultValue?: (ctx: T) => unknown
+) {
+  const getterFn = getter === undefined ? function () {} : getter;
 
   Object.defineProperty(ctx, name, {
     get: function propertyGetter() {
@@ -58,12 +62,14 @@ export function addProperty(ctx, name, getter) {
         flag(this, 'ssfi', propertyGetter);
       }
 
-      var result = getter.call(this);
+      var result = getterFn.call(this);
       if (result !== undefined) return result;
 
-      var newAssertion = new Assertion();
-      transferFlags(this, newAssertion);
-      return newAssertion;
+      if (getDefaultValue) {
+        return getDefaultValue(this);
+      }
+
+      return undefined;
     },
     configurable: true
   });
diff --git a/src/chai/utils/chainableBehavior.ts b/src/chai/utils/chainableBehavior.ts
new file mode 100644
index 00000000..95835d88
--- /dev/null
+++ b/src/chai/utils/chainableBehavior.ts
@@ -0,0 +1,4 @@
+export interface ChainableBehavior {
+  chainingBehavior: () => void;
+  method: (...args: never) => unknown;
+}
diff --git a/lib/chai/utils/compareByInspect.js b/src/chai/utils/compareByInspect.ts
similarity index 92%
rename from lib/chai/utils/compareByInspect.js
rename to src/chai/utils/compareByInspect.ts
index 5ab27a20..f04728c7 100644
--- a/lib/chai/utils/compareByInspect.js
+++ b/src/chai/utils/compareByInspect.ts
@@ -21,6 +21,6 @@ import {inspect} from './inspect.js';
  * @namespace Utils
  * @public
  */
-export function compareByInspect(a, b) {
+export function compareByInspect(a: unknown, b: unknown) {
   return inspect(a) < inspect(b) ? -1 : 1;
 }
diff --git a/lib/chai/utils/expectTypes.js b/src/chai/utils/expectTypes.ts
similarity index 90%
rename from lib/chai/utils/expectTypes.js
rename to src/chai/utils/expectTypes.ts
index ca2e1601..fef8f001 100644
--- a/lib/chai/utils/expectTypes.js
+++ b/src/chai/utils/expectTypes.ts
@@ -21,13 +21,13 @@ import {type} from './type-detect.js';
  * @name expectTypes
  * @public
  */
-export function expectTypes(obj, types) {
+export function expectTypes(obj: object, types: string[]) {
   var flagMsg = flag(obj, 'message');
-  var ssfi = flag(obj, 'ssfi');
+  var ssfi = flag(obj, 'ssfi') as Function;
 
   flagMsg = flagMsg ? flagMsg + ': ' : '';
 
-  obj = flag(obj, 'object');
+  obj = flag(obj, 'object') as object;
   types = types.map(function (t) {
     return t.toLowerCase();
   });
diff --git a/src/chai/utils/flag.ts b/src/chai/utils/flag.ts
new file mode 100644
index 00000000..c7cfd07a
--- /dev/null
+++ b/src/chai/utils/flag.ts
@@ -0,0 +1,48 @@
+/*!
+ * Chai - flag utility
+ * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
+ * MIT Licensed
+ */
+
+/**
+ * ### .flag(object, key, [value])
+ *
+ * Get or set a flag value on an object. If a
+ * value is provided it will be set, else it will
+ * return the currently set value or `undefined` if
+ * the value is not set.
+ *
+ *     utils.flag(this, 'foo', 'bar'); // setter
+ *     utils.flag(this, 'foo'); // getter, returns `bar`
+ *
+ * @param {object} obj constructed Assertion
+ * @param {string} key
+ * @param {unknown} value (optional)
+ * @returns {unknown | undefined}
+ * @namespace Utils
+ * @name flag
+ * @private
+ */
+function flag<T extends {__flags: unknown}, TKey extends keyof T['__flags']>(
+  obj: T,
+  key: TKey
+): T['__flags'][TKey];
+function flag<T extends {__flags: unknown}, TKey extends keyof T['__flags']>(
+  obj: T,
+  key: TKey,
+  value: T['__flags'][TKey]
+): void;
+function flag<T extends object>(obj: T, key: PropertyKey): unknown;
+function flag<T extends object>(obj: T, key: PropertyKey, value: unknown): void;
+function flag(obj: object, key: PropertyKey, value?: unknown): unknown {
+  const objWithFlags = obj as {__flags?: Record<PropertyKey, unknown>};
+  const flags =
+    objWithFlags.__flags || (objWithFlags.__flags = Object.create(null));
+  if (arguments.length === 3) {
+    (flags as Record<PropertyKey, unknown>)[key] = value;
+  } else {
+    return (flags as Record<PropertyKey, unknown>)[key];
+  }
+}
+
+export {flag};
diff --git a/lib/chai/utils/getActual.js b/src/chai/utils/getActual.ts
similarity index 75%
rename from lib/chai/utils/getActual.js
rename to src/chai/utils/getActual.ts
index 1b4b3aa2..66a4fcac 100644
--- a/lib/chai/utils/getActual.js
+++ b/src/chai/utils/getActual.ts
@@ -15,6 +15,6 @@
  * @namespace Utils
  * @name getActual
  */
-export function getActual(obj, args) {
-  return args.length > 4 ? args[4] : obj._obj;
+export function getActual(obj: object, args: IArguments) {
+  return args.length > 4 ? args[4] : (obj as {_obj: unknown})._obj;
 }
diff --git a/lib/chai/utils/getMessage.js b/src/chai/utils/getMessage.ts
similarity index 95%
rename from lib/chai/utils/getMessage.js
rename to src/chai/utils/getMessage.ts
index faa841f2..81e3c077 100644
--- a/lib/chai/utils/getMessage.js
+++ b/src/chai/utils/getMessage.ts
@@ -27,7 +27,7 @@ import {objDisplay} from './objDisplay.js';
  * @name getMessage
  * @public
  */
-export function getMessage(obj, args) {
+export function getMessage(obj: object, args: IArguments): string {
   var negate = flag(obj, 'negate'),
     val = flag(obj, 'object'),
     expected = args[3],
diff --git a/lib/chai/utils/getOperator.js b/src/chai/utils/getOperator.ts
similarity index 92%
rename from lib/chai/utils/getOperator.js
rename to src/chai/utils/getOperator.ts
index 7a57d786..f4cab223 100644
--- a/lib/chai/utils/getOperator.js
+++ b/src/chai/utils/getOperator.ts
@@ -5,7 +5,7 @@ import {type} from './type-detect.js';
  * @param {unknown} obj
  * @returns {boolean}
  */
-function isObjectType(obj) {
+function isObjectType(obj: object) {
   var objectType = type(obj);
   var objectTypes = ['Array', 'Object', 'Function'];
 
@@ -28,7 +28,7 @@ function isObjectType(obj) {
  * @name getOperator
  * @public
  */
-export function getOperator(obj, args) {
+export function getOperator(obj: object, args: IArguments) {
   var operator = flag(obj, 'operator');
   var negate = flag(obj, 'negate');
   var expected = args[3];
diff --git a/lib/chai/utils/getOwnEnumerableProperties.js b/src/chai/utils/getOwnEnumerableProperties.ts
similarity index 82%
rename from lib/chai/utils/getOwnEnumerableProperties.js
rename to src/chai/utils/getOwnEnumerableProperties.ts
index 9e8e830b..b515f62a 100644
--- a/lib/chai/utils/getOwnEnumerableProperties.js
+++ b/src/chai/utils/getOwnEnumerableProperties.ts
@@ -19,6 +19,6 @@ import {getOwnEnumerablePropertySymbols} from './getOwnEnumerablePropertySymbols
  * @name getOwnEnumerableProperties
  * @public
  */
-export function getOwnEnumerableProperties(obj) {
-  return Object.keys(obj).concat(getOwnEnumerablePropertySymbols(obj));
+export function getOwnEnumerableProperties(obj: object) {
+  return [...Object.keys(obj), ...getOwnEnumerablePropertySymbols(obj)];
 }
diff --git a/lib/chai/utils/getOwnEnumerablePropertySymbols.js b/src/chai/utils/getOwnEnumerablePropertySymbols.ts
similarity index 84%
rename from lib/chai/utils/getOwnEnumerablePropertySymbols.js
rename to src/chai/utils/getOwnEnumerablePropertySymbols.ts
index d8d6d096..5b4c3019 100644
--- a/lib/chai/utils/getOwnEnumerablePropertySymbols.js
+++ b/src/chai/utils/getOwnEnumerablePropertySymbols.ts
@@ -17,10 +17,10 @@
  * @name getOwnEnumerablePropertySymbols
  * @public
  */
-export function getOwnEnumerablePropertySymbols(obj) {
+export function getOwnEnumerablePropertySymbols(obj: object) {
   if (typeof Object.getOwnPropertySymbols !== 'function') return [];
 
   return Object.getOwnPropertySymbols(obj).filter(function (sym) {
-    return Object.getOwnPropertyDescriptor(obj, sym).enumerable;
+    return Object.getOwnPropertyDescriptor(obj, sym)?.enumerable;
   });
 }
diff --git a/lib/chai/utils/getProperties.js b/src/chai/utils/getProperties.ts
similarity index 85%
rename from lib/chai/utils/getProperties.js
rename to src/chai/utils/getProperties.ts
index b43c7104..37901b0b 100644
--- a/lib/chai/utils/getProperties.js
+++ b/src/chai/utils/getProperties.ts
@@ -16,13 +16,13 @@
  * @name getProperties
  * @public
  */
-export function getProperties(object) {
+export function getProperties(object: object) {
   var result = Object.getOwnPropertyNames(object);
 
   /**
-   * @param {unknown} property
+   * @param {string} property
    */
-  function addProperty(property) {
+  function addProperty(property: string) {
     if (result.indexOf(property) === -1) {
       result.push(property);
     }
diff --git a/lib/chai/utils/index.js b/src/chai/utils/index.ts
similarity index 94%
rename from lib/chai/utils/index.js
rename to src/chai/utils/index.ts
index 70d9f4c1..d6ae8860 100644
--- a/lib/chai/utils/index.js
+++ b/src/chai/utils/index.ts
@@ -47,7 +47,7 @@ export {getPathInfo, hasProperty} from 'pathval';
  * @param {Function} fn
  * @returns {string}
  */
-export function getName(fn) {
+export function getName(fn: Function) {
   return fn.name;
 }
 
@@ -103,7 +103,7 @@ export {getOperator} from './getOperator.js';
  * @param {*} obj Object to test
  * @returns {boolean}
  */
-export function isRegExp(obj) {
+export function isRegExp(obj: unknown): obj is RegExp {
   return Object.prototype.toString.call(obj) === '[object RegExp]';
 }
 
@@ -113,6 +113,6 @@ export function isRegExp(obj) {
  * @param {unknown} obj Object to test
  * @returns {boolean}
  */
-export function isNumeric(obj) {
+export function isNumeric(obj: unknown): obj is number | bigint {
   return ['Number', 'BigInt'].includes(type(obj));
 }
diff --git a/lib/chai/utils/inspect.js b/src/chai/utils/inspect.ts
similarity index 90%
rename from lib/chai/utils/inspect.js
rename to src/chai/utils/inspect.ts
index f27bf341..67bdade9 100644
--- a/lib/chai/utils/inspect.js
+++ b/src/chai/utils/inspect.ts
@@ -20,7 +20,12 @@ import {config} from '../config.js';
  * @namespace Utils
  * @name inspect
  */
-export function inspect(obj, showHidden, depth, colors) {
+export function inspect(
+  obj: unknown,
+  showHidden?: boolean,
+  depth?: number,
+  colors?: boolean
+) {
   var options = {
     colors: colors,
     depth: typeof depth === 'undefined' ? 2 : depth,
diff --git a/lib/chai/utils/isNaN.js b/src/chai/utils/isNaN.ts
similarity index 94%
rename from lib/chai/utils/isNaN.js
rename to src/chai/utils/isNaN.ts
index acc10d6f..d7f0a761 100644
--- a/lib/chai/utils/isNaN.js
+++ b/src/chai/utils/isNaN.ts
@@ -16,7 +16,7 @@
  * @name isNaN
  * @private
  */
-function _isNaN(value) {
+function _isNaN(value: unknown) {
   // Refer http://www.ecma-international.org/ecma-262/6.0/#sec-isnan-number
   // section's NOTE.
   return value !== value;
diff --git a/lib/chai/utils/isProxyEnabled.js b/src/chai/utils/isProxyEnabled.ts
similarity index 100%
rename from lib/chai/utils/isProxyEnabled.js
rename to src/chai/utils/isProxyEnabled.ts
diff --git a/lib/chai/utils/objDisplay.js b/src/chai/utils/objDisplay.ts
similarity index 77%
rename from lib/chai/utils/objDisplay.js
rename to src/chai/utils/objDisplay.ts
index cf58d5da..52495fc0 100644
--- a/lib/chai/utils/objDisplay.js
+++ b/src/chai/utils/objDisplay.ts
@@ -20,19 +20,19 @@ import {config} from '../config.js';
  * @namespace Utils
  * @public
  */
-export function objDisplay(obj) {
+export function objDisplay(obj: unknown) {
   var str = inspect(obj),
     type = Object.prototype.toString.call(obj);
 
   if (config.truncateThreshold && str.length >= config.truncateThreshold) {
     if (type === '[object Function]') {
-      return !obj.name || obj.name === ''
+      return !(obj as Function).name || (obj as Function).name === ''
         ? '[Function]'
-        : '[Function: ' + obj.name + ']';
+        : '[Function: ' + (obj as Function).name + ']';
     } else if (type === '[object Array]') {
-      return '[ Array(' + obj.length + ') ]';
+      return '[ Array(' + (obj as unknown[]).length + ') ]';
     } else if (type === '[object Object]') {
-      var keys = Object.keys(obj),
+      var keys = Object.keys(obj as Record<PropertyKey, unknown>),
         kstr =
           keys.length > 2
             ? keys.splice(0, 2).join(', ') + ', ...'
diff --git a/lib/chai/utils/overwriteChainableMethod.js b/src/chai/utils/overwriteChainableMethod.ts
similarity index 70%
rename from lib/chai/utils/overwriteChainableMethod.js
rename to src/chai/utils/overwriteChainableMethod.ts
index 27fce9f4..b80062c8 100644
--- a/lib/chai/utils/overwriteChainableMethod.js
+++ b/src/chai/utils/overwriteChainableMethod.ts
@@ -4,8 +4,7 @@
  * MIT Licensed
  */
 
-import {Assertion} from '../assertion.js';
-import {transferFlags} from './transferFlags.js';
+import {ChainableBehavior} from './chainableBehavior.js';
 
 /**
  * ### .overwriteChainableMethod(ctx, name, method, chainingBehavior)
@@ -35,35 +34,50 @@ import {transferFlags} from './transferFlags.js';
  * @param {string} name of method / property to overwrite
  * @param {Function} method function that returns a function to be used for name
  * @param {Function} chainingBehavior function that returns a function to be used for property
+ * @param {Function=} createDefaultValue
  * @namespace Utils
  * @name overwriteChainableMethod
  * @public
  */
-export function overwriteChainableMethod(ctx, name, method, chainingBehavior) {
-  var chainableBehavior = ctx.__methods[name];
+export function overwriteChainableMethod<T extends object>(
+  ctx: T,
+  name: string,
+  method: Function,
+  chainingBehavior: Function,
+  createDefaultValue?: (ctx: T) => unknown
+) {
+  var chainableBehavior = (
+    ctx as {__methods: Record<PropertyKey, ChainableBehavior>}
+  ).__methods[name];
 
   var _chainingBehavior = chainableBehavior.chainingBehavior;
   chainableBehavior.chainingBehavior =
-    function overwritingChainableMethodGetter() {
+    function overwritingChainableMethodGetter(this: T) {
       var result = chainingBehavior(_chainingBehavior).call(this);
       if (result !== undefined) {
         return result;
       }
 
-      var newAssertion = new Assertion();
-      transferFlags(this, newAssertion);
-      return newAssertion;
+      if (createDefaultValue) {
+        return createDefaultValue(this);
+      }
+
+      return undefined;
     };
 
   var _method = chainableBehavior.method;
-  chainableBehavior.method = function overwritingChainableMethodWrapper() {
+  chainableBehavior.method = function overwritingChainableMethodWrapper(
+    this: T
+  ) {
     var result = method(_method).apply(this, arguments);
     if (result !== undefined) {
       return result;
     }
 
-    var newAssertion = new Assertion();
-    transferFlags(this, newAssertion);
-    return newAssertion;
+    if (createDefaultValue) {
+      return createDefaultValue(this);
+    }
+
+    return undefined;
   };
 }
diff --git a/lib/chai/utils/overwriteMethod.js b/src/chai/utils/overwriteMethod.ts
similarity index 82%
rename from lib/chai/utils/overwriteMethod.js
rename to src/chai/utils/overwriteMethod.ts
index 0fbeb3be..09607606 100644
--- a/lib/chai/utils/overwriteMethod.js
+++ b/src/chai/utils/overwriteMethod.ts
@@ -4,11 +4,9 @@
  * MIT Licensed
  */
 
-import {Assertion} from '../assertion.js';
 import {addLengthGuard} from './addLengthGuard.js';
 import {flag} from './flag.js';
 import {proxify} from './proxify.js';
-import {transferFlags} from './transferFlags.js';
 
 /**
  * ### .overwriteMethod(ctx, name, fn)
@@ -43,15 +41,20 @@ import {transferFlags} from './transferFlags.js';
  * @name overwriteMethod
  * @public
  */
-export function overwriteMethod(ctx, name, method) {
-  var _method = ctx[name],
-    _super = function () {
-      throw new Error(name + ' is not a function');
+export function overwriteMethod<T extends object>(
+  ctx: T,
+  name: PropertyKey,
+  method: Function,
+  createDefaultValue?: (ctx: T) => unknown
+) {
+  var _method = (ctx as Record<PropertyKey, unknown>)[name],
+    _super: Function = function () {
+      throw new Error(String(name) + ' is not a function');
     };
 
   if (_method && 'function' === typeof _method) _super = _method;
 
-  var overwritingMethodWrapper = function () {
+  var overwritingMethodWrapper = function (this: T) {
     // Setting the `ssfi` flag to `overwritingMethodWrapper` causes this
     // function to be the starting point for removing implementation frames from
     // the stack trace of a failed assertion.
@@ -80,11 +83,16 @@ export function overwriteMethod(ctx, name, method) {
       return result;
     }
 
-    var newAssertion = new Assertion();
-    transferFlags(this, newAssertion);
-    return newAssertion;
+    if (createDefaultValue) {
+      return createDefaultValue(this);
+    }
+
+    return undefined;
   };
 
   addLengthGuard(overwritingMethodWrapper, name, false);
-  ctx[name] = proxify(overwritingMethodWrapper, name);
+  (ctx as Record<PropertyKey, unknown>)[name] = proxify(
+    overwritingMethodWrapper,
+    name
+  );
 }
diff --git a/lib/chai/utils/overwriteProperty.js b/src/chai/utils/overwriteProperty.ts
similarity index 90%
rename from lib/chai/utils/overwriteProperty.js
rename to src/chai/utils/overwriteProperty.ts
index d1253093..9a522384 100644
--- a/lib/chai/utils/overwriteProperty.js
+++ b/src/chai/utils/overwriteProperty.ts
@@ -4,10 +4,8 @@
  * MIT Licensed
  */
 
-import {Assertion} from '../assertion.js';
 import {flag} from './flag.js';
 import {isProxyEnabled} from './isProxyEnabled.js';
-import {transferFlags} from './transferFlags.js';
 
 /**
  * ### .overwriteProperty(ctx, name, fn)
@@ -37,11 +35,17 @@ import {transferFlags} from './transferFlags.js';
  * @param {object} ctx object whose property is to be overwritten
  * @param {string} name of property to overwrite
  * @param {Function} getter function that returns a getter function to be used for name
+ * @param {Function=} createDefaultValue
  * @namespace Utils
  * @name overwriteProperty
  * @public
  */
-export function overwriteProperty(ctx, name, getter) {
+export function overwriteProperty<T extends object>(
+  ctx: T,
+  name: PropertyKey,
+  getter: Function,
+  createDefaultValue?: (ctx: T) => unknown
+) {
   var _get = Object.getOwnPropertyDescriptor(ctx, name),
     _super = function () {};
 
@@ -80,9 +84,11 @@ export function overwriteProperty(ctx, name, getter) {
         return result;
       }
 
-      var newAssertion = new Assertion();
-      transferFlags(this, newAssertion);
-      return newAssertion;
+      if (createDefaultValue) {
+        return createDefaultValue(this);
+      }
+
+      return undefined;
     },
     configurable: true
   });
diff --git a/lib/chai/utils/proxify.js b/src/chai/utils/proxify.ts
similarity index 92%
rename from lib/chai/utils/proxify.js
rename to src/chai/utils/proxify.ts
index 4c1cf695..b92d7602 100644
--- a/lib/chai/utils/proxify.js
+++ b/src/chai/utils/proxify.ts
@@ -9,7 +9,7 @@ import {isProxyEnabled} from './isProxyEnabled.js';
  * MIT Licensed
  */
 
-const builtins = ['__flags', '__methods', '_obj', 'assert'];
+const builtins: PropertyKey[] = ['__flags', '__methods', '_obj', 'assert'];
 
 /**
  * ### .proxify(object)
@@ -30,7 +30,10 @@ const builtins = ['__flags', '__methods', '_obj', 'assert'];
  * @namespace Utils
  * @name proxify
  */
-export function proxify(obj, nonChainableMethodName) {
+export function proxify<T extends object>(
+  obj: T,
+  nonChainableMethodName?: PropertyKey
+): T {
   if (!isProxyEnabled()) return obj;
 
   return new Proxy(obj, {
@@ -48,11 +51,11 @@ export function proxify(obj, nonChainableMethodName) {
         if (nonChainableMethodName) {
           throw Error(
             'Invalid Chai property: ' +
-              nonChainableMethodName +
+              String(nonChainableMethodName) +
               '.' +
               property +
               '. See docs for proper usage of "' +
-              nonChainableMethodName +
+              String(nonChainableMethodName) +
               '".'
           );
         }
@@ -103,7 +106,7 @@ export function proxify(obj, nonChainableMethodName) {
       // being called from within another assertion. In that case, the `ssfi`
       // flag is already set to the outer assertion's starting point.
       if (builtins.indexOf(property) === -1 && !flag(target, 'lockSsfi')) {
-        flag(target, 'ssfi', proxyGetter);
+        flag(target as object, 'ssfi', proxyGetter);
       }
 
       return Reflect.get(target, property);
@@ -121,7 +124,7 @@ export function proxify(obj, nonChainableMethodName) {
  * @returns {number} min(string distance between strA and strB, cap)
  * @private
  */
-function stringDistanceCapped(strA, strB, cap) {
+function stringDistanceCapped(strA: string, strB: string, cap: number) {
   if (Math.abs(strA.length - strB.length) >= cap) {
     return cap;
   }
diff --git a/lib/chai/utils/test.js b/src/chai/utils/test.ts
similarity index 89%
rename from lib/chai/utils/test.js
rename to src/chai/utils/test.ts
index 6d64aeb3..172d0ee6 100644
--- a/lib/chai/utils/test.js
+++ b/src/chai/utils/test.ts
@@ -17,7 +17,7 @@ import {flag} from './flag.js';
  * @namespace Utils
  * @name test
  */
-export function test(obj, args) {
+export function test(obj: object, args: IArguments) {
   var negate = flag(obj, 'negate'),
     expr = args[0];
   return negate ? !expr : expr;
diff --git a/lib/chai/utils/transferFlags.js b/src/chai/utils/transferFlags.ts
similarity index 69%
rename from lib/chai/utils/transferFlags.js
rename to src/chai/utils/transferFlags.ts
index 4e5b785f..4386d27e 100644
--- a/lib/chai/utils/transferFlags.js
+++ b/src/chai/utils/transferFlags.ts
@@ -25,12 +25,21 @@
  * @name transferFlags
  * @private
  */
-export function transferFlags(assertion, object, includeAll) {
-  var flags = assertion.__flags || (assertion.__flags = Object.create(null));
+export function transferFlags(
+  assertion: object,
+  object: object,
+  includeAll?: boolean
+) {
+  const assertionWithFlags = assertion as {
+    __flags?: Record<PropertyKey, unknown>;
+  };
+  const objWithFlags = object as {__flags?: Record<PropertyKey, unknown>};
 
-  if (!object.__flags) {
-    object.__flags = Object.create(null);
-  }
+  var flags =
+    assertionWithFlags.__flags ||
+    (assertionWithFlags.__flags = Object.create(null));
+  const objFlags =
+    objWithFlags.__flags || (objWithFlags.__flags = Object.create(null));
 
   includeAll = arguments.length === 3 ? includeAll : true;
 
@@ -42,7 +51,7 @@ export function transferFlags(assertion, object, includeAll) {
         flag !== 'lockSsfi' &&
         flag != 'message')
     ) {
-      object.__flags[flag] = flags[flag];
+      objFlags[flag] = flags[flag];
     }
   }
 }
diff --git a/lib/chai/utils/type-detect.js b/src/chai/utils/type-detect.ts
similarity index 72%
rename from lib/chai/utils/type-detect.js
rename to src/chai/utils/type-detect.ts
index 573edf81..f0ddeca1 100644
--- a/lib/chai/utils/type-detect.js
+++ b/src/chai/utils/type-detect.ts
@@ -2,7 +2,7 @@
  * @param {unknown} obj
  * @returns {string}
  */
-export function type(obj) {
+export function type(obj: unknown) {
   if (typeof obj === 'undefined') {
     return 'undefined';
   }
@@ -11,7 +11,7 @@ export function type(obj) {
     return 'null';
   }
 
-  const stringTag = obj[Symbol.toStringTag];
+  const stringTag = (obj as Record<PropertyKey, unknown>)[Symbol.toStringTag];
   if (typeof stringTag === 'string') {
     return stringTag;
   }
diff --git a/src/chai/utils/types.ts b/src/chai/utils/types.ts
new file mode 100644
index 00000000..6212c16a
--- /dev/null
+++ b/src/chai/utils/types.ts
@@ -0,0 +1,18 @@
+export type Constructor<T> = {new (): T};
+
+export type OnlyIf<T, TCondition, TResult, TElse = never> = T extends TCondition
+  ? TResult
+  : TElse;
+
+export type CollectionLike<T> =
+  | Map<unknown, T>
+  | Set<T>
+  | (T extends object ? WeakSet<T> : never)
+  | Array<T>;
+
+export type KeyedObject = Map<unknown, unknown> | object | Set<unknown>;
+
+export type LengthLike =
+  | Map<unknown, unknown>
+  | Set<unknown>
+  | {length: number};
diff --git a/test/assert.js b/test/assert.js
index 89395b05..37d83294 100644
--- a/test/assert.js
+++ b/test/assert.js
@@ -1,4 +1,4 @@
-import * as chai from '../index.js';
+import * as chai from '../chai.js';
 import {globalErr as err} from './bootstrap/index.js';
 
 describe('assert', function () {
diff --git a/test/bootstrap/index.js b/test/bootstrap/index.js
index a6cb371e..39250fc3 100644
--- a/test/bootstrap/index.js
+++ b/test/bootstrap/index.js
@@ -1,4 +1,4 @@
-import * as chai from '../../index.js';
+import * as chai from '../../chai.js';
 
 var isStackSupported = false;
 if (typeof Error.captureStackTrace !== 'undefined') {
diff --git a/test/configuration.js b/test/configuration.js
index b5ac839d..7358a438 100644
--- a/test/configuration.js
+++ b/test/configuration.js
@@ -1,4 +1,4 @@
-import * as chai from '../index.js';
+import * as chai from '../chai.js';
 import {globalErr as err} from './bootstrap/index.js';
 
 import '../register-should.js'; 
diff --git a/test/display/errors.js b/test/display/errors.js
index cef1b702..eae44a01 100644
--- a/test/display/errors.js
+++ b/test/display/errors.js
@@ -1,4 +1,4 @@
-import * as chai from '../../index.js';
+import * as chai from '../../chai.js';
 
 var expect = chai.expect;
 
diff --git a/test/display/message.js b/test/display/message.js
index 7ce28125..405b0066 100644
--- a/test/display/message.js
+++ b/test/display/message.js
@@ -1,4 +1,4 @@
-import * as chai from '../../index.js'
+import * as chai from '../../chai.js'
 
 const expect = chai.expect
 
diff --git a/test/expect.js b/test/expect.js
index c9549d77..04b40151 100644
--- a/test/expect.js
+++ b/test/expect.js
@@ -1,4 +1,4 @@
-import * as chai from '../index.js';
+import * as chai from '../chai.js';
 import {globalErr as err} from './bootstrap/index.js';
 
 describe('expect', function () {
@@ -11,29 +11,34 @@ describe('expect', function () {
 
   describe('safeguards', function () {
     before(function () {
+      const getDefaultValue = (assertion) => {
+        var newAssertion = chai.Assertion.create();
+        chai.util.transferFlags(assertion, newAssertion);
+        return newAssertion;
+      };
       chai.util.addProperty(chai.Assertion.prototype, 'tmpProperty', function () {
         new chai.Assertion(42).equal(42);
-      });
+      }, getDefaultValue);
       chai.util.overwriteProperty(chai.Assertion.prototype, 'tmpProperty', function (_super) {
         return function () {
           _super.call(this);
         };
-      });
+      }, getDefaultValue);
 
       chai.util.addMethod(chai.Assertion.prototype, 'tmpMethod', function () {
         new chai.Assertion(42).equal(42);
-      });
+      }, getDefaultValue);
       chai.util.overwriteMethod(chai.Assertion.prototype, 'tmpMethod', function (_super) {
         return function () {
           _super.call(this);
         };
-      });
+      }, getDefaultValue);
 
       chai.util.addChainableMethod(chai.Assertion.prototype, 'tmpChainableMethod', function () {
         new chai.Assertion(42).equal(42);
       }, function () {
         new chai.Assertion(42).equal(42);
-      });
+      }, getDefaultValue);
       chai.util.overwriteChainableMethod(chai.Assertion.prototype, 'tmpChainableMethod', function (_super) {
         return function () {
           _super.call(this);
@@ -42,7 +47,7 @@ describe('expect', function () {
         return function () {
           _super.call(this);
         };
-      });
+      }, getDefaultValue);
     });
 
     after(function () {
diff --git a/test/globalErr.js b/test/globalErr.js
index d51b3fe8..8a3e8677 100644
--- a/test/globalErr.js
+++ b/test/globalErr.js
@@ -1,4 +1,4 @@
-import * as chai from '../index.js';
+import * as chai from '../chai.js';
 import {globalErr as err} from './bootstrap/index.js';
 
 describe('globalErr', function () {
diff --git a/test/globalShould.js b/test/globalShould.js
index 5fb11eaf..0c1b2e6b 100644
--- a/test/globalShould.js
+++ b/test/globalShould.js
@@ -1,4 +1,4 @@
-import * as chai from '../index.js';
+import * as chai from '../chai.js';
 
 describe('global should', function () {
   it('works', function () {
diff --git a/test/plugins.js b/test/plugins.js
index c9c013cc..f3c1605b 100644
--- a/test/plugins.js
+++ b/test/plugins.js
@@ -1,4 +1,4 @@
-import * as chai from '../index.js';
+import * as chai from '../chai.js';
 
 describe('plugins', function () {
 
diff --git a/test/should.js b/test/should.js
index 4b7fbc48..54c8c3ef 100644
--- a/test/should.js
+++ b/test/should.js
@@ -1,4 +1,4 @@
-import * as chai from '../index.js';
+import * as chai from '../chai.js';
 import {globalErr as err} from './bootstrap/index.js';
 
 describe('should', function() {
@@ -12,29 +12,34 @@ describe('should', function() {
 
   describe('safeguards', function () {
     before(function () {
+      const getDefaultValue = (assertion) => {
+        var newAssertion = chai.Assertion.create();
+        chai.util.transferFlags(assertion, newAssertion);
+        return newAssertion;
+      };
       chai.util.addProperty(chai.Assertion.prototype, 'tmpProperty', function () {
         new chai.Assertion(42).equal(42);
-      });
+      }, getDefaultValue);
       chai.util.overwriteProperty(chai.Assertion.prototype, 'tmpProperty', function (_super) {
         return function () {
           _super.call(this);
         };
-      });
+      }, getDefaultValue);
 
       chai.util.addMethod(chai.Assertion.prototype, 'tmpMethod', function () {
         new chai.Assertion(42).equal(42);
-      });
+      }, getDefaultValue);
       chai.util.overwriteMethod(chai.Assertion.prototype, 'tmpMethod', function (_super) {
         return function () {
           _super.call(this);
         };
-      });
+      }, getDefaultValue);
 
       chai.util.addChainableMethod(chai.Assertion.prototype, 'tmpChainableMethod', function () {
         new chai.Assertion(42).equal(42);
       }, function () {
         new chai.Assertion(42).equal(42);
-      });
+      }, getDefaultValue);
       chai.util.overwriteChainableMethod(chai.Assertion.prototype, 'tmpChainableMethod', function (_super) {
         return function () {
           _super.call(this);
@@ -43,7 +48,7 @@ describe('should', function() {
         return function () {
           _super.call(this);
         };
-      });
+      }, getDefaultValue);
     });
 
     after(function () {
diff --git a/test/type-detect/dom.js b/test/type-detect/dom.js
index bda6bdc3..e2ceeab1 100644
--- a/test/type-detect/dom.js
+++ b/test/type-detect/dom.js
@@ -1,4 +1,4 @@
-import * as chai from '../../index.js';
+import * as chai from '../../chai.js';
 
 function assert (expr, msg) {
   if (!expr) {
diff --git a/test/type-detect/index.js b/test/type-detect/index.js
index 983800d1..b1494473 100644
--- a/test/type-detect/index.js
+++ b/test/type-detect/index.js
@@ -1,4 +1,4 @@
-import * as chai from '../../index.js';
+import * as chai from '../../chai.js';
 
 function assert (expr, msg) {
   if (!expr) {
diff --git a/test/type-detect/new-ecmascript-types.js b/test/type-detect/new-ecmascript-types.js
index 256c998e..c52d3fd2 100644
--- a/test/type-detect/new-ecmascript-types.js
+++ b/test/type-detect/new-ecmascript-types.js
@@ -1,4 +1,4 @@
-import * as chai from '../../index.js';
+import * as chai from '../../chai.js';
 
 function assert (expr, msg) {
   if (!expr) {
diff --git a/test/type-detect/node.js b/test/type-detect/node.js
index 18552548..f59733b6 100644
--- a/test/type-detect/node.js
+++ b/test/type-detect/node.js
@@ -1,4 +1,4 @@
-import * as chai from '../../index.js';
+import * as chai from '../../chai.js';
 
 function assert (expr, msg) {
   if (!expr) {
diff --git a/test/type-detect/tostringtag-extras.js b/test/type-detect/tostringtag-extras.js
index 3a0f7deb..883181a0 100644
--- a/test/type-detect/tostringtag-extras.js
+++ b/test/type-detect/tostringtag-extras.js
@@ -1,4 +1,4 @@
-import * as chai from '../../index.js';
+import * as chai from '../../chai.js';
 
 function assert (expr, msg) {
   if (!expr) {
diff --git a/test/utilities.js b/test/utilities.js
index 3e956352..aacda8b1 100644
--- a/test/utilities.js
+++ b/test/utilities.js
@@ -1,4 +1,4 @@
-import * as chai from '../index.js';
+import * as chai from '../chai.js';
 
 describe('utilities', function () {
   const expect = chai.expect;
diff --git a/test/virtual-machines.js b/test/virtual-machines.js
index da5ec416..e1b53fa0 100644
--- a/test/virtual-machines.js
+++ b/test/virtual-machines.js
@@ -1,5 +1,5 @@
 import vm from 'node:vm';
-import * as chai from '../index.js';
+import * as chai from '../chai.js';
 
 const {assert} = chai;
 const vmContext = {assert};
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 00000000..beea3281
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,19 @@
+{
+  "compilerOptions": {
+    "target": "es2021",
+    "module": "nodenext",
+    "moduleResolution": "nodenext",
+    "types": [],
+    "declaration": true,
+    "sourceMap": true,
+    "outDir": "./lib",
+    "isolatedModules": true,
+    "forceConsistentCasingInFileNames": true,
+    "strict": true,
+    "noUnusedLocals": true,
+    "noUnusedParameters": true
+  },
+  "include": [
+    "src/**/*.ts"
+  ]
+}