utilful 2.5.2 → 3.0.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 +207 -38
- package/dist/array.d.mts +4 -4
- package/dist/csv.d.mts +138 -148
- package/dist/csv.mjs +72 -88
- package/dist/defu.d.mts +12 -6
- package/dist/defu.mjs +3 -3
- package/dist/emitter.d.mts +5 -5
- package/dist/emitter.mjs +25 -0
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +3 -3
- package/dist/json.d.mts +6 -13
- package/dist/json.mjs +3 -19
- package/dist/module.d.mts +5 -5
- package/dist/module.mjs +2 -1
- package/dist/object.d.mts +26 -22
- package/dist/object.mjs +13 -7
- package/dist/path.d.mts +24 -20
- package/dist/path.mjs +9 -4
- package/dist/result.d.mts +16 -8
- package/dist/result.mjs +6 -5
- package/dist/string.d.mts +17 -13
- package/dist/string.mjs +8 -2
- package/dist/types.d.mts +1 -1
- package/package.json +10 -16
package/dist/csv.mjs
CHANGED
|
@@ -6,6 +6,7 @@ const CARRIAGE_RETURN = "\r";
|
|
|
6
6
|
const ESCAPED_QUOTE = "\"\"";
|
|
7
7
|
const SPACE = " ";
|
|
8
8
|
const TAB = " ";
|
|
9
|
+
const BYTE_ORDER_MARK = "";
|
|
9
10
|
function createCSV(data, columnsOrOptions, maybeOptions = {}) {
|
|
10
11
|
let columns;
|
|
11
12
|
let options;
|
|
@@ -18,7 +19,7 @@ function createCSV(data, columnsOrOptions, maybeOptions = {}) {
|
|
|
18
19
|
}
|
|
19
20
|
if (columns.length === 0 && data.length === 0) return "";
|
|
20
21
|
const { delimiter = COMMA, addHeader = true, quoteAll = false, lineEnding = NEWLINE } = options;
|
|
21
|
-
|
|
22
|
+
assertValidCSVDelimiter(delimiter);
|
|
22
23
|
if (addHeader) {
|
|
23
24
|
const header = encodeCSVHeader(columns.map(String), delimiter, quoteAll);
|
|
24
25
|
if (data.length === 0) return header;
|
|
@@ -46,7 +47,7 @@ function createCSV(data, columnsOrOptions, maybeOptions = {}) {
|
|
|
46
47
|
*/
|
|
47
48
|
async function* createCSVStream(data, columns, options = {}) {
|
|
48
49
|
const { delimiter = COMMA, addHeader = true, quoteAll = false, lineEnding = NEWLINE } = options;
|
|
49
|
-
|
|
50
|
+
assertValidCSVDelimiter(delimiter);
|
|
50
51
|
if (addHeader) yield encodeCSVHeader(columns.map(String), delimiter, quoteAll) + lineEnding;
|
|
51
52
|
for await (const row of data) yield encodeCSVRow(row, columns, delimiter, quoteAll) + lineEnding;
|
|
52
53
|
}
|
|
@@ -71,6 +72,10 @@ async function createCSVAsync(data, columns, options = {}) {
|
|
|
71
72
|
for await (const chunk of createCSVStream(data, columns, options)) chunks.push(chunk);
|
|
72
73
|
return chunks.join("");
|
|
73
74
|
}
|
|
75
|
+
function assertValidCSVDelimiter(delimiter) {
|
|
76
|
+
if (delimiter.length !== 1) throw new RangeError(`CSV delimiter must be a single character, got "${delimiter}"`);
|
|
77
|
+
if (delimiter === DOUBLE_QUOTE || delimiter === NEWLINE || delimiter === CARRIAGE_RETURN) throw new RangeError(`CSV delimiter must not be a quote or line break character, got ${JSON.stringify(delimiter)}`);
|
|
78
|
+
}
|
|
74
79
|
function encodeCSVHeader(columns, delimiter, quoteAll) {
|
|
75
80
|
return columns.map((col) => escapeCSVValue(col, {
|
|
76
81
|
delimiter,
|
|
@@ -100,35 +105,54 @@ function escapeCSVValue(value, options = {}) {
|
|
|
100
105
|
if (value == null) return "";
|
|
101
106
|
const coercedValue = String(value);
|
|
102
107
|
const hasQuote = coercedValue.includes(DOUBLE_QUOTE);
|
|
103
|
-
if (quoteAll || coercedValue.includes(delimiter) || hasQuote || coercedValue.includes(NEWLINE) || coercedValue.includes(CARRIAGE_RETURN))
|
|
108
|
+
if (quoteAll || coercedValue.includes(delimiter) || hasQuote || coercedValue.includes(NEWLINE) || coercedValue.includes(CARRIAGE_RETURN)) {
|
|
109
|
+
const escaped = hasQuote ? coercedValue.replaceAll(DOUBLE_QUOTE, ESCAPED_QUOTE) : coercedValue;
|
|
110
|
+
return `${DOUBLE_QUOTE}${escaped}${DOUBLE_QUOTE}`;
|
|
111
|
+
}
|
|
104
112
|
return coercedValue;
|
|
105
113
|
}
|
|
106
114
|
var CSVParserCore = class {
|
|
107
|
-
delimiter;
|
|
108
|
-
trim;
|
|
109
|
-
strict;
|
|
110
|
-
onRow;
|
|
111
|
-
currentRow = [];
|
|
112
|
-
currentRowQuotedFlags = [];
|
|
113
|
-
currentField = "";
|
|
114
|
-
inQuotes = false;
|
|
115
|
-
isFieldQuoted = false;
|
|
116
|
-
currentRowNumber = 1;
|
|
117
|
-
headerRaw;
|
|
118
|
-
headerQuotedFlags;
|
|
119
|
-
headers;
|
|
120
115
|
constructor(options, onRow) {
|
|
121
|
-
|
|
122
|
-
|
|
116
|
+
this.currentRow = [];
|
|
117
|
+
this.currentField = "";
|
|
118
|
+
this.inQuotes = false;
|
|
119
|
+
this.isFieldQuoted = false;
|
|
120
|
+
this.currentRowNumber = 1;
|
|
121
|
+
this.isAtInputStart = true;
|
|
122
|
+
this.pendingChunkTail = "";
|
|
123
|
+
const { delimiter = COMMA, trim = false, strict = true } = options;
|
|
124
|
+
assertValidCSVDelimiter(delimiter);
|
|
123
125
|
this.delimiter = delimiter;
|
|
124
126
|
this.trim = trim;
|
|
125
127
|
this.strict = strict;
|
|
126
128
|
this.onRow = onRow;
|
|
127
129
|
}
|
|
128
130
|
push(chunk) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
131
|
+
let pendingText = this.pendingChunkTail + chunk;
|
|
132
|
+
this.pendingChunkTail = "";
|
|
133
|
+
if (this.isAtInputStart && pendingText.length > 0) {
|
|
134
|
+
if (pendingText[0] === BYTE_ORDER_MARK) pendingText = pendingText.slice(1);
|
|
135
|
+
this.isAtInputStart = false;
|
|
136
|
+
}
|
|
137
|
+
let holdbackIndex = pendingText.length;
|
|
138
|
+
while (holdbackIndex > 0 && pendingText[holdbackIndex - 1] === DOUBLE_QUOTE) holdbackIndex--;
|
|
139
|
+
if (holdbackIndex === pendingText.length && holdbackIndex > 0 && pendingText[holdbackIndex - 1] === CARRIAGE_RETURN) holdbackIndex--;
|
|
140
|
+
this.pendingChunkTail = pendingText.slice(holdbackIndex);
|
|
141
|
+
this.consume(pendingText.slice(0, holdbackIndex));
|
|
142
|
+
}
|
|
143
|
+
finish() {
|
|
144
|
+
if (this.pendingChunkTail.length > 0) {
|
|
145
|
+
const chunkTail = this.pendingChunkTail;
|
|
146
|
+
this.pendingChunkTail = "";
|
|
147
|
+
this.consume(chunkTail);
|
|
148
|
+
}
|
|
149
|
+
if (this.inQuotes) throw new SyntaxError(`CSV contains unterminated quoted field at row ${this.currentRowNumber}`);
|
|
150
|
+
if (this.currentField !== "" || this.currentRow.length > 0) this.appendRow();
|
|
151
|
+
}
|
|
152
|
+
consume(text) {
|
|
153
|
+
for (let i = 0; i < text.length; i++) {
|
|
154
|
+
const character = text[i];
|
|
155
|
+
const nextCharacter = i + 1 < text.length ? text[i + 1] : "";
|
|
132
156
|
if (this.isFieldQuoted && !this.inQuotes && character !== this.delimiter && (character === SPACE || character === TAB)) continue;
|
|
133
157
|
if (character === DOUBLE_QUOTE) if (this.currentField.length === 0 && !this.inQuotes) {
|
|
134
158
|
this.inQuotes = true;
|
|
@@ -145,74 +169,52 @@ var CSVParserCore = class {
|
|
|
145
169
|
} else this.currentField += character;
|
|
146
170
|
}
|
|
147
171
|
}
|
|
148
|
-
finish() {
|
|
149
|
-
if (this.inQuotes) throw new SyntaxError(`CSV contains unterminated quoted field at row ${this.currentRowNumber}`);
|
|
150
|
-
if (this.currentField !== "" || this.currentRow.length > 0) this.appendRow();
|
|
151
|
-
}
|
|
152
172
|
appendField() {
|
|
153
|
-
this.
|
|
154
|
-
this.
|
|
173
|
+
const fieldValue = this.trim && !this.isFieldQuoted ? this.currentField.trim() : this.currentField;
|
|
174
|
+
this.currentRow.push(fieldValue);
|
|
155
175
|
this.currentField = "";
|
|
156
176
|
this.isFieldQuoted = false;
|
|
157
177
|
}
|
|
158
178
|
appendRow() {
|
|
159
179
|
this.appendField();
|
|
160
|
-
if (
|
|
161
|
-
|
|
162
|
-
this.headerQuotedFlags = this.currentRowQuotedFlags;
|
|
163
|
-
this.processHeaderRow();
|
|
164
|
-
} else this.processDataRow(this.currentRow, this.currentRowQuotedFlags);
|
|
180
|
+
if (this.headers) this.processDataRow(this.currentRow, this.headers);
|
|
181
|
+
else this.processHeaderRow(this.currentRow);
|
|
165
182
|
this.currentRow = [];
|
|
166
|
-
this.currentRowQuotedFlags = [];
|
|
167
183
|
this.currentRowNumber++;
|
|
168
184
|
}
|
|
169
|
-
processHeaderRow() {
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
const
|
|
173
|
-
if (headers.filter((h) => h.length === 0).length > 0) {
|
|
174
|
-
const positions = headers.map((h, i) => h.length === 0 ? i + 1 : -1).filter((i) => i > 0).join(", ");
|
|
175
|
-
throw new SyntaxError(`CSV header row contains empty column name(s) at position(s): ${positions}`);
|
|
176
|
-
}
|
|
177
|
-
const headerSet = /* @__PURE__ */ new Set();
|
|
185
|
+
processHeaderRow(headers) {
|
|
186
|
+
const emptyHeaderPositions = headers.map((header, index) => header.length === 0 ? index + 1 : -1).filter((position) => position > 0);
|
|
187
|
+
if (emptyHeaderPositions.length > 0) throw new SyntaxError(`CSV header row contains empty column name(s) at position(s): ${emptyHeaderPositions.join(", ")}`);
|
|
188
|
+
const seenHeaderNames = /* @__PURE__ */ new Set();
|
|
178
189
|
const duplicateHeaderNames = /* @__PURE__ */ new Set();
|
|
179
|
-
for (const header of headers) if (
|
|
180
|
-
else
|
|
190
|
+
for (const header of headers) if (seenHeaderNames.has(header)) duplicateHeaderNames.add(header);
|
|
191
|
+
else seenHeaderNames.add(header);
|
|
181
192
|
if (duplicateHeaderNames.size > 0) throw new SyntaxError(`CSV header row contains duplicate column name(s): ${[...duplicateHeaderNames].join(", ")}`);
|
|
182
193
|
this.headers = headers;
|
|
183
194
|
}
|
|
184
|
-
processDataRow(fieldValues,
|
|
185
|
-
if (
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (!hasMultipleFields && !hasAnyPopulatedField) return;
|
|
191
|
-
if (fieldValues.length > headers.length) {
|
|
192
|
-
const fieldsExceedingHeaders = fieldValues.slice(headers.length);
|
|
193
|
-
const excessQuotedFlags = quotedFlags.slice(headers.length);
|
|
194
|
-
const containsNonEmptyOverflow = fieldsExceedingHeaders.some((field, idx) => isFieldPopulated(field, excessQuotedFlags[idx] ?? false));
|
|
195
|
-
if (this.strict && containsNonEmptyOverflow) {
|
|
196
|
-
const expectedCount = headers.length;
|
|
197
|
-
const actualCount = fieldValues.length;
|
|
198
|
-
const excessCount = actualCount - expectedCount;
|
|
199
|
-
throw new SyntaxError(`CSV row ${this.currentRowNumber} has ${excessCount} extra field(s): expected ${expectedCount} column(s), found ${actualCount}`);
|
|
200
|
-
}
|
|
195
|
+
processDataRow(fieldValues, headers) {
|
|
196
|
+
if (fieldValues.length === 1 && fieldValues[0].length === 0) return;
|
|
197
|
+
if (this.strict) {
|
|
198
|
+
if (fieldValues.length > headers.length) {
|
|
199
|
+
if (fieldValues.slice(headers.length).some((fieldValue) => fieldValue.length > 0)) throw new SyntaxError(`CSV row ${this.currentRowNumber} has ${fieldValues.length - headers.length} extra field(s): expected ${headers.length} column(s), found ${fieldValues.length}`);
|
|
200
|
+
} else if (fieldValues.length < headers.length) throw new SyntaxError(`CSV row ${this.currentRowNumber} has ${headers.length - fieldValues.length} missing field(s): expected ${headers.length} column(s), found ${fieldValues.length}`);
|
|
201
201
|
}
|
|
202
|
-
const rowEntries = headers.map((header, columnIndex) =>
|
|
203
|
-
|
|
204
|
-
const wasQuoted = quotedFlags[columnIndex] ?? false;
|
|
205
|
-
return [header, this.trim && !wasQuoted ? untrimmedValue.trim() : untrimmedValue];
|
|
206
|
-
});
|
|
207
|
-
const rowObject = Object.fromEntries(rowEntries);
|
|
208
|
-
this.onRow(rowObject);
|
|
202
|
+
const rowEntries = headers.map((header, columnIndex) => [header, fieldValues[columnIndex] ?? ""]);
|
|
203
|
+
this.onRow(Object.fromEntries(rowEntries));
|
|
209
204
|
}
|
|
210
205
|
};
|
|
211
206
|
/**
|
|
212
207
|
* Parses a comma-separated values (CSV) string into an array of objects.
|
|
213
208
|
*
|
|
214
209
|
* @remarks
|
|
215
|
-
* The first row of the CSV string is used as the header row.
|
|
210
|
+
* The first row of the CSV string is used as the header row. A leading
|
|
211
|
+
* UTF-8 byte order mark is stripped.
|
|
212
|
+
*
|
|
213
|
+
* Parsing tolerances (lenient deviations from RFC 4180):
|
|
214
|
+
* - LF, CR, and CRLF line endings are all accepted
|
|
215
|
+
* - Whitespace between a closing quote and the next delimiter or line break is ignored
|
|
216
|
+
* - A field is only treated as quoted if it starts with a quote; quotes inside
|
|
217
|
+
* unquoted fields are kept as literal characters
|
|
216
218
|
*
|
|
217
219
|
* @example
|
|
218
220
|
* const csv = `name,age
|
|
@@ -260,24 +262,6 @@ async function* parseCSVStream(chunks, options = {}) {
|
|
|
260
262
|
while (queue.length > 0) yield queue.shift();
|
|
261
263
|
}
|
|
262
264
|
/**
|
|
263
|
-
* Parses CSV data from an async iterable or iterable of lines.
|
|
264
|
-
*
|
|
265
|
-
* @remarks
|
|
266
|
-
* This is a convenience wrapper around `parseCSVStream` that treats each line
|
|
267
|
-
* as a chunk. Note that lines do not necessarily correspond to CSV rows due to
|
|
268
|
-
* quoted fields containing newlines. The parser handles this correctly.
|
|
269
|
-
*
|
|
270
|
-
* @example
|
|
271
|
-
* const lines = ['name,age', 'John,30', 'Jane,25']
|
|
272
|
-
*
|
|
273
|
-
* for await (const row of parseCSVFromLines<'name' | 'age'>(lines)) {
|
|
274
|
-
* console.log(row)
|
|
275
|
-
* }
|
|
276
|
-
*/
|
|
277
|
-
async function* parseCSVFromLines(lines, options) {
|
|
278
|
-
yield* parseCSVStream(lines, options);
|
|
279
|
-
}
|
|
280
|
-
/**
|
|
281
265
|
* Infers column names from data by collecting the union of keys
|
|
282
266
|
* across all rows in first-seen order.
|
|
283
267
|
*/
|
|
@@ -292,4 +276,4 @@ function inferColumns(rows) {
|
|
|
292
276
|
return columns;
|
|
293
277
|
}
|
|
294
278
|
//#endregion
|
|
295
|
-
export { createCSV, createCSVAsync, createCSVStream, escapeCSVValue, parseCSV,
|
|
279
|
+
export { createCSV, createCSVAsync, createCSVStream, escapeCSVValue, parseCSV, parseCSVStream };
|
package/dist/defu.d.mts
CHANGED
|
@@ -2,13 +2,19 @@
|
|
|
2
2
|
type PlainObject = Record<PropertyKey, any>;
|
|
3
3
|
type DefuMerger<T extends PlainObject = PlainObject> = (target: T, key: PropertyKey, value: any, namespace: string) => boolean | void;
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*/
|
|
7
|
-
type
|
|
5
|
+
* Deeply merged result type of a source object over a list of defaults.
|
|
6
|
+
*/
|
|
7
|
+
type Defu<Source, Defaults extends any[]> = Defaults extends [infer First, ...infer Rest] ? Defu<MergedObject<Source, First>, Rest> : Source;
|
|
8
|
+
type MergedObject<Source, Defaults> = Source extends PlainObject ? Defaults extends PlainObject ? { [Key in keyof Source | keyof Defaults]: MergedValue<Key extends keyof Source ? Source[Key] : undefined, Key extends keyof Defaults ? Defaults[Key] : undefined>; } : Source : Source;
|
|
9
|
+
type MergedValue<SourceValue, DefaultValue> = SourceValue extends null | undefined ? DefaultValue : SourceValue extends any[] ? DefaultValue extends any[] ? Array<SourceValue[number] | DefaultValue[number]> : SourceValue : SourceValue extends ((...args: any[]) => any) ? SourceValue : SourceValue extends PlainObject ? MergedObject<SourceValue, DefaultValue> : SourceValue;
|
|
8
10
|
/**
|
|
9
|
-
*
|
|
10
|
-
*/
|
|
11
|
+
* Defu function type that accepts a source and multiple defaults
|
|
12
|
+
*/
|
|
13
|
+
type DefuFn = <Source extends PlainObject, Defaults extends PlainObject[]>(source: Source, ...defaults: Defaults) => Defu<Source, Defaults>;
|
|
14
|
+
/**
|
|
15
|
+
* Create a defu function with optional custom merger
|
|
16
|
+
*/
|
|
11
17
|
declare function createDefu(merger?: DefuMerger): DefuFn;
|
|
12
18
|
declare const defu: DefuFn;
|
|
13
19
|
//#endregion
|
|
14
|
-
export { DefuFn, DefuMerger, createDefu, defu };
|
|
20
|
+
export { Defu, DefuFn, DefuMerger, createDefu, defu };
|
package/dist/defu.mjs
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* Create a defu function with optional custom merger
|
|
4
4
|
*/
|
|
5
5
|
function createDefu(merger) {
|
|
6
|
-
return (source, ...defaults) => {
|
|
7
|
-
return defaults.reduce((
|
|
8
|
-
};
|
|
6
|
+
return ((source, ...defaults) => {
|
|
7
|
+
return defaults.reduce((mergedResult, currentDefaults) => _defu(mergedResult, currentDefaults ?? {}, "", merger), source ?? {});
|
|
8
|
+
});
|
|
9
9
|
}
|
|
10
10
|
const defu = createDefu();
|
|
11
11
|
function _defu(source, defaults, namespace = "", merger) {
|
package/dist/emitter.d.mts
CHANGED
|
@@ -15,11 +15,11 @@ interface Emitter<Events extends Record<EventType, unknown>> {
|
|
|
15
15
|
emit<Key extends keyof Events>(type: undefined extends Events[Key] ? Key : never): void;
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
|
-
* Simple functional event emitter / pubsub.
|
|
19
|
-
*
|
|
20
|
-
* @remarks Ported from `mitt`.
|
|
21
|
-
* @see https://github.com/developit/mitt
|
|
22
|
-
*/
|
|
18
|
+
* Simple functional event emitter / pubsub.
|
|
19
|
+
*
|
|
20
|
+
* @remarks Ported from `mitt`.
|
|
21
|
+
* @see https://github.com/developit/mitt
|
|
22
|
+
*/
|
|
23
23
|
declare function createEmitter<Events extends Record<EventType, unknown>>(events?: EventHandlerMap<Events>): Emitter<Events>;
|
|
24
24
|
//#endregion
|
|
25
25
|
export { Emitter, EventHandlerList, EventHandlerMap, EventType, Handler, WildCardEventHandlerList, WildcardHandler, createEmitter };
|
package/dist/emitter.mjs
CHANGED
|
@@ -8,17 +8,42 @@
|
|
|
8
8
|
function createEmitter(events) {
|
|
9
9
|
events ||= /* @__PURE__ */ new Map();
|
|
10
10
|
return {
|
|
11
|
+
/**
|
|
12
|
+
* A Map of event names to registered handler functions.
|
|
13
|
+
*/
|
|
11
14
|
events,
|
|
15
|
+
/**
|
|
16
|
+
* Register an event handler for the given type.
|
|
17
|
+
*
|
|
18
|
+
* @memberOf createEmitter
|
|
19
|
+
*/
|
|
12
20
|
on(type, handler) {
|
|
13
21
|
const handlers = events.get(type);
|
|
14
22
|
if (handlers) handlers.push(handler);
|
|
15
23
|
else events.set(type, [handler]);
|
|
16
24
|
},
|
|
25
|
+
/**
|
|
26
|
+
* Remove an event handler for the given type.
|
|
27
|
+
*
|
|
28
|
+
* @remarks
|
|
29
|
+
* If `handler` is omitted, all handlers of the given type are removed.
|
|
30
|
+
*
|
|
31
|
+
* @memberOf createEmitter
|
|
32
|
+
*/
|
|
17
33
|
off(type, handler) {
|
|
18
34
|
const handlers = events.get(type);
|
|
19
35
|
if (handlers) if (handler) handlers.splice(handlers.indexOf(handler) >>> 0, 1);
|
|
20
36
|
else events.set(type, []);
|
|
21
37
|
},
|
|
38
|
+
/**
|
|
39
|
+
* Invoke all handlers for the given type.
|
|
40
|
+
*
|
|
41
|
+
* @remarks
|
|
42
|
+
* If present, `'*'` handlers are invoked after type-matched handlers.
|
|
43
|
+
* Manually firing '*' handlers is not supported.
|
|
44
|
+
*
|
|
45
|
+
* @memberOf createEmitter
|
|
46
|
+
*/
|
|
22
47
|
emit(type, evt) {
|
|
23
48
|
let handlers = events.get(type);
|
|
24
49
|
if (handlers) for (const handler of [...handlers]) handler(evt);
|
package/dist/index.d.mts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { MaybeArray, toArray } from "./array.mjs";
|
|
2
|
-
import { CSVCreateOptions, CSVRow, createCSV, createCSVAsync, createCSVStream, escapeCSVValue, parseCSV,
|
|
3
|
-
import { DefuFn, DefuMerger, createDefu, defu } from "./defu.mjs";
|
|
2
|
+
import { CSVCreateOptions, CSVParseOptions, CSVRow, createCSV, createCSVAsync, createCSVStream, escapeCSVValue, parseCSV, parseCSVStream } from "./csv.mjs";
|
|
3
|
+
import { Defu, DefuFn, DefuMerger, createDefu, defu } from "./defu.mjs";
|
|
4
4
|
import { Emitter, EventHandlerList, EventHandlerMap, EventType, Handler, WildCardEventHandlerList, WildcardHandler, createEmitter } from "./emitter.mjs";
|
|
5
|
-
import {
|
|
5
|
+
import { tryParseJSON } from "./json.mjs";
|
|
6
6
|
import { interopDefault } from "./module.mjs";
|
|
7
7
|
import { deepApply, isObject, memoize, objectEntries, objectKeys } from "./object.mjs";
|
|
8
8
|
import { QueryObject, QueryValue, getPathname, joinURL, withBase, withLeadingSlash, withQuery, withTrailingSlash, withoutBase, withoutLeadingSlash, withoutTrailingSlash } from "./path.mjs";
|
|
9
9
|
import { Err, ErrData, Ok, OkData, Result, ResultData, err, isErr, isOk, ok, toResult, tryCatch, unwrapResult } from "./result.mjs";
|
|
10
10
|
import { generateRandomId, template } from "./string.mjs";
|
|
11
11
|
import { AutocompletableString, BrandedType, LooseAutocomplete, UnifyIntersection } from "./types.mjs";
|
|
12
|
-
export { AutocompletableString, BrandedType, CSVCreateOptions, CSVRow, DefuFn, DefuMerger, Emitter, Err, ErrData, EventHandlerList, EventHandlerMap, EventType, Handler, LooseAutocomplete, MaybeArray, Ok, OkData, QueryObject, QueryValue, Result, ResultData, UnifyIntersection, WildCardEventHandlerList, WildcardHandler,
|
|
12
|
+
export { AutocompletableString, BrandedType, CSVCreateOptions, CSVParseOptions, CSVRow, Defu, DefuFn, DefuMerger, Emitter, Err, ErrData, EventHandlerList, EventHandlerMap, EventType, Handler, LooseAutocomplete, MaybeArray, Ok, OkData, QueryObject, QueryValue, Result, ResultData, UnifyIntersection, WildCardEventHandlerList, WildcardHandler, createCSV, createCSVAsync, createCSVStream, createDefu, createEmitter, deepApply, defu, err, escapeCSVValue, generateRandomId, getPathname, interopDefault, isErr, isObject, isOk, joinURL, memoize, objectEntries, objectKeys, ok, parseCSV, parseCSVStream, template, toArray, toResult, tryCatch, tryParseJSON, unwrapResult, withBase, withLeadingSlash, withQuery, withTrailingSlash, withoutBase, withoutLeadingSlash, withoutTrailingSlash };
|
package/dist/index.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { toArray } from "./array.mjs";
|
|
2
|
-
import { createCSV, createCSVAsync, createCSVStream, escapeCSVValue, parseCSV,
|
|
2
|
+
import { createCSV, createCSVAsync, createCSVStream, escapeCSVValue, parseCSV, parseCSVStream } from "./csv.mjs";
|
|
3
3
|
import { createDefu, defu } from "./defu.mjs";
|
|
4
4
|
import { createEmitter } from "./emitter.mjs";
|
|
5
|
-
import {
|
|
5
|
+
import { tryParseJSON } from "./json.mjs";
|
|
6
6
|
import { interopDefault } from "./module.mjs";
|
|
7
7
|
import { deepApply, isObject, memoize, objectEntries, objectKeys } from "./object.mjs";
|
|
8
8
|
import { getPathname, joinURL, withBase, withLeadingSlash, withQuery, withTrailingSlash, withoutBase, withoutLeadingSlash, withoutTrailingSlash } from "./path.mjs";
|
|
9
9
|
import { Err, Ok, err, isErr, isOk, ok, toResult, tryCatch, unwrapResult } from "./result.mjs";
|
|
10
10
|
import { generateRandomId, template } from "./string.mjs";
|
|
11
|
-
export { Err, Ok,
|
|
11
|
+
export { Err, Ok, createCSV, createCSVAsync, createCSVStream, createDefu, createEmitter, deepApply, defu, err, escapeCSVValue, generateRandomId, getPathname, interopDefault, isErr, isObject, isOk, joinURL, memoize, objectEntries, objectKeys, ok, parseCSV, parseCSVStream, template, toArray, toResult, tryCatch, tryParseJSON, unwrapResult, withBase, withLeadingSlash, withQuery, withTrailingSlash, withoutBase, withoutLeadingSlash, withoutTrailingSlash };
|
package/dist/json.d.mts
CHANGED
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
//#region src/json.d.ts
|
|
2
2
|
/**
|
|
3
|
-
* Type-safe wrapper around `JSON.
|
|
4
|
-
*
|
|
5
|
-
* @remarks
|
|
6
|
-
* Falls back to the original value if
|
|
7
|
-
*/
|
|
3
|
+
* Type-safe wrapper around `JSON.parse`.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* Falls back to the original value if parsing fails or the value is not a string.
|
|
7
|
+
*/
|
|
8
8
|
declare function tryParseJSON<T = unknown>(value: unknown): T;
|
|
9
|
-
/**
|
|
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
|
-
*/
|
|
15
|
-
declare function cloneJSON<T>(value: T): T;
|
|
16
9
|
//#endregion
|
|
17
|
-
export {
|
|
10
|
+
export { tryParseJSON };
|
package/dist/json.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
//#region src/json.ts
|
|
2
2
|
/**
|
|
3
|
-
* Type-safe wrapper around `JSON.
|
|
3
|
+
* Type-safe wrapper around `JSON.parse`.
|
|
4
4
|
*
|
|
5
5
|
* @remarks
|
|
6
|
-
* Falls back to the original value if
|
|
6
|
+
* Falls back to the original value if parsing fails or the value is not a string.
|
|
7
7
|
*/
|
|
8
8
|
function tryParseJSON(value) {
|
|
9
9
|
if (typeof value !== "string") return value;
|
|
@@ -13,21 +13,5 @@ function tryParseJSON(value) {
|
|
|
13
13
|
return value;
|
|
14
14
|
}
|
|
15
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((element) => typeof element !== "object" || element === null ? element : cloneJSON(element));
|
|
25
|
-
const result = {};
|
|
26
|
-
for (const key in value) {
|
|
27
|
-
const propertyValue = value[key];
|
|
28
|
-
result[key] = typeof propertyValue !== "object" || propertyValue === null ? propertyValue : cloneJSON(propertyValue);
|
|
29
|
-
}
|
|
30
|
-
return result;
|
|
31
|
-
}
|
|
32
16
|
//#endregion
|
|
33
|
-
export {
|
|
17
|
+
export { tryParseJSON };
|
package/dist/module.d.mts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
//#region src/module.d.ts
|
|
2
2
|
/**
|
|
3
|
-
* Interop helper for default exports.
|
|
4
|
-
*
|
|
5
|
-
* @example
|
|
6
|
-
* const mod = await interopDefault(import('./module.js'))
|
|
7
|
-
*/
|
|
3
|
+
* Interop helper for default exports.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* const mod = await interopDefault(import('./module.js'))
|
|
7
|
+
*/
|
|
8
8
|
declare function interopDefault<T>(m: T | Promise<T>): Promise<T extends {
|
|
9
9
|
default: infer U;
|
|
10
10
|
} ? U : T>;
|
package/dist/module.mjs
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
async function interopDefault(m) {
|
|
9
9
|
const resolved = await m;
|
|
10
|
-
|
|
10
|
+
if (resolved != null && (typeof resolved === "object" || typeof resolved === "function") && "default" in resolved) return resolved.default;
|
|
11
|
+
return resolved;
|
|
11
12
|
}
|
|
12
13
|
//#endregion
|
|
13
14
|
export { interopDefault };
|
package/dist/object.d.mts
CHANGED
|
@@ -1,36 +1,40 @@
|
|
|
1
1
|
//#region src/object.d.ts
|
|
2
2
|
/**
|
|
3
|
-
* A simple general purpose memoizer utility.
|
|
4
|
-
* - Lazily computes a value when accessed
|
|
5
|
-
* - Auto-caches the result by overwriting the getter
|
|
6
|
-
*
|
|
7
|
-
* @remarks
|
|
8
|
-
* Useful for deferring initialization or expensive operations. Unlike a simple getter, there is no runtime overhead after the first
|
|
9
|
-
*
|
|
10
|
-
* @example
|
|
11
|
-
* const myValue =
|
|
12
|
-
* console.log(myValue.value) // Computes value, overwrites getter
|
|
13
|
-
* console.log(myValue.value) // Returns cached value
|
|
14
|
-
* console.log(myValue.value) // Returns cached value
|
|
15
|
-
*/
|
|
3
|
+
* A simple general purpose memoizer utility.
|
|
4
|
+
* - Lazily computes a value when accessed
|
|
5
|
+
* - Auto-caches the result by overwriting the getter
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* Useful for deferring initialization or expensive operations. Unlike a simple getter, there is no runtime overhead after the first invocation, since the getter itself is overwritten with the memoized value.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* const myValue = memoize(() => 'Hello, World!')
|
|
12
|
+
* console.log(myValue.value) // Computes value, overwrites getter
|
|
13
|
+
* console.log(myValue.value) // Returns cached value
|
|
14
|
+
* console.log(myValue.value) // Returns cached value
|
|
15
|
+
*/
|
|
16
16
|
declare function memoize<T>(getter: () => T): {
|
|
17
17
|
value: T;
|
|
18
18
|
};
|
|
19
19
|
/**
|
|
20
|
-
* Strictly typed `Object.keys`.
|
|
21
|
-
*/
|
|
22
|
-
declare function objectKeys<T extends Record<any, any>>(obj: T): Array<`${keyof T
|
|
20
|
+
* Strictly typed `Object.keys`.
|
|
21
|
+
*/
|
|
22
|
+
declare function objectKeys<T extends Record<any, any>>(obj: T): Array<`${Extract<keyof T, string | number>}`>;
|
|
23
23
|
/**
|
|
24
|
-
* Strictly typed `Object.entries`.
|
|
25
|
-
*/
|
|
24
|
+
* Strictly typed `Object.entries`.
|
|
25
|
+
*/
|
|
26
26
|
declare function objectEntries<T extends Record<any, any>>(obj: T): Array<[keyof T, T[keyof T]]>;
|
|
27
27
|
/**
|
|
28
|
-
* Deeply applies a callback to every key-value pair in the given object, as well as nested objects and arrays.
|
|
29
|
-
*/
|
|
28
|
+
* Deeply applies a callback to every key-value pair in the given object, as well as nested objects and arrays (including arrays nested inside arrays).
|
|
29
|
+
*/
|
|
30
30
|
declare function deepApply<T extends Record<any, any>>(data: T, callback: (item: T, key: keyof T, value: T[keyof T]) => void): void;
|
|
31
31
|
/**
|
|
32
|
-
* Checks if a value is
|
|
33
|
-
|
|
32
|
+
* Checks if a value is an object with the plain `[object Object]` tag.
|
|
33
|
+
*
|
|
34
|
+
* @remarks
|
|
35
|
+
* Returns `true` for object literals, class instances, and `null`-prototype
|
|
36
|
+
* objects – unlike stricter plain-object checks that reject class instances.
|
|
37
|
+
*/
|
|
34
38
|
declare function isObject(value: unknown): value is Record<any, any>;
|
|
35
39
|
//#endregion
|
|
36
40
|
export { deepApply, isObject, memoize, objectEntries, objectKeys };
|
package/dist/object.mjs
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
* - Auto-caches the result by overwriting the getter
|
|
6
6
|
*
|
|
7
7
|
* @remarks
|
|
8
|
-
* Useful for deferring initialization or expensive operations. Unlike a simple getter, there is no runtime overhead after the first
|
|
8
|
+
* Useful for deferring initialization or expensive operations. Unlike a simple getter, there is no runtime overhead after the first invocation, since the getter itself is overwritten with the memoized value.
|
|
9
9
|
*
|
|
10
10
|
* @example
|
|
11
|
-
* const myValue =
|
|
11
|
+
* const myValue = memoize(() => 'Hello, World!')
|
|
12
12
|
* console.log(myValue.value) // Computes value, overwrites getter
|
|
13
13
|
* console.log(myValue.value) // Returns cached value
|
|
14
14
|
* console.log(myValue.value) // Returns cached value
|
|
@@ -33,18 +33,24 @@ function objectEntries(obj) {
|
|
|
33
33
|
return Object.entries(obj);
|
|
34
34
|
}
|
|
35
35
|
/**
|
|
36
|
-
* Deeply applies a callback to every key-value pair in the given object, as well as nested objects and arrays.
|
|
36
|
+
* Deeply applies a callback to every key-value pair in the given object, as well as nested objects and arrays (including arrays nested inside arrays).
|
|
37
37
|
*/
|
|
38
38
|
function deepApply(data, callback) {
|
|
39
39
|
for (const [key, value] of Object.entries(data)) {
|
|
40
40
|
callback(data, key, value);
|
|
41
|
-
|
|
42
|
-
for (const element of value) if (isObject(element)) deepApply(element, callback);
|
|
43
|
-
} else if (isObject(value)) deepApply(value, callback);
|
|
41
|
+
applyToNestedValue(value, callback);
|
|
44
42
|
}
|
|
45
43
|
}
|
|
44
|
+
function applyToNestedValue(value, callback) {
|
|
45
|
+
if (Array.isArray(value)) for (const element of value) applyToNestedValue(element, callback);
|
|
46
|
+
else if (isObject(value)) deepApply(value, callback);
|
|
47
|
+
}
|
|
46
48
|
/**
|
|
47
|
-
* Checks if a value is
|
|
49
|
+
* Checks if a value is an object with the plain `[object Object]` tag.
|
|
50
|
+
*
|
|
51
|
+
* @remarks
|
|
52
|
+
* Returns `true` for object literals, class instances, and `null`-prototype
|
|
53
|
+
* objects – unlike stricter plain-object checks that reject class instances.
|
|
48
54
|
*/
|
|
49
55
|
function isObject(value) {
|
|
50
56
|
return Object.prototype.toString.call(value) === "[object Object]";
|
package/dist/path.d.mts
CHANGED
|
@@ -2,42 +2,46 @@
|
|
|
2
2
|
type QueryValue = string | number | boolean | QueryValue[] | Record<string, any> | null | undefined;
|
|
3
3
|
type QueryObject = Record<string, QueryValue | QueryValue[]>;
|
|
4
4
|
/**
|
|
5
|
-
* Removes the leading slash from the given path if it has one.
|
|
6
|
-
*/
|
|
5
|
+
* Removes the leading slash from the given path if it has one.
|
|
6
|
+
*/
|
|
7
7
|
declare function withoutLeadingSlash(path?: string): string;
|
|
8
8
|
/**
|
|
9
|
-
* Adds a leading slash to the given path if it does not already have one.
|
|
10
|
-
*/
|
|
9
|
+
* Adds a leading slash to the given path if it does not already have one.
|
|
10
|
+
*/
|
|
11
11
|
declare function withLeadingSlash(path?: string): string;
|
|
12
12
|
/**
|
|
13
|
-
* Removes the trailing slash from the given path if it has one,
|
|
14
|
-
* preserving query strings and hash fragments.
|
|
15
|
-
*/
|
|
13
|
+
* Removes the trailing slash from the given path if it has one,
|
|
14
|
+
* preserving query strings and hash fragments.
|
|
15
|
+
*/
|
|
16
16
|
declare function withoutTrailingSlash(path?: string): string;
|
|
17
17
|
/**
|
|
18
|
-
* Adds a trailing slash to the given path if it does not already have one,
|
|
19
|
-
* preserving query strings and hash fragments.
|
|
20
|
-
*/
|
|
18
|
+
* Adds a trailing slash to the given path if it does not already have one,
|
|
19
|
+
* preserving query strings and hash fragments.
|
|
20
|
+
*/
|
|
21
21
|
declare function withTrailingSlash(path?: string): string;
|
|
22
22
|
/**
|
|
23
|
-
* Joins the given URL path segments, ensuring that there is only one slash between them.
|
|
24
|
-
*/
|
|
23
|
+
* Joins the given URL path segments, ensuring that there is only one slash between them.
|
|
24
|
+
*/
|
|
25
25
|
declare function joinURL(...paths: (string | undefined)[]): string;
|
|
26
26
|
/**
|
|
27
|
-
* Adds the base path to the input path, if it is not already present.
|
|
28
|
-
*/
|
|
27
|
+
* Adds the base path to the input path, if it is not already present.
|
|
28
|
+
*/
|
|
29
29
|
declare function withBase(input?: string, base?: string): string;
|
|
30
30
|
/**
|
|
31
|
-
* Removes the base path from the input path, if it is present.
|
|
32
|
-
*/
|
|
31
|
+
* Removes the base path from the input path, if it is present.
|
|
32
|
+
*/
|
|
33
33
|
declare function withoutBase(input?: string, base?: string): string;
|
|
34
34
|
/**
|
|
35
|
-
* Returns the pathname of the given path, which is the path without the query string or hash.
|
|
36
|
-
|
|
35
|
+
* Returns the pathname of the given path, which is the path without the query string or hash.
|
|
36
|
+
*
|
|
37
|
+
* @remarks
|
|
38
|
+
* Absolute URLs (with a scheme, e.g. `https://example.com/foo`) return the URL's pathname.
|
|
39
|
+
* All other inputs are returned unchanged with the query string and hash removed.
|
|
40
|
+
*/
|
|
37
41
|
declare function getPathname(path?: string): string;
|
|
38
42
|
/**
|
|
39
|
-
* Returns the URL with the given query parameters. If a query parameter is undefined, it is omitted.
|
|
40
|
-
*/
|
|
43
|
+
* Returns the URL with the given query parameters. If a query parameter is undefined, it is omitted.
|
|
44
|
+
*/
|
|
41
45
|
declare function withQuery(input: string, query?: QueryObject): string;
|
|
42
46
|
//#endregion
|
|
43
47
|
export { QueryObject, QueryValue, getPathname, joinURL, withBase, withLeadingSlash, withQuery, withTrailingSlash, withoutBase, withoutLeadingSlash, withoutTrailingSlash };
|