Skip to content

Commit

Permalink
[major] improve assertion performance, remove and getRuntimeType, Run…
Browse files Browse the repository at this point in the history
…timeType from @augment-vir/assert
  • Loading branch information
electrovir committed Dec 7, 2024
1 parent 7bde200 commit a7572b8
Show file tree
Hide file tree
Showing 13 changed files with 1,602 additions and 258 deletions.
1,624 changes: 1,511 additions & 113 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@augment-vir/mono-repo-root",
"version": "30.8.4",
"version": "31.0.0",
"private": true,
"homepage": "https://github.com/electrovir/augment-vir",
"bugs": {
Expand Down
4 changes: 2 additions & 2 deletions packages/assert/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@augment-vir/assert",
"version": "30.8.4",
"version": "31.0.0",
"description": "A collection of assertions for test and production code alike.",
"keywords": [
"augment",
Expand Down Expand Up @@ -41,7 +41,7 @@
"test:update": "npm test"
},
"dependencies": {
"@augment-vir/core": "^30.8.4",
"@augment-vir/core": "^31.0.0",
"@date-vir/duration": "^7.0.1",
"deep-eql": "^5.0.2",
"expect-type": "^1.1.0",
Expand Down
6 changes: 4 additions & 2 deletions packages/assert/src/assertions/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ function hasKey<const Key extends PropertyKey, const Parent>(
key: Key,
failureMessage?: string | undefined,
): asserts parent is CombineTypeWithKey<Key, Parent> {
const message = `'${stringify(parent)}' does not have key '${String(key)}'.`;
const doesHaveKey = hasKeyAttempts.some((attemptCallback) => {
try {
return attemptCallback(parent as object, key);
Expand All @@ -83,7 +82,10 @@ function hasKey<const Key extends PropertyKey, const Parent>(
});

if (!doesHaveKey) {
throw new AssertionError(message, failureMessage);
throw new AssertionError(
`'${stringify(parent)}' does not have key '${String(key)}'.`,
failureMessage,
);
}
}
function lacksKey<const Parent, const Key extends PropertyKey>(
Expand Down
179 changes: 62 additions & 117 deletions packages/assert/src/assertions/runtime-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,113 +24,153 @@ function isNotArray<const Actual>(
actual: Actual,
failureMessage?: string | undefined,
): asserts actual is Exclude<Actual, ReadonlyArray<unknown>> {
assertNotRuntimeType(actual, 'array', failureMessage);
if (Array.isArray(actual)) {
throw new AssertionError(`'${stringify(actual)}' is an array.`, failureMessage);
}
}
function isNotBigInt<const Actual>(
actual: Actual,
failureMessage?: string | undefined,
): asserts actual is Exclude<Actual, bigint> {
assertNotRuntimeType(actual, 'bigint', failureMessage);
if (typeof actual === 'bigint') {
throw new AssertionError(`'${stringify(actual)}' is a bigint.`, failureMessage);
}
}
function isNotBoolean<const Actual>(
actual: Actual,
failureMessage?: string | undefined,
): asserts actual is Exclude<Actual, boolean> {
assertNotRuntimeType(actual, 'boolean', failureMessage);
if (typeof actual === 'boolean') {
throw new AssertionError(`'${stringify(actual)}' is a boolean.`, failureMessage);
}
}
function isNotFunction<const Actual>(
actual: Actual,
failureMessage?: string | undefined,
): asserts actual is Exclude<Actual, AnyFunction> {
assertNotRuntimeType(actual, 'function', failureMessage);
if (typeof actual === 'function') {
throw new AssertionError(`'${stringify(actual)}' is a function.`, failureMessage);
}
}
function isNotNumber<const Actual>(
actual: Actual,
failureMessage?: string | undefined,
): asserts actual is Exclude<Actual, number> {
assertNotRuntimeType(actual, 'number', failureMessage);
if (typeof actual === 'number') {
throw new AssertionError(`'${stringify(actual)}' is a number.`, failureMessage);
}
}
function isNotObject<const Actual>(
actual: Actual,
failureMessage?: string | undefined,
): asserts actual is Exclude<Actual, UnknownObject> {
assertNotRuntimeType(actual, 'object', failureMessage);
if (!Array.isArray(actual) && typeof actual === 'object' && !!actual) {
throw new AssertionError(`'${stringify(actual)}' is a non-null object.`, failureMessage);
}
}
function isNotString<const Actual>(
actual: Actual,
failureMessage?: string | undefined,
): asserts actual is Exclude<Actual, string> {
assertNotRuntimeType(actual, 'string', failureMessage);
if (typeof actual === 'string') {
throw new AssertionError(`'${stringify(actual)}' is a string.`, failureMessage);
}
}
function isNotSymbol<const Actual>(
actual: Actual,
failureMessage?: string | undefined,
): asserts actual is Exclude<Actual, symbol> {
assertNotRuntimeType(actual, 'symbol', failureMessage);
if (typeof actual === 'symbol') {
throw new AssertionError(`'${stringify(actual)}' is a symbol.`, failureMessage);
}
}
function isNotUndefined<const Actual>(
actual: Actual,
failureMessage?: string | undefined,
): asserts actual is Exclude<Actual, undefined> {
assertNotRuntimeType(actual, 'undefined', failureMessage);
if (typeof actual === 'undefined') {
throw new AssertionError(`'${stringify(actual)}' is a undefined.`, failureMessage);
}
}
function isNotNull<const Actual>(
actual: Actual,
failureMessage?: string | undefined,
): asserts actual is Exclude<Actual, null> {
assertNotRuntimeType(actual, 'null', failureMessage);
if (actual === null) {
throw new AssertionError(`'${stringify(actual)}' is a null.`, failureMessage);
}
}

function isArray<const Actual>(
actual: Actual,
failureMessage?: string | undefined,
): asserts actual is ArrayNarrow<Actual> {
assertRuntimeType(actual, 'array', failureMessage);
if (!Array.isArray(actual)) {
throw new AssertionError(`'${stringify(actual)}' is not an array.`, failureMessage);
}
}
function isBigInt(actual: unknown, failureMessage?: string | undefined): asserts actual is bigint {
assertRuntimeType(actual, 'bigint', failureMessage);
if (typeof actual !== 'bigint') {
throw new AssertionError(`'${stringify(actual)}' is not a bigint.`, failureMessage);
}
}
function isBoolean(
actual: unknown,
failureMessage?: string | undefined,
): asserts actual is boolean {
assertRuntimeType(actual, 'boolean', failureMessage);
if (typeof actual !== 'boolean') {
throw new AssertionError(`'${stringify(actual)}' is not a boolean.`, failureMessage);
}
}
function isFunction<const Actual>(
actual: Actual,
failureMessage?: string | undefined,
): asserts actual is NarrowToActual<Actual, AnyFunction> {
assertRuntimeType(actual, 'function', failureMessage);
if (typeof actual !== 'function') {
throw new AssertionError(`'${stringify(actual)}' is not a function.`, failureMessage);
}
}
export function isNumber(
actual: unknown,
failureMessage?: string | undefined,
): asserts actual is number {
assertRuntimeType(actual, 'number', failureMessage);
if (isNaN(actual as number)) {
throw new AssertionError('Value is NaN.', failureMessage);
if (typeof actual !== 'number' || isNaN(actual)) {
throw new AssertionError(`'${stringify(actual)}' is not a number.`, failureMessage);
}
}
function isObject(
actual: unknown,
failureMessage?: string | undefined,
): asserts actual is UnknownObject {
assertRuntimeType(actual, 'object', failureMessage);
if (Array.isArray(actual) || typeof actual !== 'object' || !actual) {
throw new AssertionError(
`'${stringify(actual)}' is not a non-null object.`,
failureMessage,
);
}
}
function isString(actual: unknown, failureMessage?: string | undefined): asserts actual is string {
assertRuntimeType(actual, 'string', failureMessage);
if (typeof actual !== 'string') {
throw new AssertionError(`'${stringify(actual)}' is not a string.`, failureMessage);
}
}
function isSymbol(actual: unknown, failureMessage?: string | undefined): asserts actual is symbol {
assertRuntimeType(actual, 'symbol', failureMessage);
if (typeof actual !== 'symbol') {
throw new AssertionError(`'${stringify(actual)}' is not a symbol.`, failureMessage);
}
}
function isUndefined(
actual: unknown,
failureMessage?: string | undefined,
): asserts actual is undefined {
assertRuntimeType(actual, 'undefined', failureMessage);
if (typeof actual !== 'undefined') {
throw new AssertionError(`'${stringify(actual)}' is not a undefined.`, failureMessage);
}
}
function isNull(actual: unknown, failureMessage?: string | undefined): asserts actual is null {
assertRuntimeType(actual, 'null', failureMessage);
if (actual !== null) {
throw new AssertionError(`'${stringify(actual)}' is not nul.`, failureMessage);
}
}

const assertions: {
Expand Down Expand Up @@ -2412,98 +2452,3 @@ export const runtimeTypeGuards = {
>(),
},
} satisfies GuardGroup<typeof assertions>;

/**
* An enum representing the possible values returned by {@link getRuntimeType}. These values are
* similar to the output of the built-in
* [`typeof`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/typeof) operator
* except that this includes new types `array` and `null`, that `typeof` does not have, which are
* both distinct from `object`.
*
* @category Assert : Util
* @category Package : @augment-vir/assert
* @package [`@augment-vir/assert`](https://www.npmjs.com/package/@augment-vir/assert)
*/
export enum RuntimeType {
String = 'string',
Number = 'number',
Bigint = 'bigint',
Boolean = 'boolean',
Symbol = 'symbol',
Undefined = 'undefined',
Object = 'object',
Function = 'function',
/**
* This is not included in {@link RuntimeType.Object}. (Compared to
* [`typeof`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/typeof)
* which _does_ include `null` in the `'object'` type.)
*/
Array = 'array',
/**
* This is not included in {@link RuntimeType.Object}. (Compared to
* [`typeof`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/typeof)
* which _does_ include `null` in the `'object'` type.)
*/
Null = 'null',
}

/**
* Determines the {@link RuntimeType} of a variable. This is similar to the built-in
* [`typeof`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/typeof) operator
* except in the following ways:
*
* - This returns an enum value ({@link RuntimeType}) rather than just a string (though the enum values
* are strings anyway).
* - This includes new types `array` and `null`, that `typeof` does not have, which are both distinct
* from `object`.
*
* @category Assert : Util
* @category Package : @augment-vir/assert
* @example
*
* ```ts
* import {getRuntimeType} from '@augment-vir/assert';
*
* getRuntimeType(['a']); // RuntimeType.Array
* getRuntimeType({a: 'a'}); // RuntimeType.Object
* ```
*
* @package [`@augment-vir/assert`](https://www.npmjs.com/package/@augment-vir/assert)
*/
export function getRuntimeType(actual: unknown): RuntimeType {
if (actual === null) {
return RuntimeType.Null;
} else if (Array.isArray(actual)) {
return RuntimeType.Array;
} else {
return typeof actual as RuntimeType;
}
}

/**
* Asserts that the given actual matches the given test type. Note that an name for the actual must
* be provided for error messaging purposes.
*/
function assertRuntimeType(
actual: unknown,
testType: RuntimeType | `${RuntimeType}`,
failureMessage?: string | undefined,
) {
const actualType = getRuntimeType(actual);
if (actualType !== testType) {
throw new AssertionError(
`'${stringify(actual)}' is '${actualType}', not '${testType}'.`,
failureMessage,
);
}
}
function assertNotRuntimeType(
actual: unknown,
testType: RuntimeType | `${RuntimeType}`,
failureMessage?: string | undefined,
) {
const actualType = getRuntimeType(actual);
if (actualType === testType) {
throw new AssertionError(`'${stringify(actual)}' is '${actualType}'.`, failureMessage);
}
}
1 change: 0 additions & 1 deletion packages/assert/src/augments/assertion-exports.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
export type {Falsy, FalsyValue, Truthy} from '../assertions/boolean.js';
export type {CustomOutputAsserter} from '../assertions/output.js';
export {RuntimeType, getRuntimeType} from '../assertions/runtime-type.js';
export type {ErrorMatchOptions} from '../assertions/throws.js';
export type {CanBeEmpty, Empty} from '../assertions/values.js';
6 changes: 3 additions & 3 deletions packages/common/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@augment-vir/common",
"version": "30.8.4",
"version": "31.0.0",
"description": "A collection of augments, helpers types, functions, and classes for any JavaScript environment.",
"keywords": [
"augment",
Expand Down Expand Up @@ -39,8 +39,8 @@
"test:web": "virmator --no-deps test web"
},
"dependencies": {
"@augment-vir/assert": "^30.8.4",
"@augment-vir/core": "^30.8.4",
"@augment-vir/assert": "^31.0.0",
"@augment-vir/core": "^31.0.0",
"@date-vir/duration": "^7.0.1",
"ansi-styles": "^6.2.1",
"json5": "^2.2.3",
Expand Down
4 changes: 2 additions & 2 deletions packages/common/src/augments/promise/timed-promise.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {check} from '@augment-vir/assert';
import {ensureError} from '@augment-vir/core';
import {ensureError, type MaybePromise} from '@augment-vir/core';
import {AnyDuration, convertDuration} from '@date-vir/duration';

/**
Expand Down Expand Up @@ -37,7 +37,7 @@ export class PromiseTimeoutError extends Error {
*/
export function wrapPromiseInTimeout<T>(
duration: Readonly<AnyDuration>,
originalPromise: PromiseLike<T>,
originalPromise: MaybePromise<T>,
failureMessage?: string | undefined,
): Promise<T> {
const milliseconds = convertDuration(duration, {milliseconds: true}).milliseconds;
Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@augment-vir/core",
"version": "30.8.4",
"version": "31.0.0",
"description": "Core augment-vir augments. Use @augment-vir/common instead.",
"homepage": "https://github.com/electrovir/augment-vir",
"bugs": {
Expand Down
8 changes: 4 additions & 4 deletions packages/node/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@augment-vir/node",
"version": "30.8.4",
"version": "31.0.0",
"description": "A collection of augments, helpers types, functions, and classes only for Node.js (backend) JavaScript environments.",
"keywords": [
"augment",
Expand Down Expand Up @@ -37,16 +37,16 @@
"test:update": "npm test"
},
"dependencies": {
"@augment-vir/assert": "^30.8.4",
"@augment-vir/common": "^30.8.4",
"@augment-vir/assert": "^31.0.0",
"@augment-vir/common": "^31.0.0",
"@date-vir/duration": "^7.0.1",
"ansi-styles": "^6.2.1",
"terminate": "^2.8.0",
"type-fest": "^4.29.0",
"typed-event-target": "^4.0.2"
},
"devDependencies": {
"@augment-vir/test": "^30.8.4",
"@augment-vir/test": "^31.0.0",
"@prisma/client": "^5.22.0",
"@types/node": "^22.10.0",
"@web/dev-server-esbuild": "^1.0.3",
Expand Down
Loading

0 comments on commit a7572b8

Please sign in to comment.