@goodbyenjn/utils 26.3.0 → 26.4.1
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 +131 -66
- package/dist/chunks/{chunk-5ed3bc8a.js → chunk-11b9216b.js} +218 -351
- package/dist/chunks/chunk-1b381080.js +25 -0
- package/dist/chunks/chunk-3c6f28c7.d.ts +39 -0
- package/dist/chunks/chunk-3ce2ea14.js +36 -0
- package/dist/chunks/chunk-71e0c144.d.ts +168 -0
- package/dist/chunks/chunk-9fe6b612.d.ts +10 -0
- package/dist/chunks/chunk-bd9f56dd.d.ts +175 -0
- package/dist/common.d.ts +2 -195
- package/dist/common.js +2 -3
- package/dist/exec.d.ts +129 -0
- package/dist/exec.js +759 -0
- package/dist/fs.d.ts +422 -98
- package/dist/fs.js +999 -876
- package/dist/global-types.d.ts +146 -58
- package/dist/json.d.ts +29 -0
- package/dist/json.js +3 -0
- package/dist/remeda.d.ts +12 -12288
- package/dist/remeda.js +2 -3
- package/dist/result.d.ts +2 -2
- package/dist/result.js +2 -3
- package/dist/types.d.ts +3 -2
- package/dist/types.js +1 -1
- package/package.json +27 -18
- package/dist/chunks/chunk-b970a8d0.js +0 -2782
- package/dist/chunks/chunk-d1860346.d.ts +0 -154
- package/dist/chunks/chunk-e931fe39.d.ts +0 -11978
- package/dist/shell.d.ts +0 -111
- package/dist/shell.js +0 -781
package/README.md
CHANGED
|
@@ -11,9 +11,9 @@ A modern TypeScript/JavaScript utility library providing a comprehensive collect
|
|
|
11
11
|
- 🔒 **Type-safe**: Full TypeScript support with comprehensive type definitions and type inference
|
|
12
12
|
- 📦 **Modular**: Import only what you need with tree-shakable exports and multiple entry points
|
|
13
13
|
- 🛡️ **Result Pattern**: Functional error handling without exceptions, based on Rust-style Result types
|
|
14
|
-
- 📁 **
|
|
15
|
-
- 🐚 **
|
|
16
|
-
- 🧰 **Common Utilities**: String manipulation, math operations, promise utilities, and
|
|
14
|
+
- 📁 **VFile & FS**: Type-safe file system operations and a powerful virtual file object
|
|
15
|
+
- 🐚 **Exec**: Powerful and flexible command execution with safe and unsafe variants
|
|
16
|
+
- 🧰 **Common Utilities**: String manipulation, math operations, promise utilities, and JSON handling
|
|
17
17
|
- 📊 **Remeda Extensions**: Extended utilities built on top of [Remeda](https://remedajs.com/)
|
|
18
18
|
|
|
19
19
|
## Installation
|
|
@@ -33,9 +33,10 @@ yarn add @goodbyenjn/utils
|
|
|
33
33
|
```typescript
|
|
34
34
|
// Import what you need from the main module
|
|
35
35
|
import { sleep, template } from "@goodbyenjn/utils";
|
|
36
|
-
import {
|
|
37
|
-
import {
|
|
36
|
+
import { exec, safeExec } from "@goodbyenjn/utils/exec";
|
|
37
|
+
import { BaseVFile } from "@goodbyenjn/utils/fs";
|
|
38
38
|
import { ok, Result } from "@goodbyenjn/utils/result";
|
|
39
|
+
import { parse, safeParse } from "@goodbyenjn/utils/json";
|
|
39
40
|
```
|
|
40
41
|
|
|
41
42
|
### Common Utilities
|
|
@@ -116,6 +117,10 @@ const parts = split("-", "hello-world-js"); // ["hello", "world", "js"]
|
|
|
116
117
|
// Split string by line breaks (handles both \n and \r\n)
|
|
117
118
|
const lines = splitByLineBreak("line1\nline2\r\nline3");
|
|
118
119
|
console.log(lines); // ["line1", "line2", "line3"]
|
|
120
|
+
|
|
121
|
+
// Parse boolean value with custom default
|
|
122
|
+
const isEnabled = parseValueToBoolean("yes", false); // true
|
|
123
|
+
const debugMode = parseValueToBoolean("invalid", "auto"); // "auto"
|
|
119
124
|
```
|
|
120
125
|
|
|
121
126
|
#### Promise Utilities
|
|
@@ -150,34 +155,39 @@ setTimeout(() => resolve("done!"), 1000);
|
|
|
150
155
|
const result = await promise;
|
|
151
156
|
```
|
|
152
157
|
|
|
153
|
-
|
|
158
|
+
### Command Execution
|
|
154
159
|
|
|
155
160
|
```typescript
|
|
156
|
-
import {
|
|
161
|
+
import { exec, safeExec } from "@goodbyenjn/utils/exec";
|
|
157
162
|
|
|
158
|
-
//
|
|
159
|
-
const
|
|
160
|
-
console.log(
|
|
161
|
-
console.log(result.stderr);
|
|
163
|
+
// 1. Unsafe Execution (throws on failure)
|
|
164
|
+
const output = await exec`npm install`;
|
|
165
|
+
console.log(output.stdout);
|
|
162
166
|
|
|
163
167
|
// String command with args
|
|
164
|
-
const
|
|
165
|
-
console.log(
|
|
168
|
+
const lsOutput = await exec("ls", ["-la"]);
|
|
169
|
+
console.log(lsOutput.stdout);
|
|
170
|
+
|
|
171
|
+
// 2. Safe Execution (returns Result)
|
|
172
|
+
const safeOutput = await safeExec`npm install`;
|
|
173
|
+
if (safeOutput.isOk()) {
|
|
174
|
+
console.log("Success:", safeOutput.unwrap().stdout);
|
|
175
|
+
} else {
|
|
176
|
+
// Result contains error information (e.g., NonZeroExitError)
|
|
177
|
+
console.error("Failed:", safeOutput.unwrapErr().message);
|
|
178
|
+
}
|
|
166
179
|
|
|
167
|
-
// Pipe
|
|
168
|
-
const piped = await
|
|
180
|
+
// 3. Pipe Output
|
|
181
|
+
const piped = await exec`echo "hello"`.pipe`cat`;
|
|
169
182
|
console.log(piped.stdout);
|
|
170
183
|
|
|
171
|
-
// Iterate
|
|
172
|
-
for await (const line of
|
|
184
|
+
// 4. Iterate Output
|
|
185
|
+
for await (const line of exec`cat large-file.txt`) {
|
|
173
186
|
console.log(line);
|
|
174
187
|
}
|
|
175
188
|
|
|
176
|
-
//
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
// Factory function with options
|
|
180
|
-
const withCwd = $({ cwd: "/path/to/project" });
|
|
189
|
+
// 5. Configuration Factory
|
|
190
|
+
const withCwd = exec({ cwd: "/path/to/project" });
|
|
181
191
|
const result3 = await withCwd`npm install`;
|
|
182
192
|
```
|
|
183
193
|
|
|
@@ -232,6 +242,25 @@ const throttledScroll = throttle(() => {
|
|
|
232
242
|
window.addEventListener("scroll", throttledScroll);
|
|
233
243
|
```
|
|
234
244
|
|
|
245
|
+
#### JSON Handling
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
import { parse, stringify, safeParse, safeStringify } from "@goodbyenjn/utils/json";
|
|
249
|
+
|
|
250
|
+
// Standard JSON parsing (returns value or nil)
|
|
251
|
+
const data = parse('{"a": 1}'); // { a: 1 }
|
|
252
|
+
const invalid = parse("bad"); // nil
|
|
253
|
+
|
|
254
|
+
// Safe JSON parsing (returns Result)
|
|
255
|
+
const result = safeParse('{"a": 1}');
|
|
256
|
+
if (result.isOk()) {
|
|
257
|
+
console.log(result.unwrap().a);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Safe stringify
|
|
261
|
+
const json = safeStringify({ a: 1 }); // Result<string, Error>
|
|
262
|
+
```
|
|
263
|
+
|
|
235
264
|
### File System Operations
|
|
236
265
|
|
|
237
266
|
```typescript
|
|
@@ -244,33 +273,50 @@ import {
|
|
|
244
273
|
safeMkdir,
|
|
245
274
|
safeRm,
|
|
246
275
|
safeReadFileByLine,
|
|
276
|
+
BaseVFile,
|
|
247
277
|
} from "@goodbyenjn/utils/fs";
|
|
248
278
|
|
|
249
|
-
//
|
|
250
|
-
|
|
279
|
+
// ... (safe operations)
|
|
280
|
+
|
|
281
|
+
// BaseVFile - Unified file handling
|
|
282
|
+
const vfile = new BaseVFile("example.json");
|
|
283
|
+
|
|
284
|
+
// Fluid path manipulation
|
|
285
|
+
vfile.filename("data").extname("ts");
|
|
286
|
+
console.log(vfile.basename()); // "data.ts"
|
|
287
|
+
|
|
288
|
+
// Cross-platform path handling
|
|
289
|
+
const relative = vfile.pathname.relative(); // "data.ts" (relative to cwd)
|
|
290
|
+
const absolute = vfile.pathname(); // "/full/path/to/data.ts"
|
|
291
|
+
|
|
292
|
+
// Built-in operations (available in extended VFile implementations)
|
|
293
|
+
// await vfile.read(); // Get content
|
|
294
|
+
// await vfile.write(); // Write content
|
|
295
|
+
```
|
|
296
|
+
|
|
251
297
|
if (textResult.isOk()) {
|
|
252
|
-
|
|
298
|
+
console.log("File content:", textResult.unwrap());
|
|
253
299
|
} else {
|
|
254
|
-
|
|
300
|
+
console.error("Failed to read file:", textResult.unwrapErr().message);
|
|
255
301
|
}
|
|
256
302
|
|
|
257
303
|
// Read and parse JSON safely
|
|
258
304
|
const jsonResult = await safeReadJson("package.json");
|
|
259
305
|
if (jsonResult.isOk()) {
|
|
260
|
-
|
|
261
|
-
|
|
306
|
+
const pkg = jsonResult.unwrap();
|
|
307
|
+
console.log("Package name:", pkg.name);
|
|
262
308
|
}
|
|
263
309
|
|
|
264
310
|
// Write JSON file
|
|
265
311
|
const writeResult = await safeWriteJson("data.json", { users: [] }, { pretty: true });
|
|
266
312
|
if (writeResult.isErr()) {
|
|
267
|
-
|
|
313
|
+
console.error("Write failed:", writeResult.unwrapErr());
|
|
268
314
|
}
|
|
269
315
|
|
|
270
316
|
// Check if file exists
|
|
271
317
|
const existsResult = await safeExists("path/to/file.txt");
|
|
272
318
|
if (existsResult.isOk() && existsResult.unwrap()) {
|
|
273
|
-
|
|
319
|
+
console.log("File exists!");
|
|
274
320
|
}
|
|
275
321
|
|
|
276
322
|
// Create directories (recursive)
|
|
@@ -282,11 +328,12 @@ const rmResult = await safeRm("build", { recursive: true, force: true });
|
|
|
282
328
|
// Read file line by line
|
|
283
329
|
const lineResult = await safeReadFileByLine("large-file.log");
|
|
284
330
|
if (lineResult.isOk()) {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
331
|
+
for await (const line of lineResult.unwrap()) {
|
|
332
|
+
console.log(line);
|
|
333
|
+
}
|
|
288
334
|
}
|
|
289
|
-
|
|
335
|
+
|
|
336
|
+
````
|
|
290
337
|
|
|
291
338
|
### Glob Patterns
|
|
292
339
|
|
|
@@ -302,12 +349,12 @@ const syncFiles = globSync("**/*.test.ts", { cwd: "tests" });
|
|
|
302
349
|
|
|
303
350
|
// Convert file path to glob pattern
|
|
304
351
|
const pattern = convertPathToPattern("/home/user/project");
|
|
305
|
-
|
|
352
|
+
````
|
|
306
353
|
|
|
307
354
|
### Result Pattern - Functional Error Handling
|
|
308
355
|
|
|
309
356
|
```typescript
|
|
310
|
-
import { err, ok, Result
|
|
357
|
+
import { err, ok, Result } from "@goodbyenjn/utils/result";
|
|
311
358
|
|
|
312
359
|
// Create results explicitly
|
|
313
360
|
const success = ok(42);
|
|
@@ -315,7 +362,7 @@ const failure = err("Something went wrong");
|
|
|
315
362
|
|
|
316
363
|
// Handle results with chainable methods
|
|
317
364
|
const doubled = success
|
|
318
|
-
.map(value => value * 2)
|
|
365
|
+
.map(value => value * 2) // Supports async: .map(async v => v * 2) returns Promise<Result>
|
|
319
366
|
.mapErr(err => `Error: ${err}`)
|
|
320
367
|
.unwrapOr(0); // 84
|
|
321
368
|
|
|
@@ -323,28 +370,36 @@ const doubled = success
|
|
|
323
370
|
const result: Result<string, Error> = ok("value");
|
|
324
371
|
const transformed = result.mapErr(() => new Error("Custom error"));
|
|
325
372
|
|
|
326
|
-
// Convert throwing functions to Result
|
|
373
|
+
// Convert throwing functions or promises to Result
|
|
327
374
|
async function fetchUser(id: string) {
|
|
328
|
-
//
|
|
329
|
-
const user = await Result.
|
|
375
|
+
// Result.try catches thrown errors
|
|
376
|
+
const user = await Result.try(() => JSON.parse(userJson));
|
|
377
|
+
// Or handle promise rejections
|
|
378
|
+
const user = await Result.try(fetch(`/api/users/${id}`));
|
|
330
379
|
|
|
331
380
|
return user.map(u => u.name).mapErr(err => new Error(`Failed to parse user: ${err.message}`));
|
|
332
381
|
}
|
|
333
382
|
|
|
383
|
+
// Wrap a function to always return a Result
|
|
384
|
+
const safeParse = Result.wrap(JSON.parse, Error);
|
|
385
|
+
const data = safeParse('{"valid": true}'); // Result<any, Error>
|
|
386
|
+
|
|
334
387
|
// Combine multiple Results
|
|
335
388
|
const results = [ok(1), ok(2), err("oops"), ok(4)];
|
|
336
389
|
const combined = Result.all(...results); // Err("oops")
|
|
337
390
|
|
|
338
|
-
//
|
|
339
|
-
const
|
|
340
|
-
|
|
391
|
+
// Generator-based "do" notation for flattening Results
|
|
392
|
+
const finalResult = Result.gen(function* () {
|
|
393
|
+
const a = yield* ok(10);
|
|
394
|
+
const b = yield* ok(20);
|
|
395
|
+
return a + b;
|
|
396
|
+
}); // ok(30)
|
|
397
|
+
|
|
398
|
+
// Supports async generators
|
|
399
|
+
const asyncFinal = await Result.gen(async function* () {
|
|
400
|
+
const user = yield* await fetchUser("1");
|
|
401
|
+
return user.name;
|
|
341
402
|
});
|
|
342
|
-
|
|
343
|
-
if (safeTryExample.isOk()) {
|
|
344
|
-
console.log("Data:", safeTryExample.unwrap());
|
|
345
|
-
} else {
|
|
346
|
-
console.error("Failed:", safeTryExample.unwrapErr());
|
|
347
|
-
}
|
|
348
403
|
```
|
|
349
404
|
|
|
350
405
|
### Type Utilities
|
|
@@ -356,46 +411,56 @@ import type {
|
|
|
356
411
|
YieldType,
|
|
357
412
|
OmitByKey,
|
|
358
413
|
SetNullable,
|
|
414
|
+
TemplateFn,
|
|
359
415
|
} from "@goodbyenjn/utils/types";
|
|
360
416
|
|
|
417
|
+
// ... (other types)
|
|
418
|
+
|
|
419
|
+
// Template string function type
|
|
420
|
+
const myTag: TemplateFn<string> = (strings, ...values) => {
|
|
421
|
+
return strings[0] + values[0];
|
|
422
|
+
};
|
|
423
|
+
```
|
|
424
|
+
|
|
361
425
|
// Nullable type for values that can be null or undefined
|
|
362
426
|
type User = {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
427
|
+
id: string;
|
|
428
|
+
name: string;
|
|
429
|
+
email: Nullable<string>; // string | null | undefined
|
|
366
430
|
};
|
|
367
431
|
|
|
368
432
|
// Optional type (undefined but not null)
|
|
369
433
|
type Profile = {
|
|
370
|
-
|
|
434
|
+
bio: Optional<string>; // string | undefined
|
|
371
435
|
};
|
|
372
436
|
|
|
373
437
|
// Extract yield type from generators
|
|
374
|
-
function
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
438
|
+
function\* numberGenerator() {
|
|
439
|
+
yield 1;
|
|
440
|
+
yield 2;
|
|
441
|
+
yield 3;
|
|
378
442
|
}
|
|
379
443
|
|
|
380
444
|
type NumberType = YieldType<typeof numberGenerator>; // number
|
|
381
445
|
|
|
382
446
|
// Omit properties by their value type
|
|
383
447
|
type Config = {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
448
|
+
name: string;
|
|
449
|
+
debug: boolean;
|
|
450
|
+
verbose: boolean;
|
|
451
|
+
timeout: number;
|
|
388
452
|
};
|
|
389
453
|
type WithoutBooleans = OmitByKey<Config, boolean>; // { name: string; timeout: number }
|
|
390
454
|
|
|
391
455
|
// Set specific properties to nullable
|
|
392
456
|
type APIResponse = {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
457
|
+
id: number;
|
|
458
|
+
name: string;
|
|
459
|
+
email: string;
|
|
396
460
|
};
|
|
397
461
|
type PartialResponse = SetNullable<APIResponse, "email" | "name">; // email and name become nullable
|
|
398
|
-
|
|
462
|
+
|
|
463
|
+
````
|
|
399
464
|
|
|
400
465
|
### Extended Remeda Utilities
|
|
401
466
|
|
|
@@ -492,7 +557,7 @@ const totalAge = sumBy(
|
|
|
492
557
|
// Chunk array into groups
|
|
493
558
|
const chunked = chunk(users, 2);
|
|
494
559
|
// [[user1, user2], [user3]]
|
|
495
|
-
|
|
560
|
+
````
|
|
496
561
|
|
|
497
562
|
## API Reference
|
|
498
563
|
|