utilful 1.2.1 → 2.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 +240 -24
- package/dist/array.d.ts +7 -5
- package/dist/array.js +11 -0
- package/dist/csv.d.ts +57 -57
- package/dist/csv.js +98 -0
- package/dist/defu.d.ts +16 -0
- package/dist/defu.js +34 -0
- package/dist/emitter.d.ts +20 -14
- package/dist/emitter.js +32 -0
- package/dist/index.d.ts +12 -10
- package/dist/index.js +12 -0
- package/dist/json.d.ts +13 -11
- package/dist/json.js +34 -0
- package/dist/module.d.ts +9 -7
- package/dist/module.js +14 -0
- package/dist/object.d.ts +26 -7
- package/dist/object.js +51 -0
- package/dist/path.d.ts +21 -19
- package/dist/path.js +105 -0
- package/dist/result.d.ts +34 -0
- package/dist/result.js +45 -0
- package/dist/string.d.ts +16 -14
- package/dist/string.js +32 -0
- package/dist/types.d.ts +4 -4
- package/dist/types.js +0 -0
- package/package.json +23 -18
- package/dist/array.d.mts +0 -10
- package/dist/array.mjs +0 -6
- package/dist/csv.d.mts +0 -67
- package/dist/csv.mjs +0 -77
- package/dist/emitter.d.mts +0 -24
- package/dist/emitter.mjs +0 -63
- package/dist/index.d.mts +0 -10
- package/dist/index.mjs +0 -9
- package/dist/json.d.mts +0 -16
- package/dist/json.mjs +0 -26
- package/dist/lazy.d.mts +0 -20
- package/dist/lazy.d.ts +0 -20
- package/dist/lazy.mjs +0 -11
- package/dist/module.d.mts +0 -11
- package/dist/module.mjs +0 -6
- package/dist/object.d.mts +0 -14
- package/dist/object.mjs +0 -25
- package/dist/path.d.mts +0 -40
- package/dist/path.mjs +0 -113
- package/dist/string.d.mts +0 -19
- package/dist/string.mjs +0 -16
- package/dist/types.d.mts +0 -8
- package/dist/types.mjs +0 -1
package/README.md
CHANGED
|
@@ -8,12 +8,13 @@ A collection of TypeScript utilities that I use across my projects.
|
|
|
8
8
|
- [API](#api)
|
|
9
9
|
- [Array](#array)
|
|
10
10
|
- [CSV](#csv)
|
|
11
|
+
- [Defu](#defu)
|
|
11
12
|
- [Emitter](#emitter)
|
|
12
13
|
- [JSON](#json)
|
|
13
|
-
- [Lazy](#lazy)
|
|
14
14
|
- [Module](#module)
|
|
15
15
|
- [Object](#object)
|
|
16
16
|
- [Path](#path)
|
|
17
|
+
- [Result](#result)
|
|
17
18
|
- [String](#string)
|
|
18
19
|
|
|
19
20
|
## Installation
|
|
@@ -111,6 +112,95 @@ Jane,25`
|
|
|
111
112
|
const data = parseCSV<'name' | 'age'>(csv) // [{ name: 'John', age: '30' }, { name: 'Jane', age: '25' }]
|
|
112
113
|
```
|
|
113
114
|
|
|
115
|
+
### Defu
|
|
116
|
+
|
|
117
|
+
Recursively assign default properties. Simplified version based on [unjs/defu](https://github.com/unjs/defu).
|
|
118
|
+
|
|
119
|
+
#### `defu`
|
|
120
|
+
|
|
121
|
+
Recursively assigns missing properties from defaults to the source object. The source object takes precedence over defaults.
|
|
122
|
+
|
|
123
|
+
**Key Features:**
|
|
124
|
+
|
|
125
|
+
- **Null/undefined handling**: `null` and `undefined` values in source are replaced with defaults
|
|
126
|
+
- **Array concatenation**: Arrays are concatenated (source + defaults)
|
|
127
|
+
- **Deep merging**: Nested objects are recursively merged
|
|
128
|
+
- **Type safety**: Preserves TypeScript types
|
|
129
|
+
- **Prototype pollution protection**: Ignores `__proto__` and `constructor` keys
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
type PlainObject = Record<PropertyKey, any>
|
|
133
|
+
|
|
134
|
+
declare function defu<T extends PlainObject>(
|
|
135
|
+
source: T,
|
|
136
|
+
...defaults: PlainObject[]
|
|
137
|
+
): T
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Example:**
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
import { defu } from 'utilful'
|
|
144
|
+
|
|
145
|
+
const result = defu(
|
|
146
|
+
{ a: 1, b: { x: 1 } },
|
|
147
|
+
{ a: 2, b: { y: 2 }, c: 3 }
|
|
148
|
+
)
|
|
149
|
+
// Result: { a: 1, b: { x: 1, y: 2 }, c: 3 }
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**Array concatenation example:**
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
const result = defu(
|
|
156
|
+
{ items: ['a', 'b'] },
|
|
157
|
+
{ items: ['c', 'd'] }
|
|
158
|
+
)
|
|
159
|
+
// Result: { items: ['a', 'b', 'c', 'd'] }
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**Null/undefined handling:**
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
const result = defu(
|
|
166
|
+
{ name: null, age: undefined },
|
|
167
|
+
{ name: 'John', age: 30, city: 'NYC' }
|
|
168
|
+
)
|
|
169
|
+
// Result: { name: 'John', age: 30, city: 'NYC' }
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
#### `createDefu`
|
|
173
|
+
|
|
174
|
+
Creates a custom defu function with a custom merger.
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
type DefuMerger<T extends PlainObject = PlainObject> = (
|
|
178
|
+
target: T,
|
|
179
|
+
key: PropertyKey,
|
|
180
|
+
value: any,
|
|
181
|
+
namespace: string,
|
|
182
|
+
) => boolean | void
|
|
183
|
+
|
|
184
|
+
declare function createDefu(merger?: DefuMerger): DefuFn
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**Example:**
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
import { createDefu } from 'utilful'
|
|
191
|
+
|
|
192
|
+
// Custom merger that adds numbers instead of replacing them
|
|
193
|
+
const addNumbers = createDefu((obj, key, val) => {
|
|
194
|
+
if (typeof val === 'number' && typeof obj[key] === 'number') {
|
|
195
|
+
obj[key] += val
|
|
196
|
+
return true // Indicates the merger handled this property
|
|
197
|
+
}
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
const result = addNumbers({ cost: 15 }, { cost: 10 })
|
|
201
|
+
// Result: { cost: 25 }
|
|
202
|
+
```
|
|
203
|
+
|
|
114
204
|
### Emitter
|
|
115
205
|
|
|
116
206
|
Tiny functional event emitter / pubsub, based on [mitt](https://github.com/developit/mitt).
|
|
@@ -168,29 +258,6 @@ Clones the given JSON value.
|
|
|
168
258
|
declare function cloneJSON<T>(value: T): T
|
|
169
259
|
```
|
|
170
260
|
|
|
171
|
-
### Lazy
|
|
172
|
-
|
|
173
|
-
A simple general purpose memoizer utility.
|
|
174
|
-
|
|
175
|
-
- Lazily computes a value when accessed
|
|
176
|
-
- Auto-caches the result by overwriting the getter
|
|
177
|
-
- Typesafe
|
|
178
|
-
|
|
179
|
-
Useful for deferring initialization or expensive operations. Unlike a simple getter, there is no runtime overhead after the first invokation, since the getter itself is overwritten with the memoized value.
|
|
180
|
-
|
|
181
|
-
```ts
|
|
182
|
-
declare function lazy<T>(getter: () => T): { value: T }
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
**Example:**
|
|
186
|
-
|
|
187
|
-
```ts
|
|
188
|
-
const myValue = lazy(() => 'Hello, World!')
|
|
189
|
-
console.log(myValue.value) // Computes value, overwrites getter
|
|
190
|
-
console.log(myValue.value) // Returns cached value
|
|
191
|
-
console.log(myValue.value) // Returns cached value
|
|
192
|
-
```
|
|
193
|
-
|
|
194
261
|
### Module
|
|
195
262
|
|
|
196
263
|
#### `interopDefault`
|
|
@@ -215,6 +282,28 @@ async function loadModule() {
|
|
|
215
282
|
|
|
216
283
|
### Object
|
|
217
284
|
|
|
285
|
+
#### `memoize`
|
|
286
|
+
|
|
287
|
+
A simple general purpose memoizer utility.
|
|
288
|
+
|
|
289
|
+
- Lazily computes a value when accessed
|
|
290
|
+
- Auto-caches the result by overwriting the getter
|
|
291
|
+
|
|
292
|
+
Useful for deferring initialization or expensive operations. Unlike a simple getter, there is no runtime overhead after the first invokation, since the getter itself is overwritten with the memoized value.
|
|
293
|
+
|
|
294
|
+
```ts
|
|
295
|
+
declare function memoize<T>(getter: () => T): { value: T }
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
**Example:**
|
|
299
|
+
|
|
300
|
+
```ts
|
|
301
|
+
const myValue = lazy(() => 'Hello, World!')
|
|
302
|
+
console.log(myValue.value) // Computes value, overwrites getter
|
|
303
|
+
console.log(myValue.value) // Returns cached value
|
|
304
|
+
console.log(myValue.value) // Returns cached value
|
|
305
|
+
```
|
|
306
|
+
|
|
218
307
|
#### `objectKeys`
|
|
219
308
|
|
|
220
309
|
Strictly typed `Object.keys`.
|
|
@@ -327,6 +416,133 @@ const url = withQuery('https://example.com', {
|
|
|
327
416
|
})
|
|
328
417
|
```
|
|
329
418
|
|
|
419
|
+
### Result
|
|
420
|
+
|
|
421
|
+
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.
|
|
422
|
+
|
|
423
|
+
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`:
|
|
424
|
+
|
|
425
|
+
```ts
|
|
426
|
+
import { err, ok } from 'utilful'
|
|
427
|
+
|
|
428
|
+
function divide(a: number, b: number) {
|
|
429
|
+
if (b === 0) {
|
|
430
|
+
return err('Division by zero')
|
|
431
|
+
}
|
|
432
|
+
return ok(a / b)
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const result = divide(10, 2)
|
|
436
|
+
if (result.ok)
|
|
437
|
+
console.log('Result:', result.value)
|
|
438
|
+
else
|
|
439
|
+
console.error('Error:', result.error)
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
#### `Result`
|
|
443
|
+
|
|
444
|
+
The `Result` type represents either success (`Ok`) or failure (`Err`).
|
|
445
|
+
|
|
446
|
+
**Type Definition:**
|
|
447
|
+
|
|
448
|
+
```ts
|
|
449
|
+
type Result<T, E> = Ok<T> | Err<E>
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
#### `Ok`
|
|
453
|
+
|
|
454
|
+
The `Ok` type wraps a successful value.
|
|
455
|
+
|
|
456
|
+
**Example:**
|
|
457
|
+
|
|
458
|
+
```ts
|
|
459
|
+
const result = new Ok(42)
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
#### `Err`
|
|
463
|
+
|
|
464
|
+
The `Err` type wraps an error value.
|
|
465
|
+
|
|
466
|
+
**Example:**
|
|
467
|
+
|
|
468
|
+
```ts
|
|
469
|
+
const result = new Err('Something went wrong')
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
#### `ok`
|
|
473
|
+
|
|
474
|
+
Shorthand function to create an `Ok` result. Use it to wrap a successful value.
|
|
475
|
+
|
|
476
|
+
**Type Definition:**
|
|
477
|
+
|
|
478
|
+
```ts
|
|
479
|
+
declare function ok<T>(value: T): Ok<T>
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
#### `err`
|
|
483
|
+
|
|
484
|
+
Shorthand function to create an `Err` result. Use it to wrap an error value.
|
|
485
|
+
|
|
486
|
+
**Type Definition:**
|
|
487
|
+
|
|
488
|
+
```ts
|
|
489
|
+
declare function err<E extends string = string>(err: E): Err<E>
|
|
490
|
+
declare function err<E = unknown>(err: E): Err<E>
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
#### `toResult`
|
|
494
|
+
|
|
495
|
+
Wraps a function that might throw an error and returns a `Result` with the result of the function.
|
|
496
|
+
|
|
497
|
+
**Type Definition:**
|
|
498
|
+
|
|
499
|
+
```ts
|
|
500
|
+
declare function toResult<T, E = unknown>(fn: () => T): Result<T, E>
|
|
501
|
+
declare function toResult<T, E = unknown>(promise: Promise<T>): Promise<Result<T, E>>
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
#### `unwrapResult`
|
|
505
|
+
|
|
506
|
+
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.
|
|
507
|
+
|
|
508
|
+
**Example:**
|
|
509
|
+
|
|
510
|
+
```ts
|
|
511
|
+
const result = toResult(() => JSON.parse('{"foo":"bar"}'))
|
|
512
|
+
const { value, error } = unwrapResult(result)
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
**Type Definition:**
|
|
516
|
+
|
|
517
|
+
```ts
|
|
518
|
+
declare function unwrapResult<T>(result: Ok<T>): { value: T, error: undefined }
|
|
519
|
+
declare function unwrapResult<E>(result: Err<E>): { value: undefined, error: E }
|
|
520
|
+
declare function unwrapResult<T, E>(result: Result<T, E>): { value: T, error: undefined } | { value: undefined, error: E }
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
#### `tryCatch`
|
|
524
|
+
|
|
525
|
+
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.
|
|
526
|
+
|
|
527
|
+
**Example:**
|
|
528
|
+
|
|
529
|
+
```ts
|
|
530
|
+
import { tryCatch } from 'utilful'
|
|
531
|
+
|
|
532
|
+
// Synchronous usage
|
|
533
|
+
const { value, error } = tryCatch(() => JSON.parse('{"foo":"bar"}'))
|
|
534
|
+
|
|
535
|
+
// Asynchronous usage
|
|
536
|
+
const { value, error } = await tryCatch(fetch('https://api.example.com/data').then(r => r.json()))
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
**Type Definition:**
|
|
540
|
+
|
|
541
|
+
```ts
|
|
542
|
+
declare function tryCatch<T, E = unknown>(fn: () => T): { value: T, error: undefined } | { value: undefined, error: E }
|
|
543
|
+
declare function tryCatch<T, E = unknown>(promise: Promise<T>): Promise<{ value: T, error: undefined } | { value: undefined, error: E }>
|
|
544
|
+
```
|
|
545
|
+
|
|
330
546
|
### String
|
|
331
547
|
|
|
332
548
|
#### `template`
|
package/dist/array.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
//#region src/array.d.ts
|
|
1
2
|
/**
|
|
2
|
-
|
|
3
|
-
|
|
3
|
+
* Represents a value that can be either a single value or an array of values.
|
|
4
|
+
*/
|
|
4
5
|
type MaybeArray<T> = T | T[];
|
|
5
6
|
/**
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
* Converts `MaybeArray<T>` to `Array<T>`.
|
|
8
|
+
*/
|
|
8
9
|
declare function toArray<T>(array?: MaybeArray<T> | null | undefined): T[];
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
//#endregion
|
|
12
|
+
export { MaybeArray, toArray };
|
package/dist/array.js
ADDED
package/dist/csv.d.ts
CHANGED
|
@@ -1,67 +1,67 @@
|
|
|
1
|
+
//#region src/csv.d.ts
|
|
1
2
|
/**
|
|
2
|
-
|
|
3
|
-
|
|
3
|
+
* Represents a row in a CSV file with column names of type T.
|
|
4
|
+
*/
|
|
4
5
|
type CSVRow<T extends string = string> = Record<T, string>;
|
|
5
6
|
/**
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
7
|
+
* Converts an array of objects to a comma-separated values (CSV) string
|
|
8
|
+
* that contains only the `columns` specified.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* const data = [
|
|
12
|
+
* { name: 'John', age: '30', city: 'New York' },
|
|
13
|
+
* { name: 'Jane', age: '25', city: 'Boston' }
|
|
14
|
+
* ]
|
|
15
|
+
*
|
|
16
|
+
* const csv = createCSV(data, ['name', 'age'])
|
|
17
|
+
* // name,age
|
|
18
|
+
* // John,30
|
|
19
|
+
* // Jane,25
|
|
20
|
+
*/
|
|
20
21
|
declare function createCSV<T extends Record<string, unknown>>(data: T[], columns: (keyof T)[], options?: {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
/** @default ',' */
|
|
23
|
+
delimiter?: string;
|
|
24
|
+
/** @default true */
|
|
25
|
+
addHeader?: boolean;
|
|
26
|
+
/** @default false */
|
|
27
|
+
quoteAll?: boolean;
|
|
27
28
|
}): string;
|
|
28
29
|
/**
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
30
|
+
* Escapes a value for a CSV string.
|
|
31
|
+
*
|
|
32
|
+
* @remarks
|
|
33
|
+
* Returns an empty string if the value is `null` or `undefined`.
|
|
34
|
+
* Values containing delimiters, quotes, or line breaks are quoted.
|
|
35
|
+
* Within quoted values, double quotes are escaped by doubling them.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* escapeCSVValue('hello, world') // "hello, world"
|
|
39
|
+
* escapeCSVValue('contains "quotes"') // "contains ""quotes"""
|
|
40
|
+
*/
|
|
40
41
|
declare function escapeCSVValue(value: unknown, options?: {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
/** @default ',' */
|
|
43
|
+
delimiter?: string;
|
|
44
|
+
/** @default false */
|
|
45
|
+
quoteAll?: boolean;
|
|
45
46
|
}): string;
|
|
46
47
|
/**
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
48
|
+
* Parses a comma-separated values (CSV) string into an array of objects.
|
|
49
|
+
*
|
|
50
|
+
* @remarks
|
|
51
|
+
* The first row of the CSV string is used as the header row.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* const csv = `name,age
|
|
55
|
+
* John,30
|
|
56
|
+
* Jane,25`
|
|
57
|
+
*
|
|
58
|
+
* const data = parseCSV<'name' | 'age'>(csv)
|
|
59
|
+
* // [{ name: 'John', age: '30' }, { name: 'Jane', age: '25' }]
|
|
60
|
+
*/
|
|
60
61
|
declare function parseCSV<Header extends string>(csv?: string | null | undefined, options?: {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}): CSVRow<Header>[];
|
|
66
|
-
|
|
67
|
-
export { type CSVRow, createCSV, escapeCSVValue, parseCSV };
|
|
62
|
+
/** @default ',' */
|
|
63
|
+
delimiter?: string;
|
|
64
|
+
/** @default true */
|
|
65
|
+
trimValues?: boolean;
|
|
66
|
+
}): CSVRow<Header>[]; //#endregion
|
|
67
|
+
export { CSVRow, createCSV, escapeCSVValue, parseCSV };
|
package/dist/csv.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
//#region src/csv.ts
|
|
2
|
+
/**
|
|
3
|
+
* Converts an array of objects to a comma-separated values (CSV) string
|
|
4
|
+
* that contains only the `columns` specified.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* const data = [
|
|
8
|
+
* { name: 'John', age: '30', city: 'New York' },
|
|
9
|
+
* { name: 'Jane', age: '25', city: 'Boston' }
|
|
10
|
+
* ]
|
|
11
|
+
*
|
|
12
|
+
* const csv = createCSV(data, ['name', 'age'])
|
|
13
|
+
* // name,age
|
|
14
|
+
* // John,30
|
|
15
|
+
* // Jane,25
|
|
16
|
+
*/
|
|
17
|
+
function createCSV(data, columns, options = {}) {
|
|
18
|
+
const { delimiter = ",", addHeader = true, quoteAll = false } = options;
|
|
19
|
+
const escapeAndQuote = (value) => escapeCSVValue(value, {
|
|
20
|
+
delimiter,
|
|
21
|
+
quoteAll
|
|
22
|
+
});
|
|
23
|
+
const rows = data.map((obj) => columns.map((key) => escapeAndQuote(obj[key])).join(delimiter));
|
|
24
|
+
if (addHeader) rows.unshift(columns.map(escapeAndQuote).join(delimiter));
|
|
25
|
+
return rows.join("\n");
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Escapes a value for a CSV string.
|
|
29
|
+
*
|
|
30
|
+
* @remarks
|
|
31
|
+
* Returns an empty string if the value is `null` or `undefined`.
|
|
32
|
+
* Values containing delimiters, quotes, or line breaks are quoted.
|
|
33
|
+
* Within quoted values, double quotes are escaped by doubling them.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* escapeCSVValue('hello, world') // "hello, world"
|
|
37
|
+
* escapeCSVValue('contains "quotes"') // "contains ""quotes"""
|
|
38
|
+
*/
|
|
39
|
+
function escapeCSVValue(value, options = {}) {
|
|
40
|
+
const { delimiter = ",", quoteAll = false } = options;
|
|
41
|
+
if (value == null) return "";
|
|
42
|
+
const stringValue = String(value);
|
|
43
|
+
const needsQuoting = quoteAll || stringValue.includes(delimiter) || stringValue.includes("\"") || stringValue.includes("\n") || stringValue.includes("\r");
|
|
44
|
+
if (needsQuoting) return `"${stringValue.replaceAll("\"", "\"\"")}"`;
|
|
45
|
+
return stringValue;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Parses a comma-separated values (CSV) string into an array of objects.
|
|
49
|
+
*
|
|
50
|
+
* @remarks
|
|
51
|
+
* The first row of the CSV string is used as the header row.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* const csv = `name,age
|
|
55
|
+
* John,30
|
|
56
|
+
* Jane,25`
|
|
57
|
+
*
|
|
58
|
+
* const data = parseCSV<'name' | 'age'>(csv)
|
|
59
|
+
* // [{ name: 'John', age: '30' }, { name: 'Jane', age: '25' }]
|
|
60
|
+
*/
|
|
61
|
+
function parseCSV(csv, options = {}) {
|
|
62
|
+
if (!csv?.trim()) return [];
|
|
63
|
+
const rows = [];
|
|
64
|
+
let currentRow = [];
|
|
65
|
+
let currentField = "";
|
|
66
|
+
let inQuotes = false;
|
|
67
|
+
const { delimiter = ",", trimValues = true } = options;
|
|
68
|
+
for (let i = 0; i < csv.length; i++) {
|
|
69
|
+
const char = csv[i];
|
|
70
|
+
const nextChar = i + 1 < csv.length ? csv[i + 1] : "";
|
|
71
|
+
if (char === "\"") if (inQuotes && nextChar === "\"") {
|
|
72
|
+
currentField += "\"";
|
|
73
|
+
i++;
|
|
74
|
+
} else inQuotes = !inQuotes;
|
|
75
|
+
else if (char === delimiter && !inQuotes) {
|
|
76
|
+
currentRow.push(trimValues ? currentField.trim() : currentField);
|
|
77
|
+
currentField = "";
|
|
78
|
+
} else if ((char === "\n" || char === "\r" && nextChar === "\n") && !inQuotes) {
|
|
79
|
+
if (char === "\r") i++;
|
|
80
|
+
currentRow.push(trimValues ? currentField.trim() : currentField);
|
|
81
|
+
rows.push(currentRow);
|
|
82
|
+
currentRow = [];
|
|
83
|
+
currentField = "";
|
|
84
|
+
} else currentField += char;
|
|
85
|
+
}
|
|
86
|
+
if (currentField || currentRow.length > 0) {
|
|
87
|
+
currentRow.push(trimValues ? currentField.trim() : currentField);
|
|
88
|
+
rows.push(currentRow);
|
|
89
|
+
}
|
|
90
|
+
if (rows.length <= 1) return [];
|
|
91
|
+
const headers = rows[0];
|
|
92
|
+
return rows.slice(1).filter((row) => row.some((field) => field.trim().length > 0)).map((values) => {
|
|
93
|
+
return Object.fromEntries(headers.map((header, index) => [header, index < values.length ? values[index] : ""]));
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
//#endregion
|
|
98
|
+
export { createCSV, escapeCSVValue, parseCSV };
|
package/dist/defu.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/defu.d.ts
|
|
2
|
+
// Forked from unjs/defu (MIT)
|
|
3
|
+
type PlainObject = Record<PropertyKey, any>;
|
|
4
|
+
type DefuMerger<T extends PlainObject = PlainObject> = (target: T, key: PropertyKey, value: any, namespace: string) => boolean | void;
|
|
5
|
+
/**
|
|
6
|
+
* Defu function type that accepts a source and multiple defaults
|
|
7
|
+
*/
|
|
8
|
+
type DefuFn = <T extends PlainObject>(source: T, ...defaults: PlainObject[]) => T;
|
|
9
|
+
/**
|
|
10
|
+
* Create a defu function with optional custom merger
|
|
11
|
+
*/
|
|
12
|
+
declare function createDefu(merger?: DefuMerger): DefuFn;
|
|
13
|
+
declare const defu: DefuFn;
|
|
14
|
+
|
|
15
|
+
//#endregion
|
|
16
|
+
export { DefuFn, DefuMerger, createDefu as createDefu$1, defu as defu$1 };
|
package/dist/defu.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
//#region src/defu.ts
|
|
2
|
+
/**
|
|
3
|
+
* Create a defu function with optional custom merger
|
|
4
|
+
*/
|
|
5
|
+
function createDefu(merger) {
|
|
6
|
+
return (source, ...defaults) => {
|
|
7
|
+
return defaults.reduce((acc, current) => _defu(acc, current ?? {}, "", merger), source ?? {});
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
const defu = createDefu();
|
|
11
|
+
function _defu(source, defaults, namespace = "", merger) {
|
|
12
|
+
if (!isPlainObject(defaults)) return source;
|
|
13
|
+
const result = { ...defaults };
|
|
14
|
+
for (const [key, value] of Object.entries(source)) {
|
|
15
|
+
if (key === "__proto__" || key === "constructor") continue;
|
|
16
|
+
if (value == null) continue;
|
|
17
|
+
if (merger?.(result, key, value, namespace)) continue;
|
|
18
|
+
const currentNamespace = namespace ? `${namespace}.${key}` : key;
|
|
19
|
+
if (Array.isArray(value) && Array.isArray(result[key])) result[key] = [...value, ...result[key]];
|
|
20
|
+
else if (isPlainObject(value) && isPlainObject(result[key])) result[key] = _defu(value, result[key], currentNamespace, merger);
|
|
21
|
+
else result[key] = value;
|
|
22
|
+
}
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
function isPlainObject(value) {
|
|
26
|
+
if (value === null || typeof value !== "object") return false;
|
|
27
|
+
const prototype = Object.getPrototypeOf(value);
|
|
28
|
+
if (prototype !== null && prototype !== Object.prototype && Object.getPrototypeOf(prototype) !== null) return false;
|
|
29
|
+
if (Symbol.iterator in value) return false;
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
//#endregion
|
|
34
|
+
export { createDefu, defu };
|
package/dist/emitter.d.ts
CHANGED
|
@@ -1,24 +1,30 @@
|
|
|
1
|
+
//#region src/emitter.d.ts
|
|
2
|
+
/* eslint-disable ts/method-signature-style */
|
|
1
3
|
type EventType = string | symbol;
|
|
4
|
+
// An event handler can take an optional event argument
|
|
5
|
+
// and should not return a value
|
|
2
6
|
type Handler<T = unknown> = (event: T) => void;
|
|
3
7
|
type WildcardHandler<T = Record<string, unknown>> = (type: keyof T, event: T[keyof T]) => void;
|
|
4
8
|
type EventHandlerList<T = unknown> = Handler<T>[];
|
|
5
9
|
type WildCardEventHandlerList<T = Record<string, unknown>> = WildcardHandler<T>[];
|
|
6
|
-
|
|
10
|
+
// A map of event types and their corresponding event handlers.
|
|
11
|
+
type EventHandlerMap<Events extends Record<EventType, unknown>> = Map<keyof Events | "*", EventHandlerList<Events[keyof Events]> | WildCardEventHandlerList<Events>>;
|
|
7
12
|
interface Emitter<Events extends Record<EventType, unknown>> {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
events: EventHandlerMap<Events>;
|
|
14
|
+
on<Key extends keyof Events>(type: Key, handler: Handler<Events[Key]>): void;
|
|
15
|
+
on(type: "*", handler: WildcardHandler<Events>): void;
|
|
16
|
+
off<Key extends keyof Events>(type: Key, handler?: Handler<Events[Key]>): void;
|
|
17
|
+
off(type: "*", handler: WildcardHandler<Events>): void;
|
|
18
|
+
emit<Key extends keyof Events>(type: Key, event: Events[Key]): void;
|
|
19
|
+
emit<Key extends keyof Events>(type: undefined extends Events[Key] ? Key : never): void;
|
|
15
20
|
}
|
|
16
21
|
/**
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
* Simple functional event emitter / pubsub.
|
|
23
|
+
*
|
|
24
|
+
* @remarks Ported from `mitt`.
|
|
25
|
+
* @see https://github.com/developit/mitt
|
|
26
|
+
*/
|
|
22
27
|
declare function createEmitter<Events extends Record<EventType, unknown>>(events?: EventHandlerMap<Events>): Emitter<Events>;
|
|
23
28
|
|
|
24
|
-
|
|
29
|
+
//#endregion
|
|
30
|
+
export { Emitter, EventHandlerList, EventHandlerMap, EventType, Handler, WildCardEventHandlerList, WildcardHandler, createEmitter };
|
package/dist/emitter.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//#region src/emitter.ts
|
|
2
|
+
/**
|
|
3
|
+
* Simple functional event emitter / pubsub.
|
|
4
|
+
*
|
|
5
|
+
* @remarks Ported from `mitt`.
|
|
6
|
+
* @see https://github.com/developit/mitt
|
|
7
|
+
*/
|
|
8
|
+
function createEmitter(events) {
|
|
9
|
+
events ||= new Map();
|
|
10
|
+
return {
|
|
11
|
+
events,
|
|
12
|
+
on(type, handler) {
|
|
13
|
+
const handlers = events.get(type);
|
|
14
|
+
if (handlers) handlers.push(handler);
|
|
15
|
+
else events.set(type, [handler]);
|
|
16
|
+
},
|
|
17
|
+
off(type, handler) {
|
|
18
|
+
const handlers = events.get(type);
|
|
19
|
+
if (handlers) if (handler) handlers.splice(handlers.indexOf(handler) >>> 0, 1);
|
|
20
|
+
else events.set(type, []);
|
|
21
|
+
},
|
|
22
|
+
emit(type, evt) {
|
|
23
|
+
let handlers = events.get(type);
|
|
24
|
+
if (handlers) for (const handler of [...handlers]) handler(evt);
|
|
25
|
+
handlers = events.get("*");
|
|
26
|
+
if (handlers) for (const handler of [...handlers]) handler(type, evt);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
//#endregion
|
|
32
|
+
export { createEmitter };
|