├── .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 | }
--------------------------------------------------------------------------------