Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
"url": "https://github.com/webdeveric/utils/issues"
},
"homepage": "https://github.com/webdeveric/utils/#readme",
"packageManager": "[email protected].3+sha512.bb45e34d50a9a76e858a95837301bfb6bd6d35aea2c5d52094fa497a467c43f5c440103ce2511e9e0a2f89c3d6071baac3358fc68ac6fb75e2ceb3d2736065e6",
"packageManager": "[email protected].4+sha512.da3d715bfd22a9a105e6e8088cfc7826699332ded60c423b14ec613a185f1602206702ff0fe4c438cb15c979081ce4cb02568e364b15174503a63c7a8e2a5f6c",
"scripts": {
"clean": "rimraf ./dist/",
"prebuild": "pnpm clean",
Expand All @@ -88,7 +88,7 @@
"@commitlint/config-conventional": "^19.8.0",
"@commitlint/types": "^19.8.0",
"@types/node": "^22.13.10",
"@vitest/coverage-v8": "^3.0.8",
"@vitest/coverage-v8": "^3.0.9",
"@webdeveric/eslint-config-ts": "^0.11.0",
"@webdeveric/prettier-config": "^0.3.0",
"commitlint": "^19.8.0",
Expand All @@ -107,7 +107,7 @@
"semantic-release": "^24.2.3",
"typescript": "^5.8.2",
"validate-package-exports": "^0.8.0",
"vitest": "^3.0.8"
"vitest": "^3.0.9"
},
"pnpm": {
"onlyBuiltDependencies": [
Expand Down
424 changes: 212 additions & 212 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions src/predicate/factory/fromEnum.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { TypePredicateFn } from '../../types/functions.js';
import type { UnknownRecord } from '../../types/records.js';
import type { EnumRecord } from '../../types/records.js';

/**
* @internal
*/
const getEnumValues = <T extends UnknownRecord>(enumObject: T): Set<unknown> => {
const getEnumValues = <T extends EnumRecord>(enumObject: T): Set<unknown> => {
const entries = Object.entries(enumObject);

const ignoreKeys = entries.reduce((keys, [, value]) => {
Expand All @@ -27,7 +27,7 @@ const getEnumValues = <T extends UnknownRecord>(enumObject: T): Set<unknown> =>
/**
* Create a type predicate function that checks if the input is a member of the TypeScript `enum`
*/
export const fromEnum = <T extends UnknownRecord>(enumObject: T): TypePredicateFn<T[keyof T]> => {
export const fromEnum = <T extends EnumRecord>(enumObject: T): TypePredicateFn<T[keyof T]> => {
const values = getEnumValues(enumObject);

return (input: unknown): input is T[keyof T] => values.has(input);
Expand Down
5 changes: 3 additions & 2 deletions src/predicate/factory/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ export * from './is.js';
export * from './literal.js';
export * from './matching.js';
export * from './maybeArray.js';
export * from './maybeNull.js';
export * from './maybeUndefined.js';
export * from './nonNullable.js';
export * from './nullable.js';
export * from './optional.js';
export * from './range.js';
export * from './shape.js';
export * from './stringLength.js';
Expand Down
9 changes: 9 additions & 0 deletions src/predicate/factory/nonNullable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { TypePredicateFn } from '../../types/functions.js';

/**
* @see https://www.typescriptlang.org/docs/handbook/utility-types.html#nonnullabletype
*/
export const nonNullable =
<T>(predicate: TypePredicateFn<T>): TypePredicateFn<NonNullable<T>> =>
(input: unknown): input is NonNullable<T> =>
input != null && predicate(input);
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { TypePredicateFn } from '../../types/functions.js';

export const maybeNull =
export const nullable =
<T>(predicate: TypePredicateFn<T>): TypePredicateFn<T | null> =>
(input: unknown): input is T | null =>
input === null || predicate(input);
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { TypePredicateFn } from '../../types/functions.js';

export const maybeUndefined =
export const optional =
<T>(predicate: TypePredicateFn<T>): TypePredicateFn<T | undefined> =>
(input: unknown): input is T | undefined =>
typeof input === 'undefined' || predicate(input);
6 changes: 5 additions & 1 deletion src/predicate/factory/withLength.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ export function withLength<Fn extends TypePredicateFn<{ length: number }>, Lengt
lengthRange: LengthOrRange,
): TypePredicateFn<InferPredicateReturnType<Fn> & { length: number }> {
if (Array.isArray(lengthRange)) {
const [min = Number.NEGATIVE_INFINITY, max = Number.POSITIVE_INFINITY] = lengthRange;
const [min = 0, max = Number.POSITIVE_INFINITY] = lengthRange;

if (min < 0) {
throw new RangeError('min must be greater than or equal to 0');
}

if (min > max) {
throw new RangeError('min cannot be greater than max');
Expand Down
6 changes: 5 additions & 1 deletion src/predicate/factory/withSize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ export function withSize<Fn extends TypePredicateFn<{ size: number }>, SizeOrRan
sizeRange: SizeOrRange,
): TypePredicateFn<InferPredicateReturnType<Fn> & { size: number }> {
if (Array.isArray(sizeRange)) {
const [min = Number.NEGATIVE_INFINITY, max = Number.POSITIVE_INFINITY] = sizeRange;
const [min = 0, max = Number.POSITIVE_INFINITY] = sizeRange;

if (min < 0) {
throw new RangeError('min must be greater than or equal to 0');
}

if (min > max) {
throw new RangeError('min cannot be greater than max');
Expand Down
4 changes: 2 additions & 2 deletions src/predicate/isOptionalBigInt.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { maybeUndefined } from './factory/maybeUndefined.js';
import { optional } from './factory/optional.js';
import { isBigInt } from './isBigInt.js';

export const isOptionalBigInt = maybeUndefined(isBigInt);
export const isOptionalBigInt = optional(isBigInt);
4 changes: 2 additions & 2 deletions src/predicate/isOptionalBoolean.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { maybeUndefined } from './factory/maybeUndefined.js';
import { optional } from './factory/optional.js';
import { isBoolean } from './isBoolean.js';

export const isOptionalBoolean = maybeUndefined(isBoolean);
export const isOptionalBoolean = optional(isBoolean);
4 changes: 2 additions & 2 deletions src/predicate/isOptionalISODateString.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { maybeUndefined } from './factory/maybeUndefined.js';
import { optional } from './factory/optional.js';
import { isISODateString } from './isISODateString.js';

export const isOptionalISODateString = maybeUndefined(isISODateString);
export const isOptionalISODateString = optional(isISODateString);
4 changes: 2 additions & 2 deletions src/predicate/isOptionalNull.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { maybeUndefined } from './factory/maybeUndefined.js';
import { optional } from './factory/optional.js';
import { isNull } from './isNull.js';

export const isOptionalNull = maybeUndefined(isNull);
export const isOptionalNull = optional(isNull);
4 changes: 2 additions & 2 deletions src/predicate/isOptionalNumber.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { maybeUndefined } from './factory/maybeUndefined.js';
import { optional } from './factory/optional.js';
import { isNumber } from './isNumber.js';

export const isOptionalNumber = maybeUndefined(isNumber);
export const isOptionalNumber = optional(isNumber);
4 changes: 2 additions & 2 deletions src/predicate/isOptionalString.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { maybeUndefined } from './factory/maybeUndefined.js';
import { optional } from './factory/optional.js';
import { isString } from './isString.js';

export const isOptionalString = maybeUndefined(isString);
export const isOptionalString = optional(isString);
4 changes: 2 additions & 2 deletions src/predicate/isOptionalSymbol.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { maybeUndefined } from './factory/maybeUndefined.js';
import { optional } from './factory/optional.js';
import { isSymbol } from './isSymbol.js';

export const isOptionalSymbol = maybeUndefined(isSymbol);
export const isOptionalSymbol = optional(isSymbol);
2 changes: 2 additions & 0 deletions src/types/records.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export type NeverRecord = Record<PropertyKey, never>;

export type StringRecord = Record<string, string>;

export type EnumRecord = Record<string, string | number>;

export type RemoveIndex<Type> = {
[Key in keyof Type as symbol extends Key
? never
Expand Down
24 changes: 24 additions & 0 deletions test/predicate/factory/nonNullable.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { describe, it, expect } from 'vitest';

import { nonNullable } from '../../../src/predicate/factory/nonNullable.js';

describe('nonNullable()', () => {
const predicate = (input: unknown): input is 'testing' | null | undefined => {
const values: unknown[] = ['testing', null, undefined];

return values.includes(input);
};

it('Returns a type predicate function', () => {
expect(nonNullable(predicate)).toBeInstanceOf(Function);
});

it('Calls a type predicate function', () => {
const fn = nonNullable(predicate);

expect(fn('testing')).toBeTruthy();
expect(fn('something else')).toBeFalsy();
expect(fn(null)).toBeFalsy();
expect(fn(undefined)).toBeFalsy();
});
});
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { describe, it, expect, vi } from 'vitest';

import { maybeNull } from '../../../src/predicate/factory/maybeNull.js';
import { nullable } from '../../../src/predicate/factory/nullable.js';
import { isBoolean } from '../../../src/predicate/isBoolean.js';

describe('maybeNull()', () => {
describe('nullable()', () => {
it('Returns a type predicate function', () => {
expect(maybeNull(isBoolean)).toBeInstanceOf(Function);
expect(nullable(isBoolean)).toBeInstanceOf(Function);
});

it('Calls a type predicate function', () => {
const predicate = vi.fn(isBoolean) as unknown as typeof isBoolean;

const fn = maybeNull(predicate);
const fn = nullable(predicate);

expect(fn(true)).toBeTruthy();
expect(fn(null)).toBeTruthy();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { describe, it, expect, vi } from 'vitest';

import { maybeUndefined } from '../../../src/predicate/factory/maybeUndefined.js';
import { optional } from '../../../src/predicate/factory/optional.js';
import { isBoolean } from '../../../src/predicate/isBoolean.js';

describe('maybeUndefined()', () => {
describe('optional()', () => {
it('Returns a type predicate function', () => {
expect(maybeUndefined(isBoolean)).toBeInstanceOf(Function);
expect(optional(isBoolean)).toBeInstanceOf(Function);
});

it('Calls a type predicate function', () => {
const predicate = vi.fn(isBoolean) as unknown as typeof isBoolean;

const fn = maybeUndefined(predicate);
const fn = optional(predicate);

expect(fn(true)).toBeTruthy();
expect(fn(undefined)).toBeTruthy();
Expand Down
4 changes: 4 additions & 0 deletions test/predicate/factory/withLength.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ describe('withLength()', () => {
expect(() => {
withLength(isString, [100, 1]);
}).toThrowError();

expect(() => {
withLength(isString, [-100, 100]);
}).toThrowError();
});

it('Returns true for strings with the specified length', () => {
Expand Down
4 changes: 4 additions & 0 deletions test/predicate/factory/withSize.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ describe('withSize()', () => {
expect(() => {
withSize(instanceOf(Set), [100, 1]);
}).toThrowError();

expect(() => {
withSize(instanceOf(Set), [-100, 100]);
}).toThrowError();
});

it('Returns true for strings with the specified length', () => {
Expand Down