utilful 2.0.0 → 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 +100 -10
- 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 +23 -21
- package/dist/object.js +51 -0
- package/dist/path.d.ts +21 -19
- package/dist/path.js +105 -0
- package/dist/result.d.ts +13 -11
- 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 +18 -13
- 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/module.d.mts +0 -11
- package/dist/module.mjs +0 -6
- package/dist/object.d.mts +0 -31
- package/dist/object.mjs +0 -34
- package/dist/path.d.mts +0 -40
- package/dist/path.mjs +0 -113
- package/dist/result.d.mts +0 -32
- package/dist/result.mjs +0 -43
- 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,6 +8,7 @@ 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
14
|
- [Module](#module)
|
|
@@ -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).
|
|
@@ -386,7 +476,7 @@ Shorthand function to create an `Ok` result. Use it to wrap a successful value.
|
|
|
386
476
|
**Type Definition:**
|
|
387
477
|
|
|
388
478
|
```ts
|
|
389
|
-
function ok<T>(value: T): Ok<T>
|
|
479
|
+
declare function ok<T>(value: T): Ok<T>
|
|
390
480
|
```
|
|
391
481
|
|
|
392
482
|
#### `err`
|
|
@@ -396,8 +486,8 @@ Shorthand function to create an `Err` result. Use it to wrap an error value.
|
|
|
396
486
|
**Type Definition:**
|
|
397
487
|
|
|
398
488
|
```ts
|
|
399
|
-
function err<E extends string = string>(err: E): Err<E>
|
|
400
|
-
function err<E = unknown>(err: E): Err<E>
|
|
489
|
+
declare function err<E extends string = string>(err: E): Err<E>
|
|
490
|
+
declare function err<E = unknown>(err: E): Err<E>
|
|
401
491
|
```
|
|
402
492
|
|
|
403
493
|
#### `toResult`
|
|
@@ -407,8 +497,8 @@ Wraps a function that might throw an error and returns a `Result` with the resul
|
|
|
407
497
|
**Type Definition:**
|
|
408
498
|
|
|
409
499
|
```ts
|
|
410
|
-
function toResult<T, E = unknown>(fn: () => T): Result<T, E>
|
|
411
|
-
function toResult<T, E = unknown>(promise: Promise<T>): Promise<Result<T, E>>
|
|
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>>
|
|
412
502
|
```
|
|
413
503
|
|
|
414
504
|
#### `unwrapResult`
|
|
@@ -425,9 +515,9 @@ const { value, error } = unwrapResult(result)
|
|
|
425
515
|
**Type Definition:**
|
|
426
516
|
|
|
427
517
|
```ts
|
|
428
|
-
function unwrapResult<T>(result: Ok<T>): { value: T, error: undefined }
|
|
429
|
-
function unwrapResult<E>(result: Err<E>): { value: undefined, error: E }
|
|
430
|
-
function unwrapResult<T, E>(result: Result<T, E>): { value: T, error: undefined } | { value: undefined, error: E }
|
|
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 }
|
|
431
521
|
```
|
|
432
522
|
|
|
433
523
|
#### `tryCatch`
|
|
@@ -449,8 +539,8 @@ const { value, error } = await tryCatch(fetch('https://api.example.com/data').th
|
|
|
449
539
|
**Type Definition:**
|
|
450
540
|
|
|
451
541
|
```ts
|
|
452
|
-
function tryCatch<T, E = unknown>(fn: () => T): { value: T, error: undefined } | { value: undefined, error: E }
|
|
453
|
-
function tryCatch<T, E = unknown>(promise: Promise<T>): Promise<{ value: T, error: undefined } | { value: undefined, error: E }>
|
|
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 }>
|
|
454
544
|
```
|
|
455
545
|
|
|
456
546
|
### String
|
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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import { MaybeArray, toArray } from "./array.js";
|
|
2
|
+
import { CSVRow, createCSV, escapeCSVValue, parseCSV } from "./csv.js";
|
|
3
|
+
import { Emitter, EventHandlerList, EventHandlerMap, EventType, Handler, WildCardEventHandlerList, WildcardHandler, createEmitter } from "./emitter.js";
|
|
4
|
+
import { DefuFn, DefuMerger, createDefu$1 as createDefu, defu$1 as defu } from "./defu.js";
|
|
5
|
+
import { cloneJSON, tryParseJSON } from "./json.js";
|
|
6
|
+
import { interopDefault } from "./module.js";
|
|
7
|
+
import { deepApply, memoize, objectEntries, objectKeys } from "./object.js";
|
|
8
|
+
import { QueryObject, QueryValue, getPathname, joinURL, withBase, withLeadingSlash, withQuery, withTrailingSlash, withoutBase, withoutLeadingSlash, withoutTrailingSlash } from "./path.js";
|
|
9
|
+
import { Err, ErrData, Ok, OkData, Result, ResultData, err, ok, toResult, tryCatch, unwrapResult } from "./result.js";
|
|
10
|
+
import { generateRandomId, template } from "./string.js";
|
|
11
|
+
import { AutocompletableString, LooseAutocomplete, UnifyIntersection } from "./types.js";
|
|
12
|
+
export { AutocompletableString, CSVRow, DefuFn, DefuMerger, Emitter, Err, ErrData, EventHandlerList, EventHandlerMap, EventType, Handler, LooseAutocomplete, MaybeArray, Ok, OkData, QueryObject, QueryValue, Result, ResultData, UnifyIntersection, WildCardEventHandlerList, WildcardHandler, cloneJSON, createCSV, createDefu, createEmitter, deepApply, defu, err, escapeCSVValue, generateRandomId, getPathname, interopDefault, joinURL, memoize, objectEntries, objectKeys, ok, parseCSV, template, toArray, toResult, tryCatch, tryParseJSON, unwrapResult, withBase, withLeadingSlash, withQuery, withTrailingSlash, withoutBase, withoutLeadingSlash, withoutTrailingSlash };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { toArray } from "./array.js";
|
|
2
|
+
import { createCSV, escapeCSVValue, parseCSV } from "./csv.js";
|
|
3
|
+
import { createEmitter } from "./emitter.js";
|
|
4
|
+
import { createDefu, defu } from "./defu.js";
|
|
5
|
+
import { cloneJSON, tryParseJSON } from "./json.js";
|
|
6
|
+
import { interopDefault } from "./module.js";
|
|
7
|
+
import { deepApply, memoize, objectEntries, objectKeys } from "./object.js";
|
|
8
|
+
import { getPathname, joinURL, withBase, withLeadingSlash, withQuery, withTrailingSlash, withoutBase, withoutLeadingSlash, withoutTrailingSlash } from "./path.js";
|
|
9
|
+
import { Err, Ok, err, ok, toResult, tryCatch, unwrapResult } from "./result.js";
|
|
10
|
+
import { generateRandomId, template } from "./string.js";
|
|
11
|
+
|
|
12
|
+
export { Err, Ok, cloneJSON, createCSV, createDefu, createEmitter, deepApply, defu, err, escapeCSVValue, generateRandomId, getPathname, interopDefault, joinURL, memoize, objectEntries, objectKeys, ok, parseCSV, template, toArray, toResult, tryCatch, tryParseJSON, unwrapResult, withBase, withLeadingSlash, withQuery, withTrailingSlash, withoutBase, withoutLeadingSlash, withoutTrailingSlash };
|
package/dist/json.d.ts
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
|
+
//#region src/json.d.ts
|
|
1
2
|
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
* Type-safe wrapper around `JSON.stringify`.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* Falls back to the original value if the JSON serialization fails or the value is not a string.
|
|
7
|
+
*/
|
|
7
8
|
declare function tryParseJSON<T = unknown>(value: unknown): T;
|
|
8
9
|
/**
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
* Clones the given JSON value.
|
|
11
|
+
*
|
|
12
|
+
* @remarks
|
|
13
|
+
* The value must not contain circular references as JSON does not support them. It also must contain JSON serializable values.
|
|
14
|
+
*/
|
|
14
15
|
declare function cloneJSON<T>(value: T): T;
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
//#endregion
|
|
18
|
+
export { cloneJSON, tryParseJSON };
|
package/dist/json.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
//#region src/json.ts
|
|
2
|
+
/**
|
|
3
|
+
* Type-safe wrapper around `JSON.stringify`.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* Falls back to the original value if the JSON serialization fails or the value is not a string.
|
|
7
|
+
*/
|
|
8
|
+
function tryParseJSON(value) {
|
|
9
|
+
if (typeof value !== "string") return value;
|
|
10
|
+
try {
|
|
11
|
+
return JSON.parse(value);
|
|
12
|
+
} catch {
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Clones the given JSON value.
|
|
18
|
+
*
|
|
19
|
+
* @remarks
|
|
20
|
+
* The value must not contain circular references as JSON does not support them. It also must contain JSON serializable values.
|
|
21
|
+
*/
|
|
22
|
+
function cloneJSON(value) {
|
|
23
|
+
if (typeof value !== "object" || value === null) return value;
|
|
24
|
+
if (Array.isArray(value)) return value.map((e) => typeof e !== "object" || e === null ? e : cloneJSON(e));
|
|
25
|
+
const result = {};
|
|
26
|
+
for (const k in value) {
|
|
27
|
+
const v = value[k];
|
|
28
|
+
result[k] = typeof v !== "object" || v === null ? v : cloneJSON(v);
|
|
29
|
+
}
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
//#endregion
|
|
34
|
+
export { cloneJSON, tryParseJSON };
|