-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Forked from my personal utils package to get it going. Signed-off-by: Dirk de Visser <github@dirkdevisser.nl>
Showing
21 changed files
with
1,332 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
# Create a utils package | ||
|
||
## Context | ||
|
||
At some point it is bound to happen that we maintain 10 versions of `isNil` in this repo | ||
and countless more in private projects. I’m currently on five implementations and | ||
counting. So we need a place to put this stuff, the so-called 'utils'. | ||
|
||
There are a few main problems often cited for these kinds of packages: | ||
|
||
- Unclear rules of what is a util and what isn't. | ||
- Hard to discover what exists in the utils package, often lacking documentation or | ||
necessary context. | ||
|
||
I have few arguments to counter the above. And don't really want to add any 'rules' on | ||
what this package may contain. Which, unsurprisingly, results in the above. | ||
|
||
## Decision | ||
|
||
We are going to create, publish and maintain a `@lightbase/utils` package. This will | ||
contain: | ||
|
||
- Functional utilities and type-safety helpers like `isNil`, `isRecord`, `isRecordwith`, | ||
`assertNotNil` etc. | ||
- Common generic types like `UnionToIntersection`, `MaybePromise`, etc. | ||
|
||
## Consequences | ||
|
||
At some point in the future, we might want to add clear rules on what is and isn't a | ||
utility that should be in this package. Until then, authors and reviewers should use | ||
careful consideration before adding things. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
MIT License | ||
|
||
Copyright (c) 2024- Dirk de Visser | ||
Copyright (c) 2025- Lightbase B.V | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
# @lightbase/utils | ||
|
||
Various utilities, some lodash like, some to aid with strict TypeScript. Also includes | ||
various type utilities. | ||
|
||
## Utilities | ||
|
||
This library exports the utility functions in the following groups: | ||
|
||
## Assertion functions | ||
|
||
Type-narrowing assertions, that throw when the provided value is falsy. This uses | ||
TypeScript assert operator. | ||
|
||
### `assertIsNil` | ||
|
||
Asserts that the provided value is `null` or `undefined`. Else throws the provided error. | ||
|
||
### `assertNotNil` | ||
|
||
Asserts that the provided value is **not** `null` or `undefined. Else throws the provided | ||
error. | ||
|
||
## 'Is' functions | ||
|
||
Type-narrowing functions, returning truthy when the value is of the corresponding type. | ||
These can be used to constrain type in if-else branches. | ||
|
||
### `isNil` | ||
|
||
Returns `true` when the provided value is `null` or `undefined`. | ||
|
||
### `isRecord` | ||
|
||
Returns `true` when the provided value is an object-like. | ||
|
||
### `isRecordWith` | ||
|
||
Returns `true` when the provided value is an object-like and has the provided keys. | ||
|
||
## Invariants | ||
|
||
Generic assertion functions that don't type-narrow. Can be used to create | ||
business-specific validation functions to align business logic and related error return | ||
types. | ||
|
||
> [!INFO] | ||
> | ||
> If at some point TypeScript supports creating assertion functions via a generic | ||
> function, without manually typing the resulting assertion, this system will be applied | ||
> to asserts as well. | ||
### `createInvariant` | ||
|
||
Create an invariant function to execute business rules. Various options are supported: | ||
|
||
- A predicate callback which is executed to determine if an invariant fails | ||
- Various options for error customization: | ||
- Static error messages. | ||
- Customizable error messages on invariant invocation. | ||
- A custom error constructor, function or static method with typed arguments on | ||
invariant invocation. | ||
- Partial application of the provided custom error. | ||
|
||
## Types | ||
|
||
This library exports the following utility types: | ||
|
||
### `Prettify` | ||
|
||
Force TypeScript to resolve the result of computed types. Can be used to improve the | ||
readability in Quick-documentation popups and errors. | ||
|
||
### `MaybePromise` and `MaybeArray` | ||
|
||
Represent a value which may be wrapped in a `Promise` or `Array` respectively. | ||
|
||
### `ExtractPromise` and `ExtractArray` | ||
|
||
Extract the type wrapped in a `MaybePromise` or `MaybeArray` respectively. | ||
|
||
### `Brand` | ||
|
||
Brand types, so a general type like string is not assignable to a business constrainted | ||
type like 'email' without going through an explicit cast. See the | ||
[Zod](https://zod.dev/?id=brand) docs or the | ||
[Total TypeScript article](https://www.totaltypescript.com/four-essential-typescript-patterns) | ||
on this subject. | ||
|
||
### `PickKeysThatExtends` and `OmitKeysThatExtend` | ||
|
||
Like `Pick` and `Omit`, but instead of specifying keys, specify the type of value that | ||
should be picked or omitted. | ||
|
||
### `UnionToIntersection` | ||
|
||
Map union types to an intersection type. | ||
|
||
### `InferFunctionLikeParameters` | ||
|
||
Extract the parameter types from class constructors or functions. | ||
|
||
## Node.js | ||
|
||
This package has a specific `@encapsula/util/node` export providing the following | ||
utilities | ||
|
||
### `createAsyncLocalStorage` | ||
|
||
Wrapper around | ||
[AsyncLocalStorage](https://nodejs.org/api/async_context.html#class-asynclocalstorage), | ||
for read-only AsyncLocalStorage use. The main difference is the explicit `get` and | ||
`getOptional` functions that wrap `AsyncLocalStorage#getStore`. `get`, throws an error | ||
when not in the async-context of a store. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import { defineConfig } from "@lightbase/eslint-config"; | ||
|
||
export default defineConfig({}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
{ | ||
"name": "@lightbase/utils", | ||
"private": true, | ||
"version": "0.0.1", | ||
"type": "module", | ||
"license": "MIT", | ||
"bugs": { | ||
"url": "https://github.com/lightbase/platforms/issues" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/lightbase/platforms", | ||
"directory": "packages/utils" | ||
}, | ||
"files": ["dist/src"], | ||
"exports": { | ||
"./package.json": "./package.json", | ||
".": { | ||
"types": "./dist/src/index.d.ts", | ||
"import": "./dist/src/index.js" | ||
}, | ||
"./node": { | ||
"type": "./dist/src/node/index.d.ts", | ||
"node": "./dist/src/node/index.js" | ||
} | ||
}, | ||
"scripts": { | ||
"build": "tsc -p ./tsconfig.json", | ||
"lint": "eslint . --fix --cache --cache-strategy content --cache-location .cache/eslint/ --color", | ||
"lint:ci": "eslint .", | ||
"test": "vitest", | ||
"clean": "rm -rf ./.cache ./dist" | ||
}, | ||
"dependencies": {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { constructError } from "./error.js"; | ||
import type { ErrorConstructor } from "./error.js"; | ||
import { isNil } from "./is.js"; | ||
import type { InferFunctionLikeParameters } from "./types.js"; | ||
|
||
/** | ||
* Asserts that the provided value is null|undefined. | ||
* | ||
* Customize the error by providing an error message. Further customization can be done by | ||
* providing a class or function and the appropriate arguments. | ||
*/ | ||
export function assertIsNil(value: unknown): asserts value is null | undefined; | ||
|
||
export function assertIsNil( | ||
value: unknown, | ||
errorMessage: string, | ||
): asserts value is null | undefined; | ||
|
||
export function assertIsNil<ErrorLike extends ErrorConstructor>( | ||
value: unknown, | ||
errorConstructor: ErrorLike, | ||
...errorArguments: InferFunctionLikeParameters<ErrorLike> | ||
): asserts value is null | undefined; | ||
|
||
export function assertIsNil( | ||
value: unknown, | ||
errorMessageOrConstructor?: string | ErrorConstructor, | ||
...args: Array<unknown> | ||
): asserts value is null | undefined { | ||
if (!isNil(value)) { | ||
throw constructError(errorMessageOrConstructor ?? "Assertion failed.", args); | ||
} | ||
} | ||
|
||
/** | ||
* Asserts that the provided value is not null|undefined. | ||
* | ||
* Customize the error by providing an error message. Further customization can be done by | ||
* providing a class or function and the appropriate arguments. | ||
*/ | ||
export function assertNotNil<T>(value: T): asserts value is NonNullable<T>; | ||
|
||
export function assertNotNil<T>( | ||
value: T, | ||
errorMessage: string, | ||
): asserts value is NonNullable<T>; | ||
|
||
export function assertNotNil<T, ErrorLike extends ErrorConstructor>( | ||
value: T, | ||
errorConstructor: ErrorLike, | ||
...errorArguments: InferFunctionLikeParameters<ErrorLike> | ||
): asserts value is NonNullable<T>; | ||
|
||
export function assertNotNil<T>( | ||
value: T, | ||
errorMessageOrConstructor?: string | ErrorConstructor, | ||
...args: Array<unknown> | ||
): asserts value is NonNullable<T> { | ||
if (isNil(value)) { | ||
throw constructError(errorMessageOrConstructor ?? "Assertion failed.", args); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
/* eslint-disable @typescript-eslint/no-explicit-any */ | ||
|
||
/** | ||
* Accept any function that constructs a throwable. i.e an Error constructor or a `createError` | ||
* function. | ||
*/ | ||
export type ErrorConstructor = | ||
| (abstract new (...args: any) => any) | ||
| ((...args: any) => any); | ||
|
||
/** | ||
* Construct an error based on the provided string, class or function. | ||
*/ | ||
export function constructError( | ||
constructorFunctionOrMessage: string | ErrorConstructor, | ||
args: Array<unknown> = [], | ||
): unknown { | ||
if (typeof constructorFunctionOrMessage === "string") { | ||
return new Error(constructorFunctionOrMessage); | ||
} | ||
|
||
const isClass = Function.prototype.toString | ||
.call(constructorFunctionOrMessage) | ||
.startsWith("class"); | ||
|
||
if (isClass) { | ||
// @ts-expect-error value is a constructor. Args should be typed at the API boundary. | ||
return new constructorFunctionOrMessage(...args); | ||
} | ||
|
||
// @ts-expect-error value is a function. Args should be typed at the API boundary. | ||
return constructorFunctionOrMessage(...args); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
export type * from "./types.js"; | ||
|
||
export { isNil, isRecord, isRecordWith } from "./is.js"; | ||
|
||
export { assertIsNil, assertNotNil } from "./assert.js"; | ||
|
||
export { createInvariant } from "./invariant.js"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
/* eslint-disable @typescript-eslint/no-explicit-any */ | ||
|
||
import { constructError } from "./error.js"; | ||
import type { ErrorConstructor } from "./error.js"; | ||
import type { InferFunctionLikeParameters } from "./types.js"; | ||
|
||
/** | ||
* Accept any predicate function. | ||
*/ | ||
interface InvariantPredicate { | ||
(v: any): boolean; | ||
} | ||
|
||
/** | ||
* Check that the predicate only accepts a single parameter. | ||
*/ | ||
type ValidatePredicateType<P extends InvariantPredicate> = | ||
Parameters<P>["length"] extends 1 ? P | ||
: () => { | ||
"~invalid predicate": "Predicate functions can only accept 1 (one) parameter."; | ||
}; | ||
|
||
/** | ||
* Convert 'any' to 'unknown' of the predicate function. | ||
* | ||
* Since any is a top-type, 'extends any' doesn't work. So we use a 'hack'. Param can only | ||
* intersect with 1 if Param is typed as 'any'. Which we can then check by checking if any | ||
* value extends that intersection. | ||
*/ | ||
type ExtractPredicateParameterType<P extends InvariantPredicate> = | ||
P extends (v: infer Param) => boolean ? | ||
0 extends 1 & Param ? | ||
unknown | ||
: Param | ||
: never; | ||
|
||
/** | ||
* Get the remainder of the tuple, based on the provided full tuple and start of the tuple. | ||
*/ | ||
type InferRestTuple<Full extends Array<unknown>, Start extends Array<unknown>> = | ||
Start["length"] extends 0 ? Full | ||
: Start[0] extends Full[0] ? | ||
Full extends [infer _, ...infer FullRest] ? | ||
Start extends [infer _, ...infer StartRest] ? | ||
InferRestTuple<FullRest, StartRest> | ||
: never | ||
: never | ||
: never; | ||
|
||
/** | ||
* Create an invariant function with a customizable error to throw. | ||
* When the predicate function returns a falsy value, an error is created and thrown. | ||
* | ||
* If no options are passed for error customization an plain error is thrown, with an | ||
* optionally provided message via the return invariant function. | ||
* | ||
* @example | ||
* ```ts | ||
* const myInvariant = createInvariant({ predicate: isNil }); | ||
* myInvariant(true); // throw Error("Invariant failed"); | ||
* myInvariant(null, "Customized unused error message"); // Passes | ||
* ``` | ||
* | ||
* A static error message can be provided as well: | ||
* | ||
* @example | ||
* ```ts | ||
* const myInvariant = createInvariant({ predicate: isNil, errorMessage: "My error", }); | ||
* myInvariant(true); // throw Error("My error"); | ||
* myInvariant(null); // Passes | ||
* ``` | ||
* | ||
* Customizing the thrown error is possible via the 'errorConstructor'. It accepts any class, | ||
* function or static class method to construct the error. | ||
* | ||
* @example | ||
* ```ts | ||
* const myInvariant = createInvariant({ predicate: isNil, errorConstructor: Error }); | ||
* myInvariant(true, "My Error", { cause: e }); // throw Error("My Error", { cause: e }); | ||
* | ||
* class MyError extends Error { | ||
* constructor(public status: number) { | ||
* super(); | ||
* } | ||
* } | ||
* | ||
* const myErrorInvariant = createInvariant({ predicate: isNil, errorConstructor: MyError }); | ||
* myErrorInvariant(true, 404); // throw MyError(404); | ||
* ``` | ||
* | ||
* A function or static method can also be used to construct the error: | ||
* | ||
* @example | ||
* ```ts | ||
* function createError(isClientProblem: boolean) { | ||
* return new Error(`Problem of: ${isClientProblem ? "client" : "server"}`); | ||
* } | ||
* | ||
* const myErrorInvariant = createInvariant({ predicate: isNil, errorConstructor: createError | ||
* }); myErrorInvariant(5, true); // throw Error("Problem of client"); | ||
* ``` | ||
* | ||
* In all cases, some arguments can be provided statically: | ||
* | ||
* @example | ||
* ```ts | ||
* class MyError { | ||
* constructor(public status: number, public message: string) { | ||
* super(); | ||
* } | ||
* } | ||
* | ||
* const myErrorInvariant = createInvariant({ | ||
* predicate: isNil, | ||
* errorConstructor: MyError, | ||
* errorArguments: [400], | ||
* }); | ||
* myErrorInvariant(true, "Oops something went wrong"); // throw MyError(400, "Oops something | ||
* went wrong"); | ||
* ``` | ||
*/ | ||
export function createInvariant<const Predicate extends InvariantPredicate>(options: { | ||
predicate: ValidatePredicateType<Predicate>; | ||
}): (v: ExtractPredicateParameterType<Predicate>, errorMessage?: string) => void; | ||
|
||
export function createInvariant<const Predicate extends InvariantPredicate>(options: { | ||
predicate: ValidatePredicateType<Predicate>; | ||
errorMessage: string; | ||
}): (v: ExtractPredicateParameterType<Predicate>) => void; | ||
|
||
export function createInvariant< | ||
const Predicate extends InvariantPredicate, | ||
const ErrorLike extends ErrorConstructor, | ||
>(options: { | ||
predicate: ValidatePredicateType<Predicate>; | ||
errorConstructor: ErrorLike; | ||
}): ( | ||
v: ExtractPredicateParameterType<Predicate>, | ||
...errorArguments: InferFunctionLikeParameters<ErrorLike> | ||
) => void; | ||
|
||
export function createInvariant< | ||
const Predicate extends InvariantPredicate, | ||
const ErrorLike extends ErrorConstructor, | ||
const ErrorArguments extends Partial<InferFunctionLikeParameters<ErrorLike>>, | ||
>(options: { | ||
predicate: ValidatePredicateType<Predicate>; | ||
errorConstructor: ErrorLike; | ||
errorArguments: ErrorArguments; | ||
}): ( | ||
v: ExtractPredicateParameterType<Predicate>, | ||
...errorArguments: InferRestTuple< | ||
InferFunctionLikeParameters<ErrorLike>, | ||
ErrorArguments | ||
> | ||
) => void; | ||
|
||
export function createInvariant<const Predicate extends InvariantPredicate>(options: { | ||
predicate: ValidatePredicateType<Predicate>; | ||
errorConstructor?: ErrorConstructor; | ||
errorArguments?: Array<unknown>; | ||
errorMessage?: string; | ||
}): ( | ||
v: ExtractPredicateParameterType<Predicate>, | ||
...invariantArguments: Array<unknown> | ||
) => void { | ||
const errorArguments = options.errorArguments ?? []; | ||
|
||
return (v: unknown, ...invariantArguments: Array<unknown>): void => { | ||
const predicateResult = options.predicate(v); | ||
if (predicateResult) { | ||
return; | ||
} | ||
|
||
if (options.errorConstructor) { | ||
throw constructError(options.errorConstructor, [ | ||
...errorArguments, | ||
...invariantArguments, | ||
]); | ||
} | ||
|
||
let message = options.errorMessage; | ||
if (!message && typeof invariantArguments[0] === "string") { | ||
message = invariantArguments[0]; | ||
} | ||
|
||
throw constructError(message ?? "Invariant failed."); | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
/** | ||
* Check if the provided value is null|undefined. | ||
*/ | ||
export function isNil(value: unknown): value is null | undefined { | ||
return value === null || value === undefined; | ||
} | ||
|
||
/** | ||
* Check if the provided value is an object-like | ||
*/ | ||
export function isRecord(value: unknown): value is Record<string, unknown> { | ||
return typeof value === "object" && value !== null && !Array.isArray(value); | ||
} | ||
|
||
/** | ||
* Check if the provided value is an object-like with the provided keys | ||
*/ | ||
export function isRecordWith<T extends string>( | ||
value: unknown, | ||
keys: Array<T>, | ||
): value is Record<T & string, unknown> { | ||
return isRecord(value) && keys.every((key) => Object.hasOwn(value, key)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { AsyncLocalStorage } from "node:async_hooks"; | ||
import { assertNotNil } from "../assert.js"; | ||
|
||
/** | ||
* Wrapper around AsyncLocalStorage. | ||
* | ||
* The main difference is the explicit `get` and | ||
* `getOptional` functions that wrap `AsyncLocalStorage#getStore`. `get`, throws an error | ||
* when not in the async-context of a store. | ||
*/ | ||
export function createAsyncLocalStorage<Type>(name: string) { | ||
const storage = new AsyncLocalStorage<Type>(); | ||
|
||
return { | ||
/** | ||
* Like {@link AsyncLocalStorage#getStore}, but adds a non-null assertion when not running | ||
* in the async context. | ||
*/ | ||
get() { | ||
const result = storage.getStore(); | ||
assertNotNil(result, `No value present in the ${name} storage.`); | ||
return result; | ||
}, | ||
|
||
/** | ||
* Wrapper around {@link AsyncLocalStorage#getStore} | ||
*/ | ||
getOptional() { | ||
return storage.getStore(); | ||
}, | ||
|
||
/** | ||
* Wrapper around {@link AsyncLocalStorage#run} | ||
*/ | ||
run<ReturnType>(value: Type, fn: () => ReturnType) { | ||
return storage.run<ReturnType>(value, fn); | ||
}, | ||
|
||
/** | ||
* Wrapper around {@link AsyncLocalStorage#exit} | ||
*/ | ||
exit<ReturnType>(fn: () => ReturnType) { | ||
return storage.exit(fn); | ||
}, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { createAsyncLocalStorage } from "./async-local-storage.js"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
/** | ||
* Let TypeScript resolve nested types, so type-information shows resolved types in Quick | ||
* Type popups and error messages. | ||
* | ||
* https://x.com/mattpocockuk/status/1622730173446557697 | ||
*/ | ||
export type Prettify<T> = { [K in keyof T]: T[K] } & {}; | ||
|
||
/** | ||
* Represents a value which may be wrapped in a Promise. | ||
*/ | ||
export type MaybePromise<T> = T | Promise<T>; | ||
|
||
/** | ||
* Extract the Promise type. If T is not a Promise, T is returned. | ||
*/ | ||
export type ExtractPromise<T> = T extends Promise<infer N> ? N : T; | ||
|
||
/** | ||
* Represents a value which may be alone or in an array. | ||
*/ | ||
export type MaybeArray<T> = T | Array<T>; | ||
|
||
/** | ||
* Extract the Array type. If T is not an Array, T is returned. | ||
*/ | ||
export type ExtractArray<T> = T extends Array<infer N> ? N : T; | ||
|
||
/** | ||
* Brand types, so an unvalidated primitive is not allowed. | ||
* | ||
* @example | ||
* ``` | ||
* type Age = Brand<number, "age">; | ||
* | ||
* export function makeAge(value: number): Age { | ||
* if (value >= 0 && value <= 120) { | ||
* return value as Age; | ||
* } | ||
* | ||
* throw new Error("Invalid value for 'age'"); | ||
* } | ||
* | ||
* function isOlderThan21(age: Age) { | ||
* return age > 21; | ||
* } | ||
* | ||
* // => no type errors | ||
* isOlderThan21(makeAge(22)); | ||
* | ||
* // => type error number is not assignable to Age | ||
* isOlderThan21(18); | ||
* ``` | ||
*/ | ||
export type Brand<Type, Brand extends string> = Type & | ||
Readonly<Record<`__brand_${Brand}`, never>>; | ||
|
||
/** | ||
* Selector for {@link PickKeysThatExtend} and {@link OmitKeysThatExtend}. | ||
*/ | ||
export type PickOmitVersions = "ValueExtendsSelect" | "SelectExtendsValue"; | ||
|
||
type RunPickOmitVersions<A, B, Selector extends PickOmitVersions> = | ||
Selector extends "ValueExtendsSelect" ? | ||
A extends B ? | ||
true | ||
: false | ||
: B extends A ? true | ||
: false; | ||
|
||
/** | ||
* Extract from T, the keys that point to a value that extend Select. | ||
* | ||
* If `SelectExtendsValue` is used as the Selector, the keys are extracted for which the Select | ||
* extends the Value. | ||
* | ||
* @example | ||
* ``` | ||
* type Foo = { | ||
* bar: boolean; | ||
* baz: string; | ||
* }; | ||
* | ||
* type BooleanFoo = PickKeysThatExtend<Foo, boolean>; | ||
* //? { bar: boolean } | ||
* ``` | ||
*/ | ||
export type PickKeysThatExtend< | ||
T, | ||
Select, | ||
Selector extends PickOmitVersions = "ValueExtendsSelect", | ||
> = { | ||
[K in keyof T as RunPickOmitVersions<T[K], Select, Selector> extends true ? K | ||
: never]: T[K]; | ||
}; | ||
|
||
/** | ||
* Omit from T, the keys that point a value that extend Select. | ||
* | ||
* If `SelectExtendsValue` is used as the Selector, the keys are omitted for which the Select | ||
* extends the Value. | ||
* | ||
* | ||
* @example | ||
* ``` | ||
* type Foo = { | ||
* bar: boolean; | ||
* baz: string; | ||
* }; | ||
* | ||
* type BooleanFoo = OmitKeysThatExtend<Foo, boolean>; | ||
* //? { baz: string } | ||
* ``` | ||
*/ | ||
export type OmitKeysThatExtend< | ||
T, | ||
Select, | ||
Selector extends PickOmitVersions = "ValueExtendsSelect", | ||
> = { | ||
[K in keyof T as RunPickOmitVersions<T[K], Select, Selector> extends true ? never | ||
: K]: T[K]; | ||
}; | ||
|
||
/** | ||
* Exclude from object Type, the keys that are assignable to object B. | ||
* | ||
* @example | ||
* ```ts | ||
* type A = { a: string, b: string }; | ||
* type B = { a: string }; | ||
* | ||
* type C = ExcludeRecords<A, B>; | ||
* //? { b: string } | ||
* ``` | ||
*/ | ||
export type ExcludeRecords<A, B> = UnionToIntersection< | ||
Exclude< | ||
{ | ||
[K in keyof A]: Record<K, A[K]>; | ||
}[keyof A], | ||
{ | ||
[K in keyof B]: Record<K, B[K]>; | ||
}[keyof B] | ||
> | ||
>; | ||
|
||
/** | ||
* Convert an union type to an intersection type. | ||
* | ||
* @example | ||
* ``` | ||
* type Foo = { bar: string } | { baz: string }; | ||
* | ||
* type Intersected = UnionToIntersection<Foo>; | ||
* //? { bar: string } & { baz: string } | ||
* ``` | ||
*/ | ||
// From https://stackoverflow.com/a/50375286 CC BY-SA 4.0 | ||
export type UnionToIntersection<U> = | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
(U extends any ? (x: U) => void : never) extends (x: infer I) => void ? I : never; | ||
|
||
/** | ||
* Extract constructor like or function parameters | ||
*/ | ||
export type InferFunctionLikeParameters<T> = | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
T extends abstract new (...args: infer P) => any ? P | ||
: // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
T extends (...args: infer P) => any ? P | ||
: never; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import { describe, expect, it } from "vitest"; | ||
import { assertIsNil, assertNotNil } from "../src/assert.js"; | ||
import { isNilTestCases } from "./is.test.js"; | ||
|
||
describe("assertIsNil", () => { | ||
it.for([...isNilTestCases])( | ||
"runs through the stand isNil test cases - %s", | ||
({ input, expected }, { expect }) => { | ||
if (expected) { | ||
expect(() => { | ||
assertIsNil(input); | ||
}).not.toThrow(); | ||
} else { | ||
expect(() => { | ||
assertIsNil(input); | ||
}).toThrow("Assertion failed."); | ||
} | ||
}, | ||
); | ||
|
||
it("throws with the provided string", () => { | ||
expect(() => { | ||
assertIsNil("", "my error"); | ||
}).toThrow("my error"); | ||
}); | ||
|
||
it("throws with the provided class", () => { | ||
class Z {} | ||
|
||
expect(() => { | ||
assertIsNil("", Z); | ||
}).toThrow(Z); | ||
}); | ||
|
||
it("throws with the provided function and arguments", () => { | ||
const e = (arg: string) => new Error(arg); | ||
|
||
expect(() => { | ||
assertIsNil("", e, "foo"); | ||
}).toThrowErrorMatchingInlineSnapshot(`[Error: foo]`); | ||
}); | ||
}); | ||
|
||
describe("assertNotNil", () => { | ||
it.for([...isNilTestCases])( | ||
"runs through the stand isNil test cases - %s", | ||
({ input, expected }, { expect }) => { | ||
if (expected) { | ||
expect(() => { | ||
assertNotNil(input); | ||
}).toThrow("Assertion failed."); | ||
} else { | ||
expect(() => { | ||
assertNotNil(input); | ||
}).not.toThrow(); | ||
} | ||
}, | ||
); | ||
|
||
it("throws with the provided string", () => { | ||
expect(() => { | ||
assertNotNil(null, "my error"); | ||
}).toThrow("my error"); | ||
}); | ||
|
||
it("throws with the provided class", () => { | ||
class Z {} | ||
|
||
expect(() => { | ||
assertNotNil(undefined, Z); | ||
}).toThrow(Z); | ||
}); | ||
|
||
it("throws with the provided function and arguments", () => { | ||
const e = (arg: string) => new Error(arg); | ||
|
||
expect(() => { | ||
assertNotNil(undefined, e, "foo"); | ||
}).toThrowErrorMatchingInlineSnapshot(`[Error: foo]`); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,293 @@ | ||
import { describe, expect, expectTypeOf, it } from "vitest"; | ||
import { isNil } from "../src/index.js"; | ||
import { createInvariant } from "../src/invariant.js"; | ||
|
||
describe("createInvariant", () => { | ||
it("returns a callable function", () => { | ||
const invariant = createInvariant({ | ||
predicate: isNil, | ||
}); | ||
|
||
expect(invariant).toBeTypeOf("function"); | ||
}); | ||
|
||
describe("invariant with customizable error message", () => { | ||
const invariant = createInvariant({ | ||
predicate: isNil, | ||
}); | ||
|
||
it("has the correct type", () => { | ||
expectTypeOf(invariant).toEqualTypeOf<(value: unknown, message?: string) => void>(); | ||
}); | ||
|
||
it("uses the type of the predicate when available", () => { | ||
type User = { id: string }; | ||
|
||
expectTypeOf(createInvariant({ predicate: (_u: User) => false })).toEqualTypeOf< | ||
(value: User, message?: string) => void | ||
>(); | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
expectTypeOf(createInvariant({ predicate: (_u: any) => false })).toEqualTypeOf< | ||
(value: unknown, message?: string) => void | ||
>(); | ||
}); | ||
|
||
it("can be called", () => { | ||
expect(() => invariant(null)).not.toThrowError(); | ||
}); | ||
|
||
it("throws with a default message when the predicate fails", () => { | ||
expect(() => invariant(true)).toThrowErrorMatchingInlineSnapshot( | ||
`[Error: Invariant failed.]`, | ||
); | ||
}); | ||
|
||
it("throws with the provided message when the predicate fails", () => { | ||
expect(() => | ||
invariant(true, "Custom error message"), | ||
).toThrowErrorMatchingInlineSnapshot(`[Error: Custom error message]`); | ||
}); | ||
}); | ||
|
||
describe("invariant with static error message", () => { | ||
const invariant = createInvariant({ | ||
predicate: isNil, | ||
errorMessage: "Static error", | ||
}); | ||
|
||
it("has the correct type", () => { | ||
expectTypeOf(invariant).toEqualTypeOf<(value: unknown) => void>(); | ||
}); | ||
|
||
it("uses the type of the predicate when available", () => { | ||
type User = { id: string }; | ||
|
||
expectTypeOf( | ||
createInvariant({ | ||
predicate: (_u: User) => false, | ||
errorMessage: "Static message", | ||
}), | ||
).toEqualTypeOf<(value: User) => void>(); | ||
|
||
expectTypeOf( | ||
createInvariant({ | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
predicate: (_u: any) => false, | ||
errorMessage: "Static message", | ||
}), | ||
).toEqualTypeOf<(value: unknown) => void>(); | ||
}); | ||
|
||
it("can be called", () => { | ||
expect(() => invariant(null)).not.toThrowError(); | ||
}); | ||
|
||
it("throws with the static message when the predicate fails", () => { | ||
expect(() => invariant(true)).toThrowErrorMatchingInlineSnapshot( | ||
`[Error: Static error]`, | ||
); | ||
}); | ||
}); | ||
|
||
describe("invariant with parameter-less error constructor", () => { | ||
class MyError { | ||
public message = "Some message"; | ||
constructor() {} | ||
} | ||
|
||
const invariant = createInvariant({ | ||
predicate: isNil, | ||
errorConstructor: MyError, | ||
}); | ||
|
||
it("has the correct type", () => { | ||
expectTypeOf(invariant).toEqualTypeOf<(value: unknown) => void>(); | ||
}); | ||
|
||
it("uses the type of the predicate when available", () => { | ||
type User = { id: string }; | ||
|
||
expectTypeOf( | ||
createInvariant({ | ||
predicate: (_u: User) => false, | ||
errorConstructor: MyError, | ||
}), | ||
).toEqualTypeOf<(value: User) => void>(); | ||
|
||
expectTypeOf( | ||
createInvariant({ | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
predicate: (_u: any) => false, | ||
errorConstructor: MyError, | ||
}), | ||
).toEqualTypeOf<(value: unknown) => void>(); | ||
}); | ||
|
||
it("can be called", () => { | ||
expect(() => invariant(null)).not.toThrowError(); | ||
}); | ||
|
||
it("throws with the custom errorConstructor", () => { | ||
expect(() => invariant(true)).toThrowErrorMatchingInlineSnapshot(` | ||
MyError { | ||
"message": "Some message", | ||
} | ||
`); | ||
}); | ||
}); | ||
|
||
describe("invariant with parameter-less error constructor function", () => { | ||
class MyError { | ||
public message = "Some message"; | ||
constructor() {} | ||
} | ||
|
||
const createError = () => new MyError(); | ||
|
||
const invariant = createInvariant({ | ||
predicate: isNil, | ||
errorConstructor: createError, | ||
}); | ||
|
||
it("has the correct type", () => { | ||
expectTypeOf(invariant).toEqualTypeOf<(value: unknown) => void>(); | ||
}); | ||
|
||
it("uses the type of the predicate when available", () => { | ||
type User = { id: string }; | ||
|
||
expectTypeOf( | ||
createInvariant({ | ||
predicate: (_u: User) => false, | ||
errorConstructor: createError, | ||
}), | ||
).toEqualTypeOf<(value: User) => void>(); | ||
|
||
expectTypeOf( | ||
createInvariant({ | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
predicate: (_u: any) => false, | ||
errorConstructor: createError, | ||
}), | ||
).toEqualTypeOf<(value: unknown) => void>(); | ||
}); | ||
|
||
it("can be called", () => { | ||
expect(() => invariant(null)).not.toThrowError(); | ||
}); | ||
|
||
it("throws with the custom errorConstructor", () => { | ||
expect(() => invariant(true)).toThrowErrorMatchingInlineSnapshot(` | ||
MyError { | ||
"message": "Some message", | ||
} | ||
`); | ||
}); | ||
}); | ||
|
||
describe("invariant with parameterized error constructor", () => { | ||
class MyError { | ||
constructor( | ||
public status: number, | ||
public key: string, | ||
) {} | ||
} | ||
|
||
const invariant = createInvariant({ | ||
predicate: isNil, | ||
errorConstructor: MyError, | ||
}); | ||
|
||
it("has the correct type", () => { | ||
expectTypeOf(invariant).toEqualTypeOf< | ||
(value: unknown, status: number, key: string) => void | ||
>(); | ||
}); | ||
|
||
it("uses the type of the predicate when available", () => { | ||
type User = { id: string }; | ||
|
||
expectTypeOf( | ||
createInvariant({ | ||
predicate: (_u: User) => false, | ||
errorConstructor: MyError, | ||
}), | ||
).toEqualTypeOf<(value: User, status: number, key: string) => void>(); | ||
|
||
expectTypeOf( | ||
createInvariant({ | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
predicate: (_u: any) => false, | ||
errorConstructor: MyError, | ||
}), | ||
).toEqualTypeOf<(value: unknown, status: number, key: string) => void>(); | ||
}); | ||
|
||
it("can be called", () => { | ||
expect(() => invariant(null, 404, "oops")).not.toThrowError(); | ||
}); | ||
|
||
it("throws with the custom errorConstructor", () => { | ||
expect(() => invariant(true, 500, "oops")).toThrowErrorMatchingInlineSnapshot(` | ||
MyError { | ||
"key": "oops", | ||
"status": 500, | ||
} | ||
`); | ||
}); | ||
}); | ||
|
||
describe("invariant with partial applied parameterized error constructor", () => { | ||
class MyError { | ||
constructor( | ||
public status: number, | ||
public key: string, | ||
) {} | ||
} | ||
|
||
const invariant = createInvariant({ | ||
predicate: isNil, | ||
errorConstructor: MyError, | ||
errorArguments: [500], | ||
}); | ||
|
||
it("has the correct type", () => { | ||
expectTypeOf(invariant).toEqualTypeOf<(value: unknown, key: string) => void>(); | ||
}); | ||
|
||
it("uses the type of the predicate when available", () => { | ||
type User = { id: string }; | ||
|
||
expectTypeOf( | ||
createInvariant({ | ||
predicate: (_u: User) => false, | ||
errorConstructor: MyError, | ||
errorArguments: [500], | ||
}), | ||
).toEqualTypeOf<(value: User, key: string) => void>(); | ||
|
||
expectTypeOf( | ||
createInvariant({ | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
predicate: (_u: any) => false, | ||
errorConstructor: MyError, | ||
errorArguments: [500], | ||
}), | ||
).toEqualTypeOf<(value: unknown, key: string) => void>(); | ||
}); | ||
|
||
it("can be called", () => { | ||
expect(() => invariant(null, "oops")).not.toThrowError(); | ||
}); | ||
|
||
it("throws with the custom errorConstructor", () => { | ||
expect(() => invariant(true, "bar")).toThrowErrorMatchingInlineSnapshot(` | ||
MyError { | ||
"key": "bar", | ||
"status": 500, | ||
} | ||
`); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import { describe, it } from "vitest"; | ||
import { isNil, isRecord } from "../src/is.js"; | ||
|
||
export const isNilTestCases = [ | ||
{ input: null, expected: true }, | ||
{ input: undefined, expected: true }, | ||
{ input: true, expected: false }, | ||
{ input: false, expected: false }, | ||
{ input: "", expected: false }, | ||
{ input: "asdfa", expected: false }, | ||
{ input: 0, expected: false }, | ||
{ input: 5, expected: false }, | ||
{ input: -5, expected: false }, | ||
{ input: [], expected: false }, | ||
{ input: [1, 2, 3], expected: false }, | ||
{ input: Array(4), expected: false }, | ||
{ input: {}, expected: false }, | ||
{ input: { foo: "bar" }, expected: false }, | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment | ||
{ input: Object.create(null), expected: false }, | ||
{ input: class {}, expected: false }, | ||
{ input: new (class {})(), expected: false }, | ||
{ input: () => {}, expected: false }, | ||
]; | ||
|
||
describe("isNil", () => { | ||
it.for([...isNilTestCases])( | ||
"runs through the test cases '%s'", | ||
({ input, expected }, { expect }) => { | ||
expect(isNil(input)).toBe(expected); | ||
}, | ||
); | ||
}); | ||
|
||
export const isRecordTestCases = [ | ||
{ input: null, expected: false }, | ||
{ input: undefined, expected: false }, | ||
{ input: true, expected: false }, | ||
{ input: false, expected: false }, | ||
{ input: "", expected: false }, | ||
{ input: "asdfa", expected: false }, | ||
{ input: 0, expected: false }, | ||
{ input: 5, expected: false }, | ||
{ input: -5, expected: false }, | ||
{ input: [], expected: false }, | ||
{ input: [1, 2, 3], expected: false }, | ||
{ input: Array(4), expected: false }, | ||
{ input: {}, expected: true }, | ||
{ input: { foo: "bar" }, expected: true }, | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment | ||
{ input: Object.create(null), expected: true }, | ||
{ input: class {}, expected: false }, | ||
{ input: new (class {})(), expected: true }, | ||
{ input: () => {}, expected: false }, | ||
]; | ||
|
||
describe("isRecord", () => { | ||
it.for([...isRecordTestCases])( | ||
"runs through the test cases '%s'", | ||
({ input, expected }, { expect }) => { | ||
expect(isRecord(input)).toBe(expected); | ||
}, | ||
); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { describe, expect, it, vi } from "vitest"; | ||
import { createAsyncLocalStorage } from "../../src/node/index.js"; | ||
|
||
describe("createAsyncLocalStorage", () => { | ||
it("throws when .get is called without a value", () => { | ||
const localStorage = createAsyncLocalStorage("test"); | ||
|
||
expect(() => { | ||
localStorage.get(); | ||
}).toThrow("No value present in the test storage"); | ||
}); | ||
|
||
it("does not throw when .getOptional is called without a value", () => { | ||
const localStorage = createAsyncLocalStorage("test"); | ||
|
||
expect(() => { | ||
expect(localStorage.getOptional()).toBeUndefined(); | ||
}).not.toThrow(); | ||
}); | ||
|
||
it("returns the value the local storage was entered with", async () => { | ||
const localStorage = createAsyncLocalStorage<string>("test"); | ||
|
||
const mockFn = vi.fn(() => { | ||
expect(localStorage.get()).toBe("foo"); | ||
}); | ||
|
||
await localStorage.run("foo", async () => { | ||
await new Promise<void>((r) => { | ||
r(); | ||
mockFn(); | ||
}); | ||
|
||
mockFn(); | ||
}); | ||
|
||
expect(mockFn).toHaveBeenCalledTimes(2); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import { describe, expectTypeOf, it } from "vitest"; | ||
import type { OmitKeysThatExtend, PickKeysThatExtend, Prettify } from "../src/types.js"; | ||
|
||
describe("Prettify", () => { | ||
it("returns the same primitive", () => { | ||
expectTypeOf<Prettify<true>>().toMatchTypeOf<boolean>(); | ||
expectTypeOf<Prettify<"foo">>().toMatchTypeOf<string>(); | ||
expectTypeOf<Prettify<1>>().toMatchTypeOf<number>(); | ||
expectTypeOf<Prettify<null>>().toMatchTypeOf<null>(); | ||
}); | ||
|
||
it("returns the same object-like types", () => { | ||
expectTypeOf< | ||
Prettify< | ||
Pick< | ||
{ | ||
foo: "bar"; | ||
}, | ||
"foo" | ||
> | ||
> | ||
>().toEqualTypeOf<{ foo: "bar" }>(); | ||
expectTypeOf< | ||
Prettify< | ||
Pick< | ||
{ | ||
foo: "bar"; | ||
} & { bar: true }, | ||
"foo" | ||
> | ||
> | ||
>().toEqualTypeOf<{ foo: "bar" }>(); | ||
}); | ||
}); | ||
|
||
describe("PickKeysThatExtend", () => { | ||
it("returns an object like with keys that extend the provided type", () => { | ||
expectTypeOf< | ||
PickKeysThatExtend< | ||
{ | ||
foo: string; | ||
bar: boolean; | ||
}, | ||
string | ||
> | ||
>().toEqualTypeOf<{ foo: string }>(); | ||
}); | ||
}); | ||
|
||
describe("OmitKeysThatExtend", () => { | ||
it("returns an object like with keys that do not extend the provided type", () => { | ||
expectTypeOf< | ||
OmitKeysThatExtend< | ||
{ | ||
foo: string; | ||
bar: boolean; | ||
}, | ||
string | ||
> | ||
>().toEqualTypeOf<{ bar: boolean }>(); | ||
}); | ||
|
||
it("omits possible undefined keys", () => { | ||
expectTypeOf< | ||
OmitKeysThatExtend< | ||
{ | ||
foo: string; | ||
bar?: boolean; | ||
}, | ||
undefined, | ||
"SelectExtendsValue" | ||
> | ||
>().toEqualTypeOf<{ | ||
foo: string; | ||
}>(); | ||
}); | ||
|
||
it("omits ValueExtendsSelect", () => { | ||
expectTypeOf< | ||
OmitKeysThatExtend< | ||
{ | ||
foo: 42; | ||
bar: number; | ||
quix: string; | ||
}, | ||
number | ||
> | ||
>().toEqualTypeOf<{ quix: string }>(); | ||
}); | ||
|
||
it("omits SelectExtendsValue", () => { | ||
expectTypeOf< | ||
OmitKeysThatExtend< | ||
{ | ||
foo: 42; | ||
bar: number; | ||
}, | ||
number, | ||
"SelectExtendsValue" | ||
> | ||
>().toEqualTypeOf<{ | ||
foo: 42; | ||
}>(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"extends": "@total-typescript/tsconfig/tsc/no-dom/library-monorepo", | ||
"compilerOptions": { | ||
"outDir": "dist" | ||
}, | ||
"include": ["**/*"], | ||
"references": [] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,6 +22,9 @@ | |
}, | ||
{ | ||
"path": "./packages/pull-through-cache" | ||
}, | ||
{ | ||
"path": "./packages/utils" | ||
} | ||
] | ||
} |