@alextheman/utility 5.1.4 → 5.3.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
@@ -38,6 +38,10 @@ const FILE_PATH_REGEX = String.raw`^(?<directory>.+)[\/\\](?<base>[^\/\\]+)$`;
38
38
  //#region src/root/constants/NAMESPACE_EXPORT_REGEX.ts
39
39
  const NAMESPACE_EXPORT_REGEX = "export\\s+\\*\\s+from";
40
40
 
41
+ //#endregion
42
+ //#region src/root/constants/ONE_DAY_IN_MILLISECONDS.ts
43
+ const ONE_DAY_IN_MILLISECONDS = 1440 * 60 * 1e3;
44
+
41
45
  //#endregion
42
46
  //#region src/root/constants/VERSION_NUMBER_REGEX.ts
43
47
  const VERSION_NUMBER_REGEX = "^(?:v)?(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$";
@@ -164,6 +168,17 @@ var DataError = class DataError extends Error {
164
168
  Object.defineProperty(this, "message", { enumerable: true });
165
169
  Object.setPrototypeOf(this, new.target.prototype);
166
170
  }
171
+ static checkCaughtError(error, options) {
172
+ if (DataError.check(error)) {
173
+ 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.
174
+
175
+ Expected: ${options.expectedCode}
176
+ Received: ${error.code}
177
+ `, { cause: error });
178
+ return error;
179
+ }
180
+ throw error;
181
+ }
167
182
  /**
168
183
  * Checks whether the given input may have been caused by a DataError.
169
184
  *
@@ -176,6 +191,44 @@ var DataError = class DataError extends Error {
176
191
  const data = input;
177
192
  return typeof data === "object" && data !== null && typeof data.message === "string" && typeof data.code === "string" && "data" in data;
178
193
  }
194
+ /**
195
+ * 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.
196
+ *
197
+ * @param errorFunction - The function expected to throw the error.
198
+ * @param options - Extra options to apply.
199
+ *
200
+ * @throws {Error} Any other errors thrown by the `errorFunction` that are not a `DataError`.
201
+ * @throws {Error} If no `DataError` was thrown by the `errorFunction`
202
+ *
203
+ * @returns The `DataError` that was thrown by the `errorFunction`
204
+ */
205
+ static expectError(errorFunction, options) {
206
+ try {
207
+ errorFunction();
208
+ } catch (error) {
209
+ return DataError.checkCaughtError(error, options);
210
+ }
211
+ throw new Error("Expected a DataError to be thrown but none was thrown");
212
+ }
213
+ /**
214
+ * 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.
215
+ *
216
+ * @param errorFunction - The function expected to throw the error.
217
+ * @param options - Extra options to apply.
218
+ *
219
+ * @throws {Error} Any other errors thrown by the `errorFunction` that are not a `DataError`.
220
+ * @throws {Error} If no `DataError` was thrown by the `errorFunction`
221
+ *
222
+ * @returns The `DataError` that was thrown by the `errorFunction`
223
+ */
224
+ static async expectErrorAsync(errorFunction, options) {
225
+ try {
226
+ await errorFunction();
227
+ } catch (error) {
228
+ return DataError.checkCaughtError(error, options);
229
+ }
230
+ throw new Error("Expected a DataError to be thrown but none was thrown");
231
+ }
179
232
  };
180
233
 
181
234
  //#endregion
@@ -430,19 +483,27 @@ function randomiseArray(array) {
430
483
  * @param stop - The number to stop at (exclusive).
431
484
  * @param step - The step size between numbers, defaulting to 1.
432
485
  *
433
- * @throws {Error} If `step` is `0`.
434
- * @throws {Error} If `step` direction does not match the order of `start` and `stop`.
486
+ * @throws {DataError} If `step` is `0`.
487
+ * @throws {DataError} If `step` direction does not match the order of `start` and `stop`.
435
488
  *
436
489
  * @returns An array of numbers satisfying the range provided.
437
490
  */
438
491
  function range(start, stop, step = 1) {
439
492
  const numbers = [];
440
- if (step === 0) throw new Error("ZERO_STEP_SIZE_NOT_ALLOWED");
493
+ if (step === 0) throw new DataError({ step }, "ZERO_STEP_SIZE", "Step size cannot be zero.");
441
494
  else if (step > 0) {
442
- if (start > stop) throw new Error("INVALID_BOUNDARIES");
495
+ if (start > stop) throw new DataError({
496
+ start,
497
+ stop,
498
+ step
499
+ }, "INVALID_BOUNDARIES", "The starting value cannot be bigger than the final value if step is positive");
443
500
  for (let i = start; i < stop; i += step) numbers.push(i);
444
501
  } else if (step < 0) {
445
- if (start < stop) throw new Error("INVALID_BOUNDARIES");
502
+ if (start < stop) throw new DataError({
503
+ start,
504
+ stop,
505
+ step
506
+ }, "INVALID_BOUNDARIES", "The final value cannot be bigger than the starting value if step is negative");
446
507
  for (let i = start; i > stop; i += step) numbers.push(i);
447
508
  }
448
509
  return numbers;
@@ -622,7 +683,7 @@ function isMonthlyMultiple(firstDate, secondDate) {
622
683
  *
623
684
  * @param file - The file to convert.
624
685
  *
625
- * @throws {Error} If the file reader gives an error.
686
+ * @throws {Error | DataError} If the file reader gives an error.
626
687
  *
627
688
  * @returns A promise that resolves to the encoded base 64 string.
628
689
  */
@@ -632,7 +693,7 @@ function convertFileToBase64(file) {
632
693
  reader.readAsDataURL(file);
633
694
  reader.onload = () => {
634
695
  if (reader.result === null) {
635
- reject(/* @__PURE__ */ new Error("FILE_CONVERSION_ERROR"));
696
+ reject(new DataError({ result: reader.result }, "FILE_CONVERSION_ERROR", "Could not convert the given file."));
636
697
  return;
637
698
  }
638
699
  resolve(reader.result);
@@ -648,6 +709,9 @@ function convertFileToBase64(file) {
648
709
  function getNullableResolutionStrategy(key, strategy) {
649
710
  return (typeof strategy === "object" ? strategy[key] : strategy) ?? "empty";
650
711
  }
712
+ function isPrimitive(item) {
713
+ return typeof item === "string" || typeof item === "number" || typeof item === "boolean";
714
+ }
651
715
  /**
652
716
  * Creates FormData from a given object, resolving non-string types as appropriate.
653
717
  *
@@ -674,7 +738,7 @@ function createFormData(data, options = {
674
738
  formData.append(String(key), JSON.stringify(value));
675
739
  break;
676
740
  case "omit": break;
677
- default: throw new TypeError("SLOPPY_PURE_JAVASCRIPT_USER_ERROR");
741
+ default: throw resolutionStrategy;
678
742
  }
679
743
  }
680
744
  function resolveNullables(key, value, options) {
@@ -697,10 +761,10 @@ function createFormData(data, options = {
697
761
  if (Array.isArray(value)) {
698
762
  if (value.some((item) => {
699
763
  return item instanceof Blob;
700
- }) && (options.arrayResolution === "stringify" || typeof options.arrayResolution === "object" && options.arrayResolution[key] === "stringify")) throw new TypeError("CANNOT_STRINGIFY_BLOB");
764
+ }) && (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
765
  if (options.arrayResolution === "multiple" || typeof options.arrayResolution === "object" && options.arrayResolution[key] === "multiple") {
702
766
  for (const item of value) {
703
- if ((typeof item === "object" || !item) && !(item instanceof Blob)) throw new TypeError("NON_PRIMITIVE_ARRAY_ITEMS_FOUND");
767
+ 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
768
  if (item instanceof Blob) formData.append(String(key), item);
705
769
  else formData.append(String(key), String(item));
706
770
  }
@@ -1442,12 +1506,12 @@ async function encryptWithKey(publicKey, plaintextValue) {
1442
1506
  *
1443
1507
  * @param stringToAppendTo - The string to append a semicolon to.
1444
1508
  *
1445
- * @throws {Error} If the string contains multiple lines.
1509
+ * @throws {DataError} If the string contains multiple lines.
1446
1510
  *
1447
1511
  * @returns A string with the semicolon appended.
1448
1512
  */
1449
1513
  function appendSemicolon(stringToAppendTo) {
1450
- if (stringToAppendTo.includes("\n")) throw new Error("MULTIPLE_LINE_ERROR");
1514
+ if (stringToAppendTo.includes("\n")) throw new DataError({ stringToAppendTo }, "MULTIPLE_LINE_ERROR", "Cannot append semicolon to multi-line string.");
1451
1515
  const stringWithNoTrailingWhitespace = stringToAppendTo.trimEnd();
1452
1516
  if (stringWithNoTrailingWhitespace === "") return "";
1453
1517
  return stringWithNoTrailingWhitespace[stringWithNoTrailingWhitespace.length - 1] === ";" ? stringWithNoTrailingWhitespace : `${stringWithNoTrailingWhitespace};`;
@@ -1499,34 +1563,35 @@ function camelToKebab(string, options = { preserveConsecutiveCapitals: true }) {
1499
1563
  *
1500
1564
  * @category String Helpers
1501
1565
  *
1502
- * @param string - The string to convert.
1566
+ * @param input - The string to convert.
1503
1567
  * @param options - Options to apply to the conversion.
1504
1568
  *
1505
1569
  * @returns The string converted to camelCase.
1506
1570
  */
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");
1571
+ function kebabToCamel(input, options) {
1572
+ if (input !== input.toLowerCase()) throw new DataError({ input }, "UPPERCASE_INPUT", "Kebab-case must be purely lowercase.");
1573
+ if (input.startsWith("-") || input.endsWith("-")) throw new DataError({ input }, "TRAILING_DASHES", "Dashes at the start and/or end are not allowed.");
1574
+ if (input.includes("--")) throw new DataError({ input }, "CONSECUTIVE_DASHES", "Consecutive dashes are not allowed.");
1510
1575
  let outputString = "";
1511
1576
  let skip = false;
1512
- for (const stringIndex in [...string]) {
1577
+ for (const stringIndex in [...input]) {
1513
1578
  if (skip) {
1514
1579
  skip = false;
1515
1580
  continue;
1516
1581
  }
1517
1582
  const index = parseIntStrict(stringIndex);
1518
1583
  if (index === 0 && options?.startWithUpper) {
1519
- outputString += string[index].toUpperCase();
1584
+ outputString += input[index].toUpperCase();
1520
1585
  continue;
1521
1586
  }
1522
- if (index === string.length - 1) {
1523
- outputString += string[index];
1587
+ if (index === input.length - 1) {
1588
+ outputString += input[index];
1524
1589
  break;
1525
1590
  }
1526
- if (string[index] === "-" && /^[a-zA-Z]+$/.test(string[index + 1])) {
1527
- outputString += string[index + 1].toUpperCase();
1591
+ if (input[index] === "-" && /^[a-zA-Z]+$/.test(input[index + 1])) {
1592
+ outputString += input[index + 1].toUpperCase();
1528
1593
  skip = true;
1529
- } else outputString += string[index];
1594
+ } else outputString += input[index];
1530
1595
  }
1531
1596
  return outputString;
1532
1597
  }
@@ -1553,6 +1618,7 @@ exports.DataError = DataError;
1553
1618
  exports.Env = Env;
1554
1619
  exports.FILE_PATH_REGEX = FILE_PATH_REGEX;
1555
1620
  exports.NAMESPACE_EXPORT_REGEX = NAMESPACE_EXPORT_REGEX;
1621
+ exports.ONE_DAY_IN_MILLISECONDS = ONE_DAY_IN_MILLISECONDS;
1556
1622
  exports.VERSION_NUMBER_REGEX = VERSION_NUMBER_REGEX;
1557
1623
  exports.VersionNumber = VersionNumber;
1558
1624
  exports.VersionType = VersionType;
package/dist/index.d.cts CHANGED
@@ -7,6 +7,9 @@ declare const FILE_PATH_REGEX: string;
7
7
  //#region src/root/constants/NAMESPACE_EXPORT_REGEX.d.ts
8
8
  declare const NAMESPACE_EXPORT_REGEX = "export\\s+\\*\\s+from";
9
9
  //#endregion
10
+ //#region src/root/constants/ONE_DAY_IN_MILLISECONDS.d.ts
11
+ declare const ONE_DAY_IN_MILLISECONDS: number;
12
+ //#endregion
10
13
  //#region src/root/constants/VERSION_NUMBER_REGEX.d.ts
11
14
  declare const VERSION_NUMBER_REGEX: string;
12
15
  //#endregion
@@ -87,8 +90,8 @@ declare function randomiseArray<ItemType>(array: ItemType[]): ItemType[];
87
90
  * @param stop - The number to stop at (exclusive).
88
91
  * @param step - The step size between numbers, defaulting to 1.
89
92
  *
90
- * @throws {Error} If `step` is `0`.
91
- * @throws {Error} If `step` direction does not match the order of `start` and `stop`.
93
+ * @throws {DataError} If `step` is `0`.
94
+ * @throws {DataError} If `step` direction does not match the order of `start` and `stop`.
92
95
  *
93
96
  * @returns An array of numbers satisfying the range provided.
94
97
  */
@@ -198,7 +201,7 @@ declare function isSameDate(firstDate: Date, secondDate: Date): boolean;
198
201
  *
199
202
  * @param file - The file to convert.
200
203
  *
201
- * @throws {Error} If the file reader gives an error.
204
+ * @throws {Error | DataError} If the file reader gives an error.
202
205
  *
203
206
  * @returns A promise that resolves to the encoded base 64 string.
204
207
  */
@@ -239,6 +242,9 @@ declare class APIError extends Error {
239
242
  type RecordKey = string | number | symbol;
240
243
  //#endregion
241
244
  //#region src/root/types/DataError.d.ts
245
+ interface ExpectErrorOptions {
246
+ expectedCode?: string;
247
+ }
242
248
  /**
243
249
  * Represents errors you may get that may've been caused by a specific piece of data.
244
250
  *
@@ -256,6 +262,7 @@ declare class DataError<DataType extends Record<RecordKey, unknown> = Record<Rec
256
262
  * @param options - Extra options to pass to super Error constructor.
257
263
  */
258
264
  constructor(data: DataType, code?: string, message?: string, options?: ErrorOptions);
265
+ private static checkCaughtError;
259
266
  /**
260
267
  * Checks whether the given input may have been caused by a DataError.
261
268
  *
@@ -264,6 +271,30 @@ declare class DataError<DataType extends Record<RecordKey, unknown> = Record<Rec
264
271
  * @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
272
  */
266
273
  static check<DataType extends Record<RecordKey, unknown> = Record<RecordKey, unknown>>(input: unknown): input is DataError<DataType>;
274
+ /**
275
+ * 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.
276
+ *
277
+ * @param errorFunction - The function expected to throw the error.
278
+ * @param options - Extra options to apply.
279
+ *
280
+ * @throws {Error} Any other errors thrown by the `errorFunction` that are not a `DataError`.
281
+ * @throws {Error} If no `DataError` was thrown by the `errorFunction`
282
+ *
283
+ * @returns The `DataError` that was thrown by the `errorFunction`
284
+ */
285
+ static expectError(errorFunction: () => unknown, options?: ExpectErrorOptions): DataError;
286
+ /**
287
+ * 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.
288
+ *
289
+ * @param errorFunction - The function expected to throw the error.
290
+ * @param options - Extra options to apply.
291
+ *
292
+ * @throws {Error} Any other errors thrown by the `errorFunction` that are not a `DataError`.
293
+ * @throws {Error} If no `DataError` was thrown by the `errorFunction`
294
+ *
295
+ * @returns The `DataError` that was thrown by the `errorFunction`
296
+ */
297
+ static expectErrorAsync(errorFunction: () => Promise<unknown>, options?: ExpectErrorOptions): Promise<DataError>;
267
298
  }
268
299
  //#endregion
269
300
  //#region src/root/types/VersionNumber.d.ts
@@ -821,7 +852,7 @@ declare function encryptWithKey(publicKey: string, plaintextValue: string): Prom
821
852
  *
822
853
  * @param stringToAppendTo - The string to append a semicolon to.
823
854
  *
824
- * @throws {Error} If the string contains multiple lines.
855
+ * @throws {DataError} If the string contains multiple lines.
825
856
  *
826
857
  * @returns A string with the semicolon appended.
827
858
  */
@@ -864,12 +895,12 @@ interface KebabToCamelOptions {
864
895
  *
865
896
  * @category String Helpers
866
897
  *
867
- * @param string - The string to convert.
898
+ * @param input - The string to convert.
868
899
  * @param options - Options to apply to the conversion.
869
900
  *
870
901
  * @returns The string converted to camelCase.
871
902
  */
872
- declare function kebabToCamel(string: string, options?: KebabToCamelOptions): string;
903
+ declare function kebabToCamel(input: string, options?: KebabToCamelOptions): string;
873
904
  //#endregion
874
905
  //#region src/root/functions/stringHelpers/truncate.d.ts
875
906
  /**
@@ -1052,4 +1083,4 @@ declare function normaliseIndents(strings: TemplateStringsArray, ...interpolatio
1052
1083
  */
1053
1084
  declare const normalizeIndents: typeof normaliseIndents;
1054
1085
  //#endregion
1055
- export { APIError, ArrayElement, CallReturnType, CamelToKebabOptions, CreateEnumType, CreateFormDataOptions, CreateFormDataOptionsNullableResolution, CreateFormDataOptionsUndefinedOrNullResolution, DataError, DisallowUndefined, Env, FILE_PATH_REGEX, FormDataArrayResolutionStrategy, FormDataNullableResolutionStrategy, HTTPErrorCode, IgnoreCase, IsTypeArgumentString, KebabToCamelOptions, NAMESPACE_EXPORT_REGEX, NonUndefined, NormaliseIndentsFunction, NormaliseIndentsOptions, NormalizeIndentsFunction, NormalizeIndentsOptions, OptionalOnCondition, ParallelTuple, RecordKey, RemoveUndefined, StringListToArrayOptions, VERSION_NUMBER_REGEX, VersionNumber, FormatOptionsBase as VersionNumberToStringOptions, VersionType, addDaysToDate, appendSemicolon, camelToKebab, convertFileToBase64, createFormData, createTemplateStringsArray, deepCopy, deepFreeze, encryptWithKey, fillArray, formatDateAndTime, getRandomNumber, getRecordKeys, getStringsAndInterpolations, httpErrorCodeLookup, interpolate, interpolateObjects, isAnniversary, isLeapYear, isMonthlyMultiple, isOrdered, isSameDate, isTemplateStringsArray, kebabToCamel, normaliseIndents, normalizeIndents, omitProperties, paralleliseArrays, parseBoolean, parseEnv, parseFormData, parseIntStrict, parseVersionType, parseZodSchema, parseZodSchemaAsync, randomiseArray, range, removeDuplicates, removeUndefinedFromObject, sayHello, stringListToArray, stringifyDotenv, truncate, wait, zodVersionNumber };
1086
+ export { APIError, ArrayElement, CallReturnType, CamelToKebabOptions, CreateEnumType, CreateFormDataOptions, CreateFormDataOptionsNullableResolution, CreateFormDataOptionsUndefinedOrNullResolution, DataError, DisallowUndefined, Env, FILE_PATH_REGEX, FormDataArrayResolutionStrategy, FormDataNullableResolutionStrategy, HTTPErrorCode, IgnoreCase, IsTypeArgumentString, KebabToCamelOptions, NAMESPACE_EXPORT_REGEX, NonUndefined, NormaliseIndentsFunction, NormaliseIndentsOptions, NormalizeIndentsFunction, NormalizeIndentsOptions, ONE_DAY_IN_MILLISECONDS, OptionalOnCondition, ParallelTuple, RecordKey, RemoveUndefined, StringListToArrayOptions, VERSION_NUMBER_REGEX, VersionNumber, FormatOptionsBase as VersionNumberToStringOptions, VersionType, addDaysToDate, appendSemicolon, camelToKebab, convertFileToBase64, createFormData, createTemplateStringsArray, deepCopy, deepFreeze, encryptWithKey, fillArray, formatDateAndTime, getRandomNumber, getRecordKeys, getStringsAndInterpolations, httpErrorCodeLookup, interpolate, interpolateObjects, isAnniversary, isLeapYear, isMonthlyMultiple, isOrdered, isSameDate, isTemplateStringsArray, kebabToCamel, normaliseIndents, normalizeIndents, omitProperties, paralleliseArrays, parseBoolean, parseEnv, parseFormData, parseIntStrict, parseVersionType, parseZodSchema, parseZodSchemaAsync, randomiseArray, range, removeDuplicates, removeUndefinedFromObject, sayHello, stringListToArray, stringifyDotenv, truncate, wait, zodVersionNumber };
package/dist/index.d.ts CHANGED
@@ -7,6 +7,9 @@ declare const FILE_PATH_REGEX: string;
7
7
  //#region src/root/constants/NAMESPACE_EXPORT_REGEX.d.ts
8
8
  declare const NAMESPACE_EXPORT_REGEX = "export\\s+\\*\\s+from";
9
9
  //#endregion
10
+ //#region src/root/constants/ONE_DAY_IN_MILLISECONDS.d.ts
11
+ declare const ONE_DAY_IN_MILLISECONDS: number;
12
+ //#endregion
10
13
  //#region src/root/constants/VERSION_NUMBER_REGEX.d.ts
11
14
  declare const VERSION_NUMBER_REGEX: string;
12
15
  //#endregion
@@ -87,8 +90,8 @@ declare function randomiseArray<ItemType>(array: ItemType[]): ItemType[];
87
90
  * @param stop - The number to stop at (exclusive).
88
91
  * @param step - The step size between numbers, defaulting to 1.
89
92
  *
90
- * @throws {Error} If `step` is `0`.
91
- * @throws {Error} If `step` direction does not match the order of `start` and `stop`.
93
+ * @throws {DataError} If `step` is `0`.
94
+ * @throws {DataError} If `step` direction does not match the order of `start` and `stop`.
92
95
  *
93
96
  * @returns An array of numbers satisfying the range provided.
94
97
  */
@@ -198,7 +201,7 @@ declare function isSameDate(firstDate: Date, secondDate: Date): boolean;
198
201
  *
199
202
  * @param file - The file to convert.
200
203
  *
201
- * @throws {Error} If the file reader gives an error.
204
+ * @throws {Error | DataError} If the file reader gives an error.
202
205
  *
203
206
  * @returns A promise that resolves to the encoded base 64 string.
204
207
  */
@@ -239,6 +242,9 @@ declare class APIError extends Error {
239
242
  type RecordKey = string | number | symbol;
240
243
  //#endregion
241
244
  //#region src/root/types/DataError.d.ts
245
+ interface ExpectErrorOptions {
246
+ expectedCode?: string;
247
+ }
242
248
  /**
243
249
  * Represents errors you may get that may've been caused by a specific piece of data.
244
250
  *
@@ -256,6 +262,7 @@ declare class DataError<DataType extends Record<RecordKey, unknown> = Record<Rec
256
262
  * @param options - Extra options to pass to super Error constructor.
257
263
  */
258
264
  constructor(data: DataType, code?: string, message?: string, options?: ErrorOptions);
265
+ private static checkCaughtError;
259
266
  /**
260
267
  * Checks whether the given input may have been caused by a DataError.
261
268
  *
@@ -264,6 +271,30 @@ declare class DataError<DataType extends Record<RecordKey, unknown> = Record<Rec
264
271
  * @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
272
  */
266
273
  static check<DataType extends Record<RecordKey, unknown> = Record<RecordKey, unknown>>(input: unknown): input is DataError<DataType>;
274
+ /**
275
+ * 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.
276
+ *
277
+ * @param errorFunction - The function expected to throw the error.
278
+ * @param options - Extra options to apply.
279
+ *
280
+ * @throws {Error} Any other errors thrown by the `errorFunction` that are not a `DataError`.
281
+ * @throws {Error} If no `DataError` was thrown by the `errorFunction`
282
+ *
283
+ * @returns The `DataError` that was thrown by the `errorFunction`
284
+ */
285
+ static expectError(errorFunction: () => unknown, options?: ExpectErrorOptions): DataError;
286
+ /**
287
+ * 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.
288
+ *
289
+ * @param errorFunction - The function expected to throw the error.
290
+ * @param options - Extra options to apply.
291
+ *
292
+ * @throws {Error} Any other errors thrown by the `errorFunction` that are not a `DataError`.
293
+ * @throws {Error} If no `DataError` was thrown by the `errorFunction`
294
+ *
295
+ * @returns The `DataError` that was thrown by the `errorFunction`
296
+ */
297
+ static expectErrorAsync(errorFunction: () => Promise<unknown>, options?: ExpectErrorOptions): Promise<DataError>;
267
298
  }
268
299
  //#endregion
269
300
  //#region src/root/types/VersionNumber.d.ts
@@ -821,7 +852,7 @@ declare function encryptWithKey(publicKey: string, plaintextValue: string): Prom
821
852
  *
822
853
  * @param stringToAppendTo - The string to append a semicolon to.
823
854
  *
824
- * @throws {Error} If the string contains multiple lines.
855
+ * @throws {DataError} If the string contains multiple lines.
825
856
  *
826
857
  * @returns A string with the semicolon appended.
827
858
  */
@@ -864,12 +895,12 @@ interface KebabToCamelOptions {
864
895
  *
865
896
  * @category String Helpers
866
897
  *
867
- * @param string - The string to convert.
898
+ * @param input - The string to convert.
868
899
  * @param options - Options to apply to the conversion.
869
900
  *
870
901
  * @returns The string converted to camelCase.
871
902
  */
872
- declare function kebabToCamel(string: string, options?: KebabToCamelOptions): string;
903
+ declare function kebabToCamel(input: string, options?: KebabToCamelOptions): string;
873
904
  //#endregion
874
905
  //#region src/root/functions/stringHelpers/truncate.d.ts
875
906
  /**
@@ -1052,4 +1083,4 @@ declare function normaliseIndents(strings: TemplateStringsArray, ...interpolatio
1052
1083
  */
1053
1084
  declare const normalizeIndents: typeof normaliseIndents;
1054
1085
  //#endregion
1055
- export { APIError, type ArrayElement, type CallReturnType, CamelToKebabOptions, type CreateEnumType, type CreateFormDataOptions, type CreateFormDataOptionsNullableResolution, type CreateFormDataOptionsUndefinedOrNullResolution, DataError, type DisallowUndefined, Env, FILE_PATH_REGEX, type FormDataArrayResolutionStrategy, type FormDataNullableResolutionStrategy, type HTTPErrorCode, type IgnoreCase, type IsTypeArgumentString, KebabToCamelOptions, NAMESPACE_EXPORT_REGEX, type NonUndefined, NormaliseIndentsFunction, NormaliseIndentsOptions, NormalizeIndentsFunction, NormalizeIndentsOptions, type OptionalOnCondition, ParallelTuple, type RecordKey, type RemoveUndefined, type StringListToArrayOptions, VERSION_NUMBER_REGEX, VersionNumber, type FormatOptionsBase as VersionNumberToStringOptions, VersionType, addDaysToDate, appendSemicolon, camelToKebab, convertFileToBase64, createFormData, createTemplateStringsArray, deepCopy, deepFreeze, encryptWithKey, fillArray, formatDateAndTime, getRandomNumber, getRecordKeys, getStringsAndInterpolations, httpErrorCodeLookup, interpolate, interpolateObjects, isAnniversary, isLeapYear, isMonthlyMultiple, isOrdered, isSameDate, isTemplateStringsArray, kebabToCamel, normaliseIndents, normalizeIndents, omitProperties, paralleliseArrays, parseBoolean, parseEnv, parseFormData, parseIntStrict, parseVersionType, parseZodSchema, parseZodSchemaAsync, randomiseArray, range, removeDuplicates, removeUndefinedFromObject, sayHello, stringListToArray, stringifyDotenv, truncate, wait, zodVersionNumber };
1086
+ export { APIError, type ArrayElement, type CallReturnType, CamelToKebabOptions, type CreateEnumType, type CreateFormDataOptions, type CreateFormDataOptionsNullableResolution, type CreateFormDataOptionsUndefinedOrNullResolution, DataError, type DisallowUndefined, Env, FILE_PATH_REGEX, type FormDataArrayResolutionStrategy, type FormDataNullableResolutionStrategy, type HTTPErrorCode, type IgnoreCase, type IsTypeArgumentString, KebabToCamelOptions, NAMESPACE_EXPORT_REGEX, type NonUndefined, NormaliseIndentsFunction, NormaliseIndentsOptions, NormalizeIndentsFunction, NormalizeIndentsOptions, ONE_DAY_IN_MILLISECONDS, type OptionalOnCondition, ParallelTuple, type RecordKey, type RemoveUndefined, type StringListToArrayOptions, VERSION_NUMBER_REGEX, VersionNumber, type FormatOptionsBase as VersionNumberToStringOptions, VersionType, addDaysToDate, appendSemicolon, camelToKebab, convertFileToBase64, createFormData, createTemplateStringsArray, deepCopy, deepFreeze, encryptWithKey, fillArray, formatDateAndTime, getRandomNumber, getRecordKeys, getStringsAndInterpolations, httpErrorCodeLookup, interpolate, interpolateObjects, isAnniversary, isLeapYear, isMonthlyMultiple, isOrdered, isSameDate, isTemplateStringsArray, kebabToCamel, normaliseIndents, normalizeIndents, omitProperties, paralleliseArrays, parseBoolean, parseEnv, parseFormData, parseIntStrict, parseVersionType, parseZodSchema, parseZodSchemaAsync, randomiseArray, range, removeDuplicates, removeUndefinedFromObject, sayHello, stringListToArray, stringifyDotenv, truncate, wait, zodVersionNumber };
package/dist/index.js CHANGED
@@ -8,6 +8,10 @@ const FILE_PATH_REGEX = String.raw`^(?<directory>.+)[\/\\](?<base>[^\/\\]+)$`;
8
8
  //#region src/root/constants/NAMESPACE_EXPORT_REGEX.ts
9
9
  const NAMESPACE_EXPORT_REGEX = "export\\s+\\*\\s+from";
10
10
 
11
+ //#endregion
12
+ //#region src/root/constants/ONE_DAY_IN_MILLISECONDS.ts
13
+ const ONE_DAY_IN_MILLISECONDS = 1440 * 60 * 1e3;
14
+
11
15
  //#endregion
12
16
  //#region src/root/constants/VERSION_NUMBER_REGEX.ts
13
17
  const VERSION_NUMBER_REGEX = "^(?:v)?(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$";
@@ -134,6 +138,17 @@ var DataError = class DataError extends Error {
134
138
  Object.defineProperty(this, "message", { enumerable: true });
135
139
  Object.setPrototypeOf(this, new.target.prototype);
136
140
  }
141
+ static checkCaughtError(error, options) {
142
+ if (DataError.check(error)) {
143
+ 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.
144
+
145
+ Expected: ${options.expectedCode}
146
+ Received: ${error.code}
147
+ `, { cause: error });
148
+ return error;
149
+ }
150
+ throw error;
151
+ }
137
152
  /**
138
153
  * Checks whether the given input may have been caused by a DataError.
139
154
  *
@@ -146,6 +161,44 @@ var DataError = class DataError extends Error {
146
161
  const data = input;
147
162
  return typeof data === "object" && data !== null && typeof data.message === "string" && typeof data.code === "string" && "data" in data;
148
163
  }
164
+ /**
165
+ * 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.
166
+ *
167
+ * @param errorFunction - The function expected to throw the error.
168
+ * @param options - Extra options to apply.
169
+ *
170
+ * @throws {Error} Any other errors thrown by the `errorFunction` that are not a `DataError`.
171
+ * @throws {Error} If no `DataError` was thrown by the `errorFunction`
172
+ *
173
+ * @returns The `DataError` that was thrown by the `errorFunction`
174
+ */
175
+ static expectError(errorFunction, options) {
176
+ try {
177
+ errorFunction();
178
+ } catch (error) {
179
+ return DataError.checkCaughtError(error, options);
180
+ }
181
+ throw new Error("Expected a DataError to be thrown but none was thrown");
182
+ }
183
+ /**
184
+ * 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.
185
+ *
186
+ * @param errorFunction - The function expected to throw the error.
187
+ * @param options - Extra options to apply.
188
+ *
189
+ * @throws {Error} Any other errors thrown by the `errorFunction` that are not a `DataError`.
190
+ * @throws {Error} If no `DataError` was thrown by the `errorFunction`
191
+ *
192
+ * @returns The `DataError` that was thrown by the `errorFunction`
193
+ */
194
+ static async expectErrorAsync(errorFunction, options) {
195
+ try {
196
+ await errorFunction();
197
+ } catch (error) {
198
+ return DataError.checkCaughtError(error, options);
199
+ }
200
+ throw new Error("Expected a DataError to be thrown but none was thrown");
201
+ }
149
202
  };
150
203
 
151
204
  //#endregion
@@ -400,19 +453,27 @@ function randomiseArray(array) {
400
453
  * @param stop - The number to stop at (exclusive).
401
454
  * @param step - The step size between numbers, defaulting to 1.
402
455
  *
403
- * @throws {Error} If `step` is `0`.
404
- * @throws {Error} If `step` direction does not match the order of `start` and `stop`.
456
+ * @throws {DataError} If `step` is `0`.
457
+ * @throws {DataError} If `step` direction does not match the order of `start` and `stop`.
405
458
  *
406
459
  * @returns An array of numbers satisfying the range provided.
407
460
  */
408
461
  function range(start, stop, step = 1) {
409
462
  const numbers = [];
410
- if (step === 0) throw new Error("ZERO_STEP_SIZE_NOT_ALLOWED");
463
+ if (step === 0) throw new DataError({ step }, "ZERO_STEP_SIZE", "Step size cannot be zero.");
411
464
  else if (step > 0) {
412
- if (start > stop) throw new Error("INVALID_BOUNDARIES");
465
+ if (start > stop) throw new DataError({
466
+ start,
467
+ stop,
468
+ step
469
+ }, "INVALID_BOUNDARIES", "The starting value cannot be bigger than the final value if step is positive");
413
470
  for (let i = start; i < stop; i += step) numbers.push(i);
414
471
  } else if (step < 0) {
415
- if (start < stop) throw new Error("INVALID_BOUNDARIES");
472
+ if (start < stop) throw new DataError({
473
+ start,
474
+ stop,
475
+ step
476
+ }, "INVALID_BOUNDARIES", "The final value cannot be bigger than the starting value if step is negative");
416
477
  for (let i = start; i > stop; i += step) numbers.push(i);
417
478
  }
418
479
  return numbers;
@@ -592,7 +653,7 @@ function isMonthlyMultiple(firstDate, secondDate) {
592
653
  *
593
654
  * @param file - The file to convert.
594
655
  *
595
- * @throws {Error} If the file reader gives an error.
656
+ * @throws {Error | DataError} If the file reader gives an error.
596
657
  *
597
658
  * @returns A promise that resolves to the encoded base 64 string.
598
659
  */
@@ -602,7 +663,7 @@ function convertFileToBase64(file) {
602
663
  reader.readAsDataURL(file);
603
664
  reader.onload = () => {
604
665
  if (reader.result === null) {
605
- reject(/* @__PURE__ */ new Error("FILE_CONVERSION_ERROR"));
666
+ reject(new DataError({ result: reader.result }, "FILE_CONVERSION_ERROR", "Could not convert the given file."));
606
667
  return;
607
668
  }
608
669
  resolve(reader.result);
@@ -618,6 +679,9 @@ function convertFileToBase64(file) {
618
679
  function getNullableResolutionStrategy(key, strategy) {
619
680
  return (typeof strategy === "object" ? strategy[key] : strategy) ?? "empty";
620
681
  }
682
+ function isPrimitive(item) {
683
+ return typeof item === "string" || typeof item === "number" || typeof item === "boolean";
684
+ }
621
685
  /**
622
686
  * Creates FormData from a given object, resolving non-string types as appropriate.
623
687
  *
@@ -644,7 +708,7 @@ function createFormData(data, options = {
644
708
  formData.append(String(key), JSON.stringify(value));
645
709
  break;
646
710
  case "omit": break;
647
- default: throw new TypeError("SLOPPY_PURE_JAVASCRIPT_USER_ERROR");
711
+ default: throw resolutionStrategy;
648
712
  }
649
713
  }
650
714
  function resolveNullables(key, value, options) {
@@ -667,10 +731,10 @@ function createFormData(data, options = {
667
731
  if (Array.isArray(value)) {
668
732
  if (value.some((item) => {
669
733
  return item instanceof Blob;
670
- }) && (options.arrayResolution === "stringify" || typeof options.arrayResolution === "object" && options.arrayResolution[key] === "stringify")) throw new TypeError("CANNOT_STRINGIFY_BLOB");
734
+ }) && (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
735
  if (options.arrayResolution === "multiple" || typeof options.arrayResolution === "object" && options.arrayResolution[key] === "multiple") {
672
736
  for (const item of value) {
673
- if ((typeof item === "object" || !item) && !(item instanceof Blob)) throw new TypeError("NON_PRIMITIVE_ARRAY_ITEMS_FOUND");
737
+ 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
738
  if (item instanceof Blob) formData.append(String(key), item);
675
739
  else formData.append(String(key), String(item));
676
740
  }
@@ -1412,12 +1476,12 @@ async function encryptWithKey(publicKey, plaintextValue) {
1412
1476
  *
1413
1477
  * @param stringToAppendTo - The string to append a semicolon to.
1414
1478
  *
1415
- * @throws {Error} If the string contains multiple lines.
1479
+ * @throws {DataError} If the string contains multiple lines.
1416
1480
  *
1417
1481
  * @returns A string with the semicolon appended.
1418
1482
  */
1419
1483
  function appendSemicolon(stringToAppendTo) {
1420
- if (stringToAppendTo.includes("\n")) throw new Error("MULTIPLE_LINE_ERROR");
1484
+ if (stringToAppendTo.includes("\n")) throw new DataError({ stringToAppendTo }, "MULTIPLE_LINE_ERROR", "Cannot append semicolon to multi-line string.");
1421
1485
  const stringWithNoTrailingWhitespace = stringToAppendTo.trimEnd();
1422
1486
  if (stringWithNoTrailingWhitespace === "") return "";
1423
1487
  return stringWithNoTrailingWhitespace[stringWithNoTrailingWhitespace.length - 1] === ";" ? stringWithNoTrailingWhitespace : `${stringWithNoTrailingWhitespace};`;
@@ -1469,34 +1533,35 @@ function camelToKebab(string, options = { preserveConsecutiveCapitals: true }) {
1469
1533
  *
1470
1534
  * @category String Helpers
1471
1535
  *
1472
- * @param string - The string to convert.
1536
+ * @param input - The string to convert.
1473
1537
  * @param options - Options to apply to the conversion.
1474
1538
  *
1475
1539
  * @returns The string converted to camelCase.
1476
1540
  */
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");
1541
+ function kebabToCamel(input, options) {
1542
+ if (input !== input.toLowerCase()) throw new DataError({ input }, "UPPERCASE_INPUT", "Kebab-case must be purely lowercase.");
1543
+ if (input.startsWith("-") || input.endsWith("-")) throw new DataError({ input }, "TRAILING_DASHES", "Dashes at the start and/or end are not allowed.");
1544
+ if (input.includes("--")) throw new DataError({ input }, "CONSECUTIVE_DASHES", "Consecutive dashes are not allowed.");
1480
1545
  let outputString = "";
1481
1546
  let skip = false;
1482
- for (const stringIndex in [...string]) {
1547
+ for (const stringIndex in [...input]) {
1483
1548
  if (skip) {
1484
1549
  skip = false;
1485
1550
  continue;
1486
1551
  }
1487
1552
  const index = parseIntStrict(stringIndex);
1488
1553
  if (index === 0 && options?.startWithUpper) {
1489
- outputString += string[index].toUpperCase();
1554
+ outputString += input[index].toUpperCase();
1490
1555
  continue;
1491
1556
  }
1492
- if (index === string.length - 1) {
1493
- outputString += string[index];
1557
+ if (index === input.length - 1) {
1558
+ outputString += input[index];
1494
1559
  break;
1495
1560
  }
1496
- if (string[index] === "-" && /^[a-zA-Z]+$/.test(string[index + 1])) {
1497
- outputString += string[index + 1].toUpperCase();
1561
+ if (input[index] === "-" && /^[a-zA-Z]+$/.test(input[index + 1])) {
1562
+ outputString += input[index + 1].toUpperCase();
1498
1563
  skip = true;
1499
- } else outputString += string[index];
1564
+ } else outputString += input[index];
1500
1565
  }
1501
1566
  return outputString;
1502
1567
  }
@@ -1518,4 +1583,4 @@ function truncate(stringToTruncate, maxLength = 5) {
1518
1583
  }
1519
1584
 
1520
1585
  //#endregion
1521
- export { APIError, DataError, Env, FILE_PATH_REGEX, NAMESPACE_EXPORT_REGEX, VERSION_NUMBER_REGEX, VersionNumber, VersionType, addDaysToDate, appendSemicolon, camelToKebab, convertFileToBase64, createFormData, createTemplateStringsArray, deepCopy, deepFreeze, encryptWithKey, fillArray, formatDateAndTime, getRandomNumber, getRecordKeys, getStringsAndInterpolations, httpErrorCodeLookup, interpolate, interpolateObjects, isAnniversary, isLeapYear, isMonthlyMultiple, isOrdered, isSameDate, isTemplateStringsArray, kebabToCamel, normaliseIndents, normalizeIndents, omitProperties, paralleliseArrays, parseBoolean, parseEnv, parseFormData, parseIntStrict, parseVersionType, parseZodSchema, parseZodSchemaAsync, randomiseArray, range, removeDuplicates, removeUndefinedFromObject, sayHello, stringListToArray, stringifyDotenv, truncate, wait, zodVersionNumber };
1586
+ export { APIError, DataError, Env, FILE_PATH_REGEX, NAMESPACE_EXPORT_REGEX, ONE_DAY_IN_MILLISECONDS, VERSION_NUMBER_REGEX, VersionNumber, VersionType, addDaysToDate, appendSemicolon, camelToKebab, convertFileToBase64, createFormData, createTemplateStringsArray, deepCopy, deepFreeze, encryptWithKey, fillArray, formatDateAndTime, getRandomNumber, getRecordKeys, getStringsAndInterpolations, httpErrorCodeLookup, interpolate, interpolateObjects, isAnniversary, isLeapYear, isMonthlyMultiple, isOrdered, isSameDate, isTemplateStringsArray, kebabToCamel, normaliseIndents, normalizeIndents, omitProperties, paralleliseArrays, parseBoolean, parseEnv, parseFormData, parseIntStrict, parseVersionType, parseZodSchema, parseZodSchemaAsync, randomiseArray, range, removeDuplicates, removeUndefinedFromObject, sayHello, stringListToArray, stringifyDotenv, truncate, wait, zodVersionNumber };
@@ -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,12 +141,54 @@ 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
136
185
  //#region src/root/constants/FILE_PATH_REGEX.ts
137
186
  const FILE_PATH_REGEX = String.raw`^(?<directory>.+)[\/\\](?<base>[^\/\\]+)$`;
138
187
 
188
+ //#endregion
189
+ //#region src/root/constants/ONE_DAY_IN_MILLISECONDS.ts
190
+ const ONE_DAY_IN_MILLISECONDS = 1440 * 60 * 1e3;
191
+
139
192
  //#endregion
140
193
  //#region src/root/constants/VERSION_NUMBER_REGEX.ts
141
194
  const VERSION_NUMBER_REGEX = "^(?:v)?(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$";
@@ -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,12 +111,54 @@ 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
106
155
  //#region src/root/constants/FILE_PATH_REGEX.ts
107
156
  const FILE_PATH_REGEX = String.raw`^(?<directory>.+)[\/\\](?<base>[^\/\\]+)$`;
108
157
 
158
+ //#endregion
159
+ //#region src/root/constants/ONE_DAY_IN_MILLISECONDS.ts
160
+ const ONE_DAY_IN_MILLISECONDS = 1440 * 60 * 1e3;
161
+
109
162
  //#endregion
110
163
  //#region src/root/constants/VERSION_NUMBER_REGEX.ts
111
164
  const VERSION_NUMBER_REGEX = "^(?:v)?(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$";
@@ -73,49 +73,12 @@ const normaliseImportPath = normalizeImportPath;
73
73
  const FILE_PATH_REGEX = String.raw`^(?<directory>.+)[\/\\](?<base>[^\/\\]+)$`;
74
74
 
75
75
  //#endregion
76
- //#region src/root/constants/VERSION_NUMBER_REGEX.ts
77
- const VERSION_NUMBER_REGEX = "^(?:v)?(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$";
76
+ //#region src/root/constants/ONE_DAY_IN_MILLISECONDS.ts
77
+ const ONE_DAY_IN_MILLISECONDS = 1440 * 60 * 1e3;
78
78
 
79
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
- };
80
+ //#region src/root/constants/VERSION_NUMBER_REGEX.ts
81
+ const VERSION_NUMBER_REGEX = "^(?:v)?(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$";
119
82
 
120
83
  //#endregion
121
84
  //#region src/root/functions/arrayHelpers/fillArray.ts
@@ -419,6 +382,96 @@ const VersionType = {
419
382
  PATCH: "patch"
420
383
  };
421
384
 
385
+ //#endregion
386
+ //#region src/root/types/DataError.ts
387
+ /**
388
+ * Represents errors you may get that may've been caused by a specific piece of data.
389
+ *
390
+ * @category Types
391
+ *
392
+ * @template DataType - The type of the data that caused the error.
393
+ */
394
+ var DataError = class DataError extends Error {
395
+ code;
396
+ data;
397
+ /**
398
+ * @param data - The data that caused the error.
399
+ * @param code - A standardised code (e.g. UNEXPECTED_DATA).
400
+ * @param message - A human-readable error message (e.g. The data provided is invalid).
401
+ * @param options - Extra options to pass to super Error constructor.
402
+ */
403
+ constructor(data, code = "INVALID_DATA", message = "The data provided is invalid", options) {
404
+ super(message, options);
405
+ if (Error.captureStackTrace) Error.captureStackTrace(this, new.target);
406
+ this.name = new.target.name;
407
+ this.code = code;
408
+ this.data = data;
409
+ Object.defineProperty(this, "message", { enumerable: true });
410
+ Object.setPrototypeOf(this, new.target.prototype);
411
+ }
412
+ static checkCaughtError(error, options) {
413
+ if (DataError.check(error)) {
414
+ 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.
415
+
416
+ Expected: ${options.expectedCode}
417
+ Received: ${error.code}
418
+ `, { cause: error });
419
+ return error;
420
+ }
421
+ throw error;
422
+ }
423
+ /**
424
+ * Checks whether the given input may have been caused by a DataError.
425
+ *
426
+ * @param input - The input to check.
427
+ *
428
+ * @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`.
429
+ */
430
+ static check(input) {
431
+ if (input instanceof DataError) return true;
432
+ const data = input;
433
+ return typeof data === "object" && data !== null && typeof data.message === "string" && typeof data.code === "string" && "data" in data;
434
+ }
435
+ /**
436
+ * 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.
437
+ *
438
+ * @param errorFunction - The function expected to throw the error.
439
+ * @param options - Extra options to apply.
440
+ *
441
+ * @throws {Error} Any other errors thrown by the `errorFunction` that are not a `DataError`.
442
+ * @throws {Error} If no `DataError` was thrown by the `errorFunction`
443
+ *
444
+ * @returns The `DataError` that was thrown by the `errorFunction`
445
+ */
446
+ static expectError(errorFunction, options) {
447
+ try {
448
+ errorFunction();
449
+ } catch (error) {
450
+ return DataError.checkCaughtError(error, options);
451
+ }
452
+ throw new Error("Expected a DataError to be thrown but none was thrown");
453
+ }
454
+ /**
455
+ * 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.
456
+ *
457
+ * @param errorFunction - The function expected to throw the error.
458
+ * @param options - Extra options to apply.
459
+ *
460
+ * @throws {Error} Any other errors thrown by the `errorFunction` that are not a `DataError`.
461
+ * @throws {Error} If no `DataError` was thrown by the `errorFunction`
462
+ *
463
+ * @returns The `DataError` that was thrown by the `errorFunction`
464
+ */
465
+ static async expectErrorAsync(errorFunction, options) {
466
+ try {
467
+ await errorFunction();
468
+ } catch (error) {
469
+ return DataError.checkCaughtError(error, options);
470
+ }
471
+ throw new Error("Expected a DataError to be thrown but none was thrown");
472
+ }
473
+ };
474
+
422
475
  //#endregion
423
476
  //#region src/root/types/VersionNumber.ts
424
477
  /**
@@ -43,49 +43,12 @@ const normaliseImportPath = normalizeImportPath;
43
43
  const FILE_PATH_REGEX = String.raw`^(?<directory>.+)[\/\\](?<base>[^\/\\]+)$`;
44
44
 
45
45
  //#endregion
46
- //#region src/root/constants/VERSION_NUMBER_REGEX.ts
47
- const VERSION_NUMBER_REGEX = "^(?:v)?(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$";
46
+ //#region src/root/constants/ONE_DAY_IN_MILLISECONDS.ts
47
+ const ONE_DAY_IN_MILLISECONDS = 1440 * 60 * 1e3;
48
48
 
49
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
- };
50
+ //#region src/root/constants/VERSION_NUMBER_REGEX.ts
51
+ const VERSION_NUMBER_REGEX = "^(?:v)?(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$";
89
52
 
90
53
  //#endregion
91
54
  //#region src/root/functions/arrayHelpers/fillArray.ts
@@ -389,6 +352,96 @@ const VersionType = {
389
352
  PATCH: "patch"
390
353
  };
391
354
 
355
+ //#endregion
356
+ //#region src/root/types/DataError.ts
357
+ /**
358
+ * Represents errors you may get that may've been caused by a specific piece of data.
359
+ *
360
+ * @category Types
361
+ *
362
+ * @template DataType - The type of the data that caused the error.
363
+ */
364
+ var DataError = class DataError extends Error {
365
+ code;
366
+ data;
367
+ /**
368
+ * @param data - The data that caused the error.
369
+ * @param code - A standardised code (e.g. UNEXPECTED_DATA).
370
+ * @param message - A human-readable error message (e.g. The data provided is invalid).
371
+ * @param options - Extra options to pass to super Error constructor.
372
+ */
373
+ constructor(data, code = "INVALID_DATA", message = "The data provided is invalid", options) {
374
+ super(message, options);
375
+ if (Error.captureStackTrace) Error.captureStackTrace(this, new.target);
376
+ this.name = new.target.name;
377
+ this.code = code;
378
+ this.data = data;
379
+ Object.defineProperty(this, "message", { enumerable: true });
380
+ Object.setPrototypeOf(this, new.target.prototype);
381
+ }
382
+ static checkCaughtError(error, options) {
383
+ if (DataError.check(error)) {
384
+ 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.
385
+
386
+ Expected: ${options.expectedCode}
387
+ Received: ${error.code}
388
+ `, { cause: error });
389
+ return error;
390
+ }
391
+ throw error;
392
+ }
393
+ /**
394
+ * Checks whether the given input may have been caused by a DataError.
395
+ *
396
+ * @param input - The input to check.
397
+ *
398
+ * @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`.
399
+ */
400
+ static check(input) {
401
+ if (input instanceof DataError) return true;
402
+ const data = input;
403
+ return typeof data === "object" && data !== null && typeof data.message === "string" && typeof data.code === "string" && "data" in data;
404
+ }
405
+ /**
406
+ * 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.
407
+ *
408
+ * @param errorFunction - The function expected to throw the error.
409
+ * @param options - Extra options to apply.
410
+ *
411
+ * @throws {Error} Any other errors thrown by the `errorFunction` that are not a `DataError`.
412
+ * @throws {Error} If no `DataError` was thrown by the `errorFunction`
413
+ *
414
+ * @returns The `DataError` that was thrown by the `errorFunction`
415
+ */
416
+ static expectError(errorFunction, options) {
417
+ try {
418
+ errorFunction();
419
+ } catch (error) {
420
+ return DataError.checkCaughtError(error, options);
421
+ }
422
+ throw new Error("Expected a DataError to be thrown but none was thrown");
423
+ }
424
+ /**
425
+ * 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.
426
+ *
427
+ * @param errorFunction - The function expected to throw the error.
428
+ * @param options - Extra options to apply.
429
+ *
430
+ * @throws {Error} Any other errors thrown by the `errorFunction` that are not a `DataError`.
431
+ * @throws {Error} If no `DataError` was thrown by the `errorFunction`
432
+ *
433
+ * @returns The `DataError` that was thrown by the `errorFunction`
434
+ */
435
+ static async expectErrorAsync(errorFunction, options) {
436
+ try {
437
+ await errorFunction();
438
+ } catch (error) {
439
+ return DataError.checkCaughtError(error, options);
440
+ }
441
+ throw new Error("Expected a DataError to be thrown but none was thrown");
442
+ }
443
+ };
444
+
392
445
  //#endregion
393
446
  //#region src/root/types/VersionNumber.ts
394
447
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alextheman/utility",
3
- "version": "5.1.4",
3
+ "version": "5.3.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",