@goodbyenjn/utils 1.3.1 → 26.1.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
@@ -3,17 +3,17 @@
3
3
  [![npm version](https://badge.fury.io/js/@goodbyenjn%2Futils.svg)](https://badge.fury.io/js/@goodbyenjn%2Futils)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
 
6
- GoodbyeNJN's utility library for TypeScript and JavaScript, providing a collection of common utility functions and types.
6
+ A modern TypeScript/JavaScript utility library providing a comprehensive collection of type-safe utility functions, functional error handling with the Result pattern, and filesystem operations.
7
7
 
8
8
  ## Features
9
9
 
10
- - 🚀 **Modern**: Built with TypeScript, targeting ES modules
11
- - 🔒 **Type-safe**: Full TypeScript support with comprehensive type definitions
12
- - 📦 **Modular**: Import only what you need with tree-shakable exports
13
- - 🛡️ **Result Type**: Functional error handling with Result pattern
14
- - 📁 **File System**: Safe file system operations with Result types
15
- - 🧰 **Common Utils**: String, math, promise, process, and other utility functions
16
- - 📊 **Remeda Extensions**: Extended utilities built on top of Remeda
10
+ - 🚀 **Modern**: Built with TypeScript, targeting ES modules and modern JavaScript
11
+ - 🔒 **Type-safe**: Full TypeScript support with comprehensive type definitions and type inference
12
+ - 📦 **Modular**: Import only what you need with tree-shakable exports and multiple entry points
13
+ - 🛡️ **Result Pattern**: Functional error handling without exceptions, based on Rust-style Result types
14
+ - 📁 **Safe File System**: Type-safe file system operations with Result-based error handling
15
+ - 🧰 **Common Utilities**: String manipulation, math operations, promise utilities, shell commands, and error handling
16
+ - 📊 **Remeda Extensions**: Extended utilities built on top of [Remeda](https://remedajs.com/)
17
17
 
18
18
  ## Installation
19
19
 
@@ -27,100 +27,283 @@ yarn add @goodbyenjn/utils
27
27
 
28
28
  ## Usage
29
29
 
30
- ### Common Utilities
30
+ ### Quick Start
31
31
 
32
32
  ```typescript
33
- import { sleep, template, unindent, debounce, $ } from "@goodbyenjn/utils";
33
+ // Import what you need from the main module
34
+ import { sleep, template, $ } from "@goodbyenjn/utils";
35
+ import { safeReadFile } from "@goodbyenjn/utils/fs";
36
+ import { ok, Result } from "@goodbyenjn/utils/result";
37
+ ```
34
38
 
35
- // Promise utilities
36
- await sleep(1000); // Sleep for 1 second
39
+ ### Common Utilities
37
40
 
38
- // String utilities
39
- const text = template("Hello, {name}!", { name: "World" });
40
- console.log(text); // "Hello, World!"
41
+ #### String Operations
41
42
 
43
+ ```typescript
44
+ import { template, unindent, addPrefix, removeSuffix, join, split } from "@goodbyenjn/utils";
45
+
46
+ // String templating
47
+ const greeting = template("Hello, {name}! You are {age} years old.", {
48
+ name: "Alice",
49
+ age: 30,
50
+ });
51
+ console.log(greeting); // "Hello, Alice! You are 30 years old."
52
+
53
+ // Remove common indentation from template strings
42
54
  const code = unindent`
43
55
  function example() {
44
56
  return 'formatted';
45
57
  }
46
58
  `;
59
+ console.log(code); // properly dedented code
60
+
61
+ // Prefix and suffix operations
62
+ const withPrefix = addPrefix("@", "myfile"); // "@myfile"
63
+ const cleaned = removeSuffix(".js", "script.js"); // "script"
64
+
65
+ // String joining and splitting
66
+ const path = join("/", "home", "user", "docs"); // "/home/user/docs"
67
+ const parts = split("-", "hello-world-js"); // ["hello", "world", "js"]
68
+ ```
69
+
70
+ #### Promise Utilities
71
+
72
+ ```typescript
73
+ import { sleep, createLock, createSingleton, createPromiseWithResolvers } from "@goodbyenjn/utils";
74
+
75
+ // Sleep/delay execution
76
+ await sleep(1000); // Wait 1 second
77
+
78
+ // Create a reusable mutex lock
79
+ const lock = createLock();
80
+ await lock.acquire();
81
+ try {
82
+ // Critical section
83
+ console.log("Executing exclusively");
84
+ } finally {
85
+ lock.release();
86
+ }
87
+
88
+ // Singleton pattern factory
89
+ const getDatabase = createSingleton(() => {
90
+ console.log("Initializing database...");
91
+ return new Database();
92
+ });
93
+ const db1 = await getDatabase(); // Initializes once
94
+ const db2 = await getDatabase(); // Returns same instance
95
+
96
+ // Promise with external resolvers
97
+ const { promise, resolve, reject } = createPromiseWithResolvers<string>();
98
+ setTimeout(() => resolve("done!"), 1000);
99
+ const result = await promise;
100
+ ```
101
+
102
+ #### Shell Command Execution
103
+
104
+ ```typescript
105
+ import { $ } from "@goodbyenjn/utils";
47
106
 
48
- // Process utilities - Execute shell commands safely
107
+ // Execute shell commands safely using template literals
49
108
  const result = await $`ls -la`;
50
109
  if (result.isOk()) {
51
- console.log("stdout:", result.value.stdout);
52
- console.log("stderr:", result.value.stderr);
53
- } else {
54
- console.error("Command failed:", result.error);
110
+ console.log("Command succeeded:");
111
+ console.log("stdout:", result.unwrap().stdout);
112
+ console.log("stderr:", result.unwrap().stderr);
113
+ } else if (result.isErr()) {
114
+ console.error("Command failed:", result.unwrapErr().message);
115
+ }
116
+
117
+ // Complex command with options
118
+ const { $ } = await import("@goodbyenjn/utils");
119
+ const deployResult = await $`docker build -t myapp .`;
120
+ if (deployResult.isOk()) {
121
+ const { stdout, stderr } = deployResult.unwrap();
122
+ console.log("Build output:", stdout);
55
123
  }
56
124
 
57
- // Throttling and debouncing
58
- const debouncedFn = debounce(() => console.log("Called!"), 300);
125
+ // Using quoteShellArg for safe argument escaping
126
+ import { quoteShellArg } from "@goodbyenjn/utils";
127
+ const userInput = "'; rm -rf /;";
128
+ const safeArg = quoteShellArg(userInput); // Properly escaped for shell
59
129
  ```
60
130
 
61
- ### File System Operations
131
+ #### Math Utilities
62
132
 
63
133
  ```typescript
64
- import { safeReadFile, safeWriteFile, safeExists } from "@goodbyenjn/utils/fs";
134
+ import { linear, scale } from "@goodbyenjn/utils";
135
+
136
+ // Linear interpolation between values
137
+ const mid = linear(0.5, [0, 100]); // 50
138
+
139
+ // Scale a value from one range to another
140
+ const scaledValue = scale(75, [0, 100], [0, 1]); // 0.75
141
+ const percentage = scale(200, [0, 255], [0, 100]); // 78.43...
142
+ ```
143
+
144
+ #### Error Handling
145
+
146
+ ```typescript
147
+ import { normalizeError, getErrorMessage } from "@goodbyenjn/utils";
148
+
149
+ // Normalize any value to an Error object
150
+ const error = normalizeError("Something went wrong");
151
+ const typeError = normalizeError({ code: 500 });
152
+
153
+ // Safely extract error message
154
+ const message1 = getErrorMessage(new Error("Oops"));
155
+ const message2 = getErrorMessage("Plain string error");
156
+ const message3 = getErrorMessage(null); // "Unknown error"
157
+ ```
158
+
159
+ #### Throttling and Debouncing
160
+
161
+ ```typescript
162
+ import { debounce, throttle } from "@goodbyenjn/utils";
163
+
164
+ // Debounce - wait for inactivity before executing
165
+ const debouncedSearch = debounce((query: string) => {
166
+ console.log("Searching for:", query);
167
+ }, 300);
168
+
169
+ // Call multiple times, executes only after 300ms of inactivity
170
+ input.addEventListener("input", e => {
171
+ debouncedSearch((e.target as HTMLInputElement).value);
172
+ });
173
+
174
+ // Throttle - execute at most once per interval
175
+ const throttledScroll = throttle(() => {
176
+ console.log("Scroll position:", window.scrollY);
177
+ }, 100);
65
178
 
66
- // Safe file operations that return Result types
67
- const fileResult = await safeReadFile("config.json", "utf8");
68
- if (fileResult.isOk()) {
69
- console.log("File content:", fileResult.value);
179
+ window.addEventListener("scroll", throttledScroll);
180
+ ```
181
+
182
+ ### File System Operations
183
+
184
+ ```typescript
185
+ import {
186
+ safeReadFile,
187
+ safeWriteFile,
188
+ safeExists,
189
+ safeReadJson,
190
+ safeWriteJson,
191
+ safeMkdir,
192
+ safeRm,
193
+ safeReadFileByLine,
194
+ } from "@goodbyenjn/utils/fs";
195
+
196
+ // Read text file safely
197
+ const textResult = await safeReadFile("config.txt", "utf8");
198
+ if (textResult.isOk()) {
199
+ console.log("File content:", textResult.unwrap());
70
200
  } else {
71
- console.error("Failed to read file:", fileResult.error);
201
+ console.error("Failed to read file:", textResult.unwrapErr().message);
202
+ }
203
+
204
+ // Read and parse JSON safely
205
+ const jsonResult = await safeReadJson("package.json");
206
+ if (jsonResult.isOk()) {
207
+ const pkg = jsonResult.unwrap();
208
+ console.log("Package name:", pkg.name);
209
+ }
210
+
211
+ // Write JSON file
212
+ const writeResult = await safeWriteJson("data.json", { users: [] }, { pretty: true });
213
+ if (writeResult.isErr()) {
214
+ console.error("Write failed:", writeResult.unwrapErr());
72
215
  }
73
216
 
74
217
  // Check if file exists
75
218
  const existsResult = await safeExists("path/to/file.txt");
76
- if (existsResult.isOk() && existsResult.value) {
219
+ if (existsResult.isOk() && existsResult.unwrap()) {
77
220
  console.log("File exists!");
78
221
  }
222
+
223
+ // Create directories (recursive)
224
+ const mkResult = await safeMkdir("src/components/ui", { recursive: true });
225
+
226
+ // Delete files or directories
227
+ const rmResult = await safeRm("build", { recursive: true, force: true });
228
+
229
+ // Read file line by line
230
+ const lineResult = await safeReadFileByLine("large-file.log");
231
+ if (lineResult.isOk()) {
232
+ for await (const line of lineResult.unwrap()) {
233
+ console.log(line);
234
+ }
235
+ }
79
236
  ```
80
237
 
81
- ### Result Pattern
238
+ ### Glob Patterns
82
239
 
83
240
  ```typescript
84
- import { Ok, Err, Result } from "@goodbyenjn/utils/result";
241
+ import { glob, globSync, convertPathToPattern } from "@goodbyenjn/utils/fs";
85
242
 
86
- // Create results
87
- const success = Ok(42);
88
- const failure = Err("Something went wrong");
243
+ // Async glob pattern matching
244
+ const files = await glob("src/**/*.{ts,tsx}", { cwd: "." });
245
+ console.log("Found files:", files);
89
246
 
90
- // Handle results
91
- if (success.isOk()) {
92
- console.log("Value:", success.value); // 42
93
- }
247
+ // Synchronous version
248
+ const syncFiles = globSync("**/*.test.ts", { cwd: "tests" });
94
249
 
95
- // Convert throwing functions to Result
96
- const safeParseInt = Result.fromThrowable(parseInt);
97
- const result = safeParseInt("not-a-number");
98
- if (result.isErr()) {
99
- console.log("Parse failed:", result.error);
100
- }
250
+ // Convert file path to glob pattern
251
+ const pattern = convertPathToPattern("/home/user/project");
101
252
  ```
102
253
 
103
- ### Extended Remeda Utilities
254
+ ### Result Pattern - Functional Error Handling
104
255
 
105
256
  ```typescript
106
- import { hasOwnProperty, isPromiseLike } from "@goodbyenjn/utils/remeda";
257
+ import { err, ok, Result, safeTry } from "@goodbyenjn/utils/result";
107
258
 
108
- // Type-safe property checking
109
- const obj = { name: "John", age: 30 };
110
- if (hasOwnProperty(obj, "name")) {
111
- console.log(obj.name); // TypeScript knows this is safe
259
+ // Create results explicitly
260
+ const success = ok(42);
261
+ const failure = err("Something went wrong");
262
+
263
+ // Handle results with chainable methods
264
+ const doubled = success
265
+ .map(value => value * 2)
266
+ .mapErr(err => `Error: ${err}`)
267
+ .unwrapOr(0); // 84
268
+
269
+ // Transform error type
270
+ const result: Result<string, Error> = ok("value");
271
+ const transformed = result.mapErr(() => new Error("Custom error"));
272
+
273
+ // Convert throwing functions to Result
274
+ async function fetchUser(id: string) {
275
+ // If the function throws, it's caught and wrapped in Err
276
+ const user = await Result.fromCallable(() => JSON.parse(userJson));
277
+
278
+ return user.map(u => u.name).mapErr(err => new Error(`Failed to parse user: ${err.message}`));
112
279
  }
113
280
 
114
- // Promise detection
115
- if (isPromiseLike(someValue)) {
116
- const result = await someValue;
281
+ // Combine multiple Results
282
+ const results = [ok(1), ok(2), err("oops"), ok(4)];
283
+ const combined = Result.all(...results); // Err("oops")
284
+
285
+ // Safe try-catch alternative
286
+ const safeTryExample = await safeTry(async () => {
287
+ return await fetch("/api/data").then(r => r.json());
288
+ });
289
+
290
+ if (safeTryExample.isOk()) {
291
+ console.log("Data:", safeTryExample.unwrap());
292
+ } else {
293
+ console.error("Failed:", safeTryExample.unwrapErr());
117
294
  }
118
295
  ```
119
296
 
120
297
  ### Type Utilities
121
298
 
122
299
  ```typescript
123
- import type { Nullable, YieldType } from "@goodbyenjn/utils/types";
300
+ import type {
301
+ Nullable,
302
+ Optional,
303
+ YieldType,
304
+ OmitByKey,
305
+ SetNullable,
306
+ } from "@goodbyenjn/utils/types";
124
307
 
125
308
  // Nullable type for values that can be null or undefined
126
309
  type User = {
@@ -129,6 +312,11 @@ type User = {
129
312
  email: Nullable<string>; // string | null | undefined
130
313
  };
131
314
 
315
+ // Optional type (undefined but not null)
316
+ type Profile = {
317
+ bio: Optional<string>; // string | undefined
318
+ };
319
+
132
320
  // Extract yield type from generators
133
321
  function* numberGenerator() {
134
322
  yield 1;
@@ -137,58 +325,386 @@ function* numberGenerator() {
137
325
  }
138
326
 
139
327
  type NumberType = YieldType<typeof numberGenerator>; // number
328
+
329
+ // Omit properties by their value type
330
+ type Config = {
331
+ name: string;
332
+ debug: boolean;
333
+ verbose: boolean;
334
+ timeout: number;
335
+ };
336
+ type WithoutBooleans = OmitByKey<Config, boolean>; // { name: string; timeout: number }
337
+
338
+ // Set specific properties to nullable
339
+ type APIResponse = {
340
+ id: number;
341
+ name: string;
342
+ email: string;
343
+ };
344
+ type PartialResponse = SetNullable<APIResponse, "email" | "name">; // email and name become nullable
345
+ ```
346
+
347
+ ### Extended Remeda Utilities
348
+
349
+ The library includes 100+ utilities from [Remeda](https://remedajs.com/), a functional utility library optimized for TypeScript:
350
+
351
+ ```typescript
352
+ import {
353
+ // Property checking
354
+ hasOwnProperty,
355
+ isFunction,
356
+ isPromiseLike,
357
+
358
+ // Array operations
359
+ chunk,
360
+ compact,
361
+ drop,
362
+ dropLast,
363
+ filter,
364
+ find,
365
+ flatMap,
366
+ flatten,
367
+ map,
368
+ partition,
369
+ reverse,
370
+ slice,
371
+ take,
372
+ uniq,
373
+
374
+ // Object operations
375
+ pick,
376
+ omit,
377
+ merge,
378
+ keys,
379
+ values,
380
+ entries,
381
+
382
+ // Functional composition
383
+ pipe,
384
+ compose,
385
+
386
+ // Utility functions
387
+ clamp,
388
+ groupBy,
389
+ countBy,
390
+ sumBy,
391
+ minBy,
392
+ maxBy,
393
+ } from "@goodbyenjn/utils/remeda";
394
+
395
+ // Type-safe property checking
396
+ const obj = { name: "John", age: 30, active: true };
397
+ if (hasOwnProperty(obj, "name")) {
398
+ console.log(obj.name); // TypeScript type narrowing
399
+ }
400
+
401
+ // Function type checking
402
+ const maybeCallback: unknown = (x: number) => x * 2;
403
+ if (isFunction(maybeCallback)) {
404
+ maybeCallback(5);
405
+ }
406
+
407
+ // Promise detection
408
+ async function handleValue(value: any) {
409
+ if (isPromiseLike(value)) {
410
+ const result = await value;
411
+ console.log("Async result:", result);
412
+ }
413
+ }
414
+
415
+ // Functional data transformations
416
+ const users = [
417
+ { id: 1, name: "Alice", role: "admin", active: true },
418
+ { id: 2, name: "Bob", role: "user", active: false },
419
+ { id: 3, name: "Charlie", role: "user", active: true },
420
+ ];
421
+
422
+ // Chain operations with pipe
423
+ const adminNames = pipe(
424
+ users,
425
+ filter(u => u.role === "admin"),
426
+ map(u => u.name),
427
+ ); // ["Alice"]
428
+
429
+ // Group users by role
430
+ const byRole = groupBy(users, u => u.role);
431
+ // { admin: [...], user: [...] }
432
+
433
+ // Sum ages of active users
434
+ const totalAge = sumBy(
435
+ filter(users, u => u.active),
436
+ u => u.age ?? 0,
437
+ );
438
+
439
+ // Chunk array into groups
440
+ const chunked = chunk(users, 2);
441
+ // [[user1, user2], [user3]]
140
442
  ```
141
443
 
142
444
  ## API Reference
143
445
 
144
- ### Available Modules
446
+ ### Module Exports
145
447
 
146
- - **Main (`@goodbyenjn/utils`)** - Common utilities (string, math, promise, process, error handling, etc.)
147
- - **File System (`@goodbyenjn/utils/fs`)** - Safe file system operations
148
- - **Result (`@goodbyenjn/utils/result`)** - Functional error handling
149
- - **Remeda (`@goodbyenjn/utils/remeda`)** - Extended Remeda utilities
150
- - **Types (`@goodbyenjn/utils/types`)** - Utility types for TypeScript
448
+ #### Main Module (`@goodbyenjn/utils`)
151
449
 
152
- ### Core Functions
450
+ Common utilities for everyday programming tasks:
153
451
 
154
- #### String Utilities
452
+ ```typescript
453
+ // String utilities
454
+ export {
455
+ template,
456
+ unindent,
457
+ addPrefix,
458
+ addSuffix,
459
+ removePrefix,
460
+ removeSuffix,
461
+ split,
462
+ join,
463
+ splitWithSlash,
464
+ joinWithSlash,
465
+ toForwardSlash,
466
+ concatTemplateStrings,
467
+ };
155
468
 
156
- - `template(str, vars)` - Simple string templating
157
- - `unindent(str)` - Remove common indentation
158
- - `addPrefix/removePrefix` - Prefix manipulation
159
- - `addSuffix/removeSuffix` - Suffix manipulation
160
- - `toForwardSlash` - Convert backslashes to forward slashes
469
+ // Promise utilities
470
+ export { sleep, createLock, createSingleton, createPromiseWithResolvers };
161
471
 
162
- #### Promise Utilities
472
+ // Shell command execution
473
+ export { $, quoteShellArg };
163
474
 
164
- - `sleep(ms)` - Async sleep function
165
- - `createLock()` - Create a mutex lock
166
- - `createSingleton(factory)` - Create singleton factory
167
- - `PromiseWithResolvers` - Promise with external resolvers
475
+ // Math utilities
476
+ export { linear, scale };
168
477
 
169
- #### Process Utilities
478
+ // Error handling
479
+ export { normalizeError, getErrorMessage };
170
480
 
171
- - `$(command)` - Execute shell commands safely, returns ResultAsync with stdout/stderr
481
+ // Throttling/Debouncing
482
+ export { debounce, throttle };
172
483
 
173
- #### Math Utilities
484
+ // JSON utilities
485
+ export { stringify, parse, safeParse };
174
486
 
175
- - `linear(value, range)` - Linear interpolation
176
- - `scale(value, inRange, outRange)` - Scale value between ranges
487
+ // Parsing utilities
488
+ export { parseKeyValuePairs, parseValueToBoolean };
489
+ ```
177
490
 
178
- #### Error Handling
491
+ #### File System Module (`@goodbyenjn/utils/fs`)
492
+
493
+ Type-safe file system operations with Result pattern error handling:
494
+
495
+ ```typescript
496
+ // Safe operations (return Result types)
497
+ export {
498
+ safeReadFile,
499
+ safeReadFileSync,
500
+ safeReadJson,
501
+ safeReadJsonSync,
502
+ safeReadFileByLine,
503
+ safeWriteFile,
504
+ safeWriteFileSync,
505
+ safeWriteJson,
506
+ safeWriteJsonSync,
507
+ safeAppendFile,
508
+ safeAppendFileSync,
509
+ safeMkdir,
510
+ safeMkdirSync,
511
+ safeRm,
512
+ safeRmSync,
513
+ safeCp,
514
+ safeCpSync,
515
+ safeExists,
516
+ safeExistsSync,
517
+ };
518
+
519
+ // Glob operations
520
+ export { glob, globSync, convertPathToPattern, escapePath, isDynamicPattern };
521
+
522
+ // Unsafe operations (throw on error)
523
+ export {
524
+ readFile,
525
+ readFileSync,
526
+ readJson,
527
+ readJsonSync,
528
+ writeFile,
529
+ writeFileSync,
530
+ writeJson,
531
+ writeJsonSync,
532
+ mkdir,
533
+ mkdirSync,
534
+ // ... and more
535
+ };
536
+ ```
537
+
538
+ #### Result Module (`@goodbyenjn/utils/result`)
539
+
540
+ Functional error handling without exceptions:
541
+
542
+ ```typescript
543
+ // Main types and constructors
544
+ export { Result, ok as Ok, err as Err };
545
+
546
+ // Helper function
547
+ export { safeTry };
548
+
549
+ // Error class
550
+ export { ResultError };
551
+
552
+ // Type utilities
553
+ export type { Ok, Err, InferOkType, InferErrType, ExtractOkTypes, ExtractErrTypes, ResultAll };
554
+ ```
555
+
556
+ **Result Methods:**
557
+
558
+ - `isOk()` / `isErr()` - Type guard checks
559
+ - `map(fn)` - Transform the Ok value
560
+ - `mapErr(fn)` - Transform the Err value
561
+ - `flatMap(fn)` / `andThen(fn)` - Chain operations
562
+ - `unwrap()` - Get value or throw
563
+ - `unwrapOr(default)` - Get value with fallback
564
+ - `expect(msg)` - Unwrap with custom error message
565
+ - `inspect(fn)` - Execute side effect on Ok
566
+ - `inspectErr(fn)` - Execute side effect on Err
567
+ - `static fromCallable(fn, onThrow?)` - Convert throwing function
568
+
569
+ #### Remeda Module (`@goodbyenjn/utils/remeda`)
570
+
571
+ Extended functional utilities from Remeda:
572
+
573
+ ```typescript
574
+ // Custom implementations
575
+ export { hasOwnProperty, isFunction, isPromiseLike };
576
+
577
+ // Re-exported from Remeda (100+ functions)
578
+ export {
579
+ // Predicates
580
+ compact,
581
+ filter,
582
+ // Transformations
583
+ map,
584
+ flatMap,
585
+ flatten,
586
+ // Array operations
587
+ chunk,
588
+ slice,
589
+ take,
590
+ drop,
591
+ uniq,
592
+ reverse,
593
+ // Object operations
594
+ keys,
595
+ values,
596
+ entries,
597
+ pick,
598
+ omit,
599
+ merge,
600
+ // Aggregations
601
+ groupBy,
602
+ countBy,
603
+ sumBy,
604
+ maxBy,
605
+ minBy,
606
+ // Composition
607
+ pipe,
608
+ compose,
609
+ // And 50+ more...
610
+ };
611
+ ```
179
612
 
180
- - `normalizeError(error)` - Normalize any value to Error
181
- - `getErrorMessage(error)` - Extract error message safely
613
+ #### Types Module (`@goodbyenjn/utils/types`)
182
614
 
183
- #### Throttling
615
+ Utility types for TypeScript:
616
+
617
+ ```typescript
618
+ // Type utilities
619
+ export type {
620
+ Nullable, // T | null | undefined
621
+ Optional, // T | undefined
622
+ YieldType, // Extract yield type from generator
623
+ OmitByKey, // Omit properties by their value type
624
+ SetNullable, // Make specific properties nullable
625
+ Fn, // (args: any[]) => any
626
+ AsyncFn, // (...args: any[]) => Promise<any>
627
+ SyncFn, // (...args: any[]) => any
628
+ Promisable, // T | Promise<T>
629
+ NonEmptyTuple, // Tuple with at least one element
630
+ };
631
+ ```
632
+
633
+ ### Common Patterns
634
+
635
+ #### Error Handling with Result
636
+
637
+ ```typescript
638
+ // Instead of try-catch
639
+ async function loadConfig() {
640
+ const result = await safeReadJson("config.json");
641
+
642
+ // Pattern 1: Check and unwrap
643
+ if (result.isErr()) {
644
+ console.error("Failed to load config:", result.error);
645
+ return null;
646
+ }
647
+ return result.value;
648
+
649
+ // Pattern 2: Chain operations
650
+ // return result
651
+ // .map(cfg => validateConfig(cfg))
652
+ // .unwrapOr(defaultConfig);
653
+ }
654
+ ```
655
+
656
+ #### File Operations
657
+
658
+ ```typescript
659
+ // Safe read with fallback
660
+ const result = await safeReadFile("path.txt", "utf8");
661
+ const content = result.unwrapOr("default content");
662
+
663
+ // Or handle error explicitly
664
+ const jsonResult = await safeReadJson("data.json");
665
+ if (jsonResult.isOk()) {
666
+ processData(jsonResult.value);
667
+ } else {
668
+ logger.error(jsonResult.error);
669
+ }
670
+ ```
184
671
 
185
- - `debounce(fn, delay, options)` - Debounce function calls
186
- - `throttle(fn, delay, options)` - Throttle function calls
672
+ #### Functional Composition
673
+
674
+ ```typescript
675
+ import { pipe, filter, map } from "@goodbyenjn/utils/remeda";
676
+
677
+ const result = pipe(
678
+ data,
679
+ filter(x => x.active),
680
+ map(x => x.name),
681
+ );
682
+ ```
187
683
 
188
684
  ## Requirements
189
685
 
190
- - Node.js >= 18
191
- - TypeScript >= 4.5 (for TypeScript users)
686
+ - **Node.js**: >= 18.0.0
687
+ - **TypeScript**: >= 4.5 (for development/type checking)
688
+
689
+ Modern browsers are supported through ES module imports.
690
+
691
+ ## Versioning
692
+
693
+ **Note:** This project does **not** follow Semantic Versioning (semver). Instead, it uses a calendar-based versioning scheme:
694
+
695
+ **Version Format:** `v<YY>.<M>.<PATCH>`
696
+
697
+ - `<YY>` - Release year (e.g., 26 for 2026)
698
+ - `<M>` - Release month (1-12)
699
+ - `<PATCH>` - Patch/revision number within the same month (starting from 0)
700
+
701
+ **Example versions:**
702
+
703
+ - `v2026.01.0` - First release in January 2026
704
+ - `v2026.01.1` - Second release in January 2026
705
+ - `v2026.02.0` - First release in February 2026
706
+
707
+ This scheme provides clarity on when features were released while allowing multiple updates within the same month.
192
708
 
193
709
  ## Development
194
710
 
@@ -196,13 +712,30 @@ type NumberType = YieldType<typeof numberGenerator>; // number
196
712
  # Install dependencies
197
713
  pnpm install
198
714
 
715
+ # Development mode with watch
716
+ pnpm run dev
717
+
199
718
  # Build the library
200
719
  pnpm run build
201
720
 
202
721
  # Clean build artifacts
203
722
  pnpm run clean
723
+
724
+ # Run tests (if configured)
725
+ pnpm run test
204
726
  ```
205
727
 
728
+ ## Performance Considerations
729
+
730
+ - **Tree-shaking**: All modules are properly configured for tree-shaking. Import only what you need.
731
+ - **Result Pattern**: The Result type has minimal overhead compared to exceptions and enables better error handling.
732
+ - **Functional Composition**: Use Remeda utilities with pipe for efficient data transformations.
733
+ - **Shell Execution**: The `$` function safely escapes arguments and is suitable for production use.
734
+
735
+ ## Contributing
736
+
737
+ Contributions are welcome! Please feel free to submit a Pull Request at [GitHub Repository](https://github.com/GoodbyeNJN/utils).
738
+
206
739
  ## License
207
740
 
208
741
  MIT © [GoodbyeNJN](https://github.com/GoodbyeNJN)
@@ -211,4 +744,9 @@ MIT © [GoodbyeNJN](https://github.com/GoodbyeNJN)
211
744
 
212
745
  - [GitHub Repository](https://github.com/GoodbyeNJN/utils)
213
746
  - [npm Package](https://www.npmjs.com/package/@goodbyenjn/utils)
214
- - [Issues](https://github.com/GoodbyeNJN/utils/issues)
747
+ - [Issue Tracker](https://github.com/GoodbyeNJN/utils/issues)
748
+ - [Remeda Documentation](https://remedajs.com/)
749
+
750
+ ---
751
+
752
+ **Maintained with ❤️ by [GoodbyeNJN](https://github.com/GoodbyeNJN)**