utilful 1.0.0 → 1.1.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/LICENSE +21 -0
- package/README.md +378 -0
- package/dist/array.d.mts +10 -0
- package/dist/array.d.ts +10 -0
- package/dist/array.mjs +6 -0
- package/dist/csv.d.mts +67 -0
- package/dist/csv.d.ts +67 -0
- package/dist/csv.mjs +77 -0
- package/dist/emitter.d.mts +72 -0
- package/dist/emitter.d.ts +72 -0
- package/dist/emitter.mjs +18 -0
- package/dist/index.d.mts +10 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.mjs +9 -0
- package/dist/json.d.mts +16 -0
- package/dist/json.d.ts +16 -0
- package/dist/json.mjs +26 -0
- package/dist/lazy.d.mts +20 -0
- package/dist/lazy.d.ts +20 -0
- package/dist/lazy.mjs +11 -0
- package/dist/module.d.mts +11 -0
- package/dist/module.d.ts +11 -0
- package/dist/module.mjs +6 -0
- package/dist/object.d.mts +14 -0
- package/dist/object.d.ts +14 -0
- package/dist/object.mjs +25 -0
- package/dist/path.d.mts +40 -0
- package/dist/path.d.ts +40 -0
- package/dist/path.mjs +113 -0
- package/dist/string.d.mts +19 -0
- package/dist/string.d.ts +19 -0
- package/dist/string.mjs +16 -0
- package/dist/types.d.mts +8 -0
- package/dist/types.d.ts +8 -0
- package/dist/types.mjs +1 -0
- package/package.json +85 -6
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-PRESENT Johann Schopplich
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
# utilful
|
|
2
|
+
|
|
3
|
+
A collection of TypeScript utilities that I use across my projects.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Installation](#installation)
|
|
8
|
+
- [API](#api)
|
|
9
|
+
- [Array](#array)
|
|
10
|
+
- [CSV](#csv)
|
|
11
|
+
- [Emitter](#emitter)
|
|
12
|
+
- [JSON](#json)
|
|
13
|
+
- [Lazy](#lazy)
|
|
14
|
+
- [Module](#module)
|
|
15
|
+
- [Object](#object)
|
|
16
|
+
- [Path](#path)
|
|
17
|
+
- [String](#string)
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
Run the following command to add `utilful` to your project.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# npm
|
|
25
|
+
npm install -D utilful
|
|
26
|
+
|
|
27
|
+
# pnpm
|
|
28
|
+
pnpm add -D utilful
|
|
29
|
+
|
|
30
|
+
# yarn
|
|
31
|
+
yarn add -D utilful
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## API
|
|
35
|
+
|
|
36
|
+
### Array
|
|
37
|
+
|
|
38
|
+
#### `toArray`
|
|
39
|
+
|
|
40
|
+
Converts `MaybeArray<T>` to `Array<T>`.
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
type MaybeArray<T> = T | T[]
|
|
44
|
+
|
|
45
|
+
declare function toArray<T>(array?: MaybeArray<T> | null | undefined): T[]
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### CSV
|
|
49
|
+
|
|
50
|
+
#### `createCSV`
|
|
51
|
+
|
|
52
|
+
Converts an array of objects to a comma-separated values (CSV) string that contains only the `columns` specified.
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
declare function createCSV<T extends Record<string, unknown>>(
|
|
56
|
+
data: T[],
|
|
57
|
+
columns: (keyof T)[],
|
|
58
|
+
options?: {
|
|
59
|
+
/** @default ',' */
|
|
60
|
+
delimiter?: string
|
|
61
|
+
/** @default true */
|
|
62
|
+
includeHeaders?: boolean
|
|
63
|
+
/** @default false */
|
|
64
|
+
quoteAll?: boolean
|
|
65
|
+
}
|
|
66
|
+
): string
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Example:**
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
const data = [
|
|
73
|
+
{ name: 'John', age: '30', city: 'New York' },
|
|
74
|
+
{ name: 'Jane', age: '25', city: 'Boston' }
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
const csv = createCSV(data, ['name', 'age'])
|
|
78
|
+
// name,age
|
|
79
|
+
// John,30
|
|
80
|
+
// Jane,25
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
#### `parseCSV`
|
|
84
|
+
|
|
85
|
+
Parses a comma-separated values (CSV) string into an array of objects.
|
|
86
|
+
|
|
87
|
+
> [!NOTE]
|
|
88
|
+
> The first row of the CSV string is used as the header row.
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
type CSVRow<T extends string = string> = Record<T, string>
|
|
92
|
+
|
|
93
|
+
declare function parseCSV<Header extends string>(
|
|
94
|
+
csv?: string | null | undefined,
|
|
95
|
+
options?: {
|
|
96
|
+
/** @default ',' */
|
|
97
|
+
delimiter?: string
|
|
98
|
+
/** @default true */
|
|
99
|
+
trimValues?: boolean
|
|
100
|
+
}
|
|
101
|
+
): CSVRow<Header>[]
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Example:**
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
const csv = `name,age
|
|
108
|
+
John,30
|
|
109
|
+
Jane,25`
|
|
110
|
+
|
|
111
|
+
const data = parseCSV<'name' | 'age'>(csv) // [{ name: 'John', age: '30' }, { name: 'Jane', age: '25' }]
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Emitter
|
|
115
|
+
|
|
116
|
+
Simple and tiny event emitter library for JavaScript.
|
|
117
|
+
|
|
118
|
+
`createEmitter` accepts interface with event name to listener argument types mapping:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
import { createEmitter } from 'utilful'
|
|
122
|
+
|
|
123
|
+
interface Events {
|
|
124
|
+
set: (name: string, count: number) => void
|
|
125
|
+
tick: () => void
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const emitter = createEmitter<Events>()
|
|
129
|
+
|
|
130
|
+
// Correct calls:
|
|
131
|
+
emitter.emit('set', 'prop', 1)
|
|
132
|
+
emitter.emit('tick')
|
|
133
|
+
|
|
134
|
+
// Compilation errors:
|
|
135
|
+
emitter.emit('set', 'prop', '1')
|
|
136
|
+
emitter.emit('tick', 2)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The `on` method returns an `unbind` function. Call it and this listener will be removed from event:
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
const unbind = emitter.on('tick', (number) => {
|
|
143
|
+
console.log(`on ${number}`)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
emitter.emit('tick', 1)
|
|
147
|
+
// Prints "on 1"
|
|
148
|
+
|
|
149
|
+
unbind()
|
|
150
|
+
emitter.emit('tick', 2)
|
|
151
|
+
// Prints nothing
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
You can get the used events list by accessing the `events` property:
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
const unbind = emitter.on('tick', () => { })
|
|
158
|
+
emitter.events // => { tick: [ [Function] ] }
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### JSON
|
|
162
|
+
|
|
163
|
+
#### `tryParseJSON`
|
|
164
|
+
|
|
165
|
+
Type-safe wrapper around `JSON.stringify`.
|
|
166
|
+
|
|
167
|
+
Falls back to the original value if the JSON serialization fails or the value is not a string.
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
declare function tryParseJSON<T = unknown>(value: unknown): T
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
#### `cloneJSON`
|
|
174
|
+
|
|
175
|
+
Clones the given JSON value.
|
|
176
|
+
|
|
177
|
+
> [!NOTE]
|
|
178
|
+
> The value must not contain circular references as JSON does not support them. It also must contain JSON serializable values.
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
declare function cloneJSON<T>(value: T): T
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Lazy
|
|
185
|
+
|
|
186
|
+
A simple general purpose memoizer utility.
|
|
187
|
+
|
|
188
|
+
- Lazily computes a value when accessed
|
|
189
|
+
- Auto-caches the result by overwriting the getter
|
|
190
|
+
- Typesafe
|
|
191
|
+
|
|
192
|
+
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.
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
declare function lazy<T>(getter: () => T): { value: T }
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**Example:**
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
const myValue = lazy(() => 'Hello, World!')
|
|
202
|
+
console.log(myValue.value) // Computes value, overwrites getter
|
|
203
|
+
console.log(myValue.value) // Returns cached value
|
|
204
|
+
console.log(myValue.value) // Returns cached value
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Module
|
|
208
|
+
|
|
209
|
+
#### `interopDefault`
|
|
210
|
+
|
|
211
|
+
Interop helper for default exports.
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
declare function interopDefault<T>(m: T | Promise<T>): Promise<T extends {
|
|
215
|
+
default: infer U
|
|
216
|
+
} ? U : T>
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**Example:**
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
import { interopDefault } from 'utilful'
|
|
223
|
+
|
|
224
|
+
async function loadModule() {
|
|
225
|
+
const mod = await interopDefault(import('./module.js'))
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Object
|
|
230
|
+
|
|
231
|
+
#### `objectKeys`
|
|
232
|
+
|
|
233
|
+
Strictly typed `Object.keys`.
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
declare function objectKeys<T extends Record<any, any>>(obj: T): Array<`${keyof T & (string | number | boolean | null | undefined)}`>
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
#### `objectEntries`
|
|
240
|
+
|
|
241
|
+
Strictly typed `Object.entries`.
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
declare function objectEntries<T extends Record<any, any>>(obj: T): Array<[keyof T, T[keyof T]]>
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
#### `deepApply`
|
|
248
|
+
|
|
249
|
+
Deeply applies a callback to every key-value pair in the given object, as well as nested objects and arrays.
|
|
250
|
+
|
|
251
|
+
```ts
|
|
252
|
+
declare function deepApply<T extends Record<any, any>>(data: T, callback: (item: T, key: keyof T, value: T[keyof T]) => void): void
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Path
|
|
256
|
+
|
|
257
|
+
#### `withoutLeadingSlash`
|
|
258
|
+
|
|
259
|
+
Removes the leading slash from the given path if it has one.
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
declare function withoutLeadingSlash(path?: string): string
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
#### `withLeadingSlash`
|
|
266
|
+
|
|
267
|
+
Adds a leading slash to the given path if it does not already have one.
|
|
268
|
+
|
|
269
|
+
```ts
|
|
270
|
+
declare function withLeadingSlash(path?: string): string
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
#### `withoutTrailingSlash`
|
|
274
|
+
|
|
275
|
+
Removes the trailing slash from the given path if it has one.
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
declare function withoutTrailingSlash(path?: string): string
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
#### `withTrailingSlash`
|
|
282
|
+
|
|
283
|
+
Adds a trailing slash to the given path if it does not already have one.
|
|
284
|
+
|
|
285
|
+
```ts
|
|
286
|
+
declare function withTrailingSlash(path?: string): string
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
#### `joinURL`
|
|
290
|
+
|
|
291
|
+
Joins the given URL path segments, ensuring that there is only one slash between them.
|
|
292
|
+
|
|
293
|
+
```ts
|
|
294
|
+
declare function joinURL(...paths: (string | undefined)[]): string
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
#### `withBase`
|
|
298
|
+
|
|
299
|
+
Adds the base path to the input path, if it is not already present.
|
|
300
|
+
|
|
301
|
+
```ts
|
|
302
|
+
declare function withBase(input?: string, base?: string): string
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
#### `withoutBase`
|
|
306
|
+
|
|
307
|
+
Removes the base path from the input path, if it is present.
|
|
308
|
+
|
|
309
|
+
```ts
|
|
310
|
+
declare function withoutBase(input?: string, base?: string): string
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
#### `getPathname`
|
|
314
|
+
|
|
315
|
+
Returns the pathname of the given path, which is the path without the query string.
|
|
316
|
+
|
|
317
|
+
```ts
|
|
318
|
+
declare function getPathname(path?: string): string
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
#### `withQuery`
|
|
322
|
+
|
|
323
|
+
Returns the URL with the given query parameters. If a query parameter is undefined, it is omitted.
|
|
324
|
+
|
|
325
|
+
```ts
|
|
326
|
+
declare function withQuery(input: string, query?: QueryObject): string
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
**Example:**
|
|
330
|
+
|
|
331
|
+
```ts
|
|
332
|
+
import { withQuery } from 'utilful'
|
|
333
|
+
|
|
334
|
+
const url = withQuery('https://example.com', {
|
|
335
|
+
foo: 'bar',
|
|
336
|
+
// This key is omitted
|
|
337
|
+
baz: undefined,
|
|
338
|
+
// Object values are stringified
|
|
339
|
+
baz: { qux: 'quux' }
|
|
340
|
+
})
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### String
|
|
344
|
+
|
|
345
|
+
#### `template`
|
|
346
|
+
|
|
347
|
+
Simple template engine to replace variables in a string.
|
|
348
|
+
|
|
349
|
+
```ts
|
|
350
|
+
declare function template(
|
|
351
|
+
str: string,
|
|
352
|
+
variables: Record<string | number, any>,
|
|
353
|
+
fallback?: string | ((key: string) => string)
|
|
354
|
+
): string
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
**Example:**
|
|
358
|
+
|
|
359
|
+
```ts
|
|
360
|
+
import { template } from 'utilful'
|
|
361
|
+
|
|
362
|
+
const str = 'Hello, {name}!'
|
|
363
|
+
const variables = { name: 'world' }
|
|
364
|
+
|
|
365
|
+
console.log(template(str, variables)) // Hello, world!
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
#### `generateRandomId`
|
|
369
|
+
|
|
370
|
+
Generates a random string. The function is ported from [`nanoid`](https://github.com/ai/nanoid). You can specify the size of the string and the dictionary of characters to use.
|
|
371
|
+
|
|
372
|
+
```ts
|
|
373
|
+
declare function generateRandomId(size?: number, dict?: string): string
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
## License
|
|
377
|
+
|
|
378
|
+
[MIT](./LICENSE) License © 2024-PRESENT [Johann Schopplich](https://github.com/johannschopplich)
|
package/dist/array.d.mts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents a value that can be either a single value or an array of values.
|
|
3
|
+
*/
|
|
4
|
+
type MaybeArray<T> = T | T[];
|
|
5
|
+
/**
|
|
6
|
+
* Converts `MaybeArray<T>` to `Array<T>`.
|
|
7
|
+
*/
|
|
8
|
+
declare function toArray<T>(array?: MaybeArray<T> | null | undefined): T[];
|
|
9
|
+
|
|
10
|
+
export { type MaybeArray, toArray };
|
package/dist/array.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents a value that can be either a single value or an array of values.
|
|
3
|
+
*/
|
|
4
|
+
type MaybeArray<T> = T | T[];
|
|
5
|
+
/**
|
|
6
|
+
* Converts `MaybeArray<T>` to `Array<T>`.
|
|
7
|
+
*/
|
|
8
|
+
declare function toArray<T>(array?: MaybeArray<T> | null | undefined): T[];
|
|
9
|
+
|
|
10
|
+
export { type MaybeArray, toArray };
|
package/dist/array.mjs
ADDED
package/dist/csv.d.mts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents a row in a CSV file with column names of type T.
|
|
3
|
+
*/
|
|
4
|
+
type CSVRow<T extends string = string> = Record<T, string>;
|
|
5
|
+
/**
|
|
6
|
+
* Converts an array of objects to a comma-separated values (CSV) string
|
|
7
|
+
* that contains only the `columns` specified.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* const data = [
|
|
11
|
+
* { name: 'John', age: '30', city: 'New York' },
|
|
12
|
+
* { name: 'Jane', age: '25', city: 'Boston' }
|
|
13
|
+
* ]
|
|
14
|
+
*
|
|
15
|
+
* const csv = createCSV(data, ['name', 'age'])
|
|
16
|
+
* // name,age
|
|
17
|
+
* // John,30
|
|
18
|
+
* // Jane,25
|
|
19
|
+
*/
|
|
20
|
+
declare function createCSV<T extends Record<string, unknown>>(data: T[], columns: (keyof T)[], options?: {
|
|
21
|
+
/** @default ',' */
|
|
22
|
+
delimiter?: string;
|
|
23
|
+
/** @default true */
|
|
24
|
+
addHeader?: boolean;
|
|
25
|
+
/** @default false */
|
|
26
|
+
quoteAll?: boolean;
|
|
27
|
+
}): string;
|
|
28
|
+
/**
|
|
29
|
+
* Escapes a value for a CSV string.
|
|
30
|
+
*
|
|
31
|
+
* @remarks
|
|
32
|
+
* Returns an empty string if the value is `null` or `undefined`.
|
|
33
|
+
* Values containing delimiters, quotes, or line breaks are quoted.
|
|
34
|
+
* Within quoted values, double quotes are escaped by doubling them.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* escapeCSVValue('hello, world'); // "hello, world"
|
|
38
|
+
* escapeCSVValue('contains "quotes"'); // "contains ""quotes"""
|
|
39
|
+
*/
|
|
40
|
+
declare function escapeCSVValue(value: unknown, options?: {
|
|
41
|
+
/** @default ',' */
|
|
42
|
+
delimiter?: string;
|
|
43
|
+
/** @default false */
|
|
44
|
+
quoteAll?: boolean;
|
|
45
|
+
}): string;
|
|
46
|
+
/**
|
|
47
|
+
* Parses a comma-separated values (CSV) string into an array of objects.
|
|
48
|
+
*
|
|
49
|
+
* @remarks
|
|
50
|
+
* The first row of the CSV string is used as the header row.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* const csv = `name,age
|
|
54
|
+
* John,30
|
|
55
|
+
* Jane,25`
|
|
56
|
+
*
|
|
57
|
+
* const data = parseCSV<'name' | 'age'>(csv)
|
|
58
|
+
* // [{ name: 'John', age: '30' }, { name: 'Jane', age: '25' }]
|
|
59
|
+
*/
|
|
60
|
+
declare function parseCSV<Header extends string>(csv?: string | null | undefined, options?: {
|
|
61
|
+
/** @default ',' */
|
|
62
|
+
delimiter?: string;
|
|
63
|
+
/** @default true */
|
|
64
|
+
trimValues?: boolean;
|
|
65
|
+
}): CSVRow<Header>[];
|
|
66
|
+
|
|
67
|
+
export { type CSVRow, createCSV, escapeCSVValue, parseCSV };
|
package/dist/csv.d.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents a row in a CSV file with column names of type T.
|
|
3
|
+
*/
|
|
4
|
+
type CSVRow<T extends string = string> = Record<T, string>;
|
|
5
|
+
/**
|
|
6
|
+
* Converts an array of objects to a comma-separated values (CSV) string
|
|
7
|
+
* that contains only the `columns` specified.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* const data = [
|
|
11
|
+
* { name: 'John', age: '30', city: 'New York' },
|
|
12
|
+
* { name: 'Jane', age: '25', city: 'Boston' }
|
|
13
|
+
* ]
|
|
14
|
+
*
|
|
15
|
+
* const csv = createCSV(data, ['name', 'age'])
|
|
16
|
+
* // name,age
|
|
17
|
+
* // John,30
|
|
18
|
+
* // Jane,25
|
|
19
|
+
*/
|
|
20
|
+
declare function createCSV<T extends Record<string, unknown>>(data: T[], columns: (keyof T)[], options?: {
|
|
21
|
+
/** @default ',' */
|
|
22
|
+
delimiter?: string;
|
|
23
|
+
/** @default true */
|
|
24
|
+
addHeader?: boolean;
|
|
25
|
+
/** @default false */
|
|
26
|
+
quoteAll?: boolean;
|
|
27
|
+
}): string;
|
|
28
|
+
/**
|
|
29
|
+
* Escapes a value for a CSV string.
|
|
30
|
+
*
|
|
31
|
+
* @remarks
|
|
32
|
+
* Returns an empty string if the value is `null` or `undefined`.
|
|
33
|
+
* Values containing delimiters, quotes, or line breaks are quoted.
|
|
34
|
+
* Within quoted values, double quotes are escaped by doubling them.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* escapeCSVValue('hello, world'); // "hello, world"
|
|
38
|
+
* escapeCSVValue('contains "quotes"'); // "contains ""quotes"""
|
|
39
|
+
*/
|
|
40
|
+
declare function escapeCSVValue(value: unknown, options?: {
|
|
41
|
+
/** @default ',' */
|
|
42
|
+
delimiter?: string;
|
|
43
|
+
/** @default false */
|
|
44
|
+
quoteAll?: boolean;
|
|
45
|
+
}): string;
|
|
46
|
+
/**
|
|
47
|
+
* Parses a comma-separated values (CSV) string into an array of objects.
|
|
48
|
+
*
|
|
49
|
+
* @remarks
|
|
50
|
+
* The first row of the CSV string is used as the header row.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* const csv = `name,age
|
|
54
|
+
* John,30
|
|
55
|
+
* Jane,25`
|
|
56
|
+
*
|
|
57
|
+
* const data = parseCSV<'name' | 'age'>(csv)
|
|
58
|
+
* // [{ name: 'John', age: '30' }, { name: 'Jane', age: '25' }]
|
|
59
|
+
*/
|
|
60
|
+
declare function parseCSV<Header extends string>(csv?: string | null | undefined, options?: {
|
|
61
|
+
/** @default ',' */
|
|
62
|
+
delimiter?: string;
|
|
63
|
+
/** @default true */
|
|
64
|
+
trimValues?: boolean;
|
|
65
|
+
}): CSVRow<Header>[];
|
|
66
|
+
|
|
67
|
+
export { type CSVRow, createCSV, escapeCSVValue, parseCSV };
|
package/dist/csv.mjs
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
function createCSV(data, columns, options = {}) {
|
|
2
|
+
const {
|
|
3
|
+
delimiter = ",",
|
|
4
|
+
addHeader = true,
|
|
5
|
+
quoteAll = false
|
|
6
|
+
} = options;
|
|
7
|
+
const escapeAndQuote = (value) => escapeCSVValue(value, { delimiter, quoteAll });
|
|
8
|
+
const rows = data.map(
|
|
9
|
+
(obj) => columns.map((key) => escapeAndQuote(obj[key])).join(delimiter)
|
|
10
|
+
);
|
|
11
|
+
if (addHeader) {
|
|
12
|
+
rows.unshift(columns.map(escapeAndQuote).join(delimiter));
|
|
13
|
+
}
|
|
14
|
+
return rows.join("\n");
|
|
15
|
+
}
|
|
16
|
+
function escapeCSVValue(value, options = {}) {
|
|
17
|
+
const {
|
|
18
|
+
delimiter = ",",
|
|
19
|
+
quoteAll = false
|
|
20
|
+
} = options;
|
|
21
|
+
if (value == null) {
|
|
22
|
+
return "";
|
|
23
|
+
}
|
|
24
|
+
const stringValue = String(value);
|
|
25
|
+
const needsQuoting = quoteAll || stringValue.includes(delimiter) || stringValue.includes('"') || stringValue.includes("\n") || stringValue.includes("\r");
|
|
26
|
+
if (needsQuoting) {
|
|
27
|
+
return `"${stringValue.replaceAll('"', '""')}"`;
|
|
28
|
+
}
|
|
29
|
+
return stringValue;
|
|
30
|
+
}
|
|
31
|
+
function parseCSV(csv, options = {}) {
|
|
32
|
+
if (!csv?.trim())
|
|
33
|
+
return [];
|
|
34
|
+
const rows = [];
|
|
35
|
+
let currentRow = [];
|
|
36
|
+
let currentField = "";
|
|
37
|
+
let inQuotes = false;
|
|
38
|
+
const { delimiter = ",", trimValues = true } = options;
|
|
39
|
+
for (let i = 0; i < csv.length; i++) {
|
|
40
|
+
const char = csv[i];
|
|
41
|
+
const nextChar = i + 1 < csv.length ? csv[i + 1] : "";
|
|
42
|
+
if (char === '"') {
|
|
43
|
+
if (inQuotes && nextChar === '"') {
|
|
44
|
+
currentField += '"';
|
|
45
|
+
i++;
|
|
46
|
+
} else {
|
|
47
|
+
inQuotes = !inQuotes;
|
|
48
|
+
}
|
|
49
|
+
} else if (char === delimiter && !inQuotes) {
|
|
50
|
+
currentRow.push(trimValues ? currentField.trim() : currentField);
|
|
51
|
+
currentField = "";
|
|
52
|
+
} else if ((char === "\n" || char === "\r" && nextChar === "\n") && !inQuotes) {
|
|
53
|
+
if (char === "\r")
|
|
54
|
+
i++;
|
|
55
|
+
currentRow.push(trimValues ? currentField.trim() : currentField);
|
|
56
|
+
rows.push(currentRow);
|
|
57
|
+
currentRow = [];
|
|
58
|
+
currentField = "";
|
|
59
|
+
} else {
|
|
60
|
+
currentField += char;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (currentField || currentRow.length > 0) {
|
|
64
|
+
currentRow.push(trimValues ? currentField.trim() : currentField);
|
|
65
|
+
rows.push(currentRow);
|
|
66
|
+
}
|
|
67
|
+
if (rows.length <= 1)
|
|
68
|
+
return [];
|
|
69
|
+
const headers = rows[0];
|
|
70
|
+
return rows.slice(1).filter((row) => row.some((field) => field.trim().length > 0)).map((values) => {
|
|
71
|
+
return Object.fromEntries(
|
|
72
|
+
headers.map((header, index) => [header, index < values.length ? values[index] : ""])
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export { createCSV, escapeCSVValue, parseCSV };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
interface EventsMap {
|
|
2
|
+
[event: string]: any;
|
|
3
|
+
}
|
|
4
|
+
interface DefaultEvents extends EventsMap {
|
|
5
|
+
[event: string]: (...args: any) => void;
|
|
6
|
+
}
|
|
7
|
+
interface Unsubscribe {
|
|
8
|
+
(): void;
|
|
9
|
+
}
|
|
10
|
+
interface Emitter<Events extends EventsMap = DefaultEvents> {
|
|
11
|
+
/**
|
|
12
|
+
* Calls each of the listeners registered for a given event.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* emitter.emit('tick', tickType, tickDuration)
|
|
16
|
+
*
|
|
17
|
+
* @param event The event name.
|
|
18
|
+
* @param args The arguments for listeners.
|
|
19
|
+
*/
|
|
20
|
+
emit: <K extends keyof Events>(this: this, event: K, ...args: Parameters<Events[K]>) => void;
|
|
21
|
+
/**
|
|
22
|
+
* Event names in keys and arrays with listeners in values.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* emitter1.events = emitter2.events
|
|
26
|
+
* emitter2.events = { }
|
|
27
|
+
*/
|
|
28
|
+
events: Partial<{
|
|
29
|
+
[E in keyof Events]: Events[E][];
|
|
30
|
+
}>;
|
|
31
|
+
/**
|
|
32
|
+
* Add a listener for a given event.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* const unbind = emitter.on('tick', (tickType, tickDuration) => {
|
|
36
|
+
* count += 1
|
|
37
|
+
* })
|
|
38
|
+
*
|
|
39
|
+
* disable () {
|
|
40
|
+
* unbind()
|
|
41
|
+
* }
|
|
42
|
+
*
|
|
43
|
+
* @param event The event name.
|
|
44
|
+
* @param cb The listener function.
|
|
45
|
+
* @returns Unbind listener from event.
|
|
46
|
+
*/
|
|
47
|
+
on: <K extends keyof Events>(this: this, event: K, cb: Events[K]) => Unsubscribe;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Create event emitter.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* import { createEmitter } from 'nanoevents'
|
|
54
|
+
*
|
|
55
|
+
* class Ticker {
|
|
56
|
+
* constructor() {
|
|
57
|
+
* this.emitter = createEmitter()
|
|
58
|
+
* }
|
|
59
|
+
* on(...args) {
|
|
60
|
+
* return this.emitter.on(...args)
|
|
61
|
+
* }
|
|
62
|
+
* tick() {
|
|
63
|
+
* this.emitter.emit('tick')
|
|
64
|
+
* }
|
|
65
|
+
* }
|
|
66
|
+
*
|
|
67
|
+
* @remarks Ported from `nanoevents`.
|
|
68
|
+
* @see https://github.com/ai/nanoevents
|
|
69
|
+
*/
|
|
70
|
+
declare function createEmitter<Events extends EventsMap = DefaultEvents>(): Emitter<Events>;
|
|
71
|
+
|
|
72
|
+
export { type Emitter, createEmitter };
|