utilful 2.4.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,7 +13,6 @@ A collection of TypeScript utilities that I use across my projects.
13
13
  - [JSON](#json)
14
14
  - [Module](#module)
15
15
  - [Object](#object)
16
- - [Path](#path)
17
16
  - [Result](#result)
18
17
  - [String](#string)
19
18
 
@@ -150,13 +149,7 @@ Recursively assign default properties. Simplified version based on [unjs/defu](h
150
149
 
151
150
  Recursively assigns missing properties from defaults to the source object. The source object takes precedence over defaults.
152
151
 
153
- **Key Features:**
154
-
155
- - **Nullish handling**: `null` and `undefined` values in source are replaced with defaults
156
- - **Array concatenation**: Arrays are concatenated (source + defaults)
157
- - **Deep merging**: Nested objects are recursively merged
158
- - **Type safety**: Preserves TypeScript types
159
- - **Prototype pollution protection**: Ignores `__proto__` and `constructor` keys
152
+ The function replaces `null` and `undefined` values in the source with defaults, concatenates arrays (source + defaults), and recursively merges nested objects.
160
153
 
161
154
  ```ts
162
155
  type PlainObject = Record<PropertyKey, any>
@@ -358,191 +351,170 @@ Deeply applies a callback to every key-value pair in the given object, as well a
358
351
  declare function deepApply<T extends Record<any, any>>(data: T, callback: (item: T, key: keyof T, value: T[keyof T]) => void): void
359
352
  ```
360
353
 
361
- ### Path
362
-
363
- #### `withoutLeadingSlash`
364
-
365
- Removes the leading slash from the given path if it has one.
366
-
367
- ```ts
368
- declare function withoutLeadingSlash(path?: string): string
369
- ```
370
-
371
- #### `withLeadingSlash`
354
+ ### Result
372
355
 
373
- Adds a leading slash to the given path if it does not already have one.
356
+ The `Result` type represents either success (`Ok`) or failure (`Err`). It provides a type-safe way to handle errors without relying on exceptions.
374
357
 
375
358
  ```ts
376
- declare function withLeadingSlash(path?: string): string
359
+ type Result<T, E> = Ok<T, E> | Err<T, E>
377
360
  ```
378
361
 
379
- #### `withoutTrailingSlash`
362
+ Both `Ok` and `Err` carry phantom types for proper type inference in unions.
380
363
 
381
- Removes the trailing slash from the given path if it has one.
364
+ **Basic example:**
382
365
 
383
366
  ```ts
384
- declare function withoutTrailingSlash(path?: string): string
385
- ```
386
-
387
- #### `withTrailingSlash`
367
+ import { err, ok } from 'utilful'
388
368
 
389
- Adds a trailing slash to the given path if it does not already have one.
369
+ function divide(a: number, b: number) {
370
+ if (b === 0) {
371
+ return err('Division by zero')
372
+ }
373
+ return ok(a / b)
374
+ }
390
375
 
391
- ```ts
392
- declare function withTrailingSlash(path?: string): string
376
+ const result = divide(10, 2)
377
+ if (result.ok)
378
+ console.log('Result:', result.value)
379
+ else
380
+ console.error('Error:', result.error)
393
381
  ```
394
382
 
395
- #### `joinURL`
396
-
397
- Joins the given URL path segments, ensuring that there is only one slash between them.
383
+ **Fluent chaining:**
398
384
 
399
385
  ```ts
400
- declare function joinURL(...paths: (string | undefined)[]): string
401
- ```
402
-
403
- #### `withBase`
386
+ import { toResult } from 'utilful'
404
387
 
405
- Adds the base path to the input path, if it is not already present.
406
-
407
- ```ts
408
- declare function withBase(input?: string, base?: string): string
388
+ const name = toResult(() => JSON.parse(jsonString))
389
+ .map(data => data.user)
390
+ .map(user => user.name)
391
+ .unwrapOr('Anonymous')
409
392
  ```
410
393
 
411
- #### `withoutBase`
394
+ #### `ok`
412
395
 
413
- Removes the base path from the input path, if it is present.
396
+ Creates a successful result.
414
397
 
415
398
  ```ts
416
- declare function withoutBase(input?: string, base?: string): string
399
+ declare function ok<T, E = never>(value: T): Ok<T, E>
417
400
  ```
418
401
 
419
- #### `getPathname`
402
+ #### `err`
420
403
 
421
- Returns the pathname of the given path, which is the path without the query string.
404
+ Creates an error result.
422
405
 
423
406
  ```ts
424
- declare function getPathname(path?: string): string
407
+ declare function err<T = never, E extends string = string>(error: E): Err<T, E>
408
+ declare function err<T = never, E = unknown>(error: E): Err<T, E>
425
409
  ```
426
410
 
427
- #### `withQuery`
411
+ #### `isOk` / `isErr`
428
412
 
429
- Returns the URL with the given query parameters. If a query parameter is undefined, it is omitted.
413
+ Type guards for narrowing `Result` types.
430
414
 
431
415
  ```ts
432
- declare function withQuery(input: string, query?: QueryObject): string
416
+ declare function isOk<T, E>(result: Result<T, E>): result is Ok<T, E>
417
+ declare function isErr<T, E>(result: Result<T, E>): result is Err<T, E>
433
418
  ```
434
419
 
435
420
  **Example:**
436
421
 
437
422
  ```ts
438
- import { withQuery } from 'utilful'
439
-
440
- const url = withQuery('https://example.com', {
441
- foo: 'bar',
442
- // This key is omitted
443
- baz: undefined,
444
- // Object values are stringified
445
- baz: { qux: 'quux' }
446
- })
423
+ const result = toResult(() => JSON.parse(str))
424
+ if (isOk(result)) {
425
+ console.log(result.value) // TypeScript knows this is Ok
426
+ }
447
427
  ```
448
428
 
449
- ### Result
450
-
451
- The `Result` type that represents either success (`Ok`) or failure (`Err`). It helps to handle errors in a more explicit and type-safe way, without relying on exceptions.
429
+ #### `Result.map`
452
430
 
453
- A common use case for `Result` is error handling in functions that might fail. Here's an example of a function that divides two numbers and returns a `Result`:
431
+ Transforms the success value. No-op on `Err`.
454
432
 
455
433
  ```ts
456
- import { err, ok } from 'utilful'
457
-
458
- function divide(a: number, b: number) {
459
- if (b === 0) {
460
- return err('Division by zero')
461
- }
462
- return ok(a / b)
463
- }
464
-
465
- const result = divide(10, 2)
466
- if (result.ok)
467
- console.log('Result:', result.value)
468
- else
469
- console.error('Error:', result.error)
434
+ ok(2).map(x => x * 3) // Ok(6)
435
+ err('fail').map(x => x * 3) // Err('fail')
470
436
  ```
471
437
 
472
- #### `Result`
438
+ #### `Result.mapError`
473
439
 
474
- The `Result` type represents either success (`Ok`) or failure (`Err`).
440
+ Transforms the error value. No-op on `Ok`.
475
441
 
476
442
  ```ts
477
- type Result<T, E> = Ok<T> | Err<E>
443
+ err('fail').mapError(e => e.toUpperCase()) // Err('FAIL')
444
+ ok(42).mapError(e => e.toUpperCase()) // Ok(42)
478
445
  ```
479
446
 
480
- #### `Ok`
447
+ #### `Result.andThen`
481
448
 
482
- The `Ok` type wraps a successful value.
483
-
484
- **Example:**
449
+ Chains a function that returns a `Result`. Useful for composing fallible operations.
485
450
 
486
451
  ```ts
487
- const result = new Ok(42)
452
+ ok(2).andThen(x => x > 0 ? ok(x) : err('negative')) // Ok(2)
453
+ err('fail').andThen(x => ok(x * 2)) // Err('fail') - short-circuits
488
454
  ```
489
455
 
490
- #### `Err`
456
+ #### `Result.unwrap`
491
457
 
492
- The `Err` type wraps an error value.
493
-
494
- **Example:**
458
+ Extracts the value or throws an error.
495
459
 
496
460
  ```ts
497
- const result = new Err('Something went wrong')
461
+ ok(42).unwrap() // 42
462
+ err('fail').unwrap() // throws Error
463
+ err('fail').unwrap('custom message') // throws Error('custom message')
498
464
  ```
499
465
 
500
- #### `ok`
466
+ #### `Result.unwrapOr`
501
467
 
502
- Shorthand function to create an `Ok` result. Use it to wrap a successful value.
468
+ Extracts the value or returns a fallback.
503
469
 
504
470
  ```ts
505
- declare function ok<T>(value: T): Ok<T>
471
+ ok(42).unwrapOr(0) // 42
472
+ err('fail').unwrapOr(0) // 0
506
473
  ```
507
474
 
508
- #### `err`
475
+ #### `Result.match`
509
476
 
510
- Shorthand function to create an `Err` result. Use it to wrap an error value.
477
+ Pattern matches on the result.
511
478
 
512
479
  ```ts
513
- declare function err<E extends string = string>(err: E): Err<E>
514
- declare function err<E = unknown>(err: E): Err<E>
480
+ result.match({
481
+ ok: value => `Success: ${value}`,
482
+ err: error => `Error: ${error}`,
483
+ })
515
484
  ```
516
485
 
517
486
  #### `toResult`
518
487
 
519
- Wraps a function that might throw an error and returns a `Result` with the result of the function.
488
+ Wraps a function or promise that might throw and returns a `Result`.
520
489
 
521
490
  ```ts
522
491
  declare function toResult<T, E = unknown>(fn: () => T): Result<T, E>
523
492
  declare function toResult<T, E = unknown>(promise: Promise<T>): Promise<Result<T, E>>
524
493
  ```
525
494
 
526
- #### `unwrapResult`
527
-
528
- Unwraps a `Result`, `Ok`, or `Err` value and returns the value or error in an object. If the result is an `Ok`, the object contains the value and an `undefined` error. If the result is an `Err`, the object contains an `undefined` value and the error.
495
+ **Example:**
529
496
 
530
497
  ```ts
531
- declare function unwrapResult<T>(result: Ok<T>): { value: T, error: undefined }
532
- declare function unwrapResult<E>(result: Err<E>): { value: undefined, error: E }
533
- declare function unwrapResult<T, E>(result: Result<T, E>): { value: T, error: undefined } | { value: undefined, error: E }
498
+ // Synchronous
499
+ const result = toResult(() => JSON.parse('{"foo":"bar"}'))
500
+
501
+ // Asynchronous
502
+ const result = await toResult(fetch('https://api.example.com'))
534
503
  ```
535
504
 
536
- **Example:**
505
+ #### `unwrapResult`
506
+
507
+ Converts a `Result` to a plain object with `value` and `error` properties.
537
508
 
538
509
  ```ts
539
- const result = toResult(() => JSON.parse('{"foo":"bar"}'))
540
- const { value, error } = unwrapResult(result)
510
+ declare function unwrapResult<T, E>(result: Ok<T, E>): { value: T, error: undefined }
511
+ declare function unwrapResult<T, E>(result: Err<T, E>): { value: undefined, error: E }
512
+ declare function unwrapResult<T, E>(result: Result<T, E>): { value: T, error: undefined } | { value: undefined, error: E }
541
513
  ```
542
514
 
543
515
  #### `tryCatch`
544
516
 
545
- A simpler alternative to `toResult` + `unwrapResult`. It executes a function that might throw an error and directly returns the result in a `ResultData` format. Works with both synchronous functions and promises.
517
+ Combines `toResult` and `unwrapResult` into one step. Executes a function and returns `{ value, error }` directly.
546
518
 
547
519
  ```ts
548
520
  declare function tryCatch<T, E = unknown>(fn: () => T): { value: T, error: undefined } | { value: undefined, error: E }
@@ -552,13 +524,11 @@ declare function tryCatch<T, E = unknown>(promise: Promise<T>): Promise<{ value:
552
524
  **Example:**
553
525
 
554
526
  ```ts
555
- import { tryCatch } from 'utilful'
556
-
557
- // Synchronous usage
527
+ // Synchronous
558
528
  const { value, error } = tryCatch(() => JSON.parse('{"foo":"bar"}'))
559
529
 
560
- // Asynchronous usage
561
- const { value, error } = await tryCatch(fetch('https://api.example.com/data').then(r => r.json()))
530
+ // Asynchronous
531
+ const { value, error } = await tryCatch(fetch('https://api.example.com').then(r => r.json()))
562
532
  ```
563
533
 
564
534
  ### String
package/dist/index.d.mts CHANGED
@@ -6,7 +6,7 @@ import { cloneJSON, tryParseJSON } from "./json.mjs";
6
6
  import { interopDefault } from "./module.mjs";
7
7
  import { deepApply, memoize, objectEntries, objectKeys } from "./object.mjs";
8
8
  import { QueryObject, QueryValue, getPathname, joinURL, withBase, withLeadingSlash, withQuery, withTrailingSlash, withoutBase, withoutLeadingSlash, withoutTrailingSlash } from "./path.mjs";
9
- import { Err, ErrData, Ok, OkData, Result, ResultData, err, ok, toResult, tryCatch, unwrapResult } from "./result.mjs";
9
+ import { Err, ErrData, Ok, OkData, Result, ResultData, err, isErr, isOk, ok, toResult, tryCatch, unwrapResult } from "./result.mjs";
10
10
  import { generateRandomId, template } from "./string.mjs";
11
11
  import { AutocompletableString, BrandedType, LooseAutocomplete, UnifyIntersection } from "./types.mjs";
12
- export { AutocompletableString, BrandedType, CSVCreateOptions, CSVRow, DefuFn, DefuMerger, Emitter, Err, ErrData, EventHandlerList, EventHandlerMap, EventType, Handler, LooseAutocomplete, MaybeArray, Ok, OkData, QueryObject, QueryValue, Result, ResultData, UnifyIntersection, WildCardEventHandlerList, WildcardHandler, cloneJSON, createCSV, createCSVAsync, createCSVStream, createDefu, createEmitter, deepApply, defu, err, escapeCSVValue, generateRandomId, getPathname, interopDefault, joinURL, memoize, objectEntries, objectKeys, ok, parseCSV, parseCSVFromLines, parseCSVStream, template, toArray, toResult, tryCatch, tryParseJSON, unwrapResult, withBase, withLeadingSlash, withQuery, withTrailingSlash, withoutBase, withoutLeadingSlash, withoutTrailingSlash };
12
+ export { AutocompletableString, BrandedType, CSVCreateOptions, CSVRow, DefuFn, DefuMerger, Emitter, Err, ErrData, EventHandlerList, EventHandlerMap, EventType, Handler, LooseAutocomplete, MaybeArray, Ok, OkData, QueryObject, QueryValue, Result, ResultData, UnifyIntersection, WildCardEventHandlerList, WildcardHandler, cloneJSON, createCSV, createCSVAsync, createCSVStream, createDefu, createEmitter, deepApply, defu, err, escapeCSVValue, generateRandomId, getPathname, interopDefault, isErr, isOk, joinURL, memoize, objectEntries, objectKeys, ok, parseCSV, parseCSVFromLines, parseCSVStream, template, toArray, toResult, tryCatch, tryParseJSON, unwrapResult, withBase, withLeadingSlash, withQuery, withTrailingSlash, withoutBase, withoutLeadingSlash, withoutTrailingSlash };
package/dist/index.mjs CHANGED
@@ -6,7 +6,7 @@ import { cloneJSON, tryParseJSON } from "./json.mjs";
6
6
  import { interopDefault } from "./module.mjs";
7
7
  import { deepApply, memoize, objectEntries, objectKeys } from "./object.mjs";
8
8
  import { getPathname, joinURL, withBase, withLeadingSlash, withQuery, withTrailingSlash, withoutBase, withoutLeadingSlash, withoutTrailingSlash } from "./path.mjs";
9
- import { Err, Ok, err, ok, toResult, tryCatch, unwrapResult } from "./result.mjs";
9
+ import { Err, Ok, err, isErr, isOk, ok, toResult, tryCatch, unwrapResult } from "./result.mjs";
10
10
  import { generateRandomId, template } from "./string.mjs";
11
11
 
12
- export { Err, Ok, cloneJSON, createCSV, createCSVAsync, createCSVStream, createDefu, createEmitter, deepApply, defu, err, escapeCSVValue, generateRandomId, getPathname, interopDefault, joinURL, memoize, objectEntries, objectKeys, ok, parseCSV, parseCSVFromLines, parseCSVStream, template, toArray, toResult, tryCatch, tryParseJSON, unwrapResult, withBase, withLeadingSlash, withQuery, withTrailingSlash, withoutBase, withoutLeadingSlash, withoutTrailingSlash };
12
+ export { Err, Ok, cloneJSON, createCSV, createCSVAsync, createCSVStream, createDefu, createEmitter, deepApply, defu, err, escapeCSVValue, generateRandomId, getPathname, interopDefault, isErr, isOk, joinURL, memoize, objectEntries, objectKeys, ok, parseCSV, parseCSVFromLines, parseCSVStream, template, toArray, toResult, tryCatch, tryParseJSON, unwrapResult, withBase, withLeadingSlash, withQuery, withTrailingSlash, withoutBase, withoutLeadingSlash, withoutTrailingSlash };
package/dist/path.d.mts CHANGED
@@ -10,11 +10,13 @@ declare function withoutLeadingSlash(path?: string): string;
10
10
  */
11
11
  declare function withLeadingSlash(path?: string): string;
12
12
  /**
13
- * Removes the trailing slash from the given path if it has one.
13
+ * Removes the trailing slash from the given path if it has one,
14
+ * preserving query strings and hash fragments.
14
15
  */
15
16
  declare function withoutTrailingSlash(path?: string): string;
16
17
  /**
17
- * Adds a trailing slash to the given path if it does not already have one
18
+ * Adds a trailing slash to the given path if it does not already have one,
19
+ * preserving query strings and hash fragments.
18
20
  */
19
21
  declare function withTrailingSlash(path?: string): string;
20
22
  /**
@@ -30,7 +32,7 @@ declare function withBase(input?: string, base?: string): string;
30
32
  */
31
33
  declare function withoutBase(input?: string, base?: string): string;
32
34
  /**
33
- * Returns the pathname of the given path, which is the path without the query string.
35
+ * Returns the pathname of the given path, which is the path without the query string or hash.
34
36
  */
35
37
  declare function getPathname(path?: string): string;
36
38
  /**
package/dist/path.mjs CHANGED
@@ -3,7 +3,7 @@
3
3
  * Removes the leading slash from the given path if it has one.
4
4
  */
5
5
  function withoutLeadingSlash(path) {
6
- if (!path || path === "/") return "/";
6
+ if (!path) return "";
7
7
  return path[0] === "/" ? path.slice(1) : path;
8
8
  }
9
9
  /**
@@ -14,31 +14,48 @@ function withLeadingSlash(path) {
14
14
  return path[0] === "/" ? path : `/${path}`;
15
15
  }
16
16
  /**
17
- * Removes the trailing slash from the given path if it has one.
17
+ * Removes the trailing slash from the given path if it has one,
18
+ * preserving query strings and hash fragments.
18
19
  */
19
20
  function withoutTrailingSlash(path) {
20
21
  if (!path || path === "/") return "/";
21
- return path[path.length - 1] === "/" ? path.slice(0, -1) : path;
22
+ let pathEnd = path.length;
23
+ const queryIndex = path.indexOf("?");
24
+ const hashIndex = path.indexOf("#");
25
+ if (queryIndex !== -1) pathEnd = queryIndex;
26
+ if (hashIndex !== -1 && hashIndex < pathEnd) pathEnd = hashIndex;
27
+ if (pathEnd > 0 && path[pathEnd - 1] === "/") {
28
+ if (pathEnd === 1) return path;
29
+ return path.slice(0, pathEnd - 1) + path.slice(pathEnd);
30
+ }
31
+ return path;
22
32
  }
23
33
  /**
24
- * Adds a trailing slash to the given path if it does not already have one
34
+ * Adds a trailing slash to the given path if it does not already have one,
35
+ * preserving query strings and hash fragments.
25
36
  */
26
37
  function withTrailingSlash(path) {
27
38
  if (!path || path === "/") return "/";
28
- return path[path.length - 1] === "/" ? path : `${path}/`;
39
+ let pathEnd = path.length;
40
+ const queryIndex = path.indexOf("?");
41
+ const hashIndex = path.indexOf("#");
42
+ if (queryIndex !== -1) pathEnd = queryIndex;
43
+ if (hashIndex !== -1 && hashIndex < pathEnd) pathEnd = hashIndex;
44
+ if (pathEnd > 0 && path[pathEnd - 1] === "/") return path;
45
+ return `${path.slice(0, pathEnd)}/${path.slice(pathEnd)}`;
29
46
  }
30
47
  /**
31
48
  * Joins the given URL path segments, ensuring that there is only one slash between them.
32
49
  */
33
50
  function joinURL(...paths) {
34
51
  let result = "";
35
- for (let i = 0; i < paths.length; i++) {
36
- const path = paths[i];
37
- if (!result || result === "/") {
38
- result = path || "/";
52
+ for (const path of paths) {
53
+ if (!path) continue;
54
+ if (!result) {
55
+ result = path;
39
56
  continue;
40
57
  }
41
- if (!path || path === "/") continue;
58
+ if (path === "/") continue;
42
59
  const resultHasTrailing = result[result.length - 1] === "/";
43
60
  const pathHasLeading = path[0] === "/";
44
61
  if (resultHasTrailing && pathHasLeading) result += path.slice(1);
@@ -53,7 +70,7 @@ function joinURL(...paths) {
53
70
  function withBase(input = "", base = "") {
54
71
  if (!base || base === "/") return input;
55
72
  const _base = withoutTrailingSlash(base);
56
- if (input.startsWith(_base)) return input;
73
+ if (input.startsWith(_base) && (input.length === _base.length || input[_base.length] === "/" || input[_base.length] === "?")) return input;
57
74
  return joinURL(_base, input);
58
75
  }
59
76
  /**
@@ -62,15 +79,21 @@ function withBase(input = "", base = "") {
62
79
  function withoutBase(input = "", base = "") {
63
80
  if (!base || base === "/") return input;
64
81
  const _base = withoutTrailingSlash(base);
65
- if (!input.startsWith(_base)) return input;
82
+ if (!input.startsWith(_base) || input.length !== _base.length && input[_base.length] !== "/" && input[_base.length] !== "?") return input;
66
83
  const trimmed = input.slice(_base.length);
67
84
  return trimmed[0] === "/" ? trimmed : `/${trimmed}`;
68
85
  }
69
86
  /**
70
- * Returns the pathname of the given path, which is the path without the query string.
87
+ * Returns the pathname of the given path, which is the path without the query string or hash.
71
88
  */
72
89
  function getPathname(path = "/") {
73
- return path.startsWith("/") ? path.split("?")[0] : new URL(path, "http://localhost").pathname;
90
+ if (!path.startsWith("/")) return new URL(path, "http://localhost").pathname;
91
+ let pathEnd = path.length;
92
+ const queryIndex = path.indexOf("?");
93
+ const hashIndex = path.indexOf("#");
94
+ if (queryIndex !== -1) pathEnd = queryIndex;
95
+ if (hashIndex !== -1 && hashIndex < pathEnd) pathEnd = hashIndex;
96
+ return path.slice(0, pathEnd) || "/";
74
97
  }
75
98
  /**
76
99
  * Returns the URL with the given query parameters. If a query parameter is undefined, it is omitted.
@@ -80,7 +103,7 @@ function withQuery(input, query) {
80
103
  const searchIndex = input.indexOf("?");
81
104
  const hasExistingParams = searchIndex !== -1;
82
105
  const base = hasExistingParams ? input.slice(0, searchIndex) : input;
83
- const searchParams = hasExistingParams ? new URLSearchParams(input.slice(searchIndex + 1)) : new URLSearchParams();
106
+ const searchParams = new URLSearchParams(hasExistingParams ? input.slice(searchIndex + 1) : void 0);
84
107
  for (const [key, value] of Object.entries(query)) {
85
108
  if (value === void 0) {
86
109
  searchParams.delete(key);
package/dist/result.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region src/result.d.ts
2
- type Result<T, E> = Ok<T> | Err<E>;
2
+ type Result<T, E> = Ok<T, E> | Err<T, E>;
3
3
  interface OkData<T> {
4
4
  value: T;
5
5
  error: undefined;
@@ -9,25 +9,67 @@ interface ErrData<E> {
9
9
  error: E;
10
10
  }
11
11
  type ResultData<T, E> = OkData<T> | ErrData<E>;
12
- declare class Ok<T> {
12
+ /**
13
+ * Successful result variant.
14
+ * @template T Success value type.
15
+ * @template E Error type (phantom - for type unification).
16
+ */
17
+ declare class Ok<T, E = never> {
13
18
  readonly value: T;
14
19
  readonly ok: true;
15
20
  constructor(value: T);
21
+ /** Transforms the success value. */
22
+ map<U>(fn: (value: T) => U): Ok<U, E>;
23
+ /** No-op on Ok, returns self with new error type. */
24
+ mapError<E2>(_fn: (error: E) => E2): Ok<T, E2>;
25
+ /** Chains a Result-returning function. */
26
+ andThen<U, E2>(fn: (value: T) => Result<U, E2>): Result<U, E | E2>;
27
+ /** Extracts the value. */
28
+ unwrap(_message?: string): T;
29
+ /** Returns the value, ignoring the fallback. */
30
+ unwrapOr<U>(_fallback: U): T;
31
+ /** Pattern matches on the Result. */
32
+ match<R>(handlers: {
33
+ ok: (value: T) => R;
34
+ err: (error: E) => R;
35
+ }): R;
16
36
  }
17
- declare class Err<E> {
37
+ /**
38
+ * Error result variant.
39
+ * @template T Success type (phantom - for type unification).
40
+ * @template E Error value type.
41
+ */
42
+ declare class Err<T, E> {
18
43
  readonly error: E;
19
44
  readonly ok: false;
20
45
  constructor(error: E);
46
+ /** No-op on Err, returns self with new value type. */
47
+ map<U>(_fn: (value: T) => U): Err<U, E>;
48
+ /** Transforms the error value. */
49
+ mapError<E2>(fn: (error: E) => E2): Err<T, E2>;
50
+ /** No-op on Err, returns self with widened error type. */
51
+ andThen<U, E2>(_fn: (value: T) => Result<U, E2>): Err<U, E | E2>;
52
+ /** Throws an error with the given message. */
53
+ unwrap(message?: string): never;
54
+ /** Returns the fallback value. */
55
+ unwrapOr<U>(fallback: U): T | U;
56
+ /** Pattern matches on the Result. */
57
+ match<R>(handlers: {
58
+ ok: (value: T) => R;
59
+ err: (error: E) => R;
60
+ }): R;
21
61
  }
22
- declare function ok<T>(value: T): Ok<T>;
23
- declare function err<E extends string = string>(error: E): Err<E>;
24
- declare function err<E = unknown>(error: E): Err<E>;
62
+ declare function ok<T, E = never>(value: T): Ok<T, E>;
63
+ declare function err<T = never, E extends string = string>(error: E): Err<T, E>;
64
+ declare function err<T = never, E = unknown>(error: E): Err<T, E>;
65
+ declare function isOk<T, E>(result: Result<T, E>): result is Ok<T, E>;
66
+ declare function isErr<T, E>(result: Result<T, E>): result is Err<T, E>;
25
67
  declare function toResult<T, E = unknown>(fn: () => T): Result<T, E>;
26
68
  declare function toResult<T, E = unknown>(promise: Promise<T>): Promise<Result<T, E>>;
27
- declare function unwrapResult<T>(result: Ok<T>): OkData<T>;
28
- declare function unwrapResult<E>(result: Err<E>): ErrData<E>;
69
+ declare function unwrapResult<T, E>(result: Ok<T, E>): OkData<T>;
70
+ declare function unwrapResult<T, E>(result: Err<T, E>): ErrData<E>;
29
71
  declare function unwrapResult<T, E>(result: Result<T, E>): ResultData<T, E>;
30
72
  declare function tryCatch<T, E = unknown>(fn: () => T): ResultData<T, E>;
31
73
  declare function tryCatch<T, E = unknown>(promise: Promise<T>): Promise<ResultData<T, E>>;
32
74
  //#endregion
33
- export { Err, ErrData, Ok, OkData, Result, ResultData, err, ok, toResult, tryCatch, unwrapResult };
75
+ export { Err, ErrData, Ok, OkData, Result, ResultData, err, isErr, isOk, ok, toResult, tryCatch, unwrapResult };
package/dist/result.mjs CHANGED
@@ -1,17 +1,75 @@
1
1
  //#region src/result.ts
2
- var Ok = class {
2
+ /**
3
+ * Successful result variant.
4
+ * @template T Success value type.
5
+ * @template E Error type (phantom - for type unification).
6
+ */
7
+ var Ok = class Ok {
3
8
  value;
4
9
  ok = true;
5
10
  constructor(value) {
6
11
  this.value = value;
7
12
  }
13
+ /** Transforms the success value. */
14
+ map(fn) {
15
+ return new Ok(fn(this.value));
16
+ }
17
+ /** No-op on Ok, returns self with new error type. */
18
+ mapError(_fn) {
19
+ return this;
20
+ }
21
+ /** Chains a Result-returning function. */
22
+ andThen(fn) {
23
+ return fn(this.value);
24
+ }
25
+ /** Extracts the value. */
26
+ unwrap(_message) {
27
+ return this.value;
28
+ }
29
+ /** Returns the value, ignoring the fallback. */
30
+ unwrapOr(_fallback) {
31
+ return this.value;
32
+ }
33
+ /** Pattern matches on the Result. */
34
+ match(handlers) {
35
+ return handlers.ok(this.value);
36
+ }
8
37
  };
9
- var Err = class {
38
+ /**
39
+ * Error result variant.
40
+ * @template T Success type (phantom - for type unification).
41
+ * @template E Error value type.
42
+ */
43
+ var Err = class Err {
10
44
  error;
11
45
  ok = false;
12
46
  constructor(error) {
13
47
  this.error = error;
14
48
  }
49
+ /** No-op on Err, returns self with new value type. */
50
+ map(_fn) {
51
+ return this;
52
+ }
53
+ /** Transforms the error value. */
54
+ mapError(fn) {
55
+ return new Err(fn(this.error));
56
+ }
57
+ /** No-op on Err, returns self with widened error type. */
58
+ andThen(_fn) {
59
+ return this;
60
+ }
61
+ /** Throws an error with the given message. */
62
+ unwrap(message) {
63
+ throw new Error(message ?? `Unwrap called on Err: ${String(this.error)}`);
64
+ }
65
+ /** Returns the fallback value. */
66
+ unwrapOr(fallback) {
67
+ return fallback;
68
+ }
69
+ /** Pattern matches on the Result. */
70
+ match(handlers) {
71
+ return handlers.err(this.error);
72
+ }
15
73
  };
16
74
  function ok(value) {
17
75
  return new Ok(value);
@@ -19,6 +77,12 @@ function ok(value) {
19
77
  function err(error) {
20
78
  return new Err(error);
21
79
  }
80
+ function isOk(result) {
81
+ return result.ok === true;
82
+ }
83
+ function isErr(result) {
84
+ return result.ok === false;
85
+ }
22
86
  function toResult(fnOrPromise) {
23
87
  if (fnOrPromise instanceof Promise) return fnOrPromise.then(ok).catch(err);
24
88
  try {
@@ -42,4 +106,4 @@ function tryCatch(fnOrPromise) {
42
106
  }
43
107
 
44
108
  //#endregion
45
- export { Err, Ok, err, ok, toResult, tryCatch, unwrapResult };
109
+ export { Err, Ok, err, isErr, isOk, ok, toResult, tryCatch, unwrapResult };
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "utilful",
3
3
  "type": "module",
4
- "version": "2.4.0",
5
- "packageManager": "pnpm@10.21.0",
4
+ "version": "2.5.0",
5
+ "packageManager": "pnpm@10.28.0",
6
6
  "description": "A collection of TypeScript utilities",
7
7
  "author": "Johann Schopplich <hello@johannschopplich.com>",
8
8
  "license": "MIT",
@@ -78,13 +78,13 @@
78
78
  "release": "bumpp"
79
79
  },
80
80
  "devDependencies": {
81
- "@antfu/eslint-config": "^6.2.0",
82
- "@types/node": "^24.10.0",
83
- "bumpp": "^10.3.1",
84
- "eslint": "^9.39.1",
85
- "tsdown": "^0.16.1",
81
+ "@antfu/eslint-config": "^7.0.1",
82
+ "@types/node": "^25.0.9",
83
+ "bumpp": "^10.4.0",
84
+ "eslint": "^9.39.2",
85
+ "tsdown": "^0.19.0",
86
86
  "typescript": "^5.9.3",
87
- "vitest": "^4.0.8"
87
+ "vitest": "^4.0.17"
88
88
  },
89
89
  "pnpm": {
90
90
  "onlyBuiltDependencies": [