@alextheman/utility 5.13.0 → 5.14.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.js CHANGED
@@ -156,7 +156,7 @@ var CodeError = class CodeError extends Error {
156
156
  *
157
157
  * @template DataType - The type of the data that caused the error.
158
158
  */
159
- var DataError = class DataError extends CodeError {
159
+ var DataError$1 = class DataError$1 extends CodeError {
160
160
  data;
161
161
  /**
162
162
  * @param data - The data that caused the error.
@@ -181,7 +181,7 @@ var DataError = class DataError extends CodeError {
181
181
  * @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`.
182
182
  */
183
183
  static check(input) {
184
- if (input instanceof DataError) return true;
184
+ if (input instanceof DataError$1) return true;
185
185
  return typeof input === "object" && input !== null && "message" in input && typeof input.message === "string" && "code" in input && typeof input.code === "string" && "data" in input;
186
186
  }
187
187
  /**
@@ -230,18 +230,18 @@ var DataError = class DataError extends CodeError {
230
230
  function parseIntStrict(string, radix) {
231
231
  const trimmedString = string.trim();
232
232
  const maxAllowedAlphabeticalCharacter = radix && radix > 10 && radix <= 36 ? String.fromCharCode(87 + radix - 1) : void 0;
233
- if (!(radix && radix > 10 && radix <= 36 ? new RegExp(`^[+-]?[0-9a-${maxAllowedAlphabeticalCharacter}]+$`, "i") : /^[+-]?\d+$/).test(trimmedString)) throw new DataError(radix ? {
233
+ if (!(radix && radix > 10 && radix <= 36 ? new RegExp(`^[+-]?[0-9a-${maxAllowedAlphabeticalCharacter}]+$`, "i") : /^[+-]?\d+$/).test(trimmedString)) throw new DataError$1(radix ? {
234
234
  string,
235
235
  radix
236
236
  } : { string }, "INTEGER_PARSING_ERROR", `Only numeric values${radix && radix > 10 && radix <= 36 ? ` or character${radix !== 11 ? "s" : ""} A${radix !== 11 ? `-${maxAllowedAlphabeticalCharacter?.toUpperCase()} ` : " "}` : " "}are allowed.`);
237
237
  if (radix && radix < 10 && [...trimmedString.replace(/^[+-]/, "")].some((character) => {
238
238
  return parseInt(character) >= radix;
239
- })) throw new DataError({
239
+ })) throw new DataError$1({
240
240
  string,
241
241
  radix
242
242
  }, "INTEGER_PARSING_ERROR", "Value contains one or more digits outside of the range of the given radix.");
243
243
  const parseIntResult = parseInt(trimmedString, radix);
244
- if (isNaN(parseIntResult)) throw new DataError({ string }, "INTEGER_PARSING_ERROR", "Value is not a valid integer.");
244
+ if (isNaN(parseIntResult)) throw new DataError$1({ string }, "INTEGER_PARSING_ERROR", "Value is not a valid integer.");
245
245
  return parseIntResult;
246
246
  }
247
247
  //#endregion
@@ -304,16 +304,16 @@ function randomiseArray(array) {
304
304
  */
305
305
  function range(start, stop, step = 1) {
306
306
  const numbers = [];
307
- if (step === 0) throw new DataError({ step }, "ZERO_STEP_SIZE", "Step size cannot be zero.");
307
+ if (step === 0) throw new DataError$1({ step }, "ZERO_STEP_SIZE", "Step size cannot be zero.");
308
308
  else if (step > 0) {
309
- if (start > stop) throw new DataError({
309
+ if (start > stop) throw new DataError$1({
310
310
  start,
311
311
  stop,
312
312
  step
313
313
  }, "INVALID_BOUNDARIES", "The starting value cannot be bigger than the final value if step is positive");
314
314
  for (let i = start; i < stop; i += step) numbers.push(i);
315
315
  } else if (step < 0) {
316
- if (start < stop) throw new DataError({
316
+ if (start < stop) throw new DataError$1({
317
317
  start,
318
318
  stop,
319
319
  step
@@ -513,7 +513,7 @@ function convertFileToBase64(file) {
513
513
  reader.readAsDataURL(file);
514
514
  reader.onload = () => {
515
515
  if (reader.result === null) {
516
- reject(new DataError({ result: reader.result }, "FILE_CONVERSION_ERROR", "Could not convert the given file."));
516
+ reject(new DataError$1({ result: reader.result }, "FILE_CONVERSION_ERROR", "Could not convert the given file."));
517
517
  return;
518
518
  }
519
519
  resolve(reader.result);
@@ -580,10 +580,10 @@ function createFormData(data, options = {
580
580
  if (Array.isArray(value)) {
581
581
  if (value.some((item) => {
582
582
  return item instanceof Blob;
583
- }) && (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.");
583
+ }) && (options.arrayResolution === "stringify" || typeof options.arrayResolution === "object" && options.arrayResolution[key] === "stringify")) throw new DataError$1({ value }, "CANNOT_STRINGIFY_BLOB", "Files/blobs cannot be stringified. Please change the resolution option for this item to \"multiple\" instead.");
584
584
  if (options.arrayResolution === "multiple" || typeof options.arrayResolution === "object" && options.arrayResolution[key] === "multiple") {
585
585
  for (const item of value) {
586
- 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.");
586
+ if (!isPrimitive(item) && !(item instanceof Blob)) throw new DataError$1({ 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.");
587
587
  if (item instanceof Blob) formData.append(String(key), item);
588
588
  else formData.append(String(key), String(item));
589
589
  }
@@ -739,8 +739,8 @@ function stringifyDotenv(contents, options) {
739
739
  const { quoteStyle = "double" } = options ?? {};
740
740
  let result = "";
741
741
  for (const key in contentsCopy) {
742
- if (/[ \t\r\n]/.test(key)) throw new DataError({ [key]: contentsCopy[key] }, "INVALID_KEY", "Environment variables are not allowed to have whitespace.");
743
- if (quoteStyle === "none") if (/[ \t\r\n]/.test(contentsCopy[key]) || contentsCopy[key].includes("#") || contentsCopy[key].includes("=") || contentsCopy[key].includes("\\")) throw new DataError({ [key]: contentsCopy[key] }, "INCOMPATIBLE_QUOTE_STYLE", "Cannot use `{ quoteStyle: \"none\" }` when value has whitespace, #, =, or \\");
742
+ if (/[ \t\r\n]/.test(key)) throw new DataError$1({ [key]: contentsCopy[key] }, "INVALID_KEY", "Environment variables are not allowed to have whitespace.");
743
+ if (quoteStyle === "none") if (/[ \t\r\n]/.test(contentsCopy[key]) || contentsCopy[key].includes("#") || contentsCopy[key].includes("=") || contentsCopy[key].includes("\\")) throw new DataError$1({ [key]: contentsCopy[key] }, "INCOMPATIBLE_QUOTE_STYLE", "Cannot use `{ quoteStyle: \"none\" }` when value has whitespace, #, =, or \\");
744
744
  else {
745
745
  result += `${key}=${contentsCopy[key]}\n`;
746
746
  continue;
@@ -865,11 +865,221 @@ function removeUndefinedFromObject(object) {
865
865
  */
866
866
  function parseBoolean(inputString) {
867
867
  const normalisedString = inputString.toLowerCase();
868
- if (!["true", "false"].includes(normalisedString)) throw new DataError({ inputString }, "INVALID_BOOLEAN_STRING", "The provided boolean string must be one of `true | false`");
868
+ if (!["true", "false"].includes(normalisedString)) throw new DataError$1({ inputString }, "INVALID_BOOLEAN_STRING", "The provided boolean string must be one of `true | false`");
869
869
  return normalisedString === "true";
870
870
  }
871
871
  //#endregion
872
- //#region src/root/functions/parsers/zod/_parseZodSchema.ts
872
+ //#region src/root/types/APIError.ts
873
+ const httpErrorCodeLookup = {
874
+ 400: "BAD_REQUEST",
875
+ 401: "UNAUTHORISED",
876
+ 403: "FORBIDDEN",
877
+ 404: "NOT_FOUND",
878
+ 418: "I_AM_A_TEAPOT",
879
+ 500: "INTERNAL_SERVER_ERROR"
880
+ };
881
+ /**
882
+ * Represents common errors you may get from a HTTP API request.
883
+ *
884
+ * @category Types
885
+ */
886
+ var APIError = class APIError extends Error {
887
+ status;
888
+ /**
889
+ * @param status - A HTTP status code. Can be any number, but numbers between 400 and 600 are encouraged to fit with HTTP status code conventions.
890
+ * @param message - An error message to display alongside the status code.
891
+ * @param options - Extra options to be passed to super Error constructor.
892
+ */
893
+ constructor(status = 500, message, options) {
894
+ super(message, options);
895
+ this.status = status;
896
+ if (message) this.message = message;
897
+ else this.message = httpErrorCodeLookup[this.status] ?? "API_ERROR";
898
+ Object.defineProperty(this, "message", { enumerable: true });
899
+ Object.setPrototypeOf(this, new.target.prototype);
900
+ }
901
+ /**
902
+ * Checks whether the given input may have been caused by an APIError.
903
+ *
904
+ * @param input - The input to check.
905
+ *
906
+ * @returns `true` if the input is an APIError, and `false` otherwise. The type of the input will also be narrowed down to APIError if `true`.
907
+ */
908
+ static check(input) {
909
+ if (input instanceof APIError) return true;
910
+ const data = input;
911
+ return typeof data === "object" && data !== null && typeof data?.status === "number" && typeof data?.message === "string";
912
+ }
913
+ };
914
+ //#endregion
915
+ //#region src/root/types/VersionNumber.ts
916
+ /**
917
+ * Represents a software version number, considered to be made up of a major, minor, and patch part.
918
+ *
919
+ * @category Types
920
+ */
921
+ var VersionNumber = class VersionNumber {
922
+ static NON_NEGATIVE_TUPLE_ERROR = "Input array must be a tuple of three non-negative integers.";
923
+ /** The major number. Increments when a feature is removed or changed in a way that is not backwards-compatible with the previous release. */
924
+ major = 0;
925
+ /** The minor number. Increments when a new feature is added/deprecated and is expected to be backwards-compatible with the previous release. */
926
+ minor = 0;
927
+ /** The patch number. Increments when the next release is fixing a bug or doing a small refactor that should not be noticeable in practice. */
928
+ patch = 0;
929
+ /**
930
+ * @param input - The input to create a new instance of `VersionNumber` from.
931
+ */
932
+ constructor(input) {
933
+ if (input instanceof VersionNumber) {
934
+ this.major = input.major;
935
+ this.minor = input.minor;
936
+ this.patch = input.patch;
937
+ } else if (typeof input === "string") {
938
+ if (!VERSION_NUMBER_REGEX.test(input)) throw new DataError$1({ input }, "INVALID_VERSION", `"${input}" is not a valid version number. Version numbers must be of the format "X.Y.Z" or "vX.Y.Z", where X, Y, and Z are non-negative integers.`);
939
+ const [major, minor, patch] = VersionNumber.formatString(input, { omitPrefix: true }).split(".").map((number) => {
940
+ return parseIntStrict(number);
941
+ });
942
+ this.major = major;
943
+ this.minor = minor;
944
+ this.patch = patch;
945
+ } else if (Array.isArray(input)) {
946
+ if (input.length !== 3) throw new DataError$1({ input }, "INVALID_LENGTH", VersionNumber.NON_NEGATIVE_TUPLE_ERROR);
947
+ const [major, minor, patch] = input.map((number) => {
948
+ const parsedInteger = parseIntStrict(number?.toString());
949
+ if (parsedInteger < 0) throw new DataError$1({ input }, "NEGATIVE_INPUTS", VersionNumber.NON_NEGATIVE_TUPLE_ERROR);
950
+ return parsedInteger;
951
+ });
952
+ this.major = major;
953
+ this.minor = minor;
954
+ this.patch = patch;
955
+ } else throw new DataError$1({ input }, "INVALID_INPUT", normaliseIndents`
956
+ The provided input can not be parsed into a valid version number.
957
+ Expected either a string of format X.Y.Z or vX.Y.Z, a tuple of three numbers, or another \`VersionNumber\` instance.
958
+ `);
959
+ }
960
+ /**
961
+ * Gets the current version type of the current instance of `VersionNumber`.
962
+ *
963
+ * @returns Either `"major"`, `"minor"`, or `"patch"`, depending on the version type.
964
+ */
965
+ get type() {
966
+ if (this.minor === 0 && this.patch === 0) return VersionType.MAJOR;
967
+ if (this.patch === 0) return VersionType.MINOR;
968
+ return VersionType.PATCH;
969
+ }
970
+ static formatString(input, options) {
971
+ if (options?.omitPrefix) return input.startsWith("v") ? input.slice(1) : input;
972
+ return input.startsWith("v") ? input : `v${input}`;
973
+ }
974
+ /**
975
+ * Checks if the provided version numbers have the exact same major, minor, and patch numbers.
976
+ *
977
+ * @param firstVersion - The first version number to compare.
978
+ * @param secondVersion - The second version number to compare.
979
+ *
980
+ * @returns `true` if the provided version numbers have exactly the same major, minor, and patch numbers, and returns `false` otherwise.
981
+ */
982
+ static isEqual(firstVersion, secondVersion) {
983
+ return firstVersion.major === secondVersion.major && firstVersion.minor === secondVersion.minor && firstVersion.patch === secondVersion.patch;
984
+ }
985
+ /**
986
+ * Get a formatted string representation of the current version number
987
+ *
988
+ * @param options - Options to apply to the string formatting.
989
+ *
990
+ * @returns A formatted string representation of the current version number with the options applied.
991
+ */
992
+ format(options) {
993
+ let baseOutput = `${this.major}`;
994
+ if (!options?.omitMinor) {
995
+ baseOutput += `.${this.minor}`;
996
+ if (!options?.omitPatch) baseOutput += `.${this.patch}`;
997
+ }
998
+ return VersionNumber.formatString(baseOutput, { omitPrefix: options?.omitPrefix });
999
+ }
1000
+ /**
1001
+ * Increments the current version number by the given increment type, returning the result as a new reference in memory.
1002
+ *
1003
+ * @param incrementType - The type of increment. Can be one of the following:
1004
+ * - `"major"`: Change the major version `v1.2.3` → `v2.0.0`
1005
+ * - `"minor"`: Change the minor version `v1.2.3` → `v1.3.0`
1006
+ * - `"patch"`: Change the patch version `v1.2.3` → `v1.2.4`
1007
+ * @param incrementAmount - The amount to increment by (defaults to 1).
1008
+ *
1009
+ * @returns A new instance of `VersionNumber` with the increment applied.
1010
+ */
1011
+ increment(incrementType, incrementAmount = 1) {
1012
+ const incrementBy = parseIntStrict(String(incrementAmount));
1013
+ const calculatedRawVersion = {
1014
+ major: [
1015
+ this.major + incrementBy,
1016
+ 0,
1017
+ 0
1018
+ ],
1019
+ minor: [
1020
+ this.major,
1021
+ this.minor + incrementBy,
1022
+ 0
1023
+ ],
1024
+ patch: [
1025
+ this.major,
1026
+ this.minor,
1027
+ this.patch + incrementBy
1028
+ ]
1029
+ }[incrementType];
1030
+ try {
1031
+ return new VersionNumber(calculatedRawVersion);
1032
+ } catch (error) {
1033
+ if (DataError$1.check(error) && error.code === "NEGATIVE_INPUTS") throw new DataError$1({
1034
+ currentVersion: this.toString(),
1035
+ calculatedRawVersion: `v${calculatedRawVersion.join(".")}`,
1036
+ incrementAmount
1037
+ }, "NEGATIVE_VERSION", "Cannot apply this increment amount as it would lead to a negative version number.");
1038
+ else throw error;
1039
+ }
1040
+ }
1041
+ /**
1042
+ * Ensures that the VersionNumber behaves correctly when attempted to be coerced to a string.
1043
+ *
1044
+ * @param hint - Not used as of now, but generally used to help with numeric coercion, I think (which we most likely do not need for version numbers).
1045
+ *
1046
+ * @returns A stringified representation of the current version number, prefixed with `v`.
1047
+ */
1048
+ [Symbol.toPrimitive](hint) {
1049
+ if (hint === "number") throw new DataError$1({ thisVersion: this.toString() }, "INVALID_COERCION", "VersionNumber cannot be coerced to a number type.");
1050
+ return this.toString();
1051
+ }
1052
+ /**
1053
+ * Ensures that the VersionNumber behaves correctly when attempted to be converted to JSON.
1054
+ *
1055
+ * @returns A stringified representation of the current version number, prefixed with `v`.
1056
+ */
1057
+ toJSON() {
1058
+ return this.toString();
1059
+ }
1060
+ /**
1061
+ * Get a string representation of the current version number.
1062
+ *
1063
+ * @returns A stringified representation of the current version number with the prefix.
1064
+ */
1065
+ toString() {
1066
+ const rawString = `${this.major}.${this.minor}.${this.patch}`;
1067
+ return VersionNumber.formatString(rawString, { omitPrefix: false });
1068
+ }
1069
+ };
1070
+ const zodVersionNumber = z$1.union([
1071
+ z$1.string(),
1072
+ z$1.tuple([
1073
+ z$1.number(),
1074
+ z$1.number(),
1075
+ z$1.number()
1076
+ ]),
1077
+ z$1.instanceof(VersionNumber)
1078
+ ]).transform((rawVersionNumber) => {
1079
+ return new VersionNumber(rawVersionNumber);
1080
+ });
1081
+ //#endregion
1082
+ //#region src/root/zod/_parseZodSchema.ts
873
1083
  function _parseZodSchema(parsedResult, input, onError) {
874
1084
  if (!parsedResult.success) {
875
1085
  if (onError) {
@@ -884,7 +1094,7 @@ function _parseZodSchema(parsedResult, input, onError) {
884
1094
  const code = issue.code.toUpperCase();
885
1095
  allErrorCodes[code] = (allErrorCodes[code] ?? 0) + 1;
886
1096
  }
887
- throw new DataError({ input }, Object.entries(allErrorCodes).toSorted(([_, firstCount], [__, secondCount]) => {
1097
+ throw new DataError$1({ input }, Object.entries(allErrorCodes).toSorted(([_, firstCount], [__, secondCount]) => {
888
1098
  return secondCount - firstCount;
889
1099
  }).map(([code, count], _, allErrorCodes) => {
890
1100
  return allErrorCodes.length === 1 && count === 1 ? code : `${code}×${count}`;
@@ -893,7 +1103,7 @@ function _parseZodSchema(parsedResult, input, onError) {
893
1103
  return parsedResult.data;
894
1104
  }
895
1105
  //#endregion
896
- //#region src/root/functions/parsers/zod/parseZodSchema.ts
1106
+ //#region src/root/zod/parseZodSchema.ts
897
1107
  /**
898
1108
  * An alternative function to zodSchema.parse() that can be used to strictly parse Zod schemas.
899
1109
  *
@@ -901,6 +1111,8 @@ function _parseZodSchema(parsedResult, input, onError) {
901
1111
  *
902
1112
  * @category Parsers
903
1113
  *
1114
+ * @deprecated Please use `az.with(schema).parse(input)` instead.
1115
+ *
904
1116
  * @template SchemaType - The Zod schema type.
905
1117
  * @template ErrorType - The type of error to throw on invalid data.
906
1118
  *
@@ -916,6 +1128,60 @@ function parseZodSchema(schema, input, onError) {
916
1128
  return _parseZodSchema(schema.safeParse(input), input, onError);
917
1129
  }
918
1130
  //#endregion
1131
+ //#region src/root/zod/parseZodSchemaAsync.ts
1132
+ /**
1133
+ * An alternative function to zodSchema.parseAsync() that can be used to strictly parse asynchronous Zod schemas.
1134
+ *
1135
+ * @category Parsers
1136
+ *
1137
+ * @deprecated Please use `az.with(schema).parseAsync(input)` instead.
1138
+ *
1139
+ * @template SchemaType - The Zod schema type.
1140
+ * @template ErrorType - The type of error to throw on invalid data.
1141
+ *
1142
+ * @param schema - The Zod schema to use in parsing.
1143
+ * @param input - The data to parse.
1144
+ * @param onError - A custom error to throw on invalid data (defaults to `DataError`). May either be the error itself, or a function that returns the error or nothing. If nothing is returned, the default error is thrown instead.
1145
+ *
1146
+ * @throws {DataError} If the given data cannot be parsed according to the schema.
1147
+ *
1148
+ * @returns The parsed data from the Zod schema.
1149
+ */
1150
+ async function parseZodSchemaAsync(schema, input, onError) {
1151
+ return _parseZodSchema(await schema.safeParseAsync(input), input, onError);
1152
+ }
1153
+ //#endregion
1154
+ //#region src/root/zod/zodFieldWrapper.ts
1155
+ function zodFieldWrapper(schema) {
1156
+ return z$1.string().trim().transform((value) => {
1157
+ return value === "" ? null : value;
1158
+ }).pipe(schema);
1159
+ }
1160
+ //#endregion
1161
+ //#region src/root/zod/az.ts
1162
+ const az = {
1163
+ field: zodFieldWrapper,
1164
+ fieldNumber: () => {
1165
+ return z$1.coerce.number();
1166
+ },
1167
+ versionNumber: () => {
1168
+ return zodVersionNumber;
1169
+ },
1170
+ fieldDate: () => {
1171
+ return z$1.coerce.date();
1172
+ },
1173
+ with: (schema) => {
1174
+ return {
1175
+ parse: (input, error) => {
1176
+ return parseZodSchema(schema, input, error);
1177
+ },
1178
+ parseAsync: async (input, error) => {
1179
+ return await parseZodSchemaAsync(schema, input, error);
1180
+ }
1181
+ };
1182
+ }
1183
+ };
1184
+ //#endregion
919
1185
  //#region src/root/functions/parsers/parseEnv.ts
920
1186
  /**
921
1187
  * Represents the three common development environments.
@@ -939,7 +1205,7 @@ const Env = {
939
1205
  * @returns The specified environment if allowed.
940
1206
  */
941
1207
  function parseEnv(input) {
942
- return parseZodSchema(z.enum(Env), input, new DataError({ input }, "INVALID_ENV", "The provided environment type must be one of `test | development | production`"));
1208
+ return az.with(z.enum(Env)).parse(input, new DataError$1({ input }, "INVALID_ENV", "The provided environment type must be one of `test | development | production`"));
943
1209
  }
944
1210
  //#endregion
945
1211
  //#region src/root/functions/parsers/parseFormData.ts
@@ -977,8 +1243,8 @@ function parseFormData(formData, dataParser) {
977
1243
  * @returns The UUID again if successful.
978
1244
  */
979
1245
  function parseUUID(input) {
980
- if (!(typeof input === "string")) throw new DataError({ input }, "INVALID_TYPE", "Invalid type - expected string.");
981
- if (!UUID_REGEX.test(input)) throw new DataError({ input }, "INVALID_UUID", "The provided input does not match the expected shape for a UUID.");
1246
+ if (!(typeof input === "string")) throw new DataError$1({ input }, "INVALID_TYPE", "Invalid type - expected string.");
1247
+ if (!UUID_REGEX.test(input)) throw new DataError$1({ input }, "INVALID_UUID", "The provided input does not match the expected shape for a UUID.");
982
1248
  return input;
983
1249
  }
984
1250
  //#endregion
@@ -1005,28 +1271,7 @@ const VersionType = {
1005
1271
  * @returns The given version type if allowed.
1006
1272
  */
1007
1273
  function parseVersionType(input) {
1008
- return parseZodSchema(z$1.enum(VersionType), input, new DataError({ input }, "INVALID_VERSION_TYPE", "The provided version type must be one of `major | minor | patch`"));
1009
- }
1010
- //#endregion
1011
- //#region src/root/functions/parsers/zod/parseZodSchemaAsync.ts
1012
- /**
1013
- * An alternative function to zodSchema.parseAsync() that can be used to strictly parse asynchronous Zod schemas.
1014
- *
1015
- * @category Parsers
1016
- *
1017
- * @template SchemaType - The Zod schema type.
1018
- * @template ErrorType - The type of error to throw on invalid data.
1019
- *
1020
- * @param schema - The Zod schema to use in parsing.
1021
- * @param input - The data to parse.
1022
- * @param onError - A custom error to throw on invalid data (defaults to `DataError`). May either be the error itself, or a function that returns the error or nothing. If nothing is returned, the default error is thrown instead.
1023
- *
1024
- * @throws {DataError} If the given data cannot be parsed according to the schema.
1025
- *
1026
- * @returns The parsed data from the Zod schema.
1027
- */
1028
- async function parseZodSchemaAsync(schema, input, onError) {
1029
- return _parseZodSchema(await schema.safeParseAsync(input), input, onError);
1274
+ return az.with(z$1.enum(VersionType)).parse(input, new DataError$1({ input }, "INVALID_VERSION_TYPE", "The provided version type must be one of `major | minor | patch`"));
1030
1275
  }
1031
1276
  //#endregion
1032
1277
  //#region src/root/functions/recursive/deepCopy.ts
@@ -1092,7 +1337,7 @@ function deepFreeze(object) {
1092
1337
  * @returns A string with the semicolon appended.
1093
1338
  */
1094
1339
  function appendSemicolon(stringToAppendTo) {
1095
- if (stringToAppendTo.includes("\n")) throw new DataError({ stringToAppendTo }, "MULTIPLE_LINE_ERROR", "Cannot append semicolon to multi-line string.");
1340
+ if (stringToAppendTo.includes("\n")) throw new DataError$1({ stringToAppendTo }, "MULTIPLE_LINE_ERROR", "Cannot append semicolon to multi-line string.");
1096
1341
  const stringWithNoTrailingWhitespace = stringToAppendTo.trimEnd();
1097
1342
  if (stringWithNoTrailingWhitespace === "") return "";
1098
1343
  return stringWithNoTrailingWhitespace[stringWithNoTrailingWhitespace.length - 1] === ";" ? stringWithNoTrailingWhitespace : `${stringWithNoTrailingWhitespace};`;
@@ -1160,9 +1405,9 @@ function escapeRegexPattern(regexPattern) {
1160
1405
  * @returns The string converted to camelCase.
1161
1406
  */
1162
1407
  function kebabToCamel(input, options) {
1163
- if (input !== input.toLowerCase()) throw new DataError({ input }, "UPPERCASE_INPUT", "Kebab-case must be purely lowercase.");
1164
- if (input.startsWith("-") || input.endsWith("-")) throw new DataError({ input }, "TRAILING_DASHES", "Dashes at the start and/or end are not allowed.");
1165
- if (input.includes("--")) throw new DataError({ input }, "CONSECUTIVE_DASHES", "Consecutive dashes are not allowed.");
1408
+ if (input !== input.toLowerCase()) throw new DataError$1({ input }, "UPPERCASE_INPUT", "Kebab-case must be purely lowercase.");
1409
+ if (input.startsWith("-") || input.endsWith("-")) throw new DataError$1({ input }, "TRAILING_DASHES", "Dashes at the start and/or end are not allowed.");
1410
+ if (input.includes("--")) throw new DataError$1({ input }, "CONSECUTIVE_DASHES", "Consecutive dashes are not allowed.");
1166
1411
  let outputString = "";
1167
1412
  let skip = false;
1168
1413
  for (const stringIndex in [...input]) {
@@ -1275,7 +1520,7 @@ function createTemplateStringsArray(strings) {
1275
1520
  * ```
1276
1521
  */
1277
1522
  function getStringsAndInterpolations(strings, ...interpolations) {
1278
- if (strings.length !== interpolations.length + 1) throw new DataError({
1523
+ if (strings.length !== interpolations.length + 1) throw new DataError$1({
1279
1524
  stringsLength: strings.length,
1280
1525
  interpolationsLength: interpolations.length,
1281
1526
  strings,
@@ -1441,214 +1686,95 @@ function normaliseIndents(first, ...args) {
1441
1686
  */
1442
1687
  const normalizeIndents = normaliseIndents;
1443
1688
  //#endregion
1444
- //#region src/root/types/APIError.ts
1445
- const httpErrorCodeLookup = {
1446
- 400: "BAD_REQUEST",
1447
- 401: "UNAUTHORISED",
1448
- 403: "FORBIDDEN",
1449
- 404: "NOT_FOUND",
1450
- 418: "I_AM_A_TEAPOT",
1451
- 500: "INTERNAL_SERVER_ERROR"
1452
- };
1689
+ //#region src/root/deprecated/DataError.ts
1453
1690
  /**
1454
- * Represents common errors you may get from a HTTP API request.
1691
+ * Represents errors you may get that may've been caused by a specific piece of data.
1455
1692
  *
1456
1693
  * @category Types
1694
+ *
1695
+ * @deprecated Please use `DataError` from `@alextheman/utility/v6` instead.
1696
+ *
1697
+ * @template DataType - The type of the data that caused the error.
1457
1698
  */
1458
- var APIError = class APIError extends Error {
1459
- status;
1699
+ var DataError = class DataError extends Error {
1700
+ code;
1701
+ data;
1460
1702
  /**
1461
- * @param status - A HTTP status code. Can be any number, but numbers between 400 and 600 are encouraged to fit with HTTP status code conventions.
1462
- * @param message - An error message to display alongside the status code.
1463
- * @param options - Extra options to be passed to super Error constructor.
1703
+ * @param data - The data that caused the error.
1704
+ * @param code - A standardised code (e.g. UNEXPECTED_DATA).
1705
+ * @param message - A human-readable error message (e.g. The data provided is invalid).
1706
+ * @param options - Extra options to pass to super Error constructor.
1464
1707
  */
1465
- constructor(status = 500, message, options) {
1708
+ constructor(data, code = "INVALID_DATA", message = "The data provided is invalid", options) {
1466
1709
  super(message, options);
1467
- this.status = status;
1468
- if (message) this.message = message;
1469
- else this.message = httpErrorCodeLookup[this.status] ?? "API_ERROR";
1710
+ if (Error.captureStackTrace) Error.captureStackTrace(this, new.target);
1711
+ this.name = new.target.name;
1712
+ this.code = code;
1713
+ this.data = data;
1470
1714
  Object.defineProperty(this, "message", { enumerable: true });
1471
1715
  Object.setPrototypeOf(this, new.target.prototype);
1472
1716
  }
1717
+ static checkCaughtError(error, options) {
1718
+ if (DataError.check(error)) {
1719
+ 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.
1720
+
1721
+ Expected: ${options.expectedCode}
1722
+ Received: ${error.code}
1723
+ `, { cause: error });
1724
+ return error;
1725
+ }
1726
+ throw error;
1727
+ }
1473
1728
  /**
1474
- * Checks whether the given input may have been caused by an APIError.
1729
+ * Checks whether the given input may have been caused by a DataError.
1475
1730
  *
1476
1731
  * @param input - The input to check.
1477
1732
  *
1478
- * @returns `true` if the input is an APIError, and `false` otherwise. The type of the input will also be narrowed down to APIError if `true`.
1733
+ * @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`.
1479
1734
  */
1480
1735
  static check(input) {
1481
- if (input instanceof APIError) return true;
1736
+ if (input instanceof DataError) return true;
1482
1737
  const data = input;
1483
- return typeof data === "object" && data !== null && typeof data?.status === "number" && typeof data?.message === "string";
1738
+ return typeof data === "object" && data !== null && typeof data.message === "string" && typeof data.code === "string" && "data" in data;
1484
1739
  }
1485
- };
1486
- //#endregion
1487
- //#region src/root/types/VersionNumber.ts
1488
- /**
1489
- * Represents a software version number, considered to be made up of a major, minor, and patch part.
1490
- *
1491
- * @category Types
1492
- */
1493
- var VersionNumber = class VersionNumber {
1494
- static NON_NEGATIVE_TUPLE_ERROR = "Input array must be a tuple of three non-negative integers.";
1495
- /** The major number. Increments when a feature is removed or changed in a way that is not backwards-compatible with the previous release. */
1496
- major = 0;
1497
- /** The minor number. Increments when a new feature is added/deprecated and is expected to be backwards-compatible with the previous release. */
1498
- minor = 0;
1499
- /** The patch number. Increments when the next release is fixing a bug or doing a small refactor that should not be noticeable in practice. */
1500
- patch = 0;
1501
1740
  /**
1502
- * @param input - The input to create a new instance of `VersionNumber` from.
1503
- */
1504
- constructor(input) {
1505
- if (input instanceof VersionNumber) {
1506
- this.major = input.major;
1507
- this.minor = input.minor;
1508
- this.patch = input.patch;
1509
- } else if (typeof input === "string") {
1510
- if (!VERSION_NUMBER_REGEX.test(input)) throw new DataError({ input }, "INVALID_VERSION", `"${input}" is not a valid version number. Version numbers must be of the format "X.Y.Z" or "vX.Y.Z", where X, Y, and Z are non-negative integers.`);
1511
- const [major, minor, patch] = VersionNumber.formatString(input, { omitPrefix: true }).split(".").map((number) => {
1512
- return parseIntStrict(number);
1513
- });
1514
- this.major = major;
1515
- this.minor = minor;
1516
- this.patch = patch;
1517
- } else if (Array.isArray(input)) {
1518
- if (input.length !== 3) throw new DataError({ input }, "INVALID_LENGTH", VersionNumber.NON_NEGATIVE_TUPLE_ERROR);
1519
- const [major, minor, patch] = input.map((number) => {
1520
- const parsedInteger = parseIntStrict(number?.toString());
1521
- if (parsedInteger < 0) throw new DataError({ input }, "NEGATIVE_INPUTS", VersionNumber.NON_NEGATIVE_TUPLE_ERROR);
1522
- return parsedInteger;
1523
- });
1524
- this.major = major;
1525
- this.minor = minor;
1526
- this.patch = patch;
1527
- } else throw new DataError({ input }, "INVALID_INPUT", normaliseIndents`
1528
- The provided input can not be parsed into a valid version number.
1529
- Expected either a string of format X.Y.Z or vX.Y.Z, a tuple of three numbers, or another \`VersionNumber\` instance.
1530
- `);
1531
- }
1532
- /**
1533
- * Gets the current version type of the current instance of `VersionNumber`.
1534
- *
1535
- * @returns Either `"major"`, `"minor"`, or `"patch"`, depending on the version type.
1536
- */
1537
- get type() {
1538
- if (this.minor === 0 && this.patch === 0) return VersionType.MAJOR;
1539
- if (this.patch === 0) return VersionType.MINOR;
1540
- return VersionType.PATCH;
1541
- }
1542
- static formatString(input, options) {
1543
- if (options?.omitPrefix) return input.startsWith("v") ? input.slice(1) : input;
1544
- return input.startsWith("v") ? input : `v${input}`;
1545
- }
1546
- /**
1547
- * Checks if the provided version numbers have the exact same major, minor, and patch numbers.
1548
- *
1549
- * @param firstVersion - The first version number to compare.
1550
- * @param secondVersion - The second version number to compare.
1551
- *
1552
- * @returns `true` if the provided version numbers have exactly the same major, minor, and patch numbers, and returns `false` otherwise.
1553
- */
1554
- static isEqual(firstVersion, secondVersion) {
1555
- return firstVersion.major === secondVersion.major && firstVersion.minor === secondVersion.minor && firstVersion.patch === secondVersion.patch;
1556
- }
1557
- /**
1558
- * Get a formatted string representation of the current version number
1559
- *
1560
- * @param options - Options to apply to the string formatting.
1741
+ * 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.
1561
1742
  *
1562
- * @returns A formatted string representation of the current version number with the options applied.
1563
- */
1564
- format(options) {
1565
- let baseOutput = `${this.major}`;
1566
- if (!options?.omitMinor) {
1567
- baseOutput += `.${this.minor}`;
1568
- if (!options?.omitPatch) baseOutput += `.${this.patch}`;
1569
- }
1570
- return VersionNumber.formatString(baseOutput, { omitPrefix: options?.omitPrefix });
1571
- }
1572
- /**
1573
- * Increments the current version number by the given increment type, returning the result as a new reference in memory.
1743
+ * @param errorFunction - The function expected to throw the error.
1744
+ * @param options - Extra options to apply.
1574
1745
  *
1575
- * @param incrementType - The type of increment. Can be one of the following:
1576
- * - `"major"`: Change the major version `v1.2.3` `v2.0.0`
1577
- * - `"minor"`: Change the minor version `v1.2.3` → `v1.3.0`
1578
- * - `"patch"`: Change the patch version `v1.2.3` → `v1.2.4`
1579
- * @param incrementAmount - The amount to increment by (defaults to 1).
1746
+ * @throws {Error} Any other errors thrown by the `errorFunction` that are not a `DataError`.
1747
+ * @throws {Error} If no `DataError` was thrown by the `errorFunction`
1580
1748
  *
1581
- * @returns A new instance of `VersionNumber` with the increment applied.
1749
+ * @returns The `DataError` that was thrown by the `errorFunction`
1582
1750
  */
1583
- increment(incrementType, incrementAmount = 1) {
1584
- const incrementBy = parseIntStrict(String(incrementAmount));
1585
- const calculatedRawVersion = {
1586
- major: [
1587
- this.major + incrementBy,
1588
- 0,
1589
- 0
1590
- ],
1591
- minor: [
1592
- this.major,
1593
- this.minor + incrementBy,
1594
- 0
1595
- ],
1596
- patch: [
1597
- this.major,
1598
- this.minor,
1599
- this.patch + incrementBy
1600
- ]
1601
- }[incrementType];
1751
+ static expectError(errorFunction, options) {
1602
1752
  try {
1603
- return new VersionNumber(calculatedRawVersion);
1753
+ errorFunction();
1604
1754
  } catch (error) {
1605
- if (DataError.check(error) && error.code === "NEGATIVE_INPUTS") throw new DataError({
1606
- currentVersion: this.toString(),
1607
- calculatedRawVersion: `v${calculatedRawVersion.join(".")}`,
1608
- incrementAmount
1609
- }, "NEGATIVE_VERSION", "Cannot apply this increment amount as it would lead to a negative version number.");
1610
- else throw error;
1755
+ return DataError.checkCaughtError(error, options);
1611
1756
  }
1757
+ throw new Error("Expected a DataError to be thrown but none was thrown");
1612
1758
  }
1613
1759
  /**
1614
- * Ensures that the VersionNumber behaves correctly when attempted to be coerced to a string.
1615
- *
1616
- * @param hint - Not used as of now, but generally used to help with numeric coercion, I think (which we most likely do not need for version numbers).
1760
+ * 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.
1617
1761
  *
1618
- * @returns A stringified representation of the current version number, prefixed with `v`.
1619
- */
1620
- [Symbol.toPrimitive](hint) {
1621
- if (hint === "number") throw new DataError({ thisVersion: this.toString() }, "INVALID_COERCION", "VersionNumber cannot be coerced to a number type.");
1622
- return this.toString();
1623
- }
1624
- /**
1625
- * Ensures that the VersionNumber behaves correctly when attempted to be converted to JSON.
1762
+ * @param errorFunction - The function expected to throw the error.
1763
+ * @param options - Extra options to apply.
1626
1764
  *
1627
- * @returns A stringified representation of the current version number, prefixed with `v`.
1628
- */
1629
- toJSON() {
1630
- return this.toString();
1631
- }
1632
- /**
1633
- * Get a string representation of the current version number.
1765
+ * @throws {Error} Any other errors thrown by the `errorFunction` that are not a `DataError`.
1766
+ * @throws {Error} If no `DataError` was thrown by the `errorFunction`
1634
1767
  *
1635
- * @returns A stringified representation of the current version number with the prefix.
1768
+ * @returns The `DataError` that was thrown by the `errorFunction`
1636
1769
  */
1637
- toString() {
1638
- const rawString = `${this.major}.${this.minor}.${this.patch}`;
1639
- return VersionNumber.formatString(rawString, { omitPrefix: false });
1770
+ static async expectErrorAsync(errorFunction, options) {
1771
+ try {
1772
+ await errorFunction();
1773
+ } catch (error) {
1774
+ return DataError.checkCaughtError(error, options);
1775
+ }
1776
+ throw new Error("Expected a DataError to be thrown but none was thrown");
1640
1777
  }
1641
1778
  };
1642
- const zodVersionNumber = z$1.union([
1643
- z$1.string(),
1644
- z$1.tuple([
1645
- z$1.number(),
1646
- z$1.number(),
1647
- z$1.number()
1648
- ]),
1649
- z$1.instanceof(VersionNumber)
1650
- ]).transform((rawVersionNumber) => {
1651
- return new VersionNumber(rawVersionNumber);
1652
- });
1653
1779
  //#endregion
1654
- export { APIError, Env, FILE_PATH_PATTERN, FILE_PATH_REGEX, ONE_DAY_IN_MILLISECONDS, UUID_PATTERN, UUID_REGEX, VERSION_NUMBER_PATTERN, VERSION_NUMBER_REGEX, VersionNumber, VersionType, addDaysToDate, appendSemicolon, calculateMonthlyDifference, camelToKebab, convertFileToBase64, createFormData, createTemplateStringsArray, deepCopy, deepFreeze, escapeRegexPattern, fillArray, formatDateAndTime, getRandomNumber, getRecordKeys, getStringsAndInterpolations, httpErrorCodeLookup, interpolate, interpolateObjects, isAnniversary, isLeapYear, isMonthlyMultiple, isOrdered, isSameDate, isTemplateStringsArray, kebabToCamel, normaliseIndents, normalizeIndents, omitProperties, paralleliseArrays, parseBoolean, parseEnv, parseFormData, parseIntStrict, parseUUID, parseVersionType, parseZodSchema, parseZodSchemaAsync, randomiseArray, range, removeDuplicates, removeUndefinedFromObject, sayHello, stringListToArray, stringifyDotenv, toTitleCase, truncate, wait, zodVersionNumber };
1780
+ export { APIError, DataError, Env, FILE_PATH_PATTERN, FILE_PATH_REGEX, ONE_DAY_IN_MILLISECONDS, UUID_PATTERN, UUID_REGEX, VERSION_NUMBER_PATTERN, VERSION_NUMBER_REGEX, VersionNumber, VersionType, addDaysToDate, appendSemicolon, az, calculateMonthlyDifference, camelToKebab, convertFileToBase64, createFormData, createTemplateStringsArray, deepCopy, deepFreeze, escapeRegexPattern, fillArray, formatDateAndTime, getRandomNumber, getRecordKeys, getStringsAndInterpolations, httpErrorCodeLookup, interpolate, interpolateObjects, isAnniversary, isLeapYear, isMonthlyMultiple, isOrdered, isSameDate, isTemplateStringsArray, kebabToCamel, normaliseIndents, normalizeIndents, omitProperties, paralleliseArrays, parseBoolean, parseEnv, parseFormData, parseIntStrict, parseUUID, parseVersionType, parseZodSchema, parseZodSchemaAsync, randomiseArray, range, removeDuplicates, removeUndefinedFromObject, sayHello, stringListToArray, stringifyDotenv, toTitleCase, truncate, wait, zodVersionNumber };