├── .eslintrc.js ├── .gitignore ├── .vscode ├── extensions.json ├── settings.json └── terminals.json ├── API.md ├── README.md ├── jest.config.js ├── package-lock.json ├── package.json ├── src ├── __tests__ │ └── index.test.ts └── index.ts ├── tsconfig.json └── typedoc.json /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: "@typescript-eslint/parser", 4 | extends: [ 5 | "plugin:@typescript-eslint/recommended", 6 | "prettier/@typescript-eslint", 7 | "plugin:prettier/recommended", 8 | "plugin:import/errors", 9 | "plugin:import/warnings", 10 | "plugin:import/typescript", 11 | ], 12 | settings: { 13 | "import/parsers": { 14 | "@typescript-eslint/parser": [".ts", ".d.ts"], 15 | }, 16 | }, 17 | parserOptions: { 18 | ecmaVersion: 2018, 19 | sourceType: "module", 20 | }, 21 | rules: { 22 | "prettier/prettier": [1, { trailingComma: "all", endOfLine: "auto" }], 23 | "@typescript-eslint/no-unused-vars": [1, { argsIgnorePattern: "^_" }], 24 | "@typescript-eslint/no-unused-vars": [1, { argsIgnorePattern: "^_" }], 25 | "@typescript-eslint/naming-convention": [ 26 | "error", 27 | { 28 | selector: "interface", 29 | format: ["PascalCase"], 30 | prefix: ["I"], 31 | }, 32 | { 33 | selector: "variableLike", 34 | format: ["strictCamelCase", "UPPER_CASE"], 35 | leadingUnderscore: "allow", 36 | }, 37 | ], 38 | "@typescript-eslint/explicit-function-return-type": [ 39 | 1, 40 | { 41 | allowExpressions: true, 42 | allowTypedFunctionExpressions: true, 43 | }, 44 | ], 45 | "import/order": [ 46 | 1, 47 | { 48 | groups: [ 49 | "builtin", 50 | "external", 51 | "internal", 52 | "parent", 53 | "sibling", 54 | "index", 55 | ], 56 | "newlines-between": "always", 57 | }, 58 | ], 59 | }, 60 | }; 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | build 4 | *.tgz 5 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | // List of extensions which should be recommended for users of this workspace. 5 | "recommendations": [ 6 | "CoenraadS.bracket-pair-colorizer-2", 7 | "dbaeumer.vscode-eslint", 8 | "fabiospampinato.vscode-terminals", 9 | "GitHub.vscode-pull-request-github", 10 | "VisualStudioExptTeam.vscodeintellicode" 11 | ], 12 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace. 13 | "unwantedRecommendations": [] 14 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.fontLigatures": true, 3 | "editor.suggestSelection": "recentlyUsed", 4 | "editor.tabSize": 2, 5 | "emmet.showAbbreviationSuggestions": false, 6 | "emmet.showExpandedAbbreviation": "never", 7 | "emmet.excludeLanguages": [ 8 | "typescript", 9 | "javascript", 10 | ], 11 | "eslint.enable": true, 12 | "eslint.packageManager": "npm", 13 | "eslint.validate": [ 14 | "javascript", 15 | "typescript", 16 | ], 17 | "eslint.workingDirectories": [ 18 | { 19 | "directory": ".", 20 | "changeProcessCWD": true 21 | } 22 | ], 23 | "eslint.lintTask.enable": true, 24 | "files.associations": { 25 | "*.md": "markdown", 26 | "*.d.ts": "typescript" 27 | }, 28 | "files.autoSave": "off", 29 | "javascript.updateImportsOnFileMove.enabled": "always", 30 | "[javascript]": { 31 | "editor.formatOnSave": false 32 | }, 33 | "[typescript]": { 34 | "editor.formatOnSave": false 35 | }, 36 | "typescript.preferences.importModuleSpecifier": "relative", 37 | "typescript.suggest.paths": true, 38 | "editor.codeActionsOnSave": { 39 | "source.fixAll.eslint": true 40 | }, 41 | "editor.suggest.showSnippets": false, 42 | "editor.suggest.snippetsPreventQuickSuggestions": false, 43 | "editor.snippetSuggestions": "none" 44 | } -------------------------------------------------------------------------------- /.vscode/terminals.json: -------------------------------------------------------------------------------- 1 | { 2 | "autorun": true, 3 | "terminals": [ 4 | { 5 | "name": "typed-assert", 6 | "cwd": "[workspaceFolder]" 7 | } 8 | ] 9 | } -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | typed-assert 2 | 3 | # typed-assert 4 | 5 | ## Table of contents 6 | 7 | ### Type aliases 8 | 9 | - [Assert](API.md#assert) 10 | - [Check](API.md#check) 11 | - [SubType](API.md#subtype) 12 | - [WeakAssert](API.md#weakassert) 13 | 14 | ### Functions 15 | 16 | - [assert](API.md#assert) 17 | - [check](API.md#check) 18 | - [defaultAssert](API.md#defaultassert) 19 | - [isArray](API.md#isarray) 20 | - [isArrayOfType](API.md#isarrayoftype) 21 | - [isBoolean](API.md#isboolean) 22 | - [isDate](API.md#isdate) 23 | - [isExactly](API.md#isexactly) 24 | - [isInstanceOf](API.md#isinstanceof) 25 | - [isNever](API.md#isnever) 26 | - [isNotNull](API.md#isnotnull) 27 | - [isNotUndefined](API.md#isnotundefined) 28 | - [isNotVoid](API.md#isnotvoid) 29 | - [isNumber](API.md#isnumber) 30 | - [isOneOf](API.md#isoneof) 31 | - [isOneOfType](API.md#isoneoftype) 32 | - [isOptionOfType](API.md#isoptionoftype) 33 | - [isPromise](API.md#ispromise) 34 | - [isRecord](API.md#isrecord) 35 | - [isRecordOfType](API.md#isrecordoftype) 36 | - [isRecordWithKeys](API.md#isrecordwithkeys) 37 | - [isString](API.md#isstring) 38 | - [isUnknown](API.md#isunknown) 39 | - [safeJsonParse](API.md#safejsonparse) 40 | - [setBaseAssert](API.md#setbaseassert) 41 | 42 | ## Type aliases 43 | 44 | ### Assert 45 | 46 | Ƭ **Assert**<`Input`, `Output`\>: (`input`: `Input`, `message?`: `string`) => asserts input is SubType 47 | 48 | #### Type parameters 49 | 50 | | Name | Type | 51 | | :------ | :------ | 52 | | `Input` | `unknown` | 53 | | `Output` | `Input` | 54 | 55 | #### Type declaration 56 | 57 | ▸ (`input`, `message?`): asserts input is SubType 58 | 59 | ##### Parameters 60 | 61 | | Name | Type | 62 | | :------ | :------ | 63 | | `input` | `Input` | 64 | | `message?` | `string` | 65 | 66 | ##### Returns 67 | 68 | asserts input is SubType 69 | 70 | #### Defined in 71 | 72 | [index.ts:7](https://github.com/elierotenberg/typed-assert/blob/master/src/index.ts#L7) 73 | 74 | ___ 75 | 76 | ### Check 77 | 78 | Ƭ **Check**<`Input`, `Output`\>: (`input`: `Input`) => input is SubType 79 | 80 | #### Type parameters 81 | 82 | | Name | Type | 83 | | :------ | :------ | 84 | | `Input` | `unknown` | 85 | | `Output` | `Input` | 86 | 87 | #### Type declaration 88 | 89 | ▸ (`input`): input is SubType 90 | 91 | ##### Parameters 92 | 93 | | Name | Type | 94 | | :------ | :------ | 95 | | `input` | `Input` | 96 | 97 | ##### Returns 98 | 99 | input is SubType 100 | 101 | #### Defined in 102 | 103 | [index.ts:12](https://github.com/elierotenberg/typed-assert/blob/master/src/index.ts#L12) 104 | 105 | ___ 106 | 107 | ### SubType 108 | 109 | Ƭ **SubType**<`Input`, `Output`\>: `Output` extends `Input` ? `Output` : `never` 110 | 111 | #### Type parameters 112 | 113 | | Name | 114 | | :------ | 115 | | `Input` | 116 | | `Output` | 117 | 118 | #### Defined in 119 | 120 | [index.ts:5](https://github.com/elierotenberg/typed-assert/blob/master/src/index.ts#L5) 121 | 122 | ___ 123 | 124 | ### WeakAssert 125 | 126 | Ƭ **WeakAssert**: (`input`: `unknown`, `message?`: `string`) => `void` 127 | 128 | #### Type declaration 129 | 130 | ▸ (`input`, `message?`): `void` 131 | 132 | ##### Parameters 133 | 134 | | Name | Type | 135 | | :------ | :------ | 136 | | `input` | `unknown` | 137 | | `message?` | `string` | 138 | 139 | ##### Returns 140 | 141 | `void` 142 | 143 | #### Defined in 144 | 145 | [index.ts:3](https://github.com/elierotenberg/typed-assert/blob/master/src/index.ts#L3) 146 | 147 | ## Functions 148 | 149 | ### assert 150 | 151 | ▸ `Const` **assert**(`input`, `message?`): asserts input is true 152 | 153 | #### Parameters 154 | 155 | | Name | Type | 156 | | :------ | :------ | 157 | | `input` | `boolean` | 158 | | `message?` | `string` | 159 | 160 | #### Returns 161 | 162 | asserts input is true 163 | 164 | #### Defined in 165 | 166 | [index.ts:24](https://github.com/elierotenberg/typed-assert/blob/master/src/index.ts#L24) 167 | 168 | ___ 169 | 170 | ### check 171 | 172 | ▸ **check**<`Input`, `Output`\>(`assertT`): [`Check`](API.md#check)<`Input`, `Output`\> 173 | 174 | #### Type parameters 175 | 176 | | Name | 177 | | :------ | 178 | | `Input` | 179 | | `Output` | 180 | 181 | #### Parameters 182 | 183 | | Name | Type | 184 | | :------ | :------ | 185 | | `assertT` | [`Assert`](API.md#assert)<`Input`, `Output`\> | 186 | 187 | #### Returns 188 | 189 | [`Check`](API.md#check)<`Input`, `Output`\> 190 | 191 | #### Defined in 192 | 193 | [index.ts:209](https://github.com/elierotenberg/typed-assert/blob/master/src/index.ts#L209) 194 | 195 | ___ 196 | 197 | ### defaultAssert 198 | 199 | ▸ `Const` **defaultAssert**(`input`, `message?`): `void` 200 | 201 | #### Parameters 202 | 203 | | Name | Type | 204 | | :------ | :------ | 205 | | `input` | `unknown` | 206 | | `message?` | `string` | 207 | 208 | #### Returns 209 | 210 | `void` 211 | 212 | #### Defined in 213 | 214 | [index.ts:16](https://github.com/elierotenberg/typed-assert/blob/master/src/index.ts#L16) 215 | 216 | ___ 217 | 218 | ### isArray 219 | 220 | ▸ **isArray**(`input`, `message?`): asserts input is unknown[] 221 | 222 | #### Parameters 223 | 224 | | Name | Type | 225 | | :------ | :------ | 226 | | `input` | `unknown` | 227 | | `message` | `string` | 228 | 229 | #### Returns 230 | 231 | asserts input is unknown[] 232 | 233 | #### Defined in 234 | 235 | [index.ts:128](https://github.com/elierotenberg/typed-assert/blob/master/src/index.ts#L128) 236 | 237 | ___ 238 | 239 | ### isArrayOfType 240 | 241 | ▸ **isArrayOfType**<`T`\>(`input`, `assertT`, `message?`, `itemMessage?`): asserts input is T[] 242 | 243 | #### Type parameters 244 | 245 | | Name | 246 | | :------ | 247 | | `T` | 248 | 249 | #### Parameters 250 | 251 | | Name | Type | 252 | | :------ | :------ | 253 | | `input` | `unknown` | 254 | | `assertT` | [`Assert`](API.md#assert)<`unknown`, `T`\> | 255 | | `message` | `string` | 256 | | `itemMessage` | `string` | 257 | 258 | #### Returns 259 | 260 | asserts input is T[] 261 | 262 | #### Defined in 263 | 264 | [index.ts:147](https://github.com/elierotenberg/typed-assert/blob/master/src/index.ts#L147) 265 | 266 | ___ 267 | 268 | ### isBoolean 269 | 270 | ▸ **isBoolean**(`input`, `message?`): asserts input is boolean 271 | 272 | #### Parameters 273 | 274 | | Name | Type | 275 | | :------ | :------ | 276 | | `input` | `unknown` | 277 | | `message` | `string` | 278 | 279 | #### Returns 280 | 281 | asserts input is boolean 282 | 283 | #### Defined in 284 | 285 | [index.ts:76](https://github.com/elierotenberg/typed-assert/blob/master/src/index.ts#L76) 286 | 287 | ___ 288 | 289 | ### isDate 290 | 291 | ▸ **isDate**(`input`, `message?`): asserts input is Date 292 | 293 | #### Parameters 294 | 295 | | Name | Type | 296 | | :------ | :------ | 297 | | `input` | `unknown` | 298 | | `message` | `string` | 299 | 300 | #### Returns 301 | 302 | asserts input is Date 303 | 304 | #### Defined in 305 | 306 | [index.ts:97](https://github.com/elierotenberg/typed-assert/blob/master/src/index.ts#L97) 307 | 308 | ___ 309 | 310 | ### isExactly 311 | 312 | ▸ **isExactly**<`Input`, `Output`\>(`input`, `value`, `message?`): asserts input is SubType 313 | 314 | #### Type parameters 315 | 316 | | Name | 317 | | :------ | 318 | | `Input` | 319 | | `Output` | 320 | 321 | #### Parameters 322 | 323 | | Name | Type | 324 | | :------ | :------ | 325 | | `input` | `Input` | 326 | | `value` | `Output` | 327 | | `message` | `string` | 328 | 329 | #### Returns 330 | 331 | asserts input is SubType 332 | 333 | #### Defined in 334 | 335 | [index.ts:68](https://github.com/elierotenberg/typed-assert/blob/master/src/index.ts#L68) 336 | 337 | ___ 338 | 339 | ### isInstanceOf 340 | 341 | ▸ **isInstanceOf**<`T`\>(`input`, `constructor`, `message?`): asserts input is T 342 | 343 | #### Type parameters 344 | 345 | | Name | 346 | | :------ | 347 | | `T` | 348 | 349 | #### Parameters 350 | 351 | | Name | Type | 352 | | :------ | :------ | 353 | | `input` | `unknown` | 354 | | `constructor` | (...`args`: `any`[]) => `T` | 355 | | `message` | `string` | 356 | 357 | #### Returns 358 | 359 | asserts input is T 360 | 361 | #### Defined in 362 | 363 | [index.ts:193](https://github.com/elierotenberg/typed-assert/blob/master/src/index.ts#L193) 364 | 365 | ___ 366 | 367 | ### isNever 368 | 369 | ▸ **isNever**(`_input`, `message?`): `never` 370 | 371 | #### Parameters 372 | 373 | | Name | Type | 374 | | :------ | :------ | 375 | | `_input` | `never` | 376 | | `message` | `string` | 377 | 378 | #### Returns 379 | 380 | `never` 381 | 382 | #### Defined in 383 | 384 | [index.ts:40](https://github.com/elierotenberg/typed-assert/blob/master/src/index.ts#L40) 385 | 386 | ___ 387 | 388 | ### isNotNull 389 | 390 | ▸ **isNotNull**<`T`\>(`input`, `message?`): asserts input is T 391 | 392 | #### Type parameters 393 | 394 | | Name | 395 | | :------ | 396 | | `T` | 397 | 398 | #### Parameters 399 | 400 | | Name | Type | 401 | | :------ | :------ | 402 | | `input` | ``null`` \| `T` | 403 | | `message` | `string` | 404 | 405 | #### Returns 406 | 407 | asserts input is T 408 | 409 | #### Defined in 410 | 411 | [index.ts:47](https://github.com/elierotenberg/typed-assert/blob/master/src/index.ts#L47) 412 | 413 | ___ 414 | 415 | ### isNotUndefined 416 | 417 | ▸ **isNotUndefined**<`T`\>(`input`, `message?`): asserts input is T 418 | 419 | #### Type parameters 420 | 421 | | Name | 422 | | :------ | 423 | | `T` | 424 | 425 | #### Parameters 426 | 427 | | Name | Type | 428 | | :------ | :------ | 429 | | `input` | `undefined` \| `T` | 430 | | `message` | `string` | 431 | 432 | #### Returns 433 | 434 | asserts input is T 435 | 436 | #### Defined in 437 | 438 | [index.ts:54](https://github.com/elierotenberg/typed-assert/blob/master/src/index.ts#L54) 439 | 440 | ___ 441 | 442 | ### isNotVoid 443 | 444 | ▸ **isNotVoid**<`T`\>(`input`, `message?`): asserts input is Exclude 445 | 446 | #### Type parameters 447 | 448 | | Name | 449 | | :------ | 450 | | `T` | 451 | 452 | #### Parameters 453 | 454 | | Name | Type | 455 | | :------ | :------ | 456 | | `input` | `T` | 457 | | `message` | `string` | 458 | 459 | #### Returns 460 | 461 | asserts input is Exclude 462 | 463 | #### Defined in 464 | 465 | [index.ts:61](https://github.com/elierotenberg/typed-assert/blob/master/src/index.ts#L61) 466 | 467 | ___ 468 | 469 | ### isNumber 470 | 471 | ▸ **isNumber**(`input`, `message?`): asserts input is number 472 | 473 | #### Parameters 474 | 475 | | Name | Type | 476 | | :------ | :------ | 477 | | `input` | `unknown` | 478 | | `message` | `string` | 479 | 480 | #### Returns 481 | 482 | asserts input is number 483 | 484 | #### Defined in 485 | 486 | [index.ts:83](https://github.com/elierotenberg/typed-assert/blob/master/src/index.ts#L83) 487 | 488 | ___ 489 | 490 | ### isOneOf 491 | 492 | ▸ **isOneOf**<`Input`, `Output`\>(`input`, `values`, `message?`): asserts input is SubType 493 | 494 | #### Type parameters 495 | 496 | | Name | 497 | | :------ | 498 | | `Input` | 499 | | `Output` | 500 | 501 | #### Parameters 502 | 503 | | Name | Type | 504 | | :------ | :------ | 505 | | `input` | `Input` | 506 | | `values` | readonly `Output`[] | 507 | | `message` | `string` | 508 | 509 | #### Returns 510 | 511 | asserts input is SubType 512 | 513 | #### Defined in 514 | 515 | [index.ts:170](https://github.com/elierotenberg/typed-assert/blob/master/src/index.ts#L170) 516 | 517 | ___ 518 | 519 | ### isOneOfType 520 | 521 | ▸ **isOneOfType**<`T`\>(`input`, `assertT`, `message?`, `itemMessage?`): asserts input is T 522 | 523 | #### Type parameters 524 | 525 | | Name | 526 | | :------ | 527 | | `T` | 528 | 529 | #### Parameters 530 | 531 | | Name | Type | 532 | | :------ | :------ | 533 | | `input` | `unknown` | 534 | | `assertT` | [`Assert`](API.md#assert)<`unknown`, `T`\>[] | 535 | | `message` | `string` | 536 | | `itemMessage?` | `string` | 537 | 538 | #### Returns 539 | 540 | asserts input is T 541 | 542 | #### Defined in 543 | 544 | [index.ts:178](https://github.com/elierotenberg/typed-assert/blob/master/src/index.ts#L178) 545 | 546 | ___ 547 | 548 | ### isOptionOfType 549 | 550 | ▸ **isOptionOfType**<`Input`, `Output`\>(`input`, `assertT`, `message?`): asserts input is SubType \| SubType 551 | 552 | #### Type parameters 553 | 554 | | Name | 555 | | :------ | 556 | | `Input` | 557 | | `Output` | 558 | 559 | #### Parameters 560 | 561 | | Name | Type | 562 | | :------ | :------ | 563 | | `input` | `undefined` \| `Input` | 564 | | `assertT` | [`Assert`](API.md#assert)<`Input`, `Output`\> | 565 | | `message` | `string` | 566 | 567 | #### Returns 568 | 569 | asserts input is SubType \| SubType 570 | 571 | #### Defined in 572 | 573 | [index.ts:159](https://github.com/elierotenberg/typed-assert/blob/master/src/index.ts#L159) 574 | 575 | ___ 576 | 577 | ### isPromise 578 | 579 | ▸ **isPromise**(`input`, `message?`): asserts input is Promise 580 | 581 | #### Parameters 582 | 583 | | Name | Type | 584 | | :------ | :------ | 585 | | `input` | `unknown` | 586 | | `message` | `string` | 587 | 588 | #### Returns 589 | 590 | asserts input is Promise 591 | 592 | #### Defined in 593 | 594 | [index.ts:202](https://github.com/elierotenberg/typed-assert/blob/master/src/index.ts#L202) 595 | 596 | ___ 597 | 598 | ### isRecord 599 | 600 | ▸ **isRecord**(`input`, `message?`): asserts input is Record 601 | 602 | #### Parameters 603 | 604 | | Name | Type | 605 | | :------ | :------ | 606 | | `input` | `unknown` | 607 | | `message` | `string` | 608 | 609 | #### Returns 610 | 611 | asserts input is Record 612 | 613 | #### Defined in 614 | 615 | [index.ts:104](https://github.com/elierotenberg/typed-assert/blob/master/src/index.ts#L104) 616 | 617 | ___ 618 | 619 | ### isRecordOfType 620 | 621 | ▸ **isRecordOfType**<`T`\>(`input`, `assertT`, `message?`, `itemMessage?`): asserts input is Record 622 | 623 | #### Type parameters 624 | 625 | | Name | 626 | | :------ | 627 | | `T` | 628 | 629 | #### Parameters 630 | 631 | | Name | Type | 632 | | :------ | :------ | 633 | | `input` | `unknown` | 634 | | `assertT` | [`Assert`](API.md#assert)<`unknown`, `T`\> | 635 | | `message` | `string` | 636 | | `itemMessage` | `string` | 637 | 638 | #### Returns 639 | 640 | asserts input is Record 641 | 642 | #### Defined in 643 | 644 | [index.ts:135](https://github.com/elierotenberg/typed-assert/blob/master/src/index.ts#L135) 645 | 646 | ___ 647 | 648 | ### isRecordWithKeys 649 | 650 | ▸ **isRecordWithKeys**<`K`\>(`input`, `keys`, `message?`): asserts input is { readonly [Key in string]: unknown } 651 | 652 | #### Type parameters 653 | 654 | | Name | Type | 655 | | :------ | :------ | 656 | | `K` | extends `string` | 657 | 658 | #### Parameters 659 | 660 | | Name | Type | 661 | | :------ | :------ | 662 | | `input` | `unknown` | 663 | | `keys` | `K`[] | 664 | | `message` | `string` | 665 | 666 | #### Returns 667 | 668 | asserts input is { readonly [Key in string]: unknown } 669 | 670 | #### Defined in 671 | 672 | [index.ts:115](https://github.com/elierotenberg/typed-assert/blob/master/src/index.ts#L115) 673 | 674 | ___ 675 | 676 | ### isString 677 | 678 | ▸ **isString**(`input`, `message?`): asserts input is string 679 | 680 | #### Parameters 681 | 682 | | Name | Type | 683 | | :------ | :------ | 684 | | `input` | `unknown` | 685 | | `message` | `string` | 686 | 687 | #### Returns 688 | 689 | asserts input is string 690 | 691 | #### Defined in 692 | 693 | [index.ts:90](https://github.com/elierotenberg/typed-assert/blob/master/src/index.ts#L90) 694 | 695 | ___ 696 | 697 | ### isUnknown 698 | 699 | ▸ **isUnknown**(`_input`): \_input is unknown 700 | 701 | #### Parameters 702 | 703 | | Name | Type | 704 | | :------ | :------ | 705 | | `_input` | `unknown` | 706 | 707 | #### Returns 708 | 709 | \_input is unknown 710 | 711 | #### Defined in 712 | 713 | [index.ts:36](https://github.com/elierotenberg/typed-assert/blob/master/src/index.ts#L36) 714 | 715 | ___ 716 | 717 | ### safeJsonParse 718 | 719 | ▸ `Const` **safeJsonParse**(`json`): `unknown` 720 | 721 | #### Parameters 722 | 723 | | Name | Type | 724 | | :------ | :------ | 725 | | `json` | `string` | 726 | 727 | #### Returns 728 | 729 | `unknown` 730 | 731 | #### Defined in 732 | 733 | [index.ts:33](https://github.com/elierotenberg/typed-assert/blob/master/src/index.ts#L33) 734 | 735 | ___ 736 | 737 | ### setBaseAssert 738 | 739 | ▸ **setBaseAssert**(`assert?`): `void` 740 | 741 | #### Parameters 742 | 743 | | Name | Type | 744 | | :------ | :------ | 745 | | `assert?` | [`WeakAssert`](API.md#weakassert) | 746 | 747 | #### Returns 748 | 749 | `void` 750 | 751 | #### Defined in 752 | 753 | [index.ts:27](https://github.com/elierotenberg/typed-assert/blob/master/src/index.ts#L27) 754 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `typed-assert` 2 | 3 | `typed-assert` is a typesafe assertion library implementing the [TS 3.7 Assertion Functions](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#assertion-functions) API, without external dependencies. 4 | 5 | See the [documentation](API.md). 6 | 7 | ### Consider using [`zod`](https://github.com/colinhacks/zod) 8 | 9 | While this library does a fine job for most simple use cases, please consider using [`zod`](https://github.com/colinhacks/zod) if you need more complex assertions. 10 | 11 | ### Install 12 | 13 | ```npm install typed-assert``` 14 | 15 | or 16 | 17 | ```yarn add typed-assert``` 18 | 19 | ### Why is it useful? 20 | 21 | `typed-assert` promotes using `unknown` instead of `any` for "untrusted" values, e.g. user input, while still benefiting from incremental typing. 22 | 23 | For example, `JSON.stringify` returns `any`, which is not typesafe. With `typed-assert`, we can instead treat the result as `unknown` and gradually check the contents at runtime and still get correct type inference: 24 | 25 | ```ts 26 | import * as t from "typed-assert"; 27 | 28 | const parseConfigFile = (file: string): { readonly a: string, readonly b: number } => { 29 | const contents = JSON.parse(fs.readFileSync(file, { encoding: 'utf8'})) as unknown; 30 | // contents is "unknown" instead of any, because we don't trust the input yet 31 | t.isRecord(contents); 32 | // contents is "Record" 33 | t.isString(contents.a); 34 | // contents.a is "string" 35 | t.isNumber(contents.b): 36 | // contents.b is "number"; 37 | return { 38 | a: contents.a, 39 | b: contents.b, 40 | }; // correctly typed 41 | } 42 | ``` 43 | 44 | ### How is it different from chai, jest.expect, etc? 45 | 46 | `typed-assert` is both a compile-time and runtime assert library. It leverages the `assertion function` feature of TypeScript to help the typechecker narrow the inferred types. In many cases, this significantly reduces the need to use `any`, and promotes using `unknown` instead. 47 | 48 | For example: 49 | 50 | ```ts 51 | const u: unknown = { 52 | a: "value", 53 | b: 12, 54 | }; 55 | 56 | chai.assert.typeOf(u, "object"); 57 | // u is still "unknown" 58 | chai.assert.isNotNull(u); 59 | // u is still "unknown" 60 | chai.assert.typeof(u.a, "string"); 61 | // TS Error (ts2571): u is "unknown" 62 | 63 | import * as t from "typed-assert"; 64 | 65 | t.isRecord(u); 66 | // u is Record 67 | t.isString(u.a); 68 | // u.a is string 69 | t.isNumber(u.b); 70 | // u.b is number 71 | 72 | const v: { a: string; b: number } = u; 73 | // no need to us `as ...` 74 | ``` 75 | 76 | ### Usage 77 | 78 | `typed-assert` comes with a set of common assertions, as well as assertion combinators and utilities. 79 | 80 | See [the documentation](./API.md) for a full reference. 81 | 82 | ```ts 83 | import * as t from "typed-assert"; 84 | 85 | // Base asserts 86 | t.isExactly("a", "a"); 87 | t.isNotUndefined(null); 88 | t.isNotNull(undefined); 89 | 90 | // Asserts combinators 91 | t.isOneOf("b", ["a", "b", "c"]); 92 | t.isArrayOf([2, 3, 4], t.isNumber); 93 | 94 | // Custom composite checks 95 | interface ICustomType { 96 | readonly a: { 97 | readonly b: "c"; 98 | readonly d: string; 99 | }; 100 | readonly f?: number; 101 | } 102 | 103 | function assertCustomType(input: unknown): asserts input is ICustomType { 104 | t.isRecordWithKeys(input, ["a", "f"]); 105 | t.isRecordWithKeys(input.a, ["b", "d"]); 106 | t.isExactly(input.a.b, "c"); 107 | t.isString(input.a.d); 108 | t.isOption(input.f, t.isNumber); 109 | } 110 | 111 | const v = { 112 | a: { 113 | b: "c", 114 | d: "", 115 | }, 116 | }; 117 | assertCustomType(v); 118 | ``` 119 | 120 | This library also comes with a combinator to transform an assertion functions into a [type guard function](https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards): 121 | ```ts 122 | const checkNumber = t.check(t.isNumber); 123 | checkNumber(1) === true; 124 | checkNumber("") === false; 125 | ``` 126 | 127 | It is especially convenient when combined with functional operations such as `Array#filter`: 128 | ```ts 129 | const t = ["a", 3, "c", 4, null, 2] 130 | .filter(t.check(t.isNumber)) 131 | .map(x => x % 2 === 0 ? x : null) // x: number 132 | .filter(t.check(t.isNotNull)); 133 | // t: number[] = [4, 2] 134 | ``` 135 | 136 | To encourage using asserts when dealing with untrusted JSON input, the following function is also exported: 137 | ```ts 138 | export const safeJsonParse = (json: string): unknown => 139 | JSON.parse(json) as unknown; 140 | ``` 141 | 142 | ### Configuration 143 | 144 | This library is designed to work in the browser as well as in Node without external dependencies, and by default does not use the `assert` module from the Node stdlib, so it ships with a very basic `assert` implementation: 145 | ```ts 146 | export type WeakAssert = (input: unknown, message?: string) => void; 147 | 148 | export const defaultAssert: WeakAssert = (condition, message) => { 149 | if (!condition) { 150 | throw new TypeError(message); 151 | } 152 | }; 153 | 154 | ``` 155 | 156 | It is however possible to configure the library to use a provided base `assert` function, such as the native `assert` module: 157 | ```ts 158 | import * as t from "typed-assert"; 159 | import nodeAssert from "assert"; 160 | 161 | t.setBaseAssert(nodeAssert); 162 | ``` 163 | 164 | ### Caveats 165 | 166 | Due to limitations in the typechecker, there are syntactic restrictions in how to define and use type assertion functions. For example, you can not dynamically define an assertion function, even if it looks like a static definition. 167 | 168 | Thus the following code won't compile: 169 | ```ts 170 | function createIsExactly(value: T): (input: unknown) => asserts input is T { 171 | return function isExactly(input: unknown): asserts input is T { 172 | t.isExactly(input, value); 173 | }; 174 | } 175 | // No problem so far 176 | 177 | createIsExactly("a")(null); 178 | // Won't compile: 179 | // Assertions require the call target to be an 180 | // identifier or qualified name.ts(2776) 181 | ``` 182 | 183 | For similar reasons, it is not possible to use type-inferred arrow functions to define assertion functions: 184 | ```ts 185 | const isExactlyNull = (input: unknown): asserts input is null => assert(input === value); 186 | // No problem so far 187 | 188 | isExactlyNull("a", null): 189 | // Won't compile: 190 | // Assertions require the call target to be an 191 | // identifier or qualified name.ts(2776) 192 | ``` 193 | 194 | It is however possible to use arrow function with explicit typing of the left-hand operand: 195 | ```ts 196 | const isExactlyNull: (input: unknown) => asserts input is null = (input) => 197 | assert(input === null); 198 | 199 | isExactlyNull("a"); 200 | // No problem 201 | ``` 202 | 203 | To simplify the implementation, 204 | 205 | To simplify this pattern, this library also exports the `Assert` type as defined below: 206 | ```ts 207 | export type Assert = ( 208 | input: unknown, 209 | message?: string, 210 | ) => asserts input is T; 211 | 212 | const isExactlyNull: Assert = (input) => assert(input === null); 213 | 214 | isExactlyNull("a"); 215 | // No problem 216 | ``` 217 | 218 | For convenience, this library also exports the following types, used internally: 219 | 220 | ```ts 221 | export type WeakAssert = (input: unknown, message?: string) => void; 222 | 223 | export type SubType = Output extends Input ? Output : never; 224 | 225 | export type Assert = ( 226 | input: Input, 227 | message?: string, 228 | ) => asserts input is SubType; 229 | 230 | export type Check = ( 231 | input: Input, 232 | ) => input is SubType; 233 | ``` 234 | 235 | This way we can write: 236 | ```ts 237 | const isExactlyNull: Assert = (input) => 238 | assert(input === null); 239 | 240 | isExactlyNull("a"); 241 | ``` 242 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: "ts-jest", 3 | testEnvironment: "node", 4 | maxConcurrency: 50, 5 | moduleFileExtensions: ["js", "json", "jsx", "ts", "tsx", "d.ts"], 6 | }; 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typed-assert", 3 | "version": "1.0.9", 4 | "description": "typesafe assertion library for TypeScript 3.7+", 5 | "main": "build/index.js", 6 | "scripts": { 7 | "test:src": "jest src/**/*.test.ts", 8 | "test:build": "jest build/**/*.test.js", 9 | "clean:doc": "rm -rf doc && rm -rf API.md", 10 | "build:doc": "npm run clean:doc && typedoc && sed s/README.md/API.md/ doc/README.md > API.md && rm -rf doc", 11 | "clean:ts": "rm -rf build", 12 | "build:ts": "npm run clean:ts && tsc -p .", 13 | "build": "npm run build:doc && npm run build:ts" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/elierotenberg/typed-assert.git" 18 | }, 19 | "keywords": [ 20 | "assert", 21 | "typescript" 22 | ], 23 | "author": "Elie Rotenberg ", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/elierotenberg/typed-assert/issues" 27 | }, 28 | "homepage": "https://github.com/elierotenberg/typed-assert#readme", 29 | "devDependencies": { 30 | "@types/jest": "^27.4.0", 31 | "@types/node": "^17.0.18", 32 | "@typescript-eslint/eslint-plugin": "^5.12.0", 33 | "@typescript-eslint/parser": "^5.12.0", 34 | "eslint": "^8.9.0", 35 | "eslint-config-prettier": "^8.3.0", 36 | "eslint-plugin-import": "^2.25.4", 37 | "eslint-plugin-prettier": "^4.0.0", 38 | "jest": "^27.5.1", 39 | "prettier": "^2.5.1", 40 | "ts-jest": "^27.1.3", 41 | "typedoc": "^0.22.11", 42 | "typedoc-plugin-markdown": "^3.11.14", 43 | "typescript": "^4.5.5" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/__tests__/index.test.ts: -------------------------------------------------------------------------------- 1 | import nodeAssert from "assert"; 2 | 3 | import * as t from ".."; 4 | 5 | class C {} 6 | 7 | const fixtures = { 8 | null: null, 9 | undefined: undefined, 10 | string: "", 11 | number: 0, 12 | boolean: true, 13 | date: new Date(), 14 | promise: Promise.resolve(null), 15 | record: {} as Record, 16 | array: [] as unknown[], 17 | recordWithKeys: { 18 | a: { 19 | b: { 20 | c: "d", 21 | }, 22 | e: null, 23 | }, 24 | f: "g", 25 | } as const, 26 | recordOfStrings: { 27 | a: "", 28 | b: "", 29 | } as Record, 30 | recordOfNumbers: { 31 | a: 0, 32 | b: 1, 33 | } as Record, 34 | strings: ["a", "b", "c"] as string[], 35 | numbers: [0, 1, 2] as number[], 36 | c: new C(), 37 | }; 38 | 39 | const entries = Object.entries(fixtures) as [ 40 | keyof typeof fixtures, 41 | typeof fixtures[keyof typeof fixtures], 42 | ][]; 43 | 44 | const orNull = (t: T): T | null => t; 45 | const orUndefined = (t: T): T | undefined => t; 46 | 47 | const baseAsserts: [string, t.WeakAssert][] = [ 48 | ["default", t.defaultAssert], 49 | ["node", nodeAssert], 50 | ]; 51 | 52 | describe("typed-assert", () => { 53 | for (const [label, baseAssert] of baseAsserts) { 54 | describe(label, () => { 55 | t.setBaseAssert(baseAssert); 56 | 57 | test("isUnknown", () => { 58 | for (const value of Object.values(fixtures)) { 59 | expect(() => t.isUnknown(value)).not.toThrow(); 60 | } 61 | }); 62 | 63 | test("isNever", () => { 64 | expect(() => { 65 | const value = "a" as "a" | "b"; 66 | switch (value) { 67 | case "a": 68 | case "b": 69 | return; 70 | } 71 | t.isNever(value); 72 | }).not.toThrow(); 73 | }); 74 | 75 | test("isNotNull", () => { 76 | const v = orNull(fixtures.string); 77 | t.isNotNull(v); 78 | // v is "string" 79 | for (const [key, value] of entries) { 80 | if (key === "null") { 81 | expect(() => t.isNotNull(value)).toThrow(); 82 | } else { 83 | expect(() => t.isNotNull(value)).not.toThrow(); 84 | } 85 | } 86 | }); 87 | 88 | test("isNotUndefined", () => { 89 | const v = orUndefined(fixtures.string); 90 | t.isNotUndefined(v); 91 | // v is "string" 92 | for (const [key, value] of entries) { 93 | if (key === "undefined") { 94 | expect(() => t.isNotUndefined(value)).toThrow(); 95 | } else { 96 | expect(() => t.isNotUndefined(value)).not.toThrow(); 97 | } 98 | } 99 | }); 100 | 101 | test("isNotVoid", () => { 102 | const v = orNull(orUndefined(fixtures.string)); 103 | t.isNotVoid(v); 104 | // v is "string" 105 | for (const [key, value] of entries) { 106 | if (key === "null" || key === "undefined") { 107 | expect(() => t.isNotVoid(value)).toThrow(); 108 | } else { 109 | expect(() => t.isNotVoid(value)).not.toThrow(); 110 | } 111 | } 112 | }); 113 | 114 | test("isExactly", () => { 115 | const v = orUndefined("value"); 116 | t.isExactly(v, "value"); 117 | // v is "value" 118 | for (const [, value] of entries) { 119 | expect(() => t.isExactly(value, value)).not.toThrow(); 120 | for (const [, otherValue] of entries) { 121 | if (otherValue !== value) { 122 | expect(() => t.isExactly(value, otherValue)).toThrow(); 123 | expect(() => t.isExactly(otherValue, value)).toThrow(); 124 | } 125 | } 126 | } 127 | }); 128 | 129 | test("isBoolean", () => { 130 | const v = orNull(fixtures.boolean); 131 | t.isBoolean(v); 132 | // v is "boolean" 133 | for (const [key, value] of entries) { 134 | if (key === "boolean") { 135 | expect(() => t.isBoolean(value)).not.toThrow(); 136 | } else { 137 | expect(() => t.isBoolean(value)).toThrow(); 138 | } 139 | } 140 | }); 141 | 142 | test("isNumber", () => { 143 | const v = orNull(fixtures.number); 144 | t.isNumber(v); 145 | // v is "number" 146 | for (const [key, value] of entries) { 147 | if (key === "string") { 148 | expect(() => t.isString(value)).not.toThrow(); 149 | } else { 150 | expect(() => t.isString(value)).toThrow(); 151 | } 152 | } 153 | }); 154 | 155 | test("isString", () => { 156 | const v = orNull(fixtures.string); 157 | t.isString(v); 158 | // v is "string" 159 | for (const [key, value] of entries) { 160 | if (key === "string") { 161 | expect(() => t.isString(value)).not.toThrow(); 162 | } else { 163 | expect(() => t.isString(value)).toThrow(); 164 | } 165 | } 166 | }); 167 | 168 | test("isDate", () => { 169 | const v = orNull(fixtures.date); 170 | t.isDate(v); 171 | // v is "Date" 172 | for (const [key, value] of entries) { 173 | if (key === "date") { 174 | expect(() => t.isDate(value)).not.toThrow(); 175 | } else { 176 | expect(() => t.isDate(value)).toThrow(); 177 | } 178 | } 179 | }); 180 | 181 | test("isPromise", () => { 182 | const v = orNull(fixtures.promise); 183 | t.isPromise(v); 184 | // v is "Promise" 185 | for (const [key, value] of entries) { 186 | if (key === "promise") { 187 | expect(() => t.isPromise(value)).not.toThrow(); 188 | } else { 189 | expect(() => t.isPromise(value)).toThrow(); 190 | } 191 | } 192 | }); 193 | 194 | test("isRecord", () => { 195 | const v = orNull(fixtures.record); 196 | t.isRecord(v); 197 | // v is "Record" 198 | for (const [key, value] of entries) { 199 | if ( 200 | [ 201 | "record", 202 | "recordOfStrings", 203 | "recordOfNumbers", 204 | "recordWithKeys", 205 | "date", 206 | "promise", 207 | "array", 208 | "strings", 209 | "numbers", 210 | "c", 211 | ].includes(key) 212 | ) { 213 | expect(() => t.isRecord(value)).not.toThrow(); 214 | } else { 215 | expect(() => t.isRecord(value)).toThrow(); 216 | } 217 | } 218 | }); 219 | 220 | test("isArray", () => { 221 | const v = orNull(fixtures.array); 222 | t.isArray(v); 223 | // v is unknown[] 224 | for (const [key, value] of entries) { 225 | if (["array", "strings", "numbers"].includes(key)) { 226 | expect(() => t.isArray(value)).not.toThrow(); 227 | } else { 228 | expect(() => t.isArray(value)).toThrow(); 229 | } 230 | } 231 | }); 232 | 233 | test("isRecordWithKeys", () => { 234 | const keys = Object.keys(fixtures.recordWithKeys); 235 | const v = orNull(fixtures.recordWithKeys); 236 | t.isRecordWithKeys(v, keys); 237 | // v is typeof fixtures.recordWithKeys 238 | for (const [key, value] of entries) { 239 | if (key === "recordWithKeys") { 240 | expect(() => t.isRecordWithKeys(value, keys)).not.toThrow(); 241 | } else { 242 | expect(() => t.isRecordWithKeys(value, keys)).toThrow(); 243 | } 244 | } 245 | 246 | expect(() => 247 | t.isRecordWithKeys(fixtures.recordWithKeys, ["a"]), 248 | ).not.toThrow(); 249 | expect(() => 250 | t.isRecordWithKeys(fixtures.recordWithKeys, ["b"]), 251 | ).toThrow(); 252 | expect(() => 253 | t.isRecordWithKeys(fixtures.recordWithKeys, ["f"]), 254 | ).not.toThrow(); 255 | expect(() => 256 | t.isRecordWithKeys(fixtures.recordWithKeys.a, ["b", "e"]), 257 | ).not.toThrow(); 258 | expect(() => 259 | t.isRecordWithKeys(fixtures.recordWithKeys, ["a", "b"]), 260 | ).toThrow(); 261 | }); 262 | 263 | test("isRecordOfType", () => { 264 | const v = orNull(fixtures.recordOfNumbers); 265 | t.isRecordOfType(v, t.isNumber); 266 | // v is Record 267 | for (const [key, value] of entries) { 268 | if ( 269 | [ 270 | "record", 271 | "recordOfNumbers", 272 | "array", 273 | "numbers", 274 | "date", 275 | "promise", 276 | "c", 277 | ].includes(key) 278 | ) { 279 | expect(() => t.isRecordOfType(value, t.isNumber)).not.toThrow(); 280 | } else { 281 | expect(() => t.isRecordOfType(value, t.isNumber)).toThrow(); 282 | } 283 | } 284 | }); 285 | 286 | test("isArrayOfType", () => { 287 | const v = orNull(fixtures.numbers); 288 | t.isArrayOfType(v, t.isNumber); 289 | // v is number[] 290 | for (const [key, value] of entries) { 291 | if (["array", "numbers"].includes(key)) { 292 | expect(() => t.isArrayOfType(value, t.isNumber)).not.toThrow(); 293 | } else { 294 | expect(() => t.isArrayOfType(value, t.isNumber)).toThrow(); 295 | } 296 | } 297 | }); 298 | 299 | test("isOptionOfType", () => { 300 | const v = orNull(fixtures.number); 301 | t.isOptionOfType(v, t.isNumber); 302 | // v is "number" 303 | for (const [key, value] of entries) { 304 | if (["undefined", "number"].includes(key)) { 305 | expect(() => t.isOptionOfType(value, t.isNumber)).not.toThrow(); 306 | } else { 307 | expect(() => t.isOptionOfType(value, t.isNumber)).toThrow(); 308 | } 309 | } 310 | }); 311 | 312 | test("isOneOf", () => { 313 | const v = orNull("a"); 314 | t.isOneOf(v, ["a", "b"] as const); 315 | // v is "a" 316 | for (const [key, value] of entries) { 317 | if (key === "number") { 318 | expect(() => 319 | t.isOneOf(value, [fixtures.number, fixtures.number + 1]), 320 | ).not.toThrow(); 321 | } else { 322 | expect(() => 323 | t.isOneOf(value, [fixtures.number, fixtures.number + 1]), 324 | ).toThrow(); 325 | } 326 | } 327 | }); 328 | 329 | test("isOneOfType", () => { 330 | const v = orNull(fixtures.string); 331 | t.isOneOfType(v, [t.isString, t.isNumber]); 332 | // v is "string" 333 | for (const [key, value] of entries) { 334 | if (["string", "number"].includes(key)) { 335 | expect(() => 336 | t.isOneOfType(value, [t.isString, t.isNumber]), 337 | ).not.toThrow(); 338 | } else { 339 | expect(() => 340 | t.isOneOfType(value, [t.isString, t.isNumber]), 341 | ).toThrow(); 342 | } 343 | } 344 | }); 345 | 346 | test("isInstanceOf", () => { 347 | const v = orNull(fixtures.date); 348 | t.isInstanceOf(v, Date); 349 | // v is Date 350 | for (const [key, value] of entries) { 351 | if (key === "c") { 352 | expect(() => t.isInstanceOf(value, C)).not.toThrow(); 353 | } else { 354 | expect(() => t.isInstanceOf(value, C)).toThrow(); 355 | } 356 | } 357 | }); 358 | 359 | test("safeJsonParse", () => { 360 | const input = { 361 | a: { 362 | b: { 363 | c: "d", 364 | }, 365 | e: null, 366 | }, 367 | f: "g", 368 | }; 369 | 370 | function assertIsInput( 371 | input: unknown, 372 | ): asserts input is typeof fixtures.recordWithKeys { 373 | t.isRecordWithKeys(input, ["a", "f"]); 374 | t.isRecordWithKeys(input.a, ["b", "e"]); 375 | t.isRecordWithKeys(input.a.b, ["c"]); 376 | t.isExactly(input.a.b.c, "d" as const); 377 | t.isExactly(input.a.e, null); 378 | t.isExactly(input.f, "g"); 379 | } 380 | 381 | const json = JSON.stringify(input); 382 | const output = t.safeJsonParse(json); 383 | 384 | assertIsInput(output); 385 | 386 | const typedOutput: typeof fixtures.recordWithKeys = output; 387 | 388 | expect(typedOutput).toEqual(input); 389 | }); 390 | 391 | test("check filter", () => { 392 | const a = entries 393 | .map(([, value]) => value) 394 | .filter(t.check(t.isNumber)) 395 | .map((value) => (value % 0 === 0 ? value : null)) 396 | .filter(t.check(t.isNotNull)); 397 | for (const item of a) { 398 | expect(() => t.isNumber(item)).not.toThrow(); 399 | } 400 | }); 401 | }); 402 | } 403 | }); 404 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | const expectedToBe = (type: string): string => `expected to be ${type}`; 2 | 3 | export type WeakAssert = (input: unknown, message?: string) => void; 4 | 5 | export type SubType = Output extends Input ? Output : never; 6 | 7 | export type Assert = ( 8 | input: Input, 9 | message?: string, 10 | ) => asserts input is SubType; 11 | 12 | export type Check = ( 13 | input: Input, 14 | ) => input is SubType; 15 | 16 | export const defaultAssert: WeakAssert = (condition, message) => { 17 | if (!condition) { 18 | throw new TypeError(message); 19 | } 20 | }; 21 | 22 | let baseAssert = defaultAssert; 23 | 24 | export const assert: Assert = (condition, message) => 25 | baseAssert(condition, message); 26 | 27 | export function setBaseAssert(assert?: WeakAssert): void { 28 | if (assert) { 29 | baseAssert = assert; 30 | } 31 | } 32 | 33 | export const safeJsonParse = (json: string): unknown => 34 | JSON.parse(json) as unknown; 35 | 36 | export function isUnknown(_input: unknown): _input is unknown { 37 | return true; 38 | } 39 | 40 | export function isNever( 41 | _input: never, 42 | message: string = expectedToBe("unreachable"), 43 | ): never { 44 | throw new TypeError(message) 45 | } 46 | 47 | export function isNotNull( 48 | input: null | T, 49 | message: string = expectedToBe("not null"), 50 | ): asserts input is T { 51 | assert(input !== null, message); 52 | } 53 | 54 | export function isNotUndefined( 55 | input: undefined | T, 56 | message: string = expectedToBe("not undefined"), 57 | ): asserts input is T { 58 | assert(input !== undefined, message); 59 | } 60 | 61 | export function isNotVoid( 62 | input: T, 63 | message: string = expectedToBe("neither null nor undefined"), 64 | ): asserts input is Exclude { 65 | assert(input !== null && input !== undefined, message); 66 | } 67 | 68 | export function isExactly( 69 | input: Input, 70 | value: Output, 71 | message = expectedToBe(`exactly ${value}`), 72 | ): asserts input is SubType { 73 | assert((input as unknown) === (value as unknown), message); 74 | } 75 | 76 | export function isBoolean( 77 | input: unknown, 78 | message: string = expectedToBe("a boolean"), 79 | ): asserts input is boolean { 80 | assert(typeof input === "boolean", message); 81 | } 82 | 83 | export function isNumber( 84 | input: unknown, 85 | message: string = expectedToBe("a number"), 86 | ): asserts input is number { 87 | assert(typeof input === "number", message); 88 | } 89 | 90 | export function isString( 91 | input: unknown, 92 | message: string = expectedToBe("a string"), 93 | ): asserts input is string { 94 | assert(typeof input === "string", message); 95 | } 96 | 97 | export function isDate( 98 | input: unknown, 99 | message: string = expectedToBe("a Date"), 100 | ): asserts input is Date { 101 | assert(input instanceof Date, message); 102 | } 103 | 104 | export function isRecord( 105 | input: unknown, 106 | message: string = expectedToBe("a record"), 107 | ): asserts input is Record { 108 | assert(typeof input === "object", message); 109 | isNotNull(input, message); 110 | for (const key of Object.keys(input as Record)) { 111 | isString(key, message); 112 | } 113 | } 114 | 115 | export function isRecordWithKeys( 116 | input: unknown, 117 | keys: K[], 118 | message = expectedToBe(`a record with keys ${keys.join(", ")}`), 119 | ): asserts input is { 120 | readonly [Key in K]: unknown; 121 | } { 122 | isRecord(input, message); 123 | for (const key of keys) { 124 | isNotUndefined(input[key]); 125 | } 126 | } 127 | 128 | export function isArray( 129 | input: unknown, 130 | message: string = expectedToBe("an array"), 131 | ): asserts input is unknown[] { 132 | assert(Array.isArray(input), message); 133 | } 134 | 135 | export function isRecordOfType( 136 | input: unknown, 137 | assertT: Assert, 138 | message = expectedToBe("a record of given type"), 139 | itemMessage = expectedToBe("of given type"), 140 | ): asserts input is Record { 141 | isRecord(input, message); 142 | for (const item of Object.values(input)) { 143 | assertT(item, itemMessage); 144 | } 145 | } 146 | 147 | export function isArrayOfType( 148 | input: unknown, 149 | assertT: Assert, 150 | message = expectedToBe("an array of given type"), 151 | itemMessage = expectedToBe("of given type"), 152 | ): asserts input is T[] { 153 | isArray(input, message); 154 | for (const item of input) { 155 | assertT(item, itemMessage); 156 | } 157 | } 158 | 159 | export function isOptionOfType( 160 | input: Input | undefined, 161 | assertT: Assert, 162 | message = expectedToBe("option of given type"), 163 | ): asserts input is SubType { 164 | if (input === undefined) { 165 | return; 166 | } 167 | assertT(input, message); 168 | } 169 | 170 | export function isOneOf( 171 | input: Input, 172 | values: readonly Output[], 173 | message: string = expectedToBe(`one of ${values.join(", ")}`), 174 | ): asserts input is SubType { 175 | assert(values.includes(input as SubType), message); 176 | } 177 | 178 | export function isOneOfType( 179 | input: unknown, 180 | assertT: Assert[], 181 | message: string = expectedToBe(`one of type`), 182 | itemMessage?: string, 183 | ): asserts input is T { 184 | for (const assert of assertT) { 185 | try { 186 | (assert as WeakAssert)(input as T, itemMessage); 187 | return; 188 | } catch (_) {} 189 | } 190 | throw new TypeError(message); 191 | } 192 | 193 | export function isInstanceOf( 194 | input: unknown, 195 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 196 | constructor: new (...args: any[]) => T, 197 | message = expectedToBe("an instance of given constructor"), 198 | ): asserts input is T { 199 | assert(input instanceof constructor, message); 200 | } 201 | 202 | export function isPromise( 203 | input: unknown, 204 | message = expectedToBe("a promise"), 205 | ): asserts input is Promise { 206 | isInstanceOf(input, Promise, message); 207 | } 208 | 209 | export function check( 210 | assertT: Assert, 211 | ): Check { 212 | return (input: Input): input is SubType => { 213 | try { 214 | assertT(input); 215 | return true; 216 | } catch (_) { 217 | return false; 218 | } 219 | }; 220 | } 221 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "esnext" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */, 5 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, 6 | "lib": [ 7 | "es5", 8 | "esnext" 9 | ] /* Specify library files to be included in the compilation. */, 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true /* Report errors in .js files. */, 12 | // "jsx": "preserve" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */, 13 | "declaration": true /* Generates corresponding '.d.ts' file. */, 14 | // "declarationMap": true /* Generates a sourcemap for each corresponding '.d.ts' file. */, 15 | "sourceMap": true /* Generates corresponding '.map' file. */, 16 | // "outFile": "./", /* Concatenate and emit output to single file. */ 17 | "outDir": "build" /* Redirect output structure to the directory. */, 18 | // "rootDir": "src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */, 19 | "rootDirs": [ 20 | "src" 21 | ], 22 | // "composite": true /* Enable project compilation */, 23 | "incremental": true /* Enable incremental compilation */, 24 | // "tsBuildInfoFile": "./build/tsBuildInfo", /* Specify file to store incremental compilation information */ 25 | // "removeComments": true, /* Do not emit comments to output. */ 26 | // "noEmit": true, /* Do not emit outputs. */ 27 | "importHelpers": true /* Import emit helpers from 'tslib'. */, 28 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 29 | "isolatedModules": true /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */, 30 | /* Strict Type-Checking Options */ 31 | "strict": true /* Enable all strict type-checking options. */, 32 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 33 | // "strictNullChecks": true, /* Enable strict null checks. */ 34 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 35 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 36 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 37 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 38 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 39 | /* Additional Checks */ 40 | "noUnusedLocals": true /* Report errors on unused locals. */, 41 | "noUnusedParameters": true /* Report errors on unused parameters. */, 42 | "noImplicitReturns": true /* Report error when not all code paths in function return a value. */, 43 | "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */, 44 | /* Module Resolution Options */ 45 | "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, 46 | "baseUrl": "src" /* Base directory to resolve non-absolute module names. */, 47 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 48 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 49 | // "typeRoots": [], /* List of folders to include type definitions from. */ 50 | // "types": [], /* Type declaration files to be included in compilation. */ 51 | "allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */, 52 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 53 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 54 | /* Source Map Options */ 55 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 56 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 57 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 58 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 59 | /* Experimental Options */ 60 | "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */, 61 | "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */ 62 | }, 63 | "include": [ 64 | "src/**/*.ts", 65 | "src/**/*.d.ts" 66 | ], 67 | "exclude": [ 68 | "node_modules", 69 | "build", 70 | "typedoc" 71 | ], 72 | "compileOnSave": true 73 | } -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "entryPoints": [ 3 | "./src/index.ts" 4 | ], 5 | "exclude": "**/__tests__/**/*", 6 | "excludeExternals": true, 7 | "out": "doc", 8 | "readme": "none", 9 | "gitRevision": "master" 10 | } --------------------------------------------------------------------------------