@alextheman/utility 5.13.1 → 5.15.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.
@@ -25,7 +25,7 @@ type IsTypeArgumentString<Argument extends string> = Argument;
25
25
  type NullableOnCondition<Condition extends boolean, ResolvedTypeIfTrue> = Condition extends true ? ResolvedTypeIfTrue : ResolvedTypeIfTrue | null;
26
26
  //#endregion
27
27
  //#region src/v6/CodeError.d.ts
28
- interface ExpectErrorOptions$1<ErrorCode extends string = string> {
28
+ interface ExpectErrorOptions<ErrorCode extends string = string> {
29
29
  expectedCode?: ErrorCode;
30
30
  }
31
31
  /**
@@ -50,8 +50,21 @@ declare class CodeError<ErrorCode extends string = string> extends Error {
50
50
  *
51
51
  * @returns `true` if the input is a CodeError, and `false` otherwise. The type of the input will also be narrowed down to CodeError if `true`.
52
52
  */
53
- static check(input: unknown): input is CodeError<string>;
54
- protected static checkCaughtError<ErrorCode extends string = string>(this: typeof CodeError, error: unknown, options?: ExpectErrorOptions$1<ErrorCode>): CodeError;
53
+ static check<ErrorCode extends string = string>(input: unknown): input is CodeError<ErrorCode>;
54
+ protected static checkCaughtError<ErrorCode extends string = string>(this: typeof CodeError, error: unknown, options?: ExpectErrorOptions<ErrorCode>): CodeError<ErrorCode>;
55
+ /**
56
+ * Check a `CodeError` against its error code
57
+ *
58
+ * This will also automatically narrow down the type of the input to be `CodeError`, with its error code properly typed if this function returns true.
59
+ *
60
+ * @template ErrorCode The type of the error code
61
+ *
62
+ * @param input - The input to check.
63
+ * @param code - The expected code of the resulting error.
64
+ *
65
+ * @returns `true` if the error code matches the expected code, and `false` otherwise. The type of the input will also be narrowed down to CodeError, and its code will be narrowed to the expected code's type if the function returns `true`.
66
+ */
67
+ static checkWithCode<ErrorCode extends string = string>(input: unknown, code: ErrorCode): input is CodeError<ErrorCode>;
55
68
  /**
56
69
  * Gets the thrown `CodeError` from a given function if one was thrown, and re-throws any other errors, or throws a default `CodeError` if no error thrown.
57
70
  *
@@ -63,7 +76,7 @@ declare class CodeError<ErrorCode extends string = string> extends Error {
63
76
  *
64
77
  * @returns The `CodeError` that was thrown by the `errorFunction`
65
78
  */
66
- static expectError<ErrorCode extends string = string>(this: typeof CodeError, errorFunction: () => unknown, options?: ExpectErrorOptions$1<ErrorCode>): CodeError;
79
+ static expectError<ErrorCode extends string = string>(this: typeof CodeError, errorFunction: () => unknown, options?: ExpectErrorOptions<ErrorCode>): CodeError<ErrorCode>;
67
80
  /**
68
81
  * Gets the thrown `CodeError` from a given asynchronous function if one was thrown, and re-throws any other errors, or throws a default `CodeError` if no error thrown.
69
82
  *
@@ -75,18 +88,10 @@ declare class CodeError<ErrorCode extends string = string> extends Error {
75
88
  *
76
89
  * @returns The `CodeError` that was thrown by the `errorFunction`
77
90
  */
78
- static expectErrorAsync<ErrorCode extends string = string>(this: typeof CodeError, errorFunction: () => Promise<unknown>, options?: ExpectErrorOptions$1<ErrorCode>): Promise<CodeError>;
91
+ static expectErrorAsync<ErrorCode extends string = string>(this: typeof CodeError, errorFunction: () => Promise<unknown>, options?: ExpectErrorOptions<ErrorCode>): Promise<CodeError>;
79
92
  }
80
93
  //#endregion
81
94
  //#region src/v6/DataError.d.ts
82
- type DefaultDataErrorCode = "INVALID_DATA";
83
- interface ExpectErrorOptions<ErrorCode extends string = DefaultDataErrorCode> {
84
- expectedCode?: ErrorCode | DefaultDataErrorCode;
85
- }
86
- declare const DataErrorCode: {
87
- readonly INVALID_DATA: "INVALID_DATA";
88
- };
89
- type DataErrorCode = CreateEnumType<typeof DataErrorCode>;
90
95
  /**
91
96
  * Represents errors you may get that may've been caused by a specific piece of data.
92
97
  *
@@ -94,7 +99,7 @@ type DataErrorCode = CreateEnumType<typeof DataErrorCode>;
94
99
  *
95
100
  * @template DataType - The type of the data that caused the error.
96
101
  */
97
- declare class DataError<DataType extends object = Record<PropertyKey, unknown>, ErrorCode extends string = DataErrorCode> extends CodeError<ErrorCode | DataErrorCode> {
102
+ declare class DataError<DataType extends object = Record<PropertyKey, unknown>, ErrorCode extends string = string> extends CodeError<ErrorCode> {
98
103
  data: DataType;
99
104
  /**
100
105
  * @param data - The data that caused the error.
@@ -102,7 +107,7 @@ declare class DataError<DataType extends object = Record<PropertyKey, unknown>,
102
107
  * @param message - A human-readable error message (e.g. The data provided is invalid).
103
108
  * @param options - Extra options to pass to super Error constructor.
104
109
  */
105
- constructor(data: DataType, code?: ErrorCode | DefaultDataErrorCode, message?: string, options?: ErrorOptions);
110
+ constructor(data: DataType, code: ErrorCode, message?: string, options?: ErrorOptions);
106
111
  /**
107
112
  * Checks whether the given input may have been caused by a DataError.
108
113
  *
@@ -110,7 +115,20 @@ declare class DataError<DataType extends object = Record<PropertyKey, unknown>,
110
115
  *
111
116
  * @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
117
  */
113
- static check<DataType extends object = Record<PropertyKey, unknown>, ErrorCode extends string = DataErrorCode>(input: unknown): input is DataError<DataType, ErrorCode>;
118
+ static check<DataType extends object = Record<PropertyKey, unknown>, ErrorCode extends string = string>(input: unknown): input is DataError<DataType, ErrorCode>;
119
+ /**
120
+ * Check a `DataError` against its error code
121
+ *
122
+ * This will also automatically narrow down the type of the input to be `DataError`, with its error code properly typed if this function returns true.
123
+ *
124
+ * @template ErrorCode The type of the error code
125
+ *
126
+ * @param input - The input to check.
127
+ * @param code - The expected code of the resulting error.
128
+ *
129
+ * @returns `true` if the error code matches the expected code, and `false` otherwise. The type of the input will also be narrowed down to `DataError`, and its code will be narrowed to the expected code's type if the function returns `true`.
130
+ */
131
+ static checkWithCode<DataType extends object = Record<PropertyKey, unknown>, ErrorCode extends string = string>(input: unknown, code: ErrorCode): input is DataError<DataType, ErrorCode>;
114
132
  /**
115
133
  * 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
134
  *
@@ -122,7 +140,7 @@ declare class DataError<DataType extends object = Record<PropertyKey, unknown>,
122
140
  *
123
141
  * @returns The `DataError` that was thrown by the `errorFunction`
124
142
  */
125
- static expectError<DataType extends Record<PropertyKey, unknown>, ErrorCode extends string = DefaultDataErrorCode>(errorFunction: () => unknown, options?: ExpectErrorOptions<ErrorCode>): DataError<DataType, ErrorCode>;
143
+ static expectError<DataType extends Record<PropertyKey, unknown>, ErrorCode extends string = string>(errorFunction: () => unknown, options?: ExpectErrorOptions<ErrorCode>): DataError<DataType, ErrorCode>;
126
144
  /**
127
145
  * 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.
128
146
  *
@@ -134,7 +152,7 @@ declare class DataError<DataType extends object = Record<PropertyKey, unknown>,
134
152
  *
135
153
  * @returns The `DataError` that was thrown by the `errorFunction`
136
154
  */
137
- static expectErrorAsync<DataType extends Record<PropertyKey, unknown>, ErrorCode extends string = DefaultDataErrorCode>(errorFunction: () => Promise<unknown>, options?: ExpectErrorOptions<ErrorCode>): Promise<DataError<DataType, ErrorCode>>;
155
+ static expectErrorAsync<DataType extends Record<PropertyKey, unknown>, ErrorCode extends string = string>(errorFunction: () => Promise<unknown>, options?: ExpectErrorOptions<ErrorCode>): Promise<DataError<DataType, ErrorCode>>;
138
156
  }
139
157
  //#endregion
140
158
  //#region src/internal/DependencyGroup.d.ts
@@ -8,6 +8,10 @@ const DependencyGroup = {
8
8
  DEV_DEPENDENCIES: "devDependencies"
9
9
  };
10
10
  //#endregion
11
+ //#region src/root/constants/VERSION_NUMBER_REGEX.ts
12
+ const VERSION_NUMBER_PATTERN = String.raw`^(?:v)?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$`;
13
+ const VERSION_NUMBER_REGEX = RegExp(`^${VERSION_NUMBER_PATTERN}$`);
14
+ //#endregion
11
15
  //#region src/root/functions/arrayHelpers/fillArray.ts
12
16
  /**
13
17
  * Creates a new array where each element is the result of the provided callback.
@@ -103,6 +107,21 @@ var CodeError = class CodeError extends Error {
103
107
  throw error;
104
108
  }
105
109
  /**
110
+ * Check a `CodeError` against its error code
111
+ *
112
+ * This will also automatically narrow down the type of the input to be `CodeError`, with its error code properly typed if this function returns true.
113
+ *
114
+ * @template ErrorCode The type of the error code
115
+ *
116
+ * @param input - The input to check.
117
+ * @param code - The expected code of the resulting error.
118
+ *
119
+ * @returns `true` if the error code matches the expected code, and `false` otherwise. The type of the input will also be narrowed down to CodeError, and its code will be narrowed to the expected code's type if the function returns `true`.
120
+ */
121
+ static checkWithCode(input, code) {
122
+ return this.check(input) && input.code === code;
123
+ }
124
+ /**
106
125
  * Gets the thrown `CodeError` from a given function if one was thrown, and re-throws any other errors, or throws a default `CodeError` if no error thrown.
107
126
  *
108
127
  * @param errorFunction - The function expected to throw the error.
@@ -158,11 +177,10 @@ var DataError = class DataError extends CodeError {
158
177
  * @param message - A human-readable error message (e.g. The data provided is invalid).
159
178
  * @param options - Extra options to pass to super Error constructor.
160
179
  */
161
- constructor(data, code = "INVALID_DATA", message = "The data provided is invalid", options) {
180
+ constructor(data, code, message = "The data provided is invalid", options) {
162
181
  super(code, message, options);
163
182
  if (Error.captureStackTrace) Error.captureStackTrace(this, new.target);
164
183
  this.name = new.target.name;
165
- this.code = code;
166
184
  this.data = data;
167
185
  Object.defineProperty(this, "message", { enumerable: true });
168
186
  Object.setPrototypeOf(this, new.target.prototype);
@@ -179,6 +197,21 @@ var DataError = class DataError extends CodeError {
179
197
  return typeof input === "object" && input !== null && "message" in input && typeof input.message === "string" && "code" in input && typeof input.code === "string" && "data" in input;
180
198
  }
181
199
  /**
200
+ * Check a `DataError` against its error code
201
+ *
202
+ * This will also automatically narrow down the type of the input to be `DataError`, with its error code properly typed if this function returns true.
203
+ *
204
+ * @template ErrorCode The type of the error code
205
+ *
206
+ * @param input - The input to check.
207
+ * @param code - The expected code of the resulting error.
208
+ *
209
+ * @returns `true` if the error code matches the expected code, and `false` otherwise. The type of the input will also be narrowed down to `DataError`, and its code will be narrowed to the expected code's type if the function returns `true`.
210
+ */
211
+ static checkWithCode(input, code) {
212
+ return this.check(input) && input.code === code;
213
+ }
214
+ /**
182
215
  * 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.
183
216
  *
184
217
  * @param errorFunction - The function expected to throw the error.
@@ -208,53 +241,49 @@ var DataError = class DataError extends CodeError {
208
241
  }
209
242
  };
210
243
  //#endregion
211
- //#region src/root/functions/parsers/zod/_parseZodSchema.ts
212
- function _parseZodSchema(parsedResult, input, onError) {
213
- if (!parsedResult.success) {
214
- if (onError) {
215
- if (onError instanceof Error) throw onError;
216
- else if (typeof onError === "function") {
217
- const evaluatedError = onError(parsedResult.error);
218
- if (evaluatedError instanceof Error) throw evaluatedError;
219
- }
220
- }
221
- const allErrorCodes = {};
222
- for (const issue of parsedResult.error.issues) {
223
- const code = issue.code.toUpperCase();
224
- allErrorCodes[code] = (allErrorCodes[code] ?? 0) + 1;
225
- }
226
- throw new DataError({ input }, Object.entries(allErrorCodes).toSorted(([_, firstCount], [__, secondCount]) => {
227
- return secondCount - firstCount;
228
- }).map(([code, count], _, allErrorCodes) => {
229
- return allErrorCodes.length === 1 && count === 1 ? code : `${code}×${count}`;
230
- }).join(","), `\n\n${z.prettifyError(parsedResult.error)}\n`);
231
- }
232
- return parsedResult.data;
233
- }
234
- //#endregion
235
- //#region src/root/functions/parsers/zod/parseZodSchema.ts
244
+ //#region src/root/functions/parsers/parseIntStrict.ts
236
245
  /**
237
- * An alternative function to zodSchema.parse() that can be used to strictly parse Zod schemas.
238
- *
239
- * NOTE: Use `parseZodSchemaAsync` if your schema includes an asynchronous function.
246
+ * Converts a string to an integer and throws an error if it cannot be converted.
240
247
  *
241
248
  * @category Parsers
242
249
  *
243
- * @template SchemaType - The Zod schema type.
244
- * @template ErrorType - The type of error to throw on invalid data.
250
+ * @param string - A string to convert into a number.
251
+ * @param radix - A value between 2 and 36 that specifies the base of the number in string. If this argument is not supplied, strings with a prefix of '0x' are considered hexadecimal. All other strings are considered decimal.
245
252
  *
246
- * @param schema - The Zod schema to use in parsing.
247
- * @param input - The data to parse.
248
- * @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.
253
+ * @throws {DataError} If the provided string cannot safely be converted to an integer.
249
254
  *
250
- * @throws {DataErrorCode} If the given data cannot be parsed according to the schema.
251
- *
252
- * @returns The parsed data from the Zod schema.
255
+ * @returns The integer parsed from the input string.
253
256
  */
254
- function parseZodSchema(schema, input, onError) {
255
- return _parseZodSchema(schema.safeParse(input), input, onError);
257
+ function parseIntStrict(string, radix) {
258
+ const trimmedString = string.trim();
259
+ const maxAllowedAlphabeticalCharacter = radix && radix > 10 && radix <= 36 ? String.fromCharCode(87 + radix - 1) : void 0;
260
+ if (!(radix && radix > 10 && radix <= 36 ? new RegExp(`^[+-]?[0-9a-${maxAllowedAlphabeticalCharacter}]+$`, "i") : /^[+-]?\d+$/).test(trimmedString)) throw new DataError(radix ? {
261
+ string,
262
+ radix
263
+ } : { string }, "INTEGER_PARSING_ERROR", `Only numeric values${radix && radix > 10 && radix <= 36 ? ` or character${radix !== 11 ? "s" : ""} A${radix !== 11 ? `-${maxAllowedAlphabeticalCharacter?.toUpperCase()} ` : " "}` : " "}are allowed.`);
264
+ if (radix && radix < 10 && [...trimmedString.replace(/^[+-]/, "")].some((character) => {
265
+ return parseInt(character) >= radix;
266
+ })) throw new DataError({
267
+ string,
268
+ radix
269
+ }, "INTEGER_PARSING_ERROR", "Value contains one or more digits outside of the range of the given radix.");
270
+ const parseIntResult = parseInt(trimmedString, radix);
271
+ if (isNaN(parseIntResult)) throw new DataError({ string }, "INTEGER_PARSING_ERROR", "Value is not a valid integer.");
272
+ return parseIntResult;
256
273
  }
257
274
  //#endregion
275
+ //#region src/root/functions/parsers/parseVersionType.ts
276
+ /**
277
+ * Represents the three common software version types.
278
+ *
279
+ * @category Types
280
+ */
281
+ const VersionType = {
282
+ MAJOR: "major",
283
+ MINOR: "minor",
284
+ PATCH: "patch"
285
+ };
286
+ //#endregion
258
287
  //#region src/root/functions/taggedTemplate/interpolate.ts
259
288
  /**
260
289
  * Returns the result of interpolating a template string when given the strings and interpolations separately.
@@ -345,6 +374,276 @@ function normaliseIndents(first, ...args) {
345
374
  return reduceLines(interpolate(strings, ...[...args]).split("\n"), options);
346
375
  }
347
376
  //#endregion
377
+ //#region src/root/types/VersionNumber.ts
378
+ /**
379
+ * Represents a software version number, considered to be made up of a major, minor, and patch part.
380
+ *
381
+ * @category Types
382
+ */
383
+ var VersionNumber = class VersionNumber {
384
+ static NON_NEGATIVE_TUPLE_ERROR = "Input array must be a tuple of three non-negative integers.";
385
+ /** The major number. Increments when a feature is removed or changed in a way that is not backwards-compatible with the previous release. */
386
+ major = 0;
387
+ /** The minor number. Increments when a new feature is added/deprecated and is expected to be backwards-compatible with the previous release. */
388
+ minor = 0;
389
+ /** The patch number. Increments when the next release is fixing a bug or doing a small refactor that should not be noticeable in practice. */
390
+ patch = 0;
391
+ /**
392
+ * @param input - The input to create a new instance of `VersionNumber` from.
393
+ */
394
+ constructor(input) {
395
+ if (input instanceof VersionNumber) {
396
+ this.major = input.major;
397
+ this.minor = input.minor;
398
+ this.patch = input.patch;
399
+ } else if (typeof input === "string") {
400
+ 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.`);
401
+ const [major, minor, patch] = VersionNumber.formatString(input, { omitPrefix: true }).split(".").map((number) => {
402
+ return parseIntStrict(number);
403
+ });
404
+ this.major = major;
405
+ this.minor = minor;
406
+ this.patch = patch;
407
+ } else if (Array.isArray(input)) {
408
+ if (input.length !== 3) throw new DataError({ input }, "INVALID_LENGTH", VersionNumber.NON_NEGATIVE_TUPLE_ERROR);
409
+ const [major, minor, patch] = input.map((number) => {
410
+ const parsedInteger = parseIntStrict(number?.toString());
411
+ if (parsedInteger < 0) throw new DataError({ input }, "NEGATIVE_INPUTS", VersionNumber.NON_NEGATIVE_TUPLE_ERROR);
412
+ return parsedInteger;
413
+ });
414
+ this.major = major;
415
+ this.minor = minor;
416
+ this.patch = patch;
417
+ } else throw new DataError({ input }, "INVALID_INPUT", normaliseIndents`
418
+ The provided input can not be parsed into a valid version number.
419
+ Expected either a string of format X.Y.Z or vX.Y.Z, a tuple of three numbers, or another \`VersionNumber\` instance.
420
+ `);
421
+ }
422
+ /**
423
+ * Gets the current version type of the current instance of `VersionNumber`.
424
+ *
425
+ * @returns Either `"major"`, `"minor"`, or `"patch"`, depending on the version type.
426
+ */
427
+ get type() {
428
+ if (this.minor === 0 && this.patch === 0) return VersionType.MAJOR;
429
+ if (this.patch === 0) return VersionType.MINOR;
430
+ return VersionType.PATCH;
431
+ }
432
+ static formatString(input, options) {
433
+ if (options?.omitPrefix) return input.startsWith("v") ? input.slice(1) : input;
434
+ return input.startsWith("v") ? input : `v${input}`;
435
+ }
436
+ /**
437
+ * Checks if the provided version numbers have the exact same major, minor, and patch numbers.
438
+ *
439
+ * @param firstVersion - The first version number to compare.
440
+ * @param secondVersion - The second version number to compare.
441
+ *
442
+ * @returns `true` if the provided version numbers have exactly the same major, minor, and patch numbers, and returns `false` otherwise.
443
+ */
444
+ static isEqual(firstVersion, secondVersion) {
445
+ return firstVersion.major === secondVersion.major && firstVersion.minor === secondVersion.minor && firstVersion.patch === secondVersion.patch;
446
+ }
447
+ /**
448
+ * Get a formatted string representation of the current version number
449
+ *
450
+ * @param options - Options to apply to the string formatting.
451
+ *
452
+ * @returns A formatted string representation of the current version number with the options applied.
453
+ */
454
+ format(options) {
455
+ let baseOutput = `${this.major}`;
456
+ if (!options?.omitMinor) {
457
+ baseOutput += `.${this.minor}`;
458
+ if (!options?.omitPatch) baseOutput += `.${this.patch}`;
459
+ }
460
+ return VersionNumber.formatString(baseOutput, { omitPrefix: options?.omitPrefix });
461
+ }
462
+ /**
463
+ * Increments the current version number by the given increment type, returning the result as a new reference in memory.
464
+ *
465
+ * @param incrementType - The type of increment. Can be one of the following:
466
+ * - `"major"`: Change the major version `v1.2.3` → `v2.0.0`
467
+ * - `"minor"`: Change the minor version `v1.2.3` → `v1.3.0`
468
+ * - `"patch"`: Change the patch version `v1.2.3` → `v1.2.4`
469
+ * @param incrementAmount - The amount to increment by (defaults to 1).
470
+ *
471
+ * @returns A new instance of `VersionNumber` with the increment applied.
472
+ */
473
+ increment(incrementType, incrementAmount = 1) {
474
+ const incrementBy = parseIntStrict(String(incrementAmount));
475
+ const calculatedRawVersion = {
476
+ major: [
477
+ this.major + incrementBy,
478
+ 0,
479
+ 0
480
+ ],
481
+ minor: [
482
+ this.major,
483
+ this.minor + incrementBy,
484
+ 0
485
+ ],
486
+ patch: [
487
+ this.major,
488
+ this.minor,
489
+ this.patch + incrementBy
490
+ ]
491
+ }[incrementType];
492
+ try {
493
+ return new VersionNumber(calculatedRawVersion);
494
+ } catch (error) {
495
+ if (DataError.check(error) && error.code === "NEGATIVE_INPUTS") throw new DataError({
496
+ currentVersion: this.toString(),
497
+ calculatedRawVersion: `v${calculatedRawVersion.join(".")}`,
498
+ incrementAmount
499
+ }, "NEGATIVE_VERSION", "Cannot apply this increment amount as it would lead to a negative version number.");
500
+ else throw error;
501
+ }
502
+ }
503
+ /**
504
+ * Ensures that the VersionNumber behaves correctly when attempted to be coerced to a string.
505
+ *
506
+ * @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).
507
+ *
508
+ * @returns A stringified representation of the current version number, prefixed with `v`.
509
+ */
510
+ [Symbol.toPrimitive](hint) {
511
+ if (hint === "number") throw new DataError({ thisVersion: this.toString() }, "INVALID_COERCION", "VersionNumber cannot be coerced to a number type.");
512
+ return this.toString();
513
+ }
514
+ /**
515
+ * Ensures that the VersionNumber behaves correctly when attempted to be converted to JSON.
516
+ *
517
+ * @returns A stringified representation of the current version number, prefixed with `v`.
518
+ */
519
+ toJSON() {
520
+ return this.toString();
521
+ }
522
+ /**
523
+ * Get a string representation of the current version number.
524
+ *
525
+ * @returns A stringified representation of the current version number with the prefix.
526
+ */
527
+ toString() {
528
+ const rawString = `${this.major}.${this.minor}.${this.patch}`;
529
+ return VersionNumber.formatString(rawString, { omitPrefix: false });
530
+ }
531
+ };
532
+ const zodVersionNumber = z.union([
533
+ z.string(),
534
+ z.tuple([
535
+ z.number(),
536
+ z.number(),
537
+ z.number()
538
+ ]),
539
+ z.instanceof(VersionNumber)
540
+ ]).transform((rawVersionNumber) => {
541
+ return new VersionNumber(rawVersionNumber);
542
+ });
543
+ //#endregion
544
+ //#region src/root/zod/_parseZodSchema.ts
545
+ function _parseZodSchema(parsedResult, input, onError) {
546
+ if (!parsedResult.success) {
547
+ if (onError) {
548
+ if (onError instanceof Error) throw onError;
549
+ else if (typeof onError === "function") {
550
+ const evaluatedError = onError(parsedResult.error);
551
+ if (evaluatedError instanceof Error) throw evaluatedError;
552
+ }
553
+ }
554
+ const allErrorCodes = {};
555
+ for (const issue of parsedResult.error.issues) {
556
+ const code = issue.code.toUpperCase();
557
+ allErrorCodes[code] = (allErrorCodes[code] ?? 0) + 1;
558
+ }
559
+ throw new DataError({ input }, Object.entries(allErrorCodes).toSorted(([_, firstCount], [__, secondCount]) => {
560
+ return secondCount - firstCount;
561
+ }).map(([code, count], _, allErrorCodes) => {
562
+ return allErrorCodes.length === 1 && count === 1 ? code : `${code}×${count}`;
563
+ }).join(","), `\n\n${z.prettifyError(parsedResult.error)}\n`);
564
+ }
565
+ return parsedResult.data;
566
+ }
567
+ //#endregion
568
+ //#region src/root/zod/parseZodSchema.ts
569
+ /**
570
+ * An alternative function to zodSchema.parse() that can be used to strictly parse Zod schemas.
571
+ *
572
+ * NOTE: Use `parseZodSchemaAsync` if your schema includes an asynchronous function.
573
+ *
574
+ * @category Parsers
575
+ *
576
+ * @deprecated Please use `az.with(schema).parse(input)` instead.
577
+ *
578
+ * @template SchemaType - The Zod schema type.
579
+ * @template ErrorType - The type of error to throw on invalid data.
580
+ *
581
+ * @param schema - The Zod schema to use in parsing.
582
+ * @param input - The data to parse.
583
+ * @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.
584
+ *
585
+ * @throws {DataErrorCode} If the given data cannot be parsed according to the schema.
586
+ *
587
+ * @returns The parsed data from the Zod schema.
588
+ */
589
+ function parseZodSchema(schema, input, onError) {
590
+ return _parseZodSchema(schema.safeParse(input), input, onError);
591
+ }
592
+ //#endregion
593
+ //#region src/root/zod/parseZodSchemaAsync.ts
594
+ /**
595
+ * An alternative function to zodSchema.parseAsync() that can be used to strictly parse asynchronous Zod schemas.
596
+ *
597
+ * @category Parsers
598
+ *
599
+ * @deprecated Please use `az.with(schema).parseAsync(input)` instead.
600
+ *
601
+ * @template SchemaType - The Zod schema type.
602
+ * @template ErrorType - The type of error to throw on invalid data.
603
+ *
604
+ * @param schema - The Zod schema to use in parsing.
605
+ * @param input - The data to parse.
606
+ * @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.
607
+ *
608
+ * @throws {DataError} If the given data cannot be parsed according to the schema.
609
+ *
610
+ * @returns The parsed data from the Zod schema.
611
+ */
612
+ async function parseZodSchemaAsync(schema, input, onError) {
613
+ return _parseZodSchema(await schema.safeParseAsync(input), input, onError);
614
+ }
615
+ //#endregion
616
+ //#region src/root/zod/zodFieldWrapper.ts
617
+ function zodFieldWrapper(schema) {
618
+ return z.string().trim().transform((value) => {
619
+ return value === "" ? null : value;
620
+ }).pipe(schema);
621
+ }
622
+ //#endregion
623
+ //#region src/root/zod/az.ts
624
+ const az = {
625
+ field: zodFieldWrapper,
626
+ fieldNumber: () => {
627
+ return z.coerce.number();
628
+ },
629
+ versionNumber: () => {
630
+ return zodVersionNumber;
631
+ },
632
+ fieldDate: () => {
633
+ return z.coerce.date();
634
+ },
635
+ with: (schema) => {
636
+ return {
637
+ parse: (input, error) => {
638
+ return parseZodSchema(schema, input, error);
639
+ },
640
+ parseAsync: async (input, error) => {
641
+ return await parseZodSchemaAsync(schema, input, error);
642
+ }
643
+ };
644
+ }
645
+ };
646
+ //#endregion
348
647
  //#region src/internal/getDependenciesFromGroup.ts
349
648
  /**
350
649
  * Get the dependencies from a given dependency group in `package.json`.
@@ -358,8 +657,8 @@ function normaliseIndents(first, ...args) {
358
657
  */
359
658
  function getDependenciesFromGroup(packageInfo, dependencyGroup) {
360
659
  return {
361
- dependencies: parseZodSchema(z.record(z.string(), z.string()), packageInfo.dependencies ?? {}),
362
- devDependencies: parseZodSchema(z.record(z.string(), z.string()), packageInfo.devDependencies ?? {})
660
+ dependencies: az.with(z.record(z.string(), z.string())).parse(packageInfo.dependencies ?? {}),
661
+ devDependencies: az.with(z.record(z.string(), z.string())).parse(packageInfo.devDependencies ?? {})
363
662
  }[dependencyGroup];
364
663
  }
365
664
  //#endregion
@@ -367,7 +666,7 @@ function getDependenciesFromGroup(packageInfo, dependencyGroup) {
367
666
  async function getExpectedTgzName(packagePath, packageManager) {
368
667
  const { stdout: rawPackedTgzData } = await execa({ cwd: packagePath })`${packageManager} pack --json --dry-run`;
369
668
  const packedTgzData = parseJsonFromStdout(rawPackedTgzData);
370
- const parsedPackedTgzData = parseZodSchema(packageManager === "pnpm" ? z.object({ filename: z.string() }) : z.array(z.object({ filename: z.string() })), packedTgzData, new DataError(packedTgzData, "AMBIGUOUS_EXPECTED_FILE_NAME", "Could not figure out the expected filename."));
669
+ const parsedPackedTgzData = az.with(packageManager === "pnpm" ? z.object({ filename: z.string() }) : z.array(z.object({ filename: z.string() }))).parse(packedTgzData, new DataError(packedTgzData, "AMBIGUOUS_EXPECTED_FILE_NAME", "Could not figure out the expected filename."));
371
670
  const [normalisedTgzMetadata] = Array.isArray(parsedPackedTgzData) ? parsedPackedTgzData : [parsedPackedTgzData];
372
671
  const { filename: expectedTgzFileName } = normalisedTgzMetadata;
373
672
  return expectedTgzFileName;
@@ -248,6 +248,21 @@ var CodeError = class CodeError extends Error {
248
248
  throw error;
249
249
  }
250
250
  /**
251
+ * Check a `CodeError` against its error code
252
+ *
253
+ * This will also automatically narrow down the type of the input to be `CodeError`, with its error code properly typed if this function returns true.
254
+ *
255
+ * @template ErrorCode The type of the error code
256
+ *
257
+ * @param input - The input to check.
258
+ * @param code - The expected code of the resulting error.
259
+ *
260
+ * @returns `true` if the error code matches the expected code, and `false` otherwise. The type of the input will also be narrowed down to CodeError, and its code will be narrowed to the expected code's type if the function returns `true`.
261
+ */
262
+ static checkWithCode(input, code) {
263
+ return this.check(input) && input.code === code;
264
+ }
265
+ /**
251
266
  * Gets the thrown `CodeError` from a given function if one was thrown, and re-throws any other errors, or throws a default `CodeError` if no error thrown.
252
267
  *
253
268
  * @param errorFunction - The function expected to throw the error.
@@ -303,11 +318,10 @@ var DataError = class DataError extends CodeError {
303
318
  * @param message - A human-readable error message (e.g. The data provided is invalid).
304
319
  * @param options - Extra options to pass to super Error constructor.
305
320
  */
306
- constructor(data, code = "INVALID_DATA", message = "The data provided is invalid", options) {
321
+ constructor(data, code, message = "The data provided is invalid", options) {
307
322
  super(code, message, options);
308
323
  if (Error.captureStackTrace) Error.captureStackTrace(this, new.target);
309
324
  this.name = new.target.name;
310
- this.code = code;
311
325
  this.data = data;
312
326
  Object.defineProperty(this, "message", { enumerable: true });
313
327
  Object.setPrototypeOf(this, new.target.prototype);
@@ -324,6 +338,21 @@ var DataError = class DataError extends CodeError {
324
338
  return typeof input === "object" && input !== null && "message" in input && typeof input.message === "string" && "code" in input && typeof input.code === "string" && "data" in input;
325
339
  }
326
340
  /**
341
+ * Check a `DataError` against its error code
342
+ *
343
+ * This will also automatically narrow down the type of the input to be `DataError`, with its error code properly typed if this function returns true.
344
+ *
345
+ * @template ErrorCode The type of the error code
346
+ *
347
+ * @param input - The input to check.
348
+ * @param code - The expected code of the resulting error.
349
+ *
350
+ * @returns `true` if the error code matches the expected code, and `false` otherwise. The type of the input will also be narrowed down to `DataError`, and its code will be narrowed to the expected code's type if the function returns `true`.
351
+ */
352
+ static checkWithCode(input, code) {
353
+ return this.check(input) && input.code === code;
354
+ }
355
+ /**
327
356
  * 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.
328
357
  *
329
358
  * @param errorFunction - The function expected to throw the error.