@alextheman/utility 5.1.4 → 5.2.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/dist/index.cjs CHANGED
@@ -164,6 +164,17 @@ var DataError = class DataError extends Error {
164
164
  Object.defineProperty(this, "message", { enumerable: true });
165
165
  Object.setPrototypeOf(this, new.target.prototype);
166
166
  }
167
+ static checkCaughtError(error, options) {
168
+ if (DataError.check(error)) {
169
+ 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.
170
+
171
+ Expected: ${options.expectedCode}
172
+ Received: ${error.code}
173
+ `, { cause: error });
174
+ return error;
175
+ }
176
+ throw error;
177
+ }
167
178
  /**
168
179
  * Checks whether the given input may have been caused by a DataError.
169
180
  *
@@ -176,6 +187,44 @@ var DataError = class DataError extends Error {
176
187
  const data = input;
177
188
  return typeof data === "object" && data !== null && typeof data.message === "string" && typeof data.code === "string" && "data" in data;
178
189
  }
190
+ /**
191
+ * 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.
192
+ *
193
+ * @param errorFunction - The function expected to throw the error.
194
+ * @param options - Extra options to apply.
195
+ *
196
+ * @throws {Error} Any other errors thrown by the `errorFunction` that are not a `DataError`.
197
+ * @throws {Error} If no `DataError` was thrown by the `errorFunction`
198
+ *
199
+ * @returns The `DataError` that was thrown by the `errorFunction`
200
+ */
201
+ static expectError(errorFunction, options) {
202
+ try {
203
+ errorFunction();
204
+ } catch (error) {
205
+ return DataError.checkCaughtError(error, options);
206
+ }
207
+ throw new Error("Expected a DataError to be thrown but none was thrown");
208
+ }
209
+ /**
210
+ * 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.
211
+ *
212
+ * @param errorFunction - The function expected to throw the error.
213
+ * @param options - Extra options to apply.
214
+ *
215
+ * @throws {Error} Any other errors thrown by the `errorFunction` that are not a `DataError`.
216
+ * @throws {Error} If no `DataError` was thrown by the `errorFunction`
217
+ *
218
+ * @returns The `DataError` that was thrown by the `errorFunction`
219
+ */
220
+ static async expectErrorAsync(errorFunction, options) {
221
+ try {
222
+ await errorFunction();
223
+ } catch (error) {
224
+ return DataError.checkCaughtError(error, options);
225
+ }
226
+ throw new Error("Expected a DataError to be thrown but none was thrown");
227
+ }
179
228
  };
180
229
 
181
230
  //#endregion
@@ -430,19 +479,27 @@ function randomiseArray(array) {
430
479
  * @param stop - The number to stop at (exclusive).
431
480
  * @param step - The step size between numbers, defaulting to 1.
432
481
  *
433
- * @throws {Error} If `step` is `0`.
434
- * @throws {Error} If `step` direction does not match the order of `start` and `stop`.
482
+ * @throws {DataError} If `step` is `0`.
483
+ * @throws {DataError} If `step` direction does not match the order of `start` and `stop`.
435
484
  *
436
485
  * @returns An array of numbers satisfying the range provided.
437
486
  */
438
487
  function range(start, stop, step = 1) {
439
488
  const numbers = [];
440
- if (step === 0) throw new Error("ZERO_STEP_SIZE_NOT_ALLOWED");
489
+ if (step === 0) throw new DataError({ step }, "ZERO_STEP_SIZE", "Step size cannot be zero.");
441
490
  else if (step > 0) {
442
- if (start > stop) throw new Error("INVALID_BOUNDARIES");
491
+ if (start > stop) throw new DataError({
492
+ start,
493
+ stop,
494
+ step
495
+ }, "INVALID_BOUNDARIES", "The starting value cannot be bigger than the final value if step is positive");
443
496
  for (let i = start; i < stop; i += step) numbers.push(i);
444
497
  } else if (step < 0) {
445
- if (start < stop) throw new Error("INVALID_BOUNDARIES");
498
+ if (start < stop) throw new DataError({
499
+ start,
500
+ stop,
501
+ step
502
+ }, "INVALID_BOUNDARIES", "The final value cannot be bigger than the starting value if step is negative");
446
503
  for (let i = start; i > stop; i += step) numbers.push(i);
447
504
  }
448
505
  return numbers;
@@ -622,7 +679,7 @@ function isMonthlyMultiple(firstDate, secondDate) {
622
679
  *
623
680
  * @param file - The file to convert.
624
681
  *
625
- * @throws {Error} If the file reader gives an error.
682
+ * @throws {Error | DataError} If the file reader gives an error.
626
683
  *
627
684
  * @returns A promise that resolves to the encoded base 64 string.
628
685
  */
@@ -632,7 +689,7 @@ function convertFileToBase64(file) {
632
689
  reader.readAsDataURL(file);
633
690
  reader.onload = () => {
634
691
  if (reader.result === null) {
635
- reject(/* @__PURE__ */ new Error("FILE_CONVERSION_ERROR"));
692
+ reject(new DataError({ result: reader.result }, "FILE_CONVERSION_ERROR", "Could not convert the given file."));
636
693
  return;
637
694
  }
638
695
  resolve(reader.result);
@@ -648,6 +705,9 @@ function convertFileToBase64(file) {
648
705
  function getNullableResolutionStrategy(key, strategy) {
649
706
  return (typeof strategy === "object" ? strategy[key] : strategy) ?? "empty";
650
707
  }
708
+ function isPrimitive(item) {
709
+ return typeof item === "string" || typeof item === "number" || typeof item === "boolean";
710
+ }
651
711
  /**
652
712
  * Creates FormData from a given object, resolving non-string types as appropriate.
653
713
  *
@@ -674,7 +734,7 @@ function createFormData(data, options = {
674
734
  formData.append(String(key), JSON.stringify(value));
675
735
  break;
676
736
  case "omit": break;
677
- default: throw new TypeError("SLOPPY_PURE_JAVASCRIPT_USER_ERROR");
737
+ default: throw resolutionStrategy;
678
738
  }
679
739
  }
680
740
  function resolveNullables(key, value, options) {
@@ -697,10 +757,10 @@ function createFormData(data, options = {
697
757
  if (Array.isArray(value)) {
698
758
  if (value.some((item) => {
699
759
  return item instanceof Blob;
700
- }) && (options.arrayResolution === "stringify" || typeof options.arrayResolution === "object" && options.arrayResolution[key] === "stringify")) throw new TypeError("CANNOT_STRINGIFY_BLOB");
760
+ }) && (options.arrayResolution === "stringify" || typeof options.arrayResolution === "object" && options.arrayResolution[key] === "stringify")) throw new DataError({ value }, "CANNOT_STRINGIFY_BLOB", "Files/blobs cannot be stringified. Please change the resolution option for this item to \"multiple\" instead.");
701
761
  if (options.arrayResolution === "multiple" || typeof options.arrayResolution === "object" && options.arrayResolution[key] === "multiple") {
702
762
  for (const item of value) {
703
- if ((typeof item === "object" || !item) && !(item instanceof Blob)) throw new TypeError("NON_PRIMITIVE_ARRAY_ITEMS_FOUND");
763
+ if (!isPrimitive(item) && !(item instanceof Blob)) throw new DataError({ item }, "NON_PRIMITIVE_ARRAY_ITEMS_FOUND", "Cannot directly add non-primitive data to FormData. Please change the resolution option for this item to \"stringify\" instead.");
704
764
  if (item instanceof Blob) formData.append(String(key), item);
705
765
  else formData.append(String(key), String(item));
706
766
  }
@@ -1442,12 +1502,12 @@ async function encryptWithKey(publicKey, plaintextValue) {
1442
1502
  *
1443
1503
  * @param stringToAppendTo - The string to append a semicolon to.
1444
1504
  *
1445
- * @throws {Error} If the string contains multiple lines.
1505
+ * @throws {DataError} If the string contains multiple lines.
1446
1506
  *
1447
1507
  * @returns A string with the semicolon appended.
1448
1508
  */
1449
1509
  function appendSemicolon(stringToAppendTo) {
1450
- if (stringToAppendTo.includes("\n")) throw new Error("MULTIPLE_LINE_ERROR");
1510
+ if (stringToAppendTo.includes("\n")) throw new DataError({ stringToAppendTo }, "MULTIPLE_LINE_ERROR", "Cannot append semicolon to multi-line string.");
1451
1511
  const stringWithNoTrailingWhitespace = stringToAppendTo.trimEnd();
1452
1512
  if (stringWithNoTrailingWhitespace === "") return "";
1453
1513
  return stringWithNoTrailingWhitespace[stringWithNoTrailingWhitespace.length - 1] === ";" ? stringWithNoTrailingWhitespace : `${stringWithNoTrailingWhitespace};`;
@@ -1499,34 +1559,35 @@ function camelToKebab(string, options = { preserveConsecutiveCapitals: true }) {
1499
1559
  *
1500
1560
  * @category String Helpers
1501
1561
  *
1502
- * @param string - The string to convert.
1562
+ * @param input - The string to convert.
1503
1563
  * @param options - Options to apply to the conversion.
1504
1564
  *
1505
1565
  * @returns The string converted to camelCase.
1506
1566
  */
1507
- function kebabToCamel(string, options) {
1508
- if (string !== string.toLowerCase()) throw new Error("INVALID_KEBAB_CASE_INPUT");
1509
- if (string.startsWith("-") || string.endsWith("-") || string.includes("--")) throw new Error("INVALID_KEBAB_CASE_INPUT");
1567
+ function kebabToCamel(input, options) {
1568
+ if (input !== input.toLowerCase()) throw new DataError({ input }, "UPPERCASE_INPUT", "Kebab-case must be purely lowercase.");
1569
+ if (input.startsWith("-") || input.endsWith("-")) throw new DataError({ input }, "TRAILING_DASHES", "Dashes at the start and/or end are not allowed.");
1570
+ if (input.includes("--")) throw new DataError({ input }, "CONSECUTIVE_DASHES", "Consecutive dashes are not allowed.");
1510
1571
  let outputString = "";
1511
1572
  let skip = false;
1512
- for (const stringIndex in [...string]) {
1573
+ for (const stringIndex in [...input]) {
1513
1574
  if (skip) {
1514
1575
  skip = false;
1515
1576
  continue;
1516
1577
  }
1517
1578
  const index = parseIntStrict(stringIndex);
1518
1579
  if (index === 0 && options?.startWithUpper) {
1519
- outputString += string[index].toUpperCase();
1580
+ outputString += input[index].toUpperCase();
1520
1581
  continue;
1521
1582
  }
1522
- if (index === string.length - 1) {
1523
- outputString += string[index];
1583
+ if (index === input.length - 1) {
1584
+ outputString += input[index];
1524
1585
  break;
1525
1586
  }
1526
- if (string[index] === "-" && /^[a-zA-Z]+$/.test(string[index + 1])) {
1527
- outputString += string[index + 1].toUpperCase();
1587
+ if (input[index] === "-" && /^[a-zA-Z]+$/.test(input[index + 1])) {
1588
+ outputString += input[index + 1].toUpperCase();
1528
1589
  skip = true;
1529
- } else outputString += string[index];
1590
+ } else outputString += input[index];
1530
1591
  }
1531
1592
  return outputString;
1532
1593
  }
package/dist/index.d.cts CHANGED
@@ -87,8 +87,8 @@ declare function randomiseArray<ItemType>(array: ItemType[]): ItemType[];
87
87
  * @param stop - The number to stop at (exclusive).
88
88
  * @param step - The step size between numbers, defaulting to 1.
89
89
  *
90
- * @throws {Error} If `step` is `0`.
91
- * @throws {Error} If `step` direction does not match the order of `start` and `stop`.
90
+ * @throws {DataError} If `step` is `0`.
91
+ * @throws {DataError} If `step` direction does not match the order of `start` and `stop`.
92
92
  *
93
93
  * @returns An array of numbers satisfying the range provided.
94
94
  */
@@ -198,7 +198,7 @@ declare function isSameDate(firstDate: Date, secondDate: Date): boolean;
198
198
  *
199
199
  * @param file - The file to convert.
200
200
  *
201
- * @throws {Error} If the file reader gives an error.
201
+ * @throws {Error | DataError} If the file reader gives an error.
202
202
  *
203
203
  * @returns A promise that resolves to the encoded base 64 string.
204
204
  */
@@ -239,6 +239,9 @@ declare class APIError extends Error {
239
239
  type RecordKey = string | number | symbol;
240
240
  //#endregion
241
241
  //#region src/root/types/DataError.d.ts
242
+ interface ExpectErrorOptions {
243
+ expectedCode?: string;
244
+ }
242
245
  /**
243
246
  * Represents errors you may get that may've been caused by a specific piece of data.
244
247
  *
@@ -256,6 +259,7 @@ declare class DataError<DataType extends Record<RecordKey, unknown> = Record<Rec
256
259
  * @param options - Extra options to pass to super Error constructor.
257
260
  */
258
261
  constructor(data: DataType, code?: string, message?: string, options?: ErrorOptions);
262
+ private static checkCaughtError;
259
263
  /**
260
264
  * Checks whether the given input may have been caused by a DataError.
261
265
  *
@@ -264,6 +268,30 @@ declare class DataError<DataType extends Record<RecordKey, unknown> = Record<Rec
264
268
  * @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`.
265
269
  */
266
270
  static check<DataType extends Record<RecordKey, unknown> = Record<RecordKey, unknown>>(input: unknown): input is DataError<DataType>;
271
+ /**
272
+ * 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.
273
+ *
274
+ * @param errorFunction - The function expected to throw the error.
275
+ * @param options - Extra options to apply.
276
+ *
277
+ * @throws {Error} Any other errors thrown by the `errorFunction` that are not a `DataError`.
278
+ * @throws {Error} If no `DataError` was thrown by the `errorFunction`
279
+ *
280
+ * @returns The `DataError` that was thrown by the `errorFunction`
281
+ */
282
+ static expectError(errorFunction: () => unknown, options?: ExpectErrorOptions): DataError;
283
+ /**
284
+ * 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.
285
+ *
286
+ * @param errorFunction - The function expected to throw the error.
287
+ * @param options - Extra options to apply.
288
+ *
289
+ * @throws {Error} Any other errors thrown by the `errorFunction` that are not a `DataError`.
290
+ * @throws {Error} If no `DataError` was thrown by the `errorFunction`
291
+ *
292
+ * @returns The `DataError` that was thrown by the `errorFunction`
293
+ */
294
+ static expectErrorAsync(errorFunction: () => Promise<unknown>, options?: ExpectErrorOptions): Promise<DataError>;
267
295
  }
268
296
  //#endregion
269
297
  //#region src/root/types/VersionNumber.d.ts
@@ -821,7 +849,7 @@ declare function encryptWithKey(publicKey: string, plaintextValue: string): Prom
821
849
  *
822
850
  * @param stringToAppendTo - The string to append a semicolon to.
823
851
  *
824
- * @throws {Error} If the string contains multiple lines.
852
+ * @throws {DataError} If the string contains multiple lines.
825
853
  *
826
854
  * @returns A string with the semicolon appended.
827
855
  */
@@ -864,12 +892,12 @@ interface KebabToCamelOptions {
864
892
  *
865
893
  * @category String Helpers
866
894
  *
867
- * @param string - The string to convert.
895
+ * @param input - The string to convert.
868
896
  * @param options - Options to apply to the conversion.
869
897
  *
870
898
  * @returns The string converted to camelCase.
871
899
  */
872
- declare function kebabToCamel(string: string, options?: KebabToCamelOptions): string;
900
+ declare function kebabToCamel(input: string, options?: KebabToCamelOptions): string;
873
901
  //#endregion
874
902
  //#region src/root/functions/stringHelpers/truncate.d.ts
875
903
  /**
package/dist/index.d.ts CHANGED
@@ -87,8 +87,8 @@ declare function randomiseArray<ItemType>(array: ItemType[]): ItemType[];
87
87
  * @param stop - The number to stop at (exclusive).
88
88
  * @param step - The step size between numbers, defaulting to 1.
89
89
  *
90
- * @throws {Error} If `step` is `0`.
91
- * @throws {Error} If `step` direction does not match the order of `start` and `stop`.
90
+ * @throws {DataError} If `step` is `0`.
91
+ * @throws {DataError} If `step` direction does not match the order of `start` and `stop`.
92
92
  *
93
93
  * @returns An array of numbers satisfying the range provided.
94
94
  */
@@ -198,7 +198,7 @@ declare function isSameDate(firstDate: Date, secondDate: Date): boolean;
198
198
  *
199
199
  * @param file - The file to convert.
200
200
  *
201
- * @throws {Error} If the file reader gives an error.
201
+ * @throws {Error | DataError} If the file reader gives an error.
202
202
  *
203
203
  * @returns A promise that resolves to the encoded base 64 string.
204
204
  */
@@ -239,6 +239,9 @@ declare class APIError extends Error {
239
239
  type RecordKey = string | number | symbol;
240
240
  //#endregion
241
241
  //#region src/root/types/DataError.d.ts
242
+ interface ExpectErrorOptions {
243
+ expectedCode?: string;
244
+ }
242
245
  /**
243
246
  * Represents errors you may get that may've been caused by a specific piece of data.
244
247
  *
@@ -256,6 +259,7 @@ declare class DataError<DataType extends Record<RecordKey, unknown> = Record<Rec
256
259
  * @param options - Extra options to pass to super Error constructor.
257
260
  */
258
261
  constructor(data: DataType, code?: string, message?: string, options?: ErrorOptions);
262
+ private static checkCaughtError;
259
263
  /**
260
264
  * Checks whether the given input may have been caused by a DataError.
261
265
  *
@@ -264,6 +268,30 @@ declare class DataError<DataType extends Record<RecordKey, unknown> = Record<Rec
264
268
  * @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`.
265
269
  */
266
270
  static check<DataType extends Record<RecordKey, unknown> = Record<RecordKey, unknown>>(input: unknown): input is DataError<DataType>;
271
+ /**
272
+ * 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.
273
+ *
274
+ * @param errorFunction - The function expected to throw the error.
275
+ * @param options - Extra options to apply.
276
+ *
277
+ * @throws {Error} Any other errors thrown by the `errorFunction` that are not a `DataError`.
278
+ * @throws {Error} If no `DataError` was thrown by the `errorFunction`
279
+ *
280
+ * @returns The `DataError` that was thrown by the `errorFunction`
281
+ */
282
+ static expectError(errorFunction: () => unknown, options?: ExpectErrorOptions): DataError;
283
+ /**
284
+ * 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.
285
+ *
286
+ * @param errorFunction - The function expected to throw the error.
287
+ * @param options - Extra options to apply.
288
+ *
289
+ * @throws {Error} Any other errors thrown by the `errorFunction` that are not a `DataError`.
290
+ * @throws {Error} If no `DataError` was thrown by the `errorFunction`
291
+ *
292
+ * @returns The `DataError` that was thrown by the `errorFunction`
293
+ */
294
+ static expectErrorAsync(errorFunction: () => Promise<unknown>, options?: ExpectErrorOptions): Promise<DataError>;
267
295
  }
268
296
  //#endregion
269
297
  //#region src/root/types/VersionNumber.d.ts
@@ -821,7 +849,7 @@ declare function encryptWithKey(publicKey: string, plaintextValue: string): Prom
821
849
  *
822
850
  * @param stringToAppendTo - The string to append a semicolon to.
823
851
  *
824
- * @throws {Error} If the string contains multiple lines.
852
+ * @throws {DataError} If the string contains multiple lines.
825
853
  *
826
854
  * @returns A string with the semicolon appended.
827
855
  */
@@ -864,12 +892,12 @@ interface KebabToCamelOptions {
864
892
  *
865
893
  * @category String Helpers
866
894
  *
867
- * @param string - The string to convert.
895
+ * @param input - The string to convert.
868
896
  * @param options - Options to apply to the conversion.
869
897
  *
870
898
  * @returns The string converted to camelCase.
871
899
  */
872
- declare function kebabToCamel(string: string, options?: KebabToCamelOptions): string;
900
+ declare function kebabToCamel(input: string, options?: KebabToCamelOptions): string;
873
901
  //#endregion
874
902
  //#region src/root/functions/stringHelpers/truncate.d.ts
875
903
  /**
package/dist/index.js CHANGED
@@ -134,6 +134,17 @@ var DataError = class DataError extends Error {
134
134
  Object.defineProperty(this, "message", { enumerable: true });
135
135
  Object.setPrototypeOf(this, new.target.prototype);
136
136
  }
137
+ static checkCaughtError(error, options) {
138
+ if (DataError.check(error)) {
139
+ 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.
140
+
141
+ Expected: ${options.expectedCode}
142
+ Received: ${error.code}
143
+ `, { cause: error });
144
+ return error;
145
+ }
146
+ throw error;
147
+ }
137
148
  /**
138
149
  * Checks whether the given input may have been caused by a DataError.
139
150
  *
@@ -146,6 +157,44 @@ var DataError = class DataError extends Error {
146
157
  const data = input;
147
158
  return typeof data === "object" && data !== null && typeof data.message === "string" && typeof data.code === "string" && "data" in data;
148
159
  }
160
+ /**
161
+ * 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.
162
+ *
163
+ * @param errorFunction - The function expected to throw the error.
164
+ * @param options - Extra options to apply.
165
+ *
166
+ * @throws {Error} Any other errors thrown by the `errorFunction` that are not a `DataError`.
167
+ * @throws {Error} If no `DataError` was thrown by the `errorFunction`
168
+ *
169
+ * @returns The `DataError` that was thrown by the `errorFunction`
170
+ */
171
+ static expectError(errorFunction, options) {
172
+ try {
173
+ errorFunction();
174
+ } catch (error) {
175
+ return DataError.checkCaughtError(error, options);
176
+ }
177
+ throw new Error("Expected a DataError to be thrown but none was thrown");
178
+ }
179
+ /**
180
+ * 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.
181
+ *
182
+ * @param errorFunction - The function expected to throw the error.
183
+ * @param options - Extra options to apply.
184
+ *
185
+ * @throws {Error} Any other errors thrown by the `errorFunction` that are not a `DataError`.
186
+ * @throws {Error} If no `DataError` was thrown by the `errorFunction`
187
+ *
188
+ * @returns The `DataError` that was thrown by the `errorFunction`
189
+ */
190
+ static async expectErrorAsync(errorFunction, options) {
191
+ try {
192
+ await errorFunction();
193
+ } catch (error) {
194
+ return DataError.checkCaughtError(error, options);
195
+ }
196
+ throw new Error("Expected a DataError to be thrown but none was thrown");
197
+ }
149
198
  };
150
199
 
151
200
  //#endregion
@@ -400,19 +449,27 @@ function randomiseArray(array) {
400
449
  * @param stop - The number to stop at (exclusive).
401
450
  * @param step - The step size between numbers, defaulting to 1.
402
451
  *
403
- * @throws {Error} If `step` is `0`.
404
- * @throws {Error} If `step` direction does not match the order of `start` and `stop`.
452
+ * @throws {DataError} If `step` is `0`.
453
+ * @throws {DataError} If `step` direction does not match the order of `start` and `stop`.
405
454
  *
406
455
  * @returns An array of numbers satisfying the range provided.
407
456
  */
408
457
  function range(start, stop, step = 1) {
409
458
  const numbers = [];
410
- if (step === 0) throw new Error("ZERO_STEP_SIZE_NOT_ALLOWED");
459
+ if (step === 0) throw new DataError({ step }, "ZERO_STEP_SIZE", "Step size cannot be zero.");
411
460
  else if (step > 0) {
412
- if (start > stop) throw new Error("INVALID_BOUNDARIES");
461
+ if (start > stop) throw new DataError({
462
+ start,
463
+ stop,
464
+ step
465
+ }, "INVALID_BOUNDARIES", "The starting value cannot be bigger than the final value if step is positive");
413
466
  for (let i = start; i < stop; i += step) numbers.push(i);
414
467
  } else if (step < 0) {
415
- if (start < stop) throw new Error("INVALID_BOUNDARIES");
468
+ if (start < stop) throw new DataError({
469
+ start,
470
+ stop,
471
+ step
472
+ }, "INVALID_BOUNDARIES", "The final value cannot be bigger than the starting value if step is negative");
416
473
  for (let i = start; i > stop; i += step) numbers.push(i);
417
474
  }
418
475
  return numbers;
@@ -592,7 +649,7 @@ function isMonthlyMultiple(firstDate, secondDate) {
592
649
  *
593
650
  * @param file - The file to convert.
594
651
  *
595
- * @throws {Error} If the file reader gives an error.
652
+ * @throws {Error | DataError} If the file reader gives an error.
596
653
  *
597
654
  * @returns A promise that resolves to the encoded base 64 string.
598
655
  */
@@ -602,7 +659,7 @@ function convertFileToBase64(file) {
602
659
  reader.readAsDataURL(file);
603
660
  reader.onload = () => {
604
661
  if (reader.result === null) {
605
- reject(/* @__PURE__ */ new Error("FILE_CONVERSION_ERROR"));
662
+ reject(new DataError({ result: reader.result }, "FILE_CONVERSION_ERROR", "Could not convert the given file."));
606
663
  return;
607
664
  }
608
665
  resolve(reader.result);
@@ -618,6 +675,9 @@ function convertFileToBase64(file) {
618
675
  function getNullableResolutionStrategy(key, strategy) {
619
676
  return (typeof strategy === "object" ? strategy[key] : strategy) ?? "empty";
620
677
  }
678
+ function isPrimitive(item) {
679
+ return typeof item === "string" || typeof item === "number" || typeof item === "boolean";
680
+ }
621
681
  /**
622
682
  * Creates FormData from a given object, resolving non-string types as appropriate.
623
683
  *
@@ -644,7 +704,7 @@ function createFormData(data, options = {
644
704
  formData.append(String(key), JSON.stringify(value));
645
705
  break;
646
706
  case "omit": break;
647
- default: throw new TypeError("SLOPPY_PURE_JAVASCRIPT_USER_ERROR");
707
+ default: throw resolutionStrategy;
648
708
  }
649
709
  }
650
710
  function resolveNullables(key, value, options) {
@@ -667,10 +727,10 @@ function createFormData(data, options = {
667
727
  if (Array.isArray(value)) {
668
728
  if (value.some((item) => {
669
729
  return item instanceof Blob;
670
- }) && (options.arrayResolution === "stringify" || typeof options.arrayResolution === "object" && options.arrayResolution[key] === "stringify")) throw new TypeError("CANNOT_STRINGIFY_BLOB");
730
+ }) && (options.arrayResolution === "stringify" || typeof options.arrayResolution === "object" && options.arrayResolution[key] === "stringify")) throw new DataError({ value }, "CANNOT_STRINGIFY_BLOB", "Files/blobs cannot be stringified. Please change the resolution option for this item to \"multiple\" instead.");
671
731
  if (options.arrayResolution === "multiple" || typeof options.arrayResolution === "object" && options.arrayResolution[key] === "multiple") {
672
732
  for (const item of value) {
673
- if ((typeof item === "object" || !item) && !(item instanceof Blob)) throw new TypeError("NON_PRIMITIVE_ARRAY_ITEMS_FOUND");
733
+ if (!isPrimitive(item) && !(item instanceof Blob)) throw new DataError({ item }, "NON_PRIMITIVE_ARRAY_ITEMS_FOUND", "Cannot directly add non-primitive data to FormData. Please change the resolution option for this item to \"stringify\" instead.");
674
734
  if (item instanceof Blob) formData.append(String(key), item);
675
735
  else formData.append(String(key), String(item));
676
736
  }
@@ -1412,12 +1472,12 @@ async function encryptWithKey(publicKey, plaintextValue) {
1412
1472
  *
1413
1473
  * @param stringToAppendTo - The string to append a semicolon to.
1414
1474
  *
1415
- * @throws {Error} If the string contains multiple lines.
1475
+ * @throws {DataError} If the string contains multiple lines.
1416
1476
  *
1417
1477
  * @returns A string with the semicolon appended.
1418
1478
  */
1419
1479
  function appendSemicolon(stringToAppendTo) {
1420
- if (stringToAppendTo.includes("\n")) throw new Error("MULTIPLE_LINE_ERROR");
1480
+ if (stringToAppendTo.includes("\n")) throw new DataError({ stringToAppendTo }, "MULTIPLE_LINE_ERROR", "Cannot append semicolon to multi-line string.");
1421
1481
  const stringWithNoTrailingWhitespace = stringToAppendTo.trimEnd();
1422
1482
  if (stringWithNoTrailingWhitespace === "") return "";
1423
1483
  return stringWithNoTrailingWhitespace[stringWithNoTrailingWhitespace.length - 1] === ";" ? stringWithNoTrailingWhitespace : `${stringWithNoTrailingWhitespace};`;
@@ -1469,34 +1529,35 @@ function camelToKebab(string, options = { preserveConsecutiveCapitals: true }) {
1469
1529
  *
1470
1530
  * @category String Helpers
1471
1531
  *
1472
- * @param string - The string to convert.
1532
+ * @param input - The string to convert.
1473
1533
  * @param options - Options to apply to the conversion.
1474
1534
  *
1475
1535
  * @returns The string converted to camelCase.
1476
1536
  */
1477
- function kebabToCamel(string, options) {
1478
- if (string !== string.toLowerCase()) throw new Error("INVALID_KEBAB_CASE_INPUT");
1479
- if (string.startsWith("-") || string.endsWith("-") || string.includes("--")) throw new Error("INVALID_KEBAB_CASE_INPUT");
1537
+ function kebabToCamel(input, options) {
1538
+ if (input !== input.toLowerCase()) throw new DataError({ input }, "UPPERCASE_INPUT", "Kebab-case must be purely lowercase.");
1539
+ if (input.startsWith("-") || input.endsWith("-")) throw new DataError({ input }, "TRAILING_DASHES", "Dashes at the start and/or end are not allowed.");
1540
+ if (input.includes("--")) throw new DataError({ input }, "CONSECUTIVE_DASHES", "Consecutive dashes are not allowed.");
1480
1541
  let outputString = "";
1481
1542
  let skip = false;
1482
- for (const stringIndex in [...string]) {
1543
+ for (const stringIndex in [...input]) {
1483
1544
  if (skip) {
1484
1545
  skip = false;
1485
1546
  continue;
1486
1547
  }
1487
1548
  const index = parseIntStrict(stringIndex);
1488
1549
  if (index === 0 && options?.startWithUpper) {
1489
- outputString += string[index].toUpperCase();
1550
+ outputString += input[index].toUpperCase();
1490
1551
  continue;
1491
1552
  }
1492
- if (index === string.length - 1) {
1493
- outputString += string[index];
1553
+ if (index === input.length - 1) {
1554
+ outputString += input[index];
1494
1555
  break;
1495
1556
  }
1496
- if (string[index] === "-" && /^[a-zA-Z]+$/.test(string[index + 1])) {
1497
- outputString += string[index + 1].toUpperCase();
1557
+ if (input[index] === "-" && /^[a-zA-Z]+$/.test(input[index + 1])) {
1558
+ outputString += input[index + 1].toUpperCase();
1498
1559
  skip = true;
1499
- } else outputString += string[index];
1560
+ } else outputString += input[index];
1500
1561
  }
1501
1562
  return outputString;
1502
1563
  }
@@ -118,6 +118,17 @@ var DataError = class DataError extends Error {
118
118
  Object.defineProperty(this, "message", { enumerable: true });
119
119
  Object.setPrototypeOf(this, new.target.prototype);
120
120
  }
121
+ static checkCaughtError(error, options) {
122
+ if (DataError.check(error)) {
123
+ 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.
124
+
125
+ Expected: ${options.expectedCode}
126
+ Received: ${error.code}
127
+ `, { cause: error });
128
+ return error;
129
+ }
130
+ throw error;
131
+ }
121
132
  /**
122
133
  * Checks whether the given input may have been caused by a DataError.
123
134
  *
@@ -130,6 +141,44 @@ var DataError = class DataError extends Error {
130
141
  const data = input;
131
142
  return typeof data === "object" && data !== null && typeof data.message === "string" && typeof data.code === "string" && "data" in data;
132
143
  }
144
+ /**
145
+ * 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.
146
+ *
147
+ * @param errorFunction - The function expected to throw the error.
148
+ * @param options - Extra options to apply.
149
+ *
150
+ * @throws {Error} Any other errors thrown by the `errorFunction` that are not a `DataError`.
151
+ * @throws {Error} If no `DataError` was thrown by the `errorFunction`
152
+ *
153
+ * @returns The `DataError` that was thrown by the `errorFunction`
154
+ */
155
+ static expectError(errorFunction, options) {
156
+ try {
157
+ errorFunction();
158
+ } catch (error) {
159
+ return DataError.checkCaughtError(error, options);
160
+ }
161
+ throw new Error("Expected a DataError to be thrown but none was thrown");
162
+ }
163
+ /**
164
+ * 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.
165
+ *
166
+ * @param errorFunction - The function expected to throw the error.
167
+ * @param options - Extra options to apply.
168
+ *
169
+ * @throws {Error} Any other errors thrown by the `errorFunction` that are not a `DataError`.
170
+ * @throws {Error} If no `DataError` was thrown by the `errorFunction`
171
+ *
172
+ * @returns The `DataError` that was thrown by the `errorFunction`
173
+ */
174
+ static async expectErrorAsync(errorFunction, options) {
175
+ try {
176
+ await errorFunction();
177
+ } catch (error) {
178
+ return DataError.checkCaughtError(error, options);
179
+ }
180
+ throw new Error("Expected a DataError to be thrown but none was thrown");
181
+ }
133
182
  };
134
183
 
135
184
  //#endregion
@@ -9,6 +9,9 @@ import { ExecaMethod } from "execa";
9
9
  type RecordKey = string | number | symbol;
10
10
  //#endregion
11
11
  //#region src/root/types/DataError.d.ts
12
+ interface ExpectErrorOptions {
13
+ expectedCode?: string;
14
+ }
12
15
  /**
13
16
  * Represents errors you may get that may've been caused by a specific piece of data.
14
17
  *
@@ -26,6 +29,7 @@ declare class DataError<DataType extends Record<RecordKey, unknown> = Record<Rec
26
29
  * @param options - Extra options to pass to super Error constructor.
27
30
  */
28
31
  constructor(data: DataType, code?: string, message?: string, options?: ErrorOptions);
32
+ private static checkCaughtError;
29
33
  /**
30
34
  * Checks whether the given input may have been caused by a DataError.
31
35
  *
@@ -34,6 +38,30 @@ declare class DataError<DataType extends Record<RecordKey, unknown> = Record<Rec
34
38
  * @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`.
35
39
  */
36
40
  static check<DataType extends Record<RecordKey, unknown> = Record<RecordKey, unknown>>(input: unknown): input is DataError<DataType>;
41
+ /**
42
+ * 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.
43
+ *
44
+ * @param errorFunction - The function expected to throw the error.
45
+ * @param options - Extra options to apply.
46
+ *
47
+ * @throws {Error} Any other errors thrown by the `errorFunction` that are not a `DataError`.
48
+ * @throws {Error} If no `DataError` was thrown by the `errorFunction`
49
+ *
50
+ * @returns The `DataError` that was thrown by the `errorFunction`
51
+ */
52
+ static expectError(errorFunction: () => unknown, options?: ExpectErrorOptions): DataError;
53
+ /**
54
+ * 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.
55
+ *
56
+ * @param errorFunction - The function expected to throw the error.
57
+ * @param options - Extra options to apply.
58
+ *
59
+ * @throws {Error} Any other errors thrown by the `errorFunction` that are not a `DataError`.
60
+ * @throws {Error} If no `DataError` was thrown by the `errorFunction`
61
+ *
62
+ * @returns The `DataError` that was thrown by the `errorFunction`
63
+ */
64
+ static expectErrorAsync(errorFunction: () => Promise<unknown>, options?: ExpectErrorOptions): Promise<DataError>;
37
65
  }
38
66
  //#endregion
39
67
  //#region src/root/types/CreateEnumType.d.ts
@@ -10,6 +10,9 @@ import { ExecaMethod } from "execa";
10
10
  type RecordKey = string | number | symbol;
11
11
  //#endregion
12
12
  //#region src/root/types/DataError.d.ts
13
+ interface ExpectErrorOptions {
14
+ expectedCode?: string;
15
+ }
13
16
  /**
14
17
  * Represents errors you may get that may've been caused by a specific piece of data.
15
18
  *
@@ -27,6 +30,7 @@ declare class DataError<DataType extends Record<RecordKey, unknown> = Record<Rec
27
30
  * @param options - Extra options to pass to super Error constructor.
28
31
  */
29
32
  constructor(data: DataType, code?: string, message?: string, options?: ErrorOptions);
33
+ private static checkCaughtError;
30
34
  /**
31
35
  * Checks whether the given input may have been caused by a DataError.
32
36
  *
@@ -35,6 +39,30 @@ declare class DataError<DataType extends Record<RecordKey, unknown> = Record<Rec
35
39
  * @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`.
36
40
  */
37
41
  static check<DataType extends Record<RecordKey, unknown> = Record<RecordKey, unknown>>(input: unknown): input is DataError<DataType>;
42
+ /**
43
+ * 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.
44
+ *
45
+ * @param errorFunction - The function expected to throw the error.
46
+ * @param options - Extra options to apply.
47
+ *
48
+ * @throws {Error} Any other errors thrown by the `errorFunction` that are not a `DataError`.
49
+ * @throws {Error} If no `DataError` was thrown by the `errorFunction`
50
+ *
51
+ * @returns The `DataError` that was thrown by the `errorFunction`
52
+ */
53
+ static expectError(errorFunction: () => unknown, options?: ExpectErrorOptions): DataError;
54
+ /**
55
+ * 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.
56
+ *
57
+ * @param errorFunction - The function expected to throw the error.
58
+ * @param options - Extra options to apply.
59
+ *
60
+ * @throws {Error} Any other errors thrown by the `errorFunction` that are not a `DataError`.
61
+ * @throws {Error} If no `DataError` was thrown by the `errorFunction`
62
+ *
63
+ * @returns The `DataError` that was thrown by the `errorFunction`
64
+ */
65
+ static expectErrorAsync(errorFunction: () => Promise<unknown>, options?: ExpectErrorOptions): Promise<DataError>;
38
66
  }
39
67
  //#endregion
40
68
  //#region src/root/types/CreateEnumType.d.ts
@@ -88,6 +88,17 @@ var DataError = class DataError extends Error {
88
88
  Object.defineProperty(this, "message", { enumerable: true });
89
89
  Object.setPrototypeOf(this, new.target.prototype);
90
90
  }
91
+ static checkCaughtError(error, options) {
92
+ if (DataError.check(error)) {
93
+ 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.
94
+
95
+ Expected: ${options.expectedCode}
96
+ Received: ${error.code}
97
+ `, { cause: error });
98
+ return error;
99
+ }
100
+ throw error;
101
+ }
91
102
  /**
92
103
  * Checks whether the given input may have been caused by a DataError.
93
104
  *
@@ -100,6 +111,44 @@ var DataError = class DataError extends Error {
100
111
  const data = input;
101
112
  return typeof data === "object" && data !== null && typeof data.message === "string" && typeof data.code === "string" && "data" in data;
102
113
  }
114
+ /**
115
+ * 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.
116
+ *
117
+ * @param errorFunction - The function expected to throw the error.
118
+ * @param options - Extra options to apply.
119
+ *
120
+ * @throws {Error} Any other errors thrown by the `errorFunction` that are not a `DataError`.
121
+ * @throws {Error} If no `DataError` was thrown by the `errorFunction`
122
+ *
123
+ * @returns The `DataError` that was thrown by the `errorFunction`
124
+ */
125
+ static expectError(errorFunction, options) {
126
+ try {
127
+ errorFunction();
128
+ } catch (error) {
129
+ return DataError.checkCaughtError(error, options);
130
+ }
131
+ throw new Error("Expected a DataError to be thrown but none was thrown");
132
+ }
133
+ /**
134
+ * 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.
135
+ *
136
+ * @param errorFunction - The function expected to throw the error.
137
+ * @param options - Extra options to apply.
138
+ *
139
+ * @throws {Error} Any other errors thrown by the `errorFunction` that are not a `DataError`.
140
+ * @throws {Error} If no `DataError` was thrown by the `errorFunction`
141
+ *
142
+ * @returns The `DataError` that was thrown by the `errorFunction`
143
+ */
144
+ static async expectErrorAsync(errorFunction, options) {
145
+ try {
146
+ await errorFunction();
147
+ } catch (error) {
148
+ return DataError.checkCaughtError(error, options);
149
+ }
150
+ throw new Error("Expected a DataError to be thrown but none was thrown");
151
+ }
103
152
  };
104
153
 
105
154
  //#endregion
@@ -76,47 +76,6 @@ const FILE_PATH_REGEX = String.raw`^(?<directory>.+)[\/\\](?<base>[^\/\\]+)$`;
76
76
  //#region src/root/constants/VERSION_NUMBER_REGEX.ts
77
77
  const VERSION_NUMBER_REGEX = "^(?:v)?(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$";
78
78
 
79
- //#endregion
80
- //#region src/root/types/DataError.ts
81
- /**
82
- * Represents errors you may get that may've been caused by a specific piece of data.
83
- *
84
- * @category Types
85
- *
86
- * @template DataType - The type of the data that caused the error.
87
- */
88
- var DataError = class DataError extends Error {
89
- code;
90
- data;
91
- /**
92
- * @param data - The data that caused the error.
93
- * @param code - A standardised code (e.g. UNEXPECTED_DATA).
94
- * @param message - A human-readable error message (e.g. The data provided is invalid).
95
- * @param options - Extra options to pass to super Error constructor.
96
- */
97
- constructor(data, code = "INVALID_DATA", message = "The data provided is invalid", options) {
98
- super(message, options);
99
- if (Error.captureStackTrace) Error.captureStackTrace(this, new.target);
100
- this.name = new.target.name;
101
- this.code = code;
102
- this.data = data;
103
- Object.defineProperty(this, "message", { enumerable: true });
104
- Object.setPrototypeOf(this, new.target.prototype);
105
- }
106
- /**
107
- * Checks whether the given input may have been caused by a DataError.
108
- *
109
- * @param input - The input to check.
110
- *
111
- * @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`.
112
- */
113
- static check(input) {
114
- if (input instanceof DataError) return true;
115
- const data = input;
116
- return typeof data === "object" && data !== null && typeof data.message === "string" && typeof data.code === "string" && "data" in data;
117
- }
118
- };
119
-
120
79
  //#endregion
121
80
  //#region src/root/functions/arrayHelpers/fillArray.ts
122
81
  /**
@@ -419,6 +378,96 @@ const VersionType = {
419
378
  PATCH: "patch"
420
379
  };
421
380
 
381
+ //#endregion
382
+ //#region src/root/types/DataError.ts
383
+ /**
384
+ * Represents errors you may get that may've been caused by a specific piece of data.
385
+ *
386
+ * @category Types
387
+ *
388
+ * @template DataType - The type of the data that caused the error.
389
+ */
390
+ var DataError = class DataError extends Error {
391
+ code;
392
+ data;
393
+ /**
394
+ * @param data - The data that caused the error.
395
+ * @param code - A standardised code (e.g. UNEXPECTED_DATA).
396
+ * @param message - A human-readable error message (e.g. The data provided is invalid).
397
+ * @param options - Extra options to pass to super Error constructor.
398
+ */
399
+ constructor(data, code = "INVALID_DATA", message = "The data provided is invalid", options) {
400
+ super(message, options);
401
+ if (Error.captureStackTrace) Error.captureStackTrace(this, new.target);
402
+ this.name = new.target.name;
403
+ this.code = code;
404
+ this.data = data;
405
+ Object.defineProperty(this, "message", { enumerable: true });
406
+ Object.setPrototypeOf(this, new.target.prototype);
407
+ }
408
+ static checkCaughtError(error, options) {
409
+ if (DataError.check(error)) {
410
+ 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.
411
+
412
+ Expected: ${options.expectedCode}
413
+ Received: ${error.code}
414
+ `, { cause: error });
415
+ return error;
416
+ }
417
+ throw error;
418
+ }
419
+ /**
420
+ * Checks whether the given input may have been caused by a DataError.
421
+ *
422
+ * @param input - The input to check.
423
+ *
424
+ * @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`.
425
+ */
426
+ static check(input) {
427
+ if (input instanceof DataError) return true;
428
+ const data = input;
429
+ return typeof data === "object" && data !== null && typeof data.message === "string" && typeof data.code === "string" && "data" in data;
430
+ }
431
+ /**
432
+ * 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.
433
+ *
434
+ * @param errorFunction - The function expected to throw the error.
435
+ * @param options - Extra options to apply.
436
+ *
437
+ * @throws {Error} Any other errors thrown by the `errorFunction` that are not a `DataError`.
438
+ * @throws {Error} If no `DataError` was thrown by the `errorFunction`
439
+ *
440
+ * @returns The `DataError` that was thrown by the `errorFunction`
441
+ */
442
+ static expectError(errorFunction, options) {
443
+ try {
444
+ errorFunction();
445
+ } catch (error) {
446
+ return DataError.checkCaughtError(error, options);
447
+ }
448
+ throw new Error("Expected a DataError to be thrown but none was thrown");
449
+ }
450
+ /**
451
+ * 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.
452
+ *
453
+ * @param errorFunction - The function expected to throw the error.
454
+ * @param options - Extra options to apply.
455
+ *
456
+ * @throws {Error} Any other errors thrown by the `errorFunction` that are not a `DataError`.
457
+ * @throws {Error} If no `DataError` was thrown by the `errorFunction`
458
+ *
459
+ * @returns The `DataError` that was thrown by the `errorFunction`
460
+ */
461
+ static async expectErrorAsync(errorFunction, options) {
462
+ try {
463
+ await errorFunction();
464
+ } catch (error) {
465
+ return DataError.checkCaughtError(error, options);
466
+ }
467
+ throw new Error("Expected a DataError to be thrown but none was thrown");
468
+ }
469
+ };
470
+
422
471
  //#endregion
423
472
  //#region src/root/types/VersionNumber.ts
424
473
  /**
@@ -46,47 +46,6 @@ const FILE_PATH_REGEX = String.raw`^(?<directory>.+)[\/\\](?<base>[^\/\\]+)$`;
46
46
  //#region src/root/constants/VERSION_NUMBER_REGEX.ts
47
47
  const VERSION_NUMBER_REGEX = "^(?:v)?(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$";
48
48
 
49
- //#endregion
50
- //#region src/root/types/DataError.ts
51
- /**
52
- * Represents errors you may get that may've been caused by a specific piece of data.
53
- *
54
- * @category Types
55
- *
56
- * @template DataType - The type of the data that caused the error.
57
- */
58
- var DataError = class DataError extends Error {
59
- code;
60
- data;
61
- /**
62
- * @param data - The data that caused the error.
63
- * @param code - A standardised code (e.g. UNEXPECTED_DATA).
64
- * @param message - A human-readable error message (e.g. The data provided is invalid).
65
- * @param options - Extra options to pass to super Error constructor.
66
- */
67
- constructor(data, code = "INVALID_DATA", message = "The data provided is invalid", options) {
68
- super(message, options);
69
- if (Error.captureStackTrace) Error.captureStackTrace(this, new.target);
70
- this.name = new.target.name;
71
- this.code = code;
72
- this.data = data;
73
- Object.defineProperty(this, "message", { enumerable: true });
74
- Object.setPrototypeOf(this, new.target.prototype);
75
- }
76
- /**
77
- * Checks whether the given input may have been caused by a DataError.
78
- *
79
- * @param input - The input to check.
80
- *
81
- * @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`.
82
- */
83
- static check(input) {
84
- if (input instanceof DataError) return true;
85
- const data = input;
86
- return typeof data === "object" && data !== null && typeof data.message === "string" && typeof data.code === "string" && "data" in data;
87
- }
88
- };
89
-
90
49
  //#endregion
91
50
  //#region src/root/functions/arrayHelpers/fillArray.ts
92
51
  /**
@@ -389,6 +348,96 @@ const VersionType = {
389
348
  PATCH: "patch"
390
349
  };
391
350
 
351
+ //#endregion
352
+ //#region src/root/types/DataError.ts
353
+ /**
354
+ * Represents errors you may get that may've been caused by a specific piece of data.
355
+ *
356
+ * @category Types
357
+ *
358
+ * @template DataType - The type of the data that caused the error.
359
+ */
360
+ var DataError = class DataError extends Error {
361
+ code;
362
+ data;
363
+ /**
364
+ * @param data - The data that caused the error.
365
+ * @param code - A standardised code (e.g. UNEXPECTED_DATA).
366
+ * @param message - A human-readable error message (e.g. The data provided is invalid).
367
+ * @param options - Extra options to pass to super Error constructor.
368
+ */
369
+ constructor(data, code = "INVALID_DATA", message = "The data provided is invalid", options) {
370
+ super(message, options);
371
+ if (Error.captureStackTrace) Error.captureStackTrace(this, new.target);
372
+ this.name = new.target.name;
373
+ this.code = code;
374
+ this.data = data;
375
+ Object.defineProperty(this, "message", { enumerable: true });
376
+ Object.setPrototypeOf(this, new.target.prototype);
377
+ }
378
+ static checkCaughtError(error, options) {
379
+ if (DataError.check(error)) {
380
+ 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.
381
+
382
+ Expected: ${options.expectedCode}
383
+ Received: ${error.code}
384
+ `, { cause: error });
385
+ return error;
386
+ }
387
+ throw error;
388
+ }
389
+ /**
390
+ * Checks whether the given input may have been caused by a DataError.
391
+ *
392
+ * @param input - The input to check.
393
+ *
394
+ * @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`.
395
+ */
396
+ static check(input) {
397
+ if (input instanceof DataError) return true;
398
+ const data = input;
399
+ return typeof data === "object" && data !== null && typeof data.message === "string" && typeof data.code === "string" && "data" in data;
400
+ }
401
+ /**
402
+ * 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.
403
+ *
404
+ * @param errorFunction - The function expected to throw the error.
405
+ * @param options - Extra options to apply.
406
+ *
407
+ * @throws {Error} Any other errors thrown by the `errorFunction` that are not a `DataError`.
408
+ * @throws {Error} If no `DataError` was thrown by the `errorFunction`
409
+ *
410
+ * @returns The `DataError` that was thrown by the `errorFunction`
411
+ */
412
+ static expectError(errorFunction, options) {
413
+ try {
414
+ errorFunction();
415
+ } catch (error) {
416
+ return DataError.checkCaughtError(error, options);
417
+ }
418
+ throw new Error("Expected a DataError to be thrown but none was thrown");
419
+ }
420
+ /**
421
+ * 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.
422
+ *
423
+ * @param errorFunction - The function expected to throw the error.
424
+ * @param options - Extra options to apply.
425
+ *
426
+ * @throws {Error} Any other errors thrown by the `errorFunction` that are not a `DataError`.
427
+ * @throws {Error} If no `DataError` was thrown by the `errorFunction`
428
+ *
429
+ * @returns The `DataError` that was thrown by the `errorFunction`
430
+ */
431
+ static async expectErrorAsync(errorFunction, options) {
432
+ try {
433
+ await errorFunction();
434
+ } catch (error) {
435
+ return DataError.checkCaughtError(error, options);
436
+ }
437
+ throw new Error("Expected a DataError to be thrown but none was thrown");
438
+ }
439
+ };
440
+
392
441
  //#endregion
393
442
  //#region src/root/types/VersionNumber.ts
394
443
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alextheman/utility",
3
- "version": "5.1.4",
3
+ "version": "5.2.0",
4
4
  "description": "Helpful utility functions.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -36,7 +36,7 @@
36
36
  "zod": "^4.3.6"
37
37
  },
38
38
  "devDependencies": {
39
- "@alextheman/eslint-plugin": "^5.7.1",
39
+ "@alextheman/eslint-plugin": "^5.8.2",
40
40
  "@types/node": "^25.3.0",
41
41
  "alex-c-line": "^1.28.0",
42
42
  "dotenv-cli": "^11.0.0",