├── .github └── workflows │ ├── jsr.yml │ ├── npm.yml │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── deno.jsonc ├── mod.ts ├── scripts └── build_npm.ts ├── unnullish.ts └── unnullish_test.ts /.github/workflows/jsr.yml: -------------------------------------------------------------------------------- 1 | name: jsr 2 | 3 | env: 4 | DENO_VERSION: 1.x 5 | 6 | on: 7 | push: 8 | tags: 9 | - "v*" 10 | 11 | permissions: 12 | contents: read 13 | id-token: write 14 | 15 | jobs: 16 | publish: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | with: 21 | fetch-depth: 0 22 | - uses: denoland/setup-deno@v1 23 | with: 24 | deno-version: ${{ env.DENO_VERSION }} 25 | - name: Publish 26 | run: | 27 | deno run -A jsr:@david/publish-on-tag@0.1.3 28 | -------------------------------------------------------------------------------- /.github/workflows/npm.yml: -------------------------------------------------------------------------------- 1 | name: npm 2 | 3 | env: 4 | DENO_VERSION: 1.x 5 | NODE_VERSION: 16.x 6 | 7 | on: 8 | push: 9 | tags: 10 | - "v*" 11 | 12 | jobs: 13 | publish: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | with: 18 | fetch-depth: 0 19 | - uses: denoland/setup-deno@v1 20 | with: 21 | deno-version: ${{ env.DENO_VERSION }} 22 | - uses: actions/setup-node@v3 23 | with: 24 | node-version: ${{ env.NODE_VERSION }} 25 | registry-url: "https://registry.npmjs.org" 26 | - name: Build 27 | run: deno task build-npm 28 | - name: Publish 29 | run: | 30 | cd npm 31 | npm publish 32 | env: 33 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 34 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | env: 4 | DENO_VERSION: 1.x 5 | 6 | on: 7 | schedule: 8 | - cron: "0 7 * * 0" 9 | push: 10 | branches: 11 | - main 12 | pull_request: 13 | workflow_dispatch: 14 | 15 | jobs: 16 | check: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | - uses: denoland/setup-deno@v1 21 | with: 22 | deno-version: ${{ env.DENO_VERSION }} 23 | - name: Format 24 | run: | 25 | deno fmt --check 26 | - name: Lint 27 | run: deno lint 28 | - name: Type check 29 | run: deno task check 30 | 31 | test: 32 | runs-on: ubuntu-latest 33 | steps: 34 | - uses: actions/checkout@v4 35 | - uses: denoland/setup-deno@v1 36 | with: 37 | deno-version: ${{ env.DENO_VERSION }} 38 | - name: Test 39 | run: | 40 | deno task test 41 | timeout-minutes: 5 42 | - name: JSR publish (dry-run) 43 | run: | 44 | deno publish --dry-run 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /npm 2 | deno.lock 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022 Alisue 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # unnullish 2 | 3 | [![jsr](https://img.shields.io/jsr/v/%40lambdalisue/unnullish?logo=javascript&logoColor=white)](https://jsr.io/@lambdalisue/unnullish) 4 | [![denoland](https://img.shields.io/github/v/release/lambdalisue/deno-unnullish?logo=deno&label=denoland)](https://github.com/lambdalisue/deno-unnullish/releases) 5 | [![npm](http://img.shields.io/badge/available%20on-npm-lightgrey.svg?logo=npm&logoColor=white)](https://www.npmjs.com/package/unnullish) 6 | [![deno doc](https://doc.deno.land/badge.svg)](https://doc.deno.land/https/deno.land/x/unnullish/mod.ts) 7 | [![Test](https://github.com/lambdalisue/deno-unnullish/workflows/Test/badge.svg)](https://github.com/lambdalisue/deno-unnullish/actions?query=workflow%3ATest) 8 | [![npm version](https://badge.fury.io/js/unnullish.svg)](https://badge.fury.io/js/unnullish) 9 | 10 | `unnullish` returns `undefined` if `value` is nullish, otherwise it executes 11 | `callback` and returns the result. It is an opposite function of the 12 | [nullish coalescing operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator) 13 | (`??`). 14 | 15 | [nullish coalescing operator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator 16 | 17 | ## Usage 18 | 19 | ### unnullish 20 | 21 | - `unnullish(value: T | null | undefined, callback(v: T) => R): R | undefined` 22 | 23 | The function is useful when you want to apply some transformation functions to 24 | optional values. For example, 25 | 26 | ```typescript 27 | import { unnullish } from "https://deno.land/x/unnullish@$MODULE_VERSION/mod.ts"; 28 | 29 | type Options = { 30 | foo?: string; 31 | bar?: number; 32 | }; 33 | 34 | function sayHello(v: string): string { 35 | return `Hello ${v}`; 36 | } 37 | 38 | const options: Options = { 39 | foo: unnullish(Deno.env.get("foo"), (v) => sayHello(v)), 40 | // instead of 41 | //foo: Deno.env.get("foo") != null 42 | // ? sayHello(Deno.env.get("foo")) 43 | // : undefined, 44 | 45 | bar: unnullish(Deno.env.get("bar"), (v) => parseInt(v, 10)), 46 | // instead of 47 | //bar: Deno.env.get("bar") != null 48 | // ? parseInt(Deno.env.get("bar"), 10) 49 | // : undefined, 50 | }; 51 | ``` 52 | 53 | Note that the function returns `undefined` even the input is `null`, mean that 54 | you may need to use nullish coalescing operator to normalize the result. For 55 | example, 56 | 57 | ```typescript 58 | import { unnullish } from "https://deno.land/x/unnullish@$MODULE_VERSION/mod.ts"; 59 | 60 | console.log(unnullish(null, () => 0)); 61 | // -> undefined 62 | console.log(unnullish(undefined, () => 0)); 63 | // -> undefined 64 | 65 | console.log(unnullish(null, () => 0) ?? null); 66 | // -> null 67 | console.log(unnullish(undefined, () => 0) ?? null); 68 | // -> null 69 | ``` 70 | 71 | ## License 72 | 73 | The code follows MIT license written in [LICENSE](./LICENSE). Contributors need 74 | to agree that any modifications sent in this repository follow the license. 75 | -------------------------------------------------------------------------------- /deno.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@lambdalisue/unnullish", 3 | "version": "0.0.0", 4 | "exports": "./mod.ts", 5 | "tasks": { 6 | "build-npm": "deno run -A scripts/build_npm.ts $(git describe --tags --always --dirty)", 7 | "test": "deno test -A --parallel --doc --shuffle", 8 | "check": "deno check **/*.ts" 9 | }, 10 | "imports": { 11 | "@deno/dnt": "jsr:@deno/dnt@^0.41.1", 12 | "@lambdalisue/unreachable": "jsr:@lambdalisue/unreachable@^1.0.1", 13 | "@std/assert": "jsr:@std/assert@^0.221.0", 14 | "https://deno.land/x/unnullish@$MODULE_VERSION/": "./" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /mod.ts: -------------------------------------------------------------------------------- 1 | export * from "./unnullish.ts"; 2 | -------------------------------------------------------------------------------- /scripts/build_npm.ts: -------------------------------------------------------------------------------- 1 | import { build, emptyDir } from "@deno/dnt"; 2 | 3 | const name = "unnullish"; 4 | const version = Deno.args[0]; 5 | if (!version) { 6 | throw new Error("No version argument is specified"); 7 | } 8 | console.log("*".repeat(80)); 9 | console.log(`${name} ${version}`); 10 | console.log("*".repeat(80)); 11 | 12 | await emptyDir("./npm"); 13 | 14 | await build({ 15 | typeCheck: false, 16 | entryPoints: ["./mod.ts"], 17 | outDir: "./npm", 18 | shims: { 19 | // see JS docs for overview and more options 20 | deno: "dev", 21 | }, 22 | package: { 23 | // package.json properties 24 | name, 25 | version, 26 | author: "Alisue ", 27 | license: "MIT", 28 | repository: "https://github.com/lambdalisue/deno-unnullish", 29 | }, 30 | }); 31 | 32 | // post build steps 33 | Deno.copyFileSync("LICENSE", "npm/LICENSE"); 34 | Deno.copyFileSync("README.md", "npm/README.md"); 35 | -------------------------------------------------------------------------------- /unnullish.ts: -------------------------------------------------------------------------------- 1 | export type Nullish = undefined | null; 2 | 3 | /** 4 | * Returns `undefined` if `value` is nullish, otherwise 5 | * it executes `callback` and returns the result. 6 | * 7 | * This function is the opposite of the Nullish coalescing operator (??). 8 | */ 9 | export function unnullish( 10 | value: T | Nullish, 11 | callback: (v: T) => R, 12 | ): R | undefined { 13 | if (value == null) { 14 | return undefined; 15 | } 16 | return callback(value); 17 | } 18 | -------------------------------------------------------------------------------- /unnullish_test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "@std/assert"; 2 | import { unreachable } from "@lambdalisue/unreachable"; 3 | import { unnullish } from "./unnullish.ts"; 4 | 5 | Deno.test("unnullish() returns undefined when the value is undefined", () => { 6 | assertEquals(unnullish(undefined, unreachable), undefined); 7 | // Type-check 8 | const _: string | undefined = unnullish("" as string | undefined, () => ""); 9 | }); 10 | 11 | Deno.test("unnullish() returns undefined when the value is null", () => { 12 | assertEquals(unnullish(null, unreachable), undefined); 13 | // Type-check 14 | const _: string | undefined = unnullish("" as string | null, () => ""); 15 | }); 16 | 17 | Deno.test("unnullish() returns the result of the callback when the value is string", () => { 18 | assertEquals(unnullish("hello", (v) => v.toUpperCase()), "HELLO"); 19 | }); 20 | 21 | Deno.test("unnullish() returns the result of the callback when the value is number", () => { 22 | assertEquals(unnullish(10, (v) => v * 2), 20); 23 | }); 24 | 25 | Deno.test("unnullish() returns the result of the callback when the value is array", () => { 26 | assertEquals( 27 | unnullish(["a", "b", "c"], (v) => v.map((v) => v.toUpperCase())), 28 | ["A", "B", "C"], 29 | ); 30 | }); 31 | 32 | Deno.test("unnullish() returns the result of the callback when the value is object", () => { 33 | assertEquals( 34 | unnullish({ a: "a", b: "b", c: "c" }, (v) => ({ d: "d", ...v })), 35 | { a: "a", b: "b", c: "c", d: "d" }, 36 | ); 37 | }); 38 | --------------------------------------------------------------------------------