@alextheman/utility 5.11.3 → 5.12.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.
@@ -0,0 +1,406 @@
1
+ //#region src/root/functions/arrayHelpers/fillArray.ts
2
+ /**
3
+ * Creates a new array where each element is the result of the provided callback.
4
+ *
5
+ * If the callback returns at least one Promise, the entire result will be wrapped
6
+ * in a `Promise` and resolved with `Promise.all`. Otherwise, a plain array is returned.
7
+ *
8
+ * @category Array Helpers
9
+ *
10
+ * @template ItemType - The return type of the callback (awaited if any items are a Promise) that becomes the type of the array items.
11
+ *
12
+ * @param callback - A function invoked with the current index. May return a value or a Promise.
13
+ * @param length - The desired length of the resulting array.
14
+ *
15
+ * @returns An array of the callback results, or a Promise resolving to one if the callback is async.
16
+ */
17
+ function fillArray(callback, length = 1) {
18
+ const outputArray = new Array(length).fill(null).map((_, index) => {
19
+ return callback(index);
20
+ });
21
+ if (outputArray.some((item) => {
22
+ return item instanceof Promise;
23
+ })) return Promise.all(outputArray);
24
+ return outputArray;
25
+ }
26
+ //#endregion
27
+ //#region src/root/functions/arrayHelpers/paralleliseArrays.ts
28
+ /**
29
+ * Creates a new array of tuples, each containing the item at the given index from both arrays.
30
+ *
31
+ * If `secondArray` is shorter than `firstArray`, the second position in the tuple
32
+ * will be `undefined`. Iteration always uses the length of the first array.
33
+ *
34
+ * @category Array Helpers
35
+ *
36
+ * @template FirstArrayItem
37
+ * @template SecondArrayItem
38
+ *
39
+ * @param firstArray - The first array. Each item in this will take up the first tuple spot.
40
+ * @param secondArray - The second array. Each item in this will take up the second tuple spot.
41
+ *
42
+ * @returns An array of `[firstItem, secondItem]` tuples for each index in `firstArray`.
43
+ */
44
+ function paralleliseArrays(firstArray, secondArray) {
45
+ const outputArray = [];
46
+ for (let i = 0; i < firstArray.length; i++) outputArray.push([firstArray[i], secondArray[i]]);
47
+ return outputArray;
48
+ }
49
+ //#endregion
50
+ //#region src/root/functions/taggedTemplate/interpolate.ts
51
+ /**
52
+ * Returns the result of interpolating a template string when given the strings and interpolations separately.
53
+ *
54
+ * You can pass a template string directly by doing:
55
+ *
56
+ * ```
57
+ * interpolate`Template string here`;
58
+ * ```
59
+ *
60
+ * In this case, it will be functionally the same as if you just wrote the template string by itself.
61
+ *
62
+ * @category Tagged Template
63
+ *
64
+ * @template InterpolationsType - The type of the interpolations.
65
+ *
66
+ * @param strings - The strings from the template to process.
67
+ * @param interpolations - An array of all interpolations from the template.
68
+ *
69
+ * @returns A new string with the strings and interpolations from the template applied.
70
+ */
71
+ function interpolate(strings, ...interpolations) {
72
+ let result = "";
73
+ for (const [string, interpolation = ""] of paralleliseArrays(strings, interpolations)) result += string + interpolation;
74
+ return result;
75
+ }
76
+ //#endregion
77
+ //#region src/root/functions/taggedTemplate/normaliseIndents.ts
78
+ function calculateTabSize(line, whitespaceLength) {
79
+ const potentialWhitespacePart = line.slice(0, whitespaceLength);
80
+ const trimmedString = line.trimStart();
81
+ if (potentialWhitespacePart.trim() !== "") return 0;
82
+ const tabSize = line.length - (trimmedString.length + whitespaceLength);
83
+ return tabSize < 0 ? 0 : tabSize;
84
+ }
85
+ function getWhitespaceLength(lines) {
86
+ const [firstNonEmptyLine] = lines.filter((line) => {
87
+ return line.trim() !== "";
88
+ });
89
+ return firstNonEmptyLine.length - firstNonEmptyLine.trimStart().length;
90
+ }
91
+ function reduceLines(lines, { preserveTabs = true }) {
92
+ const slicedLines = lines.slice(1);
93
+ const isFirstLineEmpty = lines[0].trim() === "";
94
+ const whitespaceLength = getWhitespaceLength(isFirstLineEmpty ? lines : slicedLines);
95
+ return (isFirstLineEmpty ? slicedLines : lines).map((line) => {
96
+ const tabSize = calculateTabSize(line, whitespaceLength);
97
+ return (preserveTabs ? fillArray(() => {
98
+ return " ";
99
+ }, tabSize).join("") : "") + line.trimStart();
100
+ }).join("\n");
101
+ }
102
+ /**
103
+ * Applies any options if provided, then removes any extraneous indents from a multi-line template string.
104
+ *
105
+ * You can pass a template string directly by doing:
106
+ *
107
+ * ```typescript
108
+ * normaliseIndents`Template string here
109
+ * with a new line
110
+ * and another new line`;
111
+ * ```
112
+ *
113
+ * You may also pass the options first, then invoke the resulting function with a template string:
114
+ *
115
+ * ```typescript
116
+ * normaliseIndents({ preserveTabs: false })`Template string here
117
+ * with a new line
118
+ * and another new line`;
119
+ * ```
120
+ *
121
+ * @category Tagged Template
122
+ *
123
+ * @param first - The strings from the template to process, or the options to apply.
124
+ * @param args - An array of all interpolations from the template.
125
+ *
126
+ * @returns An additional function to invoke, or a new string with the strings and interpolations from the template applied, and extraneous indents removed.
127
+ */
128
+ function normaliseIndents(first, ...args) {
129
+ if (typeof first === "object" && first !== null && !Array.isArray(first)) {
130
+ const options = first;
131
+ return (strings, ...interpolations) => {
132
+ return normaliseIndents(strings, ...interpolations, options);
133
+ };
134
+ }
135
+ const strings = first;
136
+ const options = typeof args[args.length - 1] === "object" && !Array.isArray(args[args.length - 1]) ? args.pop() : {};
137
+ return reduceLines(interpolate(strings, ...[...args]).split("\n"), options);
138
+ }
139
+ //#endregion
140
+ //#region src/root/functions/miscellaneous/sayHello.ts
141
+ /**
142
+ * Returns a string representing the lyrics to the package's theme song, Commit To You
143
+ *
144
+ * [Pls listen!](https://www.youtube.com/watch?v=mH-Sg-8EnxM)
145
+ *
146
+ * @returns The lyrics string in markdown format.
147
+ */
148
+ function sayHello() {
149
+ return normaliseIndents`
150
+ # Commit To You
151
+
152
+ ### Verse 1
153
+
154
+ I know you've been checking me out,
155
+ Shall we take it to the next level now?
156
+ 'Cause I really wanna be there all for you,
157
+ All for you!
158
+ Come on now, let's make a fresh start!
159
+ Pin my number, then you can take me out!
160
+ Can't you see I really do care about you,
161
+ About you!
162
+
163
+ ### Pre-chorus 1
164
+ Although our calendars are imperfect, at best,
165
+ I'd like to organise time with you! (with you!).
166
+ Just tell me when and I'll make it clear,
167
+ All clear for you,
168
+ All clear for you!
169
+ (One, two, three, go!)
170
+
171
+ ### Chorus
172
+ I wanna be of utility, I'll help you on the run!
173
+ I'll be the one here in the back, while you go have some fun!
174
+ Looking out for you tonight, I'll be the one you can rely on!
175
+ Watch you go and watch me pass by,
176
+ I'll be here!
177
+ I'll commit to you!
178
+
179
+ ### Verse 2
180
+ Though sometimes it won't be easy,
181
+ You'll be here to bring out the best in me,
182
+ And I'll hold myself to high standards for you!
183
+ All for you!
184
+ We'll grow as a pair, you and me,
185
+ We'll build up a healthy dependency,
186
+ You can build with me and I'll develop with you!
187
+ I'm with you!
188
+
189
+ ### Pre-chorus 2
190
+ I'll be with you when you're up or you're down,
191
+ We'll deal with all our problems together (together!)
192
+ Just tell me what you want, I'll make it clear,
193
+ All clear for you,
194
+ All clear for you!
195
+ (One, three, one, go!)
196
+
197
+ ### Chorus
198
+ I wanna be of utility, I'll help you on the run!
199
+ (help you on the run!)
200
+ I'll be the one here in the back, while you go have some fun!
201
+ (you go have some fun!)
202
+ Looking out for you tonight, I'll be the one you can rely on!
203
+ Watch you go and watch me pass by,
204
+ I'll be here!
205
+ I'll commit to you!
206
+
207
+ ### Bridge
208
+ Looking into our stack!
209
+ I'll commit to you!
210
+ We've got a lot to unpack!
211
+ I'll commit to you!
212
+ The environment that we're in!
213
+ I'll commit to you!
214
+ Delicate as a string!
215
+ I'll commit to you!
216
+
217
+ But I think you're my type!
218
+ I'll commit to you!
219
+ Oh, this feels all so right!
220
+ I'll commit to you!
221
+ Nothing stopping us now!
222
+ I'll commit to you!
223
+ Let's show them what we're about!
224
+ Two, three, four, go!
225
+
226
+ ### Final Chorus
227
+ I wanna be of utility, I'll help you on the run!
228
+ (help you on the run!)
229
+ I'll be the one here in the back, while you go have some fun!
230
+ (you go have some fun!)
231
+ Looking out for you tonight, I'll be the one you can rely on!
232
+ Watch you go and watch me pass by,
233
+ I'll be here!
234
+ I'll commit to you!
235
+
236
+ I wanna be of utility, I'll help you on the run!
237
+ (I'll commit to you!)
238
+ I'll be the one here in the back, while you go have some fun!
239
+ (I'll commit to you!)
240
+ Looking out for you tonight, I'll be the one you can rely on!
241
+ (I'll commit to you!)
242
+ Watch you go and watch me pass by,
243
+ (I'll commit to you!)
244
+ I'll be here!
245
+
246
+ ### Outro
247
+ I'll commit to you!
248
+ I'll commit to you!
249
+ I'll commit to you!
250
+ `;
251
+ }
252
+ //#endregion
253
+ //#region src/v6/CodeError.ts
254
+ /**
255
+ * Represents errors that can be described using a standardised error code, and a human-readable error message.
256
+ *
257
+ * @category Types
258
+ *
259
+ * @template ErrorCode The type of the standardised error code.
260
+ */
261
+ var CodeError = class CodeError extends Error {
262
+ code;
263
+ /**
264
+ * @param code - A standardised code (e.g. UNEXPECTED_DATA).
265
+ * @param message - A human-readable error message (e.g. The data provided is invalid).
266
+ * @param options - Extra options to pass to super Error constructor.
267
+ */
268
+ constructor(code, message = "Something went wrong.", options) {
269
+ super(message, options);
270
+ if (Error.captureStackTrace) Error.captureStackTrace(this, new.target);
271
+ this.name = new.target.name;
272
+ this.code = code;
273
+ Object.defineProperty(this, "message", { enumerable: true });
274
+ Object.setPrototypeOf(this, new.target.prototype);
275
+ }
276
+ /**
277
+ * Checks whether the given input may have been caused by a CodeError.
278
+ *
279
+ * @param input - The input to check.
280
+ *
281
+ * @returns `true` if the input is a CodeError, and `false` otherwise. The type of the input will also be narrowed down to CodeError if `true`.
282
+ */
283
+ static check(input) {
284
+ if (input instanceof CodeError) return true;
285
+ return typeof input === "object" && input !== null && "message" in input && typeof input.message === "string" && "code" in input && typeof input.code === "string";
286
+ }
287
+ static checkCaughtError(error, options) {
288
+ if (this.check(error)) {
289
+ if (options?.expectedCode && error.code !== options.expectedCode) throw new Error(normaliseIndents`The error code on the thrown error does not match the expected error code.
290
+
291
+ Expected: ${options.expectedCode}
292
+ Received: ${error.code}
293
+ `, { cause: error });
294
+ return error;
295
+ }
296
+ throw error;
297
+ }
298
+ /**
299
+ * Gets the thrown `CodeError` from a given function if one was thrown, and re-throws any other errors, or throws a default `CodeError` if no error thrown.
300
+ *
301
+ * @param errorFunction - The function expected to throw the error.
302
+ * @param options - Extra options to apply.
303
+ *
304
+ * @throws {Error} Any other errors thrown by the `errorFunction` that are not a `CodeError`.
305
+ * @throws {Error} If no `CodeError` was thrown by the `errorFunction`
306
+ *
307
+ * @returns The `CodeError` that was thrown by the `errorFunction`
308
+ */
309
+ static expectError(errorFunction, options) {
310
+ try {
311
+ errorFunction();
312
+ } catch (error) {
313
+ return this.checkCaughtError(error, options);
314
+ }
315
+ throw new Error(`Expected a ${this.name} to be thrown but none was thrown`);
316
+ }
317
+ /**
318
+ * Gets the thrown `CodeError` from a given asynchronous function if one was thrown, and re-throws any other errors, or throws a default `CodeError` if no error thrown.
319
+ *
320
+ * @param errorFunction - The function expected to throw the error.
321
+ * @param options - Extra options to apply.
322
+ *
323
+ * @throws {Error} Any other errors thrown by the `errorFunction` that are not a `CodeError`.
324
+ * @throws {Error} If no `CodeError` was thrown by the `errorFunction`
325
+ *
326
+ * @returns The `CodeError` that was thrown by the `errorFunction`
327
+ */
328
+ static async expectErrorAsync(errorFunction, options) {
329
+ try {
330
+ await errorFunction();
331
+ } catch (error) {
332
+ return this.checkCaughtError(error, options);
333
+ }
334
+ throw new Error(`Expected a ${this.name} to be thrown but none was thrown`);
335
+ }
336
+ };
337
+ //#endregion
338
+ //#region src/v6/DataError.ts
339
+ const DataErrorCode = { INVALID_DATA: "INVALID_DATA" };
340
+ /**
341
+ * Represents errors you may get that may've been caused by a specific piece of data.
342
+ *
343
+ * @category Types
344
+ *
345
+ * @template DataType - The type of the data that caused the error.
346
+ */
347
+ var DataError = class DataError extends CodeError {
348
+ data;
349
+ /**
350
+ * @param data - The data that caused the error.
351
+ * @param code - A standardised code (e.g. UNEXPECTED_DATA).
352
+ * @param message - A human-readable error message (e.g. The data provided is invalid).
353
+ * @param options - Extra options to pass to super Error constructor.
354
+ */
355
+ constructor(data, code = "INVALID_DATA", message = "The data provided is invalid", options) {
356
+ super(code, message, options);
357
+ if (Error.captureStackTrace) Error.captureStackTrace(this, new.target);
358
+ this.name = new.target.name;
359
+ this.code = code;
360
+ this.data = data;
361
+ Object.defineProperty(this, "message", { enumerable: true });
362
+ Object.setPrototypeOf(this, new.target.prototype);
363
+ }
364
+ /**
365
+ * Checks whether the given input may have been caused by a DataError.
366
+ *
367
+ * @param input - The input to check.
368
+ *
369
+ * @returns `true` if the input is a DataError, and `false` otherwise. The type of the input will also be narrowed down to DataError if `true`.
370
+ */
371
+ static check(input) {
372
+ if (input instanceof DataError) return true;
373
+ const data = input;
374
+ return typeof data === "object" && data !== null && typeof data.message === "string" && typeof data.code === "string" && "data" in data;
375
+ }
376
+ /**
377
+ * Gets the thrown `DataError` from a given function if one was thrown, and re-throws any other errors, or throws a default `DataError` if no error thrown.
378
+ *
379
+ * @param errorFunction - The function expected to throw the error.
380
+ * @param options - Extra options to apply.
381
+ *
382
+ * @throws {Error} Any other errors thrown by the `errorFunction` that are not a `DataError`.
383
+ * @throws {Error} If no `DataError` was thrown by the `errorFunction`
384
+ *
385
+ * @returns The `DataError` that was thrown by the `errorFunction`
386
+ */
387
+ static expectError(errorFunction, options) {
388
+ return super.expectError(errorFunction, options);
389
+ }
390
+ /**
391
+ * Gets the thrown `DataError` from a given asynchronous function if one was thrown, and re-throws any other errors, or throws a default `DataError` if no error thrown.
392
+ *
393
+ * @param errorFunction - The function expected to throw the error.
394
+ * @param options - Extra options to apply.
395
+ *
396
+ * @throws {Error} Any other errors thrown by the `errorFunction` that are not a `DataError`.
397
+ * @throws {Error} If no `DataError` was thrown by the `errorFunction`
398
+ *
399
+ * @returns The `DataError` that was thrown by the `errorFunction`
400
+ */
401
+ static async expectErrorAsync(errorFunction, options) {
402
+ return await super.expectErrorAsync(errorFunction, options);
403
+ }
404
+ };
405
+ //#endregion
406
+ export { CodeError, DataError, DataErrorCode, sayHello };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alextheman/utility",
3
- "version": "5.11.3",
3
+ "version": "5.12.0",
4
4
  "description": "Helpful utility functions.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -25,6 +25,11 @@
25
25
  "types": "./dist/node/index.d.ts",
26
26
  "import": "./dist/node/index.js",
27
27
  "require": "./dist/node/index.cjs"
28
+ },
29
+ "./v6": {
30
+ "types": "./dist/v6/index.d.ts",
31
+ "import": "./dist/v6/index.js",
32
+ "require": "./dist/v6/index.cjs"
28
33
  }
29
34
  },
30
35
  "files": [