diff --git a/README.md b/README.md index 762a99e..fef3423 100644 --- a/README.md +++ b/README.md @@ -1,165 +1,172 @@ -# GuardX +
-Protect your typescript/javascript projects from unnecessary if/else branches to check null/undefined values. +# GuardX -GuardX help developers to have type-safety during development (compile time) and type-check on runtime. Your IDE understands better when a value is null/undefined, and your app won't crash because of accessing a property of null or undefined value. +**Runtime guards and type-safe results for TypeScript and JavaScript.** -## Purpose +[![npm version](https://img.shields.io/npm/v/guardx.svg)](https://www.npmjs.com/package/guardx) +[![npm downloads](https://img.shields.io/npm/dm/guardx.svg)](https://www.npmjs.com/package/guardx) +[![license](https://img.shields.io/npm/l/guardx.svg)](https://github.com/DManavi/guardx/blob/main/LICENSE.md) -As a TypeScript/Javascript developer you may end up being in a situation like this. +[Installation](#installation) · [Quick start](#quick-start) · [API](#api) · [Development](#development) -```typescript +
-/** - * A function that may return undefined - */ -function findUserById(userId: number): User | undefined { - // an operation that may return undefined or the user -} +GuardX helps you validate values at runtime while preserving TypeScript's type +narrowing. Use assertion functions when invalid data should stop execution, +type guards when you want to handle it yourself, or result objects when you +want to avoid repetitive `try`/`catch` blocks. -function updateUserInfo(userId: number, emailAddress: string;): void { - const user = findUserById(userId); // Now the user is either a User or an undefined value +## Why GuardX? - // I should either type-check the user like below - if (typeof user === 'undefined') { - throw Error('User not found'); - } +- **Type-safe assertions** narrow values after a runtime check. +- **Composable type guards** fit naturally into conditionals and filters. +- **Result-based error handling** keeps success and failure paths explicit. +- **ESM and CommonJS support** lets you use the same API in either module system. +- **Focused imports** keep call sites clear: `guardx/assert`, `guardx/check`, + `guardx/run`, and `guardx/util`. - user.emailAddress = emailAddress; +## Installation - // or use ? operator - user?.emailAddress = emailAddress; -} +```bash +npm install guardx ``` -Seems easy? +```bash +pnpm add guardx +``` -- What if I have tons of places like this (which we always have)? -- What if I want to catch the error and behave differently based on the error? +```bash +yarn add guardx +``` -## Usage +## Quick start -### assert +### Assert that a value is defined -Assert module help you to throw error based on a certain criteria. This way the app won't run and an error is thrown immediately. +Assertions throw at runtime and narrow the value for all code that follows. ```typescript import * as assert from 'guardx/assert'; -/** - * A function that may return undefined - */ -function findUserById(userId: number): User | undefined { - // an operation that may return undefined or the user -} +type User = { + id: number; + emailAddress: string; +}; + +declare function findUserById(userId: number): User | undefined; const user = findUserById(123); -assert.isDefined(user); // or assert.isNotNullOrUndefined(user); +assert.isDefined(user, 'User not found'); -// from now on, the user is always an instance of User -// I can safely access user properties both in development time and runtime -user.emailAddress = 'new-email@somewhere.com'; +// TypeScript now knows that user is a User. +user.emailAddress = 'new-email@example.com'; ``` -There are other methods available. Check [them](#references) in API references page. +You can pass either an error message or an `Error` instance: -### check +```typescript +assert.isDefined(user, new Error('User not found')); +``` -Check module helps typescript to have understand types better by type guards. But, it doesn't break the application on runtime. So developer is responsible for reacting to the unwanted types (e.g. null or undefined). +### Narrow a value with a type guard + +Checks return booleans and leave the response to your application. ```typescript import * as check from 'guardx/check'; -/** - * A function that may return undefined - */ -function findUserById(userId: number): User | undefined { - // an operation that may return undefined or the user -} +declare const value: string | null | undefined; -const user = findUserById(123); - -if (check.isNullOrUndefined(user)) { - // custom logic to handle null or undefined values - return; +if (check.isNullOrUndefined(value)) { + console.log('No value was provided'); +} else { + // value is narrowed to string. + console.log(value.toUpperCase()); } - -// from now on, the user is always an instance of User -// I can safely access user properties both in development time and runtime -user.emailAddress = 'new-email@somewhere.com'; ``` -There are other methods available. Check [them](#references) in API references page. +### Replace `try`/`catch` with a result -### run - -Run module help developers to run both sync and async functions and get both result and error on the same line w/o writing a try/catch block. This helps you have a cleaner and more readable code. +`safe` and `safeAsync` return a discriminated union, so checking `success` +narrows the result to either its output or error. ```typescript -/** - * A function that may return undefined - */ -function findUserById(userId: number): User | undefined { - // an operation that may return undefined or the user +import * as run from 'guardx/run'; + +const result = run.safe(() => JSON.parse('{"ready":true}')); + +if (result.success) { + console.log(result.output); +} else { + console.error(result.error); } +``` -// w/o guardx +For promises, use `safeAsync`: -let user: User | undefined; +```typescript +const result = await run.safeAsync(() => fetch('/api/users/123')); -try { - user = findUserById(123); -} catch (err) { - // log the error or set an alarm +if (!result.success) { + console.error('Request failed', result.error); } +``` -// do something with the user object +## API -// w/ guardx -import * as run from 'guardx/run'; +You can import every module from the package root: -const result = run.safe(() => findUserById(123)); // or you can use bind method here +```typescript +import { assert, check, run, util } from 'guardx'; +``` -// it also works with async functions +Or import only the module you need: -const result = run.safeAsync(async () => findUserById(123)); // or you can use bind method here +```typescript +import * as assert from 'guardx/assert'; +import * as check from 'guardx/check'; +import * as run from 'guardx/run'; +import * as util from 'guardx/util'; +``` -// failed to execute the function -if (!result.success) { - // Now typescript knows result is an object like this - // { - // success: false, - // error: Error - // } +| Module | Purpose | Available functions | +| --- | --- | --- | +| `assert` | Validate a value or throw | `isDefined`, `isNotNullOrUndefined`, `isNotNull`, `isNotUndefined`, `isUndefined`, `isEqual`, `isNotEqual`, `isOneOf`, `isTrue`, `isFalse`, `isBoolean`, `isString`, `isNumber`, `isBigInt`, `isSymbol`, `isFunction`, `isObject`, `isArray` | +| `check` | Narrow values with type guards | `isDefined`, `isNullOrUndefined`, `isNull`, `isUndefined`, `isString`, `isBoolean`, `isNumber`, `isBigInt`, `isSymbol`, `isFunction`, `isObject` | +| `run` | Capture function output or errors | `safe`, `safeAsync` | +| `util` | Shared utility helpers | `defaultTo`, `fail` | - console.error('Failed', result.error); -} +See the generated [documentation](https://dmanavi.github.io/guardx_website/) +for additional API details. -// code was executed successfully -if (result.success) { - // Now typescript knows result is an object like this - // { - // success: true, - // output: Error - // } -} -``` +## Development -There are other methods available. Check [them](#references) in API references page. +GuardX uses [pnpm](https://pnpm.io/), [Nx](https://nx.dev/), and +[Vitest](https://vitest.dev/). Node.js 20 is used in CI. -### util +```bash +pnpm install +pnpm exec nx test guardx +pnpm exec nx lint guardx +pnpm exec nx build guardx +``` -TBD. +To generate the API documentation locally: -There are other methods available. Check [them](#references) in API references page. +```bash +pnpm exec nx docs guardx +``` -## Development +## Contributing -TBD. +Bug reports and pull requests are welcome. Before opening a pull request, +please run the test, lint, and build commands above. -## References +- [Report a bug or request a feature](https://github.com/DManavi/guardx/issues) +- [View the changelog](https://github.com/DManavi/guardx/blob/main/CHANGELOG.md) -[API Documentation](https://dmanavi.github.io/guardx_website/) +## License -[Compile Typescript Packages to Multiple format](https://nx.dev/recipes/tips-n-tricks/compile-multiple-formats) +GuardX is available under the [MIT License](https://github.com/DManavi/guardx/blob/main/LICENSE.md).