From 8ac9d32dae2784e900c74041c88076c61a9f1e9b Mon Sep 17 00:00:00 2001 From: jacksonneal <32206530+jacksonneal@users.noreply.github.com> Date: Wed, 9 Oct 2024 10:10:44 -0400 Subject: [PATCH] NonNullableKeys (#11) --- libs/ts-standard/ex/filter.ex.ts | 7 ++++++- libs/ts-standard/ex/type.ex.ts | 16 ++++++++++++---- libs/ts-standard/src/filter.ts | 23 ++++++++++++++++++++++- libs/ts-standard/src/index.ts | 2 +- libs/ts-standard/src/type.ts | 13 +++++++++++++ libs/ts-standard/test/filter.test.ts | 24 ++++++++++++++++++++---- 6 files changed, 74 insertions(+), 11 deletions(-) diff --git a/libs/ts-standard/ex/filter.ex.ts b/libs/ts-standard/ex/filter.ex.ts index e2aa332..66cbbca 100644 --- a/libs/ts-standard/ex/filter.ex.ts +++ b/libs/ts-standard/ex/filter.ex.ts @@ -6,7 +6,12 @@ import { isNonNullable } from "@"; +import { isNonNullableKeys } from "../src/filter"; import { is } from "./shared.ex"; -// isNonNullable +// `isNonNullable` is(["hello", "there", null, undefined].filter(isNonNullable)); + +// `isNonNullableKeys` +is<{ hello: string; }[]>([{ hello: "there" }, { hello: null }, { hello: undefined }] + .filter(isNonNullableKeys(["hello"]))); diff --git a/libs/ts-standard/ex/type.ex.ts b/libs/ts-standard/ex/type.ex.ts index 43c4ba3..649ce61 100644 --- a/libs/ts-standard/ex/type.ex.ts +++ b/libs/ts-standard/ex/type.ex.ts @@ -4,17 +4,25 @@ * @module */ -import { Nullable, Opt } from "@"; +import { NonNullableKeys, Nullable, Opt } from "@"; import { is } from "./shared.ex"; -// Opt +// `Opt` is>("hello"); is>(null); -// @ts-expect-error `undefined` is not `Opt` +// @ts-expect-error Argument of type `undefined` is not assignable to parameter of type `Opt`. is>(undefined); -// Nullable +// `Nullable` is>("hello"); is>(null); is>(undefined); + +// `NonNullableKeys` +interface Hello { hello: Nullable }; +is>({ hello: "hello" }) +// @ts-expect-error Type `null` is not assignable to type `string`. +is>({ hello: null }) +// @ts-expect-error Type `undefined` is not assignable to type `string`. +is>({ hello: undefined }) diff --git a/libs/ts-standard/src/filter.ts b/libs/ts-standard/src/filter.ts index 477f43e..09fb49a 100644 --- a/libs/ts-standard/src/filter.ts +++ b/libs/ts-standard/src/filter.ts @@ -4,7 +4,7 @@ * @module */ -import { Nullable } from "@"; +import { NonNullableKeys, Nullable } from "@"; /** * Check if a value is not `null` and not `undefined`. @@ -23,3 +23,24 @@ import { Nullable } from "@"; export function isNonNullable(value: Nullable): value is NonNullable { 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; }; + * const isString = isNonNullableKeys(["hello"])({ hello: "hello" }); // true + * const isNull = isNonNullableKeys(["hello"])({ hello: null }); // false + * const isUndefined = isNonNullableKeys(["hello"])({ hello: undefined }); // false + */ +export function isNonNullableKeys( + keys: K[] +): (value: T) => value is NonNullableKeys> & T { + return (value: T): value is NonNullableKeys> & T => + keys.every((key) => isNonNullable(value[key])); +} diff --git a/libs/ts-standard/src/index.ts b/libs/ts-standard/src/index.ts index 53a6075..cc6158e 100644 --- a/libs/ts-standard/src/index.ts +++ b/libs/ts-standard/src/index.ts @@ -5,4 +5,4 @@ */ export { isNonNullable } from './filter'; -export type { Nullable, Opt } from './type'; +export type { NonNullableKeys, Nullable, Opt } from './type'; diff --git a/libs/ts-standard/src/type.ts b/libs/ts-standard/src/type.ts index ad1b7ce..4a5ac33 100644 --- a/libs/ts-standard/src/type.ts +++ b/libs/ts-standard/src/type.ts @@ -27,3 +27,16 @@ export type Opt = T | null; * const nullableUndefined: Nullable = undefined; */ export type Nullable = Opt | undefined; + +/** + * Type that maps all properties in `T` to `NonNullable` + * + * @typeParam T - type to map + * + * @example + * interface Hello { hello: Nullable; }; + * type HelloNonNullableKeys = NonNullableKeys; // { hello: string; } + */ +export type NonNullableKeys = { + [K in keyof T]: NonNullable; +}; diff --git a/libs/ts-standard/test/filter.test.ts b/libs/ts-standard/test/filter.test.ts index 18c83be..a5e312a 100644 --- a/libs/ts-standard/test/filter.test.ts +++ b/libs/ts-standard/test/filter.test.ts @@ -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 }; + 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(keys as (keyof Hello)[])(value)).toBe(expected); + }); +});