@seedcord/utils 0.3.8 → 0.4.0-next.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 +1 -1
- package/dist/index.cjs +490 -273
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -265
- package/dist/index.d.mts +759 -0
- package/dist/index.mjs +461 -254
- package/dist/index.mjs.map +1 -1
- package/package.json +24 -20
- package/dist/index.d.ts +0 -265
package/dist/index.cjs
CHANGED
|
@@ -1,336 +1,553 @@
|
|
|
1
|
-
'
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
12
|
+
key = keys[i];
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
14
|
+
__defProp(to, key, {
|
|
15
|
+
get: ((k) => from[k]).bind(null, key),
|
|
16
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
24
|
+
value: mod,
|
|
25
|
+
enumerable: true
|
|
26
|
+
}) : target, mod));
|
|
2
27
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
28
|
+
//#endregion
|
|
29
|
+
let node_fs_promises = require("node:fs/promises");
|
|
30
|
+
let node_path = require("node:path");
|
|
31
|
+
node_path = __toESM(node_path, 1);
|
|
6
32
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
enumerable: true,
|
|
16
|
-
get: function () { return e[k]; }
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
n.default = e;
|
|
22
|
-
return Object.freeze(n);
|
|
33
|
+
//#region src/misc/assertNever.ts
|
|
34
|
+
/**
|
|
35
|
+
* Exhaustiveness guard for discriminated unions. Place in the `default` branch of a `switch` over a
|
|
36
|
+
* union's discriminant: if a new variant is added without a matching case, the call fails to compile.
|
|
37
|
+
* Throws at runtime if reached with a value the types said was impossible.
|
|
38
|
+
*/
|
|
39
|
+
function assertNever(value) {
|
|
40
|
+
throw new Error(`Unhandled discriminated union member: ${JSON.stringify(value)}`);
|
|
23
41
|
}
|
|
24
42
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/misc/directory.ts
|
|
45
|
+
/**
|
|
46
|
+
* Determines if a directory entry is a TypeScript or JavaScript file.
|
|
47
|
+
*
|
|
48
|
+
* @param entry - The directory entry to check.
|
|
49
|
+
* @returns True if the entry is a file ending with .ts or .js.
|
|
50
|
+
*/
|
|
29
51
|
function isTsOrJsFile(entry) {
|
|
30
|
-
|
|
52
|
+
return entry.isFile() && (entry.name.endsWith(".ts") || entry.name.endsWith(".js")) && !entry.name.endsWith(".d.ts") && !entry.name.endsWith(".map");
|
|
31
53
|
}
|
|
32
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Recursively traverses through a directory, importing all .ts and .js files and applying a callback to each import.
|
|
56
|
+
*
|
|
57
|
+
* @param dir - The directory path to traverse.
|
|
58
|
+
* @param callback - A function that will be called for each imported module. It receives the full file path, the file's relative path, and the imported module as arguments.
|
|
59
|
+
* @returns A Promise that resolves when the traversal is complete.
|
|
60
|
+
*/
|
|
33
61
|
async function traverseDirectory(dir, callback, logger) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
62
|
+
let entries;
|
|
63
|
+
try {
|
|
64
|
+
entries = await (0, node_fs_promises.readdir)(dir, { withFileTypes: true });
|
|
65
|
+
} catch (err) {
|
|
66
|
+
logger.error(`Failed to read directory ${dir}`, err);
|
|
67
|
+
entries = [];
|
|
68
|
+
}
|
|
69
|
+
for (const entry of entries) {
|
|
70
|
+
const fullPath = node_path.join(dir, entry.name);
|
|
71
|
+
const relativePath = node_path.relative(process.cwd(), fullPath);
|
|
72
|
+
if (entry.isDirectory()) await traverseDirectory(fullPath, callback, logger);
|
|
73
|
+
else if (isTsOrJsFile(entry)) await callback(fullPath, relativePath, await import(fullPath));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Formats a file path relative to the current working directory.
|
|
78
|
+
* @param filePath - The file path to format.
|
|
79
|
+
* @param options - Formatting options.
|
|
80
|
+
* @returns The formatted file path.
|
|
81
|
+
*/
|
|
82
|
+
function formatFilePath(filePath, options = {}) {
|
|
83
|
+
const { onlyDir = false, prefix = "./" } = options;
|
|
84
|
+
return `${prefix}${onlyDir ? node_path.relative(process.cwd(), filePath.replace(/\/[^/]*$/, "")) : node_path.relative(process.cwd(), filePath)}`;
|
|
53
85
|
}
|
|
54
|
-
__name(traverseDirectory, "traverseDirectory");
|
|
55
86
|
|
|
56
|
-
|
|
87
|
+
//#endregion
|
|
88
|
+
//#region src/misc/fyShuffle.ts
|
|
89
|
+
/**
|
|
90
|
+
* Shuffles an array using the Fisher-Yates algorithm.
|
|
91
|
+
* This function creates a new array with the same elements in a random order,
|
|
92
|
+
* without modifying the original array.
|
|
93
|
+
*
|
|
94
|
+
* @typeParam TArray - The type of elements in the array
|
|
95
|
+
* @param items - The array to shuffle
|
|
96
|
+
* @returns A new array with the same elements in a random order
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```typescript
|
|
100
|
+
* const numbers = [1, 2, 3, 4, 5];
|
|
101
|
+
* const shuffled = fyShuffle(numbers);
|
|
102
|
+
* // shuffled might be [3, 1, 5, 2, 4]
|
|
103
|
+
* // numbers is still [1, 2, 3, 4, 5]
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
57
106
|
function fyShuffle(items) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
];
|
|
65
|
-
}
|
|
66
|
-
return array;
|
|
107
|
+
const array = items.slice();
|
|
108
|
+
for (let i = array.length - 1; i > 0; i--) {
|
|
109
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
110
|
+
[array[i], array[j]] = [array[j], array[i]];
|
|
111
|
+
}
|
|
112
|
+
return array;
|
|
67
113
|
}
|
|
68
|
-
__name(fyShuffle, "fyShuffle");
|
|
69
114
|
|
|
70
|
-
|
|
115
|
+
//#endregion
|
|
116
|
+
//#region src/numbers/currentTime.ts
|
|
117
|
+
/**
|
|
118
|
+
* Return current time in seconds
|
|
119
|
+
*/
|
|
71
120
|
function currentTime() {
|
|
72
|
-
|
|
121
|
+
return Math.floor(Date.now() / 1e3);
|
|
73
122
|
}
|
|
74
|
-
__name(currentTime, "currentTime");
|
|
75
123
|
|
|
76
|
-
|
|
124
|
+
//#endregion
|
|
125
|
+
//#region src/numbers/generateCode.ts
|
|
126
|
+
/**
|
|
127
|
+
* Generates a random numeric code with the specified number of digits.
|
|
128
|
+
*
|
|
129
|
+
* @param digits - The number of digits for the generated code.
|
|
130
|
+
* @returns A random numeric code with the specified number of digits.
|
|
131
|
+
*/
|
|
77
132
|
function generateCode(digits) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
133
|
+
const min = Math.pow(10, digits - 1);
|
|
134
|
+
const max = Math.pow(10, digits) - 1;
|
|
135
|
+
return Math.floor(Math.random() * (max - min + 1) + min);
|
|
81
136
|
}
|
|
82
|
-
__name(generateCode, "generateCode");
|
|
83
|
-
function hexToNumber(hex) {
|
|
84
|
-
if (typeof hex !== "string") {
|
|
85
|
-
throw new services.SeedcordTypeError(services.SeedcordErrorCode.UtilHexInputType);
|
|
86
|
-
}
|
|
87
|
-
const normalized = hex.replace(/^#/, "");
|
|
88
|
-
if (!/^[0-9a-fA-F]+$/.test(normalized)) {
|
|
89
|
-
throw new services.SeedcordError(services.SeedcordErrorCode.UtilHexInvalid);
|
|
90
|
-
}
|
|
91
|
-
const converted = parseInt(normalized, 16);
|
|
92
|
-
if (Number.isNaN(converted)) {
|
|
93
|
-
throw new services.SeedcordError(services.SeedcordErrorCode.UtilHexInvalid);
|
|
94
|
-
}
|
|
95
|
-
return converted;
|
|
96
|
-
}
|
|
97
|
-
__name(hexToNumber, "hexToNumber");
|
|
98
137
|
|
|
99
|
-
|
|
138
|
+
//#endregion
|
|
139
|
+
//#region src/numbers/ordinal.ts
|
|
140
|
+
/**
|
|
141
|
+
* Returns the ordinal suffix for a given number.
|
|
142
|
+
*
|
|
143
|
+
* @param n - The number to get the ordinal for
|
|
144
|
+
* @returns The number with its ordinal suffix
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* ordinal(1); // "1st"
|
|
148
|
+
* ordinal(22); // "22nd"
|
|
149
|
+
* ordinal(13); // "13th"
|
|
150
|
+
*/
|
|
100
151
|
function ordinal(n) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
return `${n}${suffix}`;
|
|
152
|
+
const s = [
|
|
153
|
+
"th",
|
|
154
|
+
"st",
|
|
155
|
+
"nd",
|
|
156
|
+
"rd"
|
|
157
|
+
];
|
|
158
|
+
const v = n % 100;
|
|
159
|
+
const suffix = s[(v - 20) % 10] ?? s[v] ?? s[0];
|
|
160
|
+
if (!suffix) return `${n}th`;
|
|
161
|
+
return `${n}${suffix}`;
|
|
112
162
|
}
|
|
113
|
-
__name(ordinal, "ordinal");
|
|
114
163
|
|
|
115
|
-
|
|
164
|
+
//#endregion
|
|
165
|
+
//#region src/numbers/percentage.ts
|
|
166
|
+
/**
|
|
167
|
+
* Takes two numbers and returns the percentage of the first number in the second number with two decimal places.
|
|
168
|
+
*
|
|
169
|
+
* @param num1 - The first number.
|
|
170
|
+
* @param num2 - The second number.
|
|
171
|
+
*
|
|
172
|
+
* @returns The percentage of the first number in the second number with two decimal places.
|
|
173
|
+
*/
|
|
116
174
|
function percentage(num1, num2) {
|
|
117
|
-
|
|
175
|
+
return Number((num1 / num2 * 100).toFixed(2));
|
|
118
176
|
}
|
|
119
|
-
__name(percentage, "percentage");
|
|
120
177
|
|
|
121
|
-
|
|
178
|
+
//#endregion
|
|
179
|
+
//#region src/numbers/round.ts
|
|
180
|
+
/**
|
|
181
|
+
* Rounds a number to a specified number of decimal places.
|
|
182
|
+
*
|
|
183
|
+
* @param num - The number to be rounded.
|
|
184
|
+
* @param precision - The number of decimal places to round to.
|
|
185
|
+
* @returns The rounded number.
|
|
186
|
+
*/
|
|
122
187
|
function round(num, precision) {
|
|
123
|
-
|
|
124
|
-
|
|
188
|
+
const factor = Math.pow(10, precision);
|
|
189
|
+
return Math.round((num + Number.EPSILON) * factor) / factor;
|
|
125
190
|
}
|
|
126
|
-
__name(round, "round");
|
|
127
191
|
|
|
128
|
-
|
|
192
|
+
//#endregion
|
|
193
|
+
//#region src/numbers/roundToDenomination.ts
|
|
194
|
+
/**
|
|
195
|
+
* Rounds a number to a string representation with a denomination suffix.
|
|
196
|
+
* @param num - The number to round.
|
|
197
|
+
* @example
|
|
198
|
+
* ```ts
|
|
199
|
+
* roundToDenomination(1234); // "1.2K"
|
|
200
|
+
* roundToDenomination(10000, ['k', 'm', 'b', 't', 'q']); // "10k"
|
|
201
|
+
* roundToDenomination(12345678); // "12.3M"
|
|
202
|
+
* ```
|
|
203
|
+
* @returns The rounded number as a string with a denomination suffix.
|
|
204
|
+
*/
|
|
129
205
|
function roundToDenomination(num, opts) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
result = Math.ceil(Number(result)).toString();
|
|
155
|
-
}
|
|
156
|
-
if (result.endsWith(".0")) {
|
|
157
|
-
result = result.substring(0, result.length - 2);
|
|
158
|
-
}
|
|
159
|
-
if (result === "1000") {
|
|
160
|
-
index += 1;
|
|
161
|
-
result = "1";
|
|
162
|
-
}
|
|
163
|
-
return result + (index >= 0 ? suffixes[index] : "");
|
|
206
|
+
const { suffixes = [
|
|
207
|
+
"K",
|
|
208
|
+
"M",
|
|
209
|
+
"B",
|
|
210
|
+
"T",
|
|
211
|
+
"Q"
|
|
212
|
+
], precision = 1 } = opts ?? {};
|
|
213
|
+
if (num < 1e4) return num.toString();
|
|
214
|
+
let index = -1;
|
|
215
|
+
let temp = num;
|
|
216
|
+
while (temp >= 1e3 && index < suffixes.length - 1) {
|
|
217
|
+
temp /= 1e3;
|
|
218
|
+
index++;
|
|
219
|
+
}
|
|
220
|
+
let result;
|
|
221
|
+
if (temp % 1 === 0) result = temp.toString();
|
|
222
|
+
else result = (Math.round(temp * Math.pow(10, precision + 1)) / Math.pow(10, precision + 1)).toFixed(precision);
|
|
223
|
+
if (result.endsWith(".9")) result = Math.ceil(Number(result)).toString();
|
|
224
|
+
if (result.endsWith(".0")) result = result.substring(0, result.length - 2);
|
|
225
|
+
if (result === "1000") {
|
|
226
|
+
index += 1;
|
|
227
|
+
result = "1";
|
|
228
|
+
}
|
|
229
|
+
return result + (index >= 0 ? suffixes[index] : "");
|
|
164
230
|
}
|
|
165
|
-
__name(roundToDenomination, "roundToDenomination");
|
|
166
231
|
|
|
167
|
-
|
|
232
|
+
//#endregion
|
|
233
|
+
//#region src/objects/filterCirculars.ts
|
|
234
|
+
/**
|
|
235
|
+
* Creates a clean, JSON safe copy of a value and replaces circular references with a marker.
|
|
236
|
+
*
|
|
237
|
+
* In `json` mode it behaves like stringify then parse with a replacer that handles cycles and BigInt.
|
|
238
|
+
* In `decycle` mode it first clones without using toJSON, then you can stringify the result later.
|
|
239
|
+
*
|
|
240
|
+
* @typeParam ObjType - Type of the input value.
|
|
241
|
+
* @typeParam Marker - Marker string used for circular references.
|
|
242
|
+
*
|
|
243
|
+
* @param value - The value to clone safely.
|
|
244
|
+
* @param options - Optional configuration.
|
|
245
|
+
*
|
|
246
|
+
* @returns A JSON safe structure with circular references replaced by the marker.
|
|
247
|
+
*
|
|
248
|
+
* @example
|
|
249
|
+
* ```ts
|
|
250
|
+
* interface Test {
|
|
251
|
+
* name: string;
|
|
252
|
+
* self?: Test;
|
|
253
|
+
* }
|
|
254
|
+
*
|
|
255
|
+
* const obj: Test = { name: 'seedcord' };
|
|
256
|
+
* obj.self = obj;
|
|
257
|
+
*
|
|
258
|
+
* const clean = filterCirculars(obj);
|
|
259
|
+
* // ^? { name: string; self?: "[Circular]" | { ... } }
|
|
260
|
+
* console.log(clean.self); // "[Circular]"
|
|
261
|
+
* ```
|
|
262
|
+
*/
|
|
168
263
|
function filterCirculars(value, options) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
264
|
+
const logger = options?.logger;
|
|
265
|
+
const marker = options?.marker ?? "[Circular]";
|
|
266
|
+
if ((options?.mode ?? "decycle") === "json") return json(value, marker, logger);
|
|
267
|
+
try {
|
|
268
|
+
return decycle(value, marker);
|
|
269
|
+
} catch (error) {
|
|
270
|
+
logger?.error("filterCirculars decycle error", error);
|
|
271
|
+
return { "[unserializable]": "decycle failed" };
|
|
272
|
+
}
|
|
179
273
|
}
|
|
180
|
-
|
|
274
|
+
/**
|
|
275
|
+
* Attempts to build a JSONified object using JSON.stringify and JSON.parse.
|
|
276
|
+
*
|
|
277
|
+
* @internal
|
|
278
|
+
*/
|
|
181
279
|
function json(value, marker, logger) {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
logger?.error("filterCirculars parse error", error);
|
|
208
|
-
logger?.error("bad JSON", json2);
|
|
209
|
-
return value;
|
|
210
|
-
}
|
|
280
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
281
|
+
let json;
|
|
282
|
+
try {
|
|
283
|
+
json = JSON.stringify(value, (_k, v) => {
|
|
284
|
+
if (typeof v === "bigint") return v.toString();
|
|
285
|
+
if (typeof v === "object" && v !== null) {
|
|
286
|
+
const obj = v;
|
|
287
|
+
if (seen.has(obj)) return marker;
|
|
288
|
+
seen.add(obj);
|
|
289
|
+
}
|
|
290
|
+
return v;
|
|
291
|
+
});
|
|
292
|
+
} catch (error) {
|
|
293
|
+
logger?.error("filterCirculars stringify error", error);
|
|
294
|
+
if (typeof value === "object" && value !== null) logger?.error("top level keys", Object.keys(value));
|
|
295
|
+
return { "[unserializable]": "stringify failed" };
|
|
296
|
+
}
|
|
297
|
+
if (typeof json !== "string") return { "[unserializable]": "stringify returned undefined" };
|
|
298
|
+
try {
|
|
299
|
+
return JSON.parse(json);
|
|
300
|
+
} catch (error) {
|
|
301
|
+
logger?.error("filterCirculars parse error", error);
|
|
302
|
+
logger?.error("bad JSON", json);
|
|
303
|
+
return { "[unserializable]": "parse failed" };
|
|
304
|
+
}
|
|
211
305
|
}
|
|
212
|
-
|
|
306
|
+
/**
|
|
307
|
+
* Builds a JSON safe clone without calling toJSON on class instances.
|
|
308
|
+
*
|
|
309
|
+
* @internal
|
|
310
|
+
*/
|
|
213
311
|
function decycle(input, marker, seen = /* @__PURE__ */ new WeakSet()) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
312
|
+
const recur = (val) => {
|
|
313
|
+
if (val === null) return null;
|
|
314
|
+
const t = typeof val;
|
|
315
|
+
if (t === "bigint") return val.toString();
|
|
316
|
+
if (t !== "object") return val;
|
|
317
|
+
const obj = val;
|
|
318
|
+
if (seen.has(obj)) return marker;
|
|
319
|
+
seen.add(obj);
|
|
320
|
+
if (obj instanceof Date) return obj.toISOString();
|
|
321
|
+
if (obj instanceof RegExp) return obj.toString();
|
|
322
|
+
if (Array.isArray(obj)) return obj.map((item) => recur(item));
|
|
323
|
+
if (obj instanceof Map) return Array.from(obj, ([k, v]) => [recur(k), recur(v)]);
|
|
324
|
+
if (obj instanceof Set) return Array.from(obj, (v) => recur(v));
|
|
325
|
+
const out = {};
|
|
326
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
327
|
+
if (typeof v === "function") continue;
|
|
328
|
+
out[k] = recur(v);
|
|
329
|
+
}
|
|
330
|
+
return out;
|
|
331
|
+
};
|
|
332
|
+
return recur(input);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
//#endregion
|
|
336
|
+
//#region src/objects/hasKeys.ts
|
|
337
|
+
/**
|
|
338
|
+
* Checks for the presence of nested keys in an object that's possibly a distributed union, narrowing the object type accordingly.
|
|
339
|
+
*
|
|
340
|
+
* Checks if an object has the specified nested keys and that their values are not null or undefined. If they are not, the object type is narrowed to reflect the presence of these keys with their respective types from the original distributed union object.
|
|
341
|
+
*
|
|
342
|
+
* @param obj - The object to check.
|
|
343
|
+
* @param keys - An array of dot-notation paths to check.
|
|
344
|
+
* @returns True if all keys exist and are non-null/defined, narrowing the object type.
|
|
345
|
+
*
|
|
346
|
+
* @example
|
|
347
|
+
* ```ts
|
|
348
|
+
* interface Test {
|
|
349
|
+
* a?: {
|
|
350
|
+
* b?: {
|
|
351
|
+
* c?: string;
|
|
352
|
+
* };
|
|
353
|
+
* } | null;
|
|
354
|
+
* x?: number | null;
|
|
355
|
+
* }
|
|
356
|
+
*
|
|
357
|
+
* const obj: Test = { a: { b: { c: 'hello' } }, x: 42 };
|
|
358
|
+
*
|
|
359
|
+
* if (hasKeys(obj, ['a.b.c', 'x'])) {
|
|
360
|
+
* // Here, obj is narrowed to:
|
|
361
|
+
* // {
|
|
362
|
+
* // a: { b: { c: string } };
|
|
363
|
+
* // x: number;
|
|
364
|
+
* // }
|
|
365
|
+
* console.log(obj.a.b.c.toUpperCase()); // Safe to access and use
|
|
366
|
+
* console.log(obj.x.toFixed(2)); // Safe to access and use
|
|
367
|
+
* }
|
|
368
|
+
* ```
|
|
369
|
+
*/
|
|
370
|
+
function hasKeys(obj, keys) {
|
|
371
|
+
return keys.every((key) => {
|
|
372
|
+
const parts = key.split(".");
|
|
373
|
+
let current = obj;
|
|
374
|
+
for (const part of parts) {
|
|
375
|
+
if (current === null || current === void 0 || typeof current !== "object" && typeof current !== "function") return false;
|
|
376
|
+
if (!(part in current)) return false;
|
|
377
|
+
current = current[part];
|
|
378
|
+
}
|
|
379
|
+
return current !== null && current !== void 0;
|
|
380
|
+
});
|
|
244
381
|
}
|
|
245
|
-
__name(decycle, "decycle");
|
|
246
382
|
|
|
247
|
-
|
|
383
|
+
//#endregion
|
|
384
|
+
//#region src/objects/keepDefined.ts
|
|
385
|
+
/**
|
|
386
|
+
* Copies only the keys whose values are defined.
|
|
387
|
+
*
|
|
388
|
+
* @typeParam TObject - the original object type you're pulling from
|
|
389
|
+
* @typeParam TKey - the keys to copy when defined
|
|
390
|
+
* @param source - the object to read values from
|
|
391
|
+
* @param keys - optional list of keys to include when present. If omitted, all keys are considered
|
|
392
|
+
*
|
|
393
|
+
* @example
|
|
394
|
+
* ```ts
|
|
395
|
+
* interface Config {
|
|
396
|
+
* host?: string;
|
|
397
|
+
* port?: number;
|
|
398
|
+
* user?: string;
|
|
399
|
+
* password?: string;
|
|
400
|
+
* }
|
|
401
|
+
*
|
|
402
|
+
* const config: Config = {
|
|
403
|
+
* host: 'localhost',
|
|
404
|
+
* port: undefined,
|
|
405
|
+
* user: 'admin',
|
|
406
|
+
* password: undefined
|
|
407
|
+
* };
|
|
408
|
+
*
|
|
409
|
+
* const definedConfig = keepDefined(config, 'host', 'port', 'user', 'password');
|
|
410
|
+
* // Result: { host: 'localhost', user: 'admin' }
|
|
411
|
+
* ```
|
|
412
|
+
*/
|
|
248
413
|
function keepDefined(source, ...keys) {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
257
|
-
return result;
|
|
414
|
+
const selectedKeys = keys.length > 0 ? keys : Object.keys(source);
|
|
415
|
+
const result = {};
|
|
416
|
+
for (const key of selectedKeys) {
|
|
417
|
+
const value = source[key];
|
|
418
|
+
if (value !== void 0 && value !== null) result[key] = value;
|
|
419
|
+
}
|
|
420
|
+
return result;
|
|
258
421
|
}
|
|
259
|
-
__name(keepDefined, "keepDefined");
|
|
260
422
|
|
|
261
|
-
|
|
423
|
+
//#endregion
|
|
424
|
+
//#region src/strings/capitalize.ts
|
|
425
|
+
/**
|
|
426
|
+
* Returns the word with its first letter capitalized and the rest in lowercase.
|
|
427
|
+
* @param word - The word to be formatted.
|
|
428
|
+
* @returns The formatted word.
|
|
429
|
+
*/
|
|
262
430
|
function capitalize(word) {
|
|
263
|
-
|
|
431
|
+
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
|
|
264
432
|
}
|
|
265
|
-
__name(capitalize, "capitalize");
|
|
266
433
|
|
|
267
|
-
|
|
434
|
+
//#endregion
|
|
435
|
+
//#region src/strings/longestStringLength.ts
|
|
436
|
+
/**
|
|
437
|
+
* Function takes an array of strings or numbers and returns the number of characters in the longest string/number
|
|
438
|
+
*
|
|
439
|
+
* @param arr - The array of strings or numbers
|
|
440
|
+
* @returns The length of the longest element when converted to string
|
|
441
|
+
*/
|
|
268
442
|
function longestStringLength(arr) {
|
|
269
|
-
|
|
443
|
+
return Math.max(...arr.map((el) => el.toString().length));
|
|
270
444
|
}
|
|
271
|
-
__name(longestStringLength, "longestStringLength");
|
|
272
445
|
|
|
273
|
-
|
|
446
|
+
//#endregion
|
|
447
|
+
//#region src/strings/generateAsciiTable.ts
|
|
448
|
+
/**
|
|
449
|
+
* Generates an ASCII table from the provided data.
|
|
450
|
+
*
|
|
451
|
+
* @param data - The data to be displayed in the table.
|
|
452
|
+
* @returns The generated ASCII table as a string.
|
|
453
|
+
*/
|
|
274
454
|
function generateAsciiTable(data) {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
455
|
+
if (data.length === 0) return "";
|
|
456
|
+
const firstRow = data[0];
|
|
457
|
+
if (!firstRow || firstRow.length === 0) return "";
|
|
458
|
+
let table = "";
|
|
459
|
+
const columnWidths = [];
|
|
460
|
+
for (let i = 0; i < firstRow.length; i++) {
|
|
461
|
+
let maxWidth = 0;
|
|
462
|
+
for (const row of data) {
|
|
463
|
+
const cell = row[i];
|
|
464
|
+
if (cell !== void 0) maxWidth = Math.max(maxWidth, cell.length);
|
|
465
|
+
}
|
|
466
|
+
columnWidths.push(maxWidth);
|
|
467
|
+
}
|
|
468
|
+
const createLine = (char, left, intersect, right) => {
|
|
469
|
+
let line = left;
|
|
470
|
+
columnWidths.forEach((width, index) => {
|
|
471
|
+
line += char.repeat(width + 2);
|
|
472
|
+
if (index < columnWidths.length - 1) line += intersect;
|
|
473
|
+
else line += right;
|
|
474
|
+
});
|
|
475
|
+
line += "\n";
|
|
476
|
+
return line;
|
|
477
|
+
};
|
|
478
|
+
table += createLine("═", "╔", "╦", "╗");
|
|
479
|
+
data.forEach((row, rowIndex) => {
|
|
480
|
+
table += "║";
|
|
481
|
+
row.forEach((cell, columnIndex) => {
|
|
482
|
+
const columnWidth = columnWidths[columnIndex];
|
|
483
|
+
if (columnWidth !== void 0) table += ` ${cell.padEnd(columnWidth)} ║`;
|
|
484
|
+
});
|
|
485
|
+
table += "\n";
|
|
486
|
+
if (rowIndex < data.length - 1) table += createLine("─", "╠", "╬", "╣");
|
|
487
|
+
else table += createLine("═", "╚", "╩", "╝");
|
|
488
|
+
});
|
|
489
|
+
return table;
|
|
310
490
|
}
|
|
311
|
-
__name(generateAsciiTable, "generateAsciiTable");
|
|
312
491
|
|
|
313
|
-
|
|
492
|
+
//#endregion
|
|
493
|
+
//#region src/strings/prettify.ts
|
|
494
|
+
/**
|
|
495
|
+
* Converts a string from any common naming convention to human-readable format.
|
|
496
|
+
* Accepts camelCase, PascalCase, snake_case, and kebab-case input.
|
|
497
|
+
*
|
|
498
|
+
* @param key - The string to convert
|
|
499
|
+
* @param opts - Optional configuration
|
|
500
|
+
* @returns A space-separated, human-readable string
|
|
501
|
+
*
|
|
502
|
+
* @example
|
|
503
|
+
* prettify("camelCaseString") // "camel Case String"
|
|
504
|
+
* prettify("PascalCaseString") // "Pascal Case String"
|
|
505
|
+
* prettify("snake_case_string") // "snake case string"
|
|
506
|
+
* prettify("kebab-case-string") // "kebab case string"
|
|
507
|
+
* prettify("mixedCase_string-name") // "mixed Case string name"
|
|
508
|
+
*/
|
|
314
509
|
function prettify(key, opts) {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
510
|
+
const result = key.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").trim();
|
|
511
|
+
if (opts?.capitalize) return capitalize(result);
|
|
512
|
+
return result;
|
|
318
513
|
}
|
|
319
|
-
__name(prettify, "prettify");
|
|
320
514
|
|
|
321
|
-
|
|
515
|
+
//#endregion
|
|
516
|
+
//#region src/strings/prettyDifference.ts
|
|
517
|
+
/**
|
|
518
|
+
* Calculates the difference between two numbers and formats it as a string with a '+' prefix for positive differences.
|
|
519
|
+
*
|
|
520
|
+
* @param numBefore - The initial number value
|
|
521
|
+
* @param numAfter - The final number value
|
|
522
|
+
* @returns A string representing the difference, with a '+' sign for positive differences
|
|
523
|
+
*
|
|
524
|
+
* @example
|
|
525
|
+
* // Returns "+5"
|
|
526
|
+
* prettyDifference(10, 15);
|
|
527
|
+
*
|
|
528
|
+
* @example
|
|
529
|
+
* // Returns "-3"
|
|
530
|
+
* prettyDifference(10, 7);
|
|
531
|
+
*/
|
|
322
532
|
function prettyDifference(numBefore, numAfter) {
|
|
323
|
-
|
|
533
|
+
return (numAfter - numBefore > 0 ? `+${numAfter - numBefore}` : numAfter - numBefore).toString();
|
|
324
534
|
}
|
|
325
|
-
__name(prettyDifference, "prettyDifference");
|
|
326
535
|
|
|
536
|
+
//#endregion
|
|
537
|
+
//#region src/index.ts
|
|
538
|
+
/** Package version */
|
|
539
|
+
const version = "0.4.0-next.0";
|
|
540
|
+
|
|
541
|
+
//#endregion
|
|
542
|
+
exports.assertNever = assertNever;
|
|
327
543
|
exports.capitalize = capitalize;
|
|
328
544
|
exports.currentTime = currentTime;
|
|
329
545
|
exports.filterCirculars = filterCirculars;
|
|
546
|
+
exports.formatFilePath = formatFilePath;
|
|
330
547
|
exports.fyShuffle = fyShuffle;
|
|
331
548
|
exports.generateAsciiTable = generateAsciiTable;
|
|
332
549
|
exports.generateCode = generateCode;
|
|
333
|
-
exports.
|
|
550
|
+
exports.hasKeys = hasKeys;
|
|
334
551
|
exports.isTsOrJsFile = isTsOrJsFile;
|
|
335
552
|
exports.keepDefined = keepDefined;
|
|
336
553
|
exports.longestStringLength = longestStringLength;
|
|
@@ -341,5 +558,5 @@ exports.prettyDifference = prettyDifference;
|
|
|
341
558
|
exports.round = round;
|
|
342
559
|
exports.roundToDenomination = roundToDenomination;
|
|
343
560
|
exports.traverseDirectory = traverseDirectory;
|
|
344
|
-
|
|
561
|
+
exports.version = version;
|
|
345
562
|
//# sourceMappingURL=index.cjs.map
|