diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml index 2f8f865..d79ec5f 100644 --- a/.github/workflows/npm.yml +++ b/.github/workflows/npm.yml @@ -13,13 +13,13 @@ jobs: publish: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: denoland/setup-deno@v1 with: deno-version: ${{ env.DENO_VERSION }} - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} registry-url: "https://registry.npmjs.org" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 668b6fe..55dc85e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,7 +16,7 @@ jobs: check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: denoland/setup-deno@v1 with: deno-version: ${{ env.DENO_VERSION }} @@ -31,19 +31,25 @@ jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: denoland/setup-deno@v1 with: deno-version: ${{ env.DENO_VERSION }} - name: Test run: | - deno task test + deno task test:coverage timeout-minutes: 5 + - run: | + deno task coverage --lcov > coverage.lcov + - uses: codecov/codecov-action@v3 + with: + os: ${{ runner.os }} + files: ./coverage.lcov bench: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: denoland/setup-deno@v1 with: deno-version: ${{ env.DENO_VERSION }} @@ -55,11 +61,11 @@ jobs: build-npm: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: denoland/setup-deno@v1 with: deno-version: ${{ env.DENO_VERSION }} - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} registry-url: "https://registry.npmjs.org" diff --git a/.gitignore b/.gitignore index 609a268..78a6d6d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /npm +/docs deno.lock +.coverage diff --git a/README.md b/README.md index f307b00..c90f438 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ [![deno doc](https://doc.deno.land/badge.svg)](https://doc.deno.land/https/deno.land/x/unknownutil/mod.ts) [![Test](https://github.com/lambdalisue/deno-unknownutil/workflows/Test/badge.svg)](https://github.com/lambdalisue/deno-unknownutil/actions?query=workflow%3ATest) [![npm version](https://badge.fury.io/js/unknownutil.svg)](https://badge.fury.io/js/unknownutil) +[![codecov](https://codecov.io/github/lambdalisue/deno-unknownutil/graph/badge.svg?token=pfbLRGU5AM)](https://codecov.io/github/lambdalisue/deno-unknownutil) A utility pack for handling `unknown` type. @@ -15,14 +16,14 @@ A utility pack for handling `unknown` type. It provides `is` module for type predicate functions and `assert`, `ensure`, and `maybe` helper functions. -### is* +### is\* Type predicate function is a function which returns `true` if a given value is expected type. For example, `isString` (or `is.String`) returns `true` if a given value is `string`. ```typescript -import { is } from "./mod.ts"; +import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; const a: unknown = "Hello"; if (is.String(a)) { @@ -34,18 +35,23 @@ Additionally, `is*Of` (or `is.*Of`) functions return type predicate functions to predicate types of `x` more precisely like: ```typescript -import { is, PredicateType } from "./mod.ts"; +import { + is, + PredicateType, +} from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; const isArticle = is.ObjectOf({ title: is.String, body: is.String, - refs: is.ArrayOf(is.OneOf([ - is.String, - is.ObjectOf({ - name: is.String, - url: is.String, - }), - ])), + refs: is.ArrayOf( + is.OneOf([ + is.String, + is.ObjectOf({ + name: is.String, + url: is.String, + }), + ]), + ), }); type Article = PredicateType; @@ -53,10 +59,7 @@ type Article = PredicateType; const a: unknown = { title: "Awesome article", body: "This is an awesome article", - refs: [ - { name: "Deno", url: "https://deno.land/" }, - "https://github.com", - ], + refs: [{ name: "Deno", url: "https://deno.land/" }, "https://github.com"], }; if (isArticle(a)) { // a is narrowed to the type of `isArticle` @@ -79,7 +82,10 @@ The `assert` function does nothing if a given value is expected type. Otherwise, it throws an `AssertError` exception like: ```typescript -import { assert, is } from "./mod.ts"; +import { + assert, + is, +} from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; const a: unknown = "Hello"; @@ -97,7 +103,10 @@ The `ensure` function return the value as-is if a given value is expected type. Otherwise, it throws an `AssertError` exception like: ```typescript -import { ensure, is } from "./mod.ts"; +import { + ensure, + is, +} from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; const a: unknown = "Hello"; @@ -116,7 +125,10 @@ Otherwise, it returns `undefined` that suites with like: ```typescript -import { is, maybe } from "./mod.ts"; +import { + is, + maybe, +} from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; const a: unknown = "Hello"; @@ -124,6 +136,24 @@ const a: unknown = "Hello"; const _: string = maybe(a, is.String) ?? "default value"; ``` +## Node.js (npm) + +To use `unknownutil` in [Node.js][nodejs], install `unknownutil` like + +```console +npm i unknownutil +``` + +Then import `is`, `assert`, `ensure`, and `maybe` like: + +```typescript, ignore +import { assert, ensure, is, maybe } from "unknownutil"; + +// ... +``` + +[nodejs]: https://nodejs.org/ + ## Migration See [GitHub Wiki](https://github.com/lambdalisue/deno-unknownutil/wiki) for diff --git a/deno.jsonc b/deno.jsonc index c14b2aa..7743d08 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -1,9 +1,15 @@ { "lock": false, + "imports": { + "https://deno.land/x/unknownutil@$MODULE_VERSION/": "./" + }, "tasks": { "build-npm": "deno run -A scripts/build_npm.ts $(git describe --tags --always --dirty)", - "test": "deno test --unstable -A --parallel --doc", - "check": "deno check --unstable $(find . -name '*.ts')", - "upgrade": "deno run -A https://deno.land/x/udd/main.ts $(find . -name '*.ts' -not -path '*/npm/*')" + "test": "deno test -A --parallel --shuffle --doc", + "test:coverage": "deno task test --coverage=.coverage", + "check": "deno check ./**/*.ts", + "coverage": "deno coverage .coverage --exclude=is_bench.ts", + "upgrade": "deno run -q -A https://deno.land/x/molt@0.14.2/cli.ts ./**/*.ts", + "upgrade:commit": "deno task -q upgrade --commit --prefix :package: --pre-commit=fmt" } } diff --git a/is.ts b/is.ts index 4430ee4..e1e7524 100644 --- a/is.ts +++ b/is.ts @@ -86,7 +86,7 @@ export type TupleOf[]> = { * Return a type predicate function that returns `true` if the type of `x` is `TupleOf`. * * ```ts - * import is from "./is.ts"; + * import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; * * const predTup = [is.Number, is.String, is.Boolean] as const; * const a: unknown = [0, "a", true]; @@ -129,7 +129,7 @@ export type UniformTupleOf< * Return a type predicate function that returns `true` if the type of `x` is `UniformTupleOf`. * * ```ts - * import is from "./is.ts"; + * import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; * * const a: unknown = [0, 1, 2, 3, 4]; * if (is.UniformTupleOf(5)(a)) { @@ -223,7 +223,7 @@ export type ObjectOf>> = FlatType< * Otherwise, the number of keys of `x` must be greater than or equal to the number of keys of `predObj`. * * ```ts - * import is from "./is.ts"; + * import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; * * const predObj = { * a: is.Number, @@ -310,7 +310,7 @@ export function isAsyncFunction( * Return `true` if the type of `x` is instance of `ctor`. * * ```ts - * import is from "./is.ts"; + * import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; * * const a: unknown = new Date(); * if (is.InstanceOf(Date)(a)) { @@ -396,7 +396,8 @@ export function isLiteralOf(literal: T): Predicate { * Return a type predicate function that returns `true` if the type of `x` is one of literal type in `preds`. * * ```ts - * import is from "./is.ts"; + * import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * * const a: unknown = "hello"; * if (is.LiteralOneOf(["hello", "world"] as const)(a)) { * // a is narrowed to "hello" | "world" @@ -424,7 +425,7 @@ export type OneOf = T extends Predicate[] ? U : never; * Return a type predicate function that returns `true` if the type of `x` is `OneOf`. * * ```ts - * import is from "./is.ts"; + * import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; * * const preds = [is.Number, is.String, is.Boolean]; * const a: unknown = 0; @@ -457,7 +458,7 @@ export type AllOf = UnionToIntersection>; * Return a type predicate function that returns `true` if the type of `x` is `AllOf`. * * ```ts - * import is from "./is.ts"; + * import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; * * const preds = [is.ObjectOf({ a: is.Number }), is.ObjectOf({ b: is.String })]; * const a: unknown = { a: 0, b: "a" }; @@ -488,7 +489,7 @@ export type OptionalPredicate = Predicate & { * Return a type predicate function that returns `true` if the type of `x` is `T` or `undefined`. * * ```ts - * import is from "./is.ts"; + * import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; * * const a: unknown = "a"; * if (is.OptionalOf(is.String)(a)) { diff --git a/mod.ts b/mod.ts index d7d5a16..189a1df 100644 --- a/mod.ts +++ b/mod.ts @@ -1,3 +1,134 @@ +/** + * A utility pack for handling `unknown` type. + * + * ## Usage + * + * It provides `is` module for type predicate functions and `assert`, `ensure`, and + * `maybe` helper functions. + * + * ### is\* + * + * Type predicate function is a function which returns `true` if a given value is + * expected type. For example, `isString` (or `is.String`) returns `true` if a + * given value is `string`. + * + * ```typescript + * import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * const a: unknown = "Hello"; + * if (is.String(a)) { + * // 'a' is 'string' in this block + * } + * ``` + * + * Additionally, `is*Of` (or `is.*Of`) functions return type predicate functions to + * predicate types of `x` more precisely like: + * + * ```typescript + * import { + * is, + * PredicateType, + * } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * const isArticle = is.ObjectOf({ + * title: is.String, + * body: is.String, + * refs: is.ArrayOf( + * is.OneOf([ + * is.String, + * is.ObjectOf({ + * name: is.String, + * url: is.String, + * }), + * ]), + * ), + * }); + * + * type Article = PredicateType; + * + * const a: unknown = { + * title: "Awesome article", + * body: "This is an awesome article", + * refs: [{ name: "Deno", url: "https://deno.land/" }, "https://github.com"], + * }; + * if (isArticle(a)) { + * // a is narrowed to the type of `isArticle` + * console.log(a.title); + * console.log(a.body); + * for (const ref of a.refs) { + * if (is.String(ref)) { + * console.log(ref); + * } else { + * console.log(ref.name); + * console.log(ref.url); + * } + * } + * } + * ``` + * + * ### assert + * + * The `assert` function does nothing if a given value is expected type. Otherwise, + * it throws an `AssertError` exception like: + * + * ```typescript + * import { + * assert, + * is, + * } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * const a: unknown = "Hello"; + * + * // `assert` does nothing or throws an `AssertError` + * assert(a, is.String); + * // a is now narrowed to string + * + * // With custom message + * assert(a, is.String, { message: "a must be a string" }); + * ``` + * + * ### ensure + * + * The `ensure` function return the value as-is if a given value is expected type. + * Otherwise, it throws an `AssertError` exception like: + * + * ```typescript + * import { + * ensure, + * is, + * } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * const a: unknown = "Hello"; + * + * // `ensure` returns `string` or throws an `AssertError` + * const _: string = ensure(a, is.String); + * + * // With custom message + * const __: string = ensure(a, is.String, { message: "a must be a string" }); + * ``` + * + * ### maybe + * + * The `maybe` function return the value as-is if a given value is expected type. + * Otherwise, it returns `undefined` that suites with + * [nullish coalescing operator (`??`)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing) + * like: + * + * ```typescript + * import { + * is, + * maybe, + * } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * const a: unknown = "Hello"; + * + * // `maybe` returns `string | undefined` so it suites with `??` + * const _: string = maybe(a, is.String) ?? "default value"; + * ``` + * + * @module + */ + export * from "./is.ts"; export * from "./util.ts"; diff --git a/util.ts b/util.ts index 4bd3015..d363998 100644 --- a/util.ts +++ b/util.ts @@ -45,8 +45,7 @@ export class AssertError extends Error { * @param factory The factory function. * @example * ```ts - * import { setAssertMessageFactory } from "./util.ts"; - * import is from "./is.ts"; + * import { is, setAssertMessageFactory } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; * * setAssertMessageFactory((x, pred) => { * if (pred === is.String) { @@ -69,8 +68,7 @@ export function setAssertMessageFactory(factory: AssertMessageFactory): void { * Asserts that the given value satisfies the provided predicate. * * ```ts - * import { assert } from "./util.ts"; - * import is from "./is.ts"; + * import { assert, is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; * * const a: unknown = "hello"; * assert(a, is.String); @@ -99,8 +97,7 @@ export function assert( * Ensures that the given value satisfies the provided predicate. * * ```ts - * import { ensure } from "./util.ts"; - * import is from "./is.ts"; + * import { ensure, is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; * * const a: unknown = "hello"; * const _: string = ensure(a, is.String); @@ -125,8 +122,7 @@ export function ensure( * Returns the input value if it satisfies the provided predicate, or `undefined` otherwise. * * ```ts - * import { maybe } from "./util.ts"; - * import is from "./is.ts"; + * import { is, maybe } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; * * const a: unknown = "hello"; * const _: string = maybe(a, is.String) ?? "default value";