Skip to content

Commit

Permalink
NonNullableKeys (#11)
Browse files Browse the repository at this point in the history
  • Loading branch information
jacksonneal authored Oct 9, 2024
1 parent e88543b commit 8ac9d32
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 11 deletions.
7 changes: 6 additions & 1 deletion libs/ts-standard/ex/filter.ex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@

import { isNonNullable } from "@";

import { isNonNullableKeys } from "../src/filter";
import { is } from "./shared.ex";

// isNonNullable
// `isNonNullable<T>`
is<string[]>(["hello", "there", null, undefined].filter(isNonNullable));

// `isNonNullableKeys<T>`
is<{ hello: string; }[]>([{ hello: "there" }, { hello: null }, { hello: undefined }]
.filter(isNonNullableKeys(["hello"])));
16 changes: 12 additions & 4 deletions libs/ts-standard/ex/type.ex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,25 @@
* @module
*/

import { Nullable, Opt } from "@";
import { NonNullableKeys, Nullable, Opt } from "@";

import { is } from "./shared.ex";

// Opt<T>
// `Opt<T>`
is<Opt<string>>("hello");
is<Opt<string>>(null);
// @ts-expect-error `undefined` is not `Opt`
// @ts-expect-error Argument of type `undefined` is not assignable to parameter of type `Opt<string>`.
is<Opt<string>>(undefined);

// Nullable<T>
// `Nullable<T>`
is<Nullable<string>>("hello");
is<Nullable<string>>(null);
is<Nullable<string>>(undefined);

// `NonNullableKeys<T>`
interface Hello { hello: Nullable<string> };
is<NonNullableKeys<Hello>>({ hello: "hello" })
// @ts-expect-error Type `null` is not assignable to type `string`.
is<NonNullableKeys<Hello>>({ hello: null })
// @ts-expect-error Type `undefined` is not assignable to type `string`.
is<NonNullableKeys<Hello>>({ hello: undefined })
23 changes: 22 additions & 1 deletion libs/ts-standard/src/filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* @module
*/

import { Nullable } from "@";
import { NonNullableKeys, Nullable } from "@";

/**
* Check if a value is not `null` and not `undefined`.
Expand All @@ -23,3 +23,24 @@ import { Nullable } from "@";
export function isNonNullable<T>(value: Nullable<T>): value is NonNullable<T> {
return value != null;
}

/**
* Check if an object has `NonNullable` values for the given keys.
*
* @typeParam T - type of value to check
*
* @param keys - to check values of
* @returns - function to check a given value
*
* @example
* interface Hello { hello: Nullable<string>; };
* const isString = isNonNullableKeys<Hello>(["hello"])({ hello: "hello" }); // true
* const isNull = isNonNullableKeys<Hello>(["hello"])({ hello: null }); // false
* const isUndefined = isNonNullableKeys<Hello>(["hello"])({ hello: undefined }); // false
*/
export function isNonNullableKeys<T, K extends keyof T = keyof T>(
keys: K[]
): (value: T) => value is NonNullableKeys<Pick<T, K>> & T {
return (value: T): value is NonNullableKeys<Pick<T, K>> & T =>
keys.every((key) => isNonNullable(value[key]));
}
2 changes: 1 addition & 1 deletion libs/ts-standard/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
*/

export { isNonNullable } from './filter';
export type { Nullable, Opt } from './type';
export type { NonNullableKeys, Nullable, Opt } from './type';
13 changes: 13 additions & 0 deletions libs/ts-standard/src/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,16 @@ export type Opt<T> = T | null;
* const nullableUndefined: Nullable<string> = undefined;
*/
export type Nullable<T> = Opt<T> | undefined;

/**
* Type that maps all properties in `T` to `NonNullable`
*
* @typeParam T - type to map
*
* @example
* interface Hello { hello: Nullable<string>; };
* type HelloNonNullableKeys = NonNullableKeys<Hello>; // { hello: string; }
*/
export type NonNullableKeys<T> = {
[K in keyof T]: NonNullable<T[K]>;
};
24 changes: 20 additions & 4 deletions libs/ts-standard/test/filter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,37 @@ import { describe } from 'node:test';

import { expect, test } from 'vitest'

import { isNonNullable } from '@';
import { isNonNullable, Nullable } from '@';

import { isNonNullableKeys } from '../src/filter';

void describe('isNonNullable', () => {
test.each([
["hello", true],
[null, false],
[undefined, false],
])('isNonNullable(%o) -> %o', (value, expected) => {
])('%# isNonNullable(%o) -> %o', (value, expected) => {
expect(isNonNullable(value)).toBe(expected);
});

test.each([
[[], []],
[["hello", null, "there", undefined], ["hello", "there"]],
])('%o.filter(isNonNullable) -> %o', (value, expected) => {
])('%# %o.filter(isNonNullable) -> %o', (value, expected) => {
expect(value.filter(isNonNullable)).toStrictEqual(expected);
})
});
})

void describe('isNonNullableKeys', () => {
interface Hello { hello: Nullable<string> };
test.each([
[{ hello: "hello" }, ["hello"], true],
[{ hello: "hello" }, [], true],
[{ hello: null }, ["hello"], false],
[{ hello: null }, [], true],
[{ hello: undefined }, ["hello"], false],
[{ hello: undefined }, [], true],
])('%# isNonNullableKeys(%o) -> %o', (value, keys, expected) => {
expect(isNonNullableKeys<Hello>(keys as (keyof Hello)[])(value)).toBe(expected);
});
});

0 comments on commit 8ac9d32

Please sign in to comment.