@alextheman/utility 5.11.3 → 5.13.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 +605 -544
- package/dist/index.d.cts +174 -59
- package/dist/index.d.ts +174 -59
- package/dist/index.js +606 -544
- package/dist/internal/index.cjs +244 -185
- package/dist/internal/index.d.cts +101 -41
- package/dist/internal/index.d.ts +101 -41
- package/dist/internal/index.js +244 -185
- package/dist/node/index.cjs +197 -138
- package/dist/node/index.d.cts +1 -1
- package/dist/node/index.d.ts +1 -1
- package/dist/node/index.js +197 -138
- package/dist/v6/index.cjs +409 -0
- package/dist/v6/index.d.cts +137 -0
- package/dist/v6/index.d.ts +137 -0
- package/dist/v6/index.js +405 -0
- package/package.json +6 -1
package/dist/index.cjs
CHANGED
|
@@ -87,77 +87,42 @@ function paralleliseArrays(firstArray, secondArray) {
|
|
|
87
87
|
return outputArray;
|
|
88
88
|
}
|
|
89
89
|
//#endregion
|
|
90
|
-
//#region src/
|
|
91
|
-
const httpErrorCodeLookup = {
|
|
92
|
-
400: "BAD_REQUEST",
|
|
93
|
-
401: "UNAUTHORISED",
|
|
94
|
-
403: "FORBIDDEN",
|
|
95
|
-
404: "NOT_FOUND",
|
|
96
|
-
418: "I_AM_A_TEAPOT",
|
|
97
|
-
500: "INTERNAL_SERVER_ERROR"
|
|
98
|
-
};
|
|
99
|
-
/**
|
|
100
|
-
* Represents common errors you may get from a HTTP API request.
|
|
101
|
-
*
|
|
102
|
-
* @category Types
|
|
103
|
-
*/
|
|
104
|
-
var APIError = class APIError extends Error {
|
|
105
|
-
status;
|
|
106
|
-
/**
|
|
107
|
-
* @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.
|
|
108
|
-
* @param message - An error message to display alongside the status code.
|
|
109
|
-
* @param options - Extra options to be passed to super Error constructor.
|
|
110
|
-
*/
|
|
111
|
-
constructor(status = 500, message, options) {
|
|
112
|
-
super(message, options);
|
|
113
|
-
this.status = status;
|
|
114
|
-
if (message) this.message = message;
|
|
115
|
-
else this.message = httpErrorCodeLookup[this.status] ?? "API_ERROR";
|
|
116
|
-
Object.defineProperty(this, "message", { enumerable: true });
|
|
117
|
-
Object.setPrototypeOf(this, new.target.prototype);
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Checks whether the given input may have been caused by an APIError.
|
|
121
|
-
*
|
|
122
|
-
* @param input - The input to check.
|
|
123
|
-
*
|
|
124
|
-
* @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`.
|
|
125
|
-
*/
|
|
126
|
-
static check(input) {
|
|
127
|
-
if (input instanceof APIError) return true;
|
|
128
|
-
const data = input;
|
|
129
|
-
return typeof data === "object" && data !== null && typeof data?.status === "number" && typeof data?.message === "string";
|
|
130
|
-
}
|
|
131
|
-
};
|
|
132
|
-
//#endregion
|
|
133
|
-
//#region src/root/types/DataError.ts
|
|
90
|
+
//#region src/v6/CodeError.ts
|
|
134
91
|
/**
|
|
135
|
-
* Represents errors
|
|
92
|
+
* Represents errors that can be described using a standardised error code, and a human-readable error message.
|
|
136
93
|
*
|
|
137
94
|
* @category Types
|
|
138
95
|
*
|
|
139
|
-
* @template
|
|
96
|
+
* @template ErrorCode The type of the standardised error code.
|
|
140
97
|
*/
|
|
141
|
-
var
|
|
98
|
+
var CodeError = class CodeError extends Error {
|
|
142
99
|
code;
|
|
143
|
-
data;
|
|
144
100
|
/**
|
|
145
|
-
* @param data - The data that caused the error.
|
|
146
101
|
* @param code - A standardised code (e.g. UNEXPECTED_DATA).
|
|
147
102
|
* @param message - A human-readable error message (e.g. The data provided is invalid).
|
|
148
103
|
* @param options - Extra options to pass to super Error constructor.
|
|
149
104
|
*/
|
|
150
|
-
constructor(
|
|
105
|
+
constructor(code, message = "Something went wrong.", options) {
|
|
151
106
|
super(message, options);
|
|
152
107
|
if (Error.captureStackTrace) Error.captureStackTrace(this, new.target);
|
|
153
108
|
this.name = new.target.name;
|
|
154
109
|
this.code = code;
|
|
155
|
-
this.data = data;
|
|
156
110
|
Object.defineProperty(this, "message", { enumerable: true });
|
|
157
111
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
158
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* Checks whether the given input may have been caused by a CodeError.
|
|
115
|
+
*
|
|
116
|
+
* @param input - The input to check.
|
|
117
|
+
*
|
|
118
|
+
* @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`.
|
|
119
|
+
*/
|
|
120
|
+
static check(input) {
|
|
121
|
+
if (input instanceof CodeError) return true;
|
|
122
|
+
return typeof input === "object" && input !== null && "message" in input && typeof input.message === "string" && "code" in input && typeof input.code === "string";
|
|
123
|
+
}
|
|
159
124
|
static checkCaughtError(error, options) {
|
|
160
|
-
if (
|
|
125
|
+
if (this.check(error)) {
|
|
161
126
|
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.
|
|
162
127
|
|
|
163
128
|
Expected: ${options.expectedCode}
|
|
@@ -168,223 +133,110 @@ var DataError = class DataError extends Error {
|
|
|
168
133
|
throw error;
|
|
169
134
|
}
|
|
170
135
|
/**
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
* @param input - The input to check.
|
|
174
|
-
*
|
|
175
|
-
* @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`.
|
|
176
|
-
*/
|
|
177
|
-
static check(input) {
|
|
178
|
-
if (input instanceof DataError) return true;
|
|
179
|
-
const data = input;
|
|
180
|
-
return typeof data === "object" && data !== null && typeof data.message === "string" && typeof data.code === "string" && "data" in data;
|
|
181
|
-
}
|
|
182
|
-
/**
|
|
183
|
-
* 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.
|
|
136
|
+
* 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.
|
|
184
137
|
*
|
|
185
138
|
* @param errorFunction - The function expected to throw the error.
|
|
186
139
|
* @param options - Extra options to apply.
|
|
187
140
|
*
|
|
188
|
-
* @throws {Error} Any other errors thrown by the `errorFunction` that are not a `
|
|
189
|
-
* @throws {Error} If no `
|
|
141
|
+
* @throws {Error} Any other errors thrown by the `errorFunction` that are not a `CodeError`.
|
|
142
|
+
* @throws {Error} If no `CodeError` was thrown by the `errorFunction`
|
|
190
143
|
*
|
|
191
|
-
* @returns The `
|
|
144
|
+
* @returns The `CodeError` that was thrown by the `errorFunction`
|
|
192
145
|
*/
|
|
193
146
|
static expectError(errorFunction, options) {
|
|
194
147
|
try {
|
|
195
148
|
errorFunction();
|
|
196
149
|
} catch (error) {
|
|
197
|
-
return
|
|
150
|
+
return this.checkCaughtError(error, options);
|
|
198
151
|
}
|
|
199
|
-
throw new Error(
|
|
152
|
+
throw new Error(`Expected a ${this.name} to be thrown but none was thrown`);
|
|
200
153
|
}
|
|
201
154
|
/**
|
|
202
|
-
* Gets the thrown `
|
|
155
|
+
* 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.
|
|
203
156
|
*
|
|
204
157
|
* @param errorFunction - The function expected to throw the error.
|
|
205
158
|
* @param options - Extra options to apply.
|
|
206
159
|
*
|
|
207
|
-
* @throws {Error} Any other errors thrown by the `errorFunction` that are not a `
|
|
208
|
-
* @throws {Error} If no `
|
|
160
|
+
* @throws {Error} Any other errors thrown by the `errorFunction` that are not a `CodeError`.
|
|
161
|
+
* @throws {Error} If no `CodeError` was thrown by the `errorFunction`
|
|
209
162
|
*
|
|
210
|
-
* @returns The `
|
|
163
|
+
* @returns The `CodeError` that was thrown by the `errorFunction`
|
|
211
164
|
*/
|
|
212
165
|
static async expectErrorAsync(errorFunction, options) {
|
|
213
166
|
try {
|
|
214
167
|
await errorFunction();
|
|
215
168
|
} catch (error) {
|
|
216
|
-
return
|
|
169
|
+
return this.checkCaughtError(error, options);
|
|
217
170
|
}
|
|
218
|
-
throw new Error(
|
|
171
|
+
throw new Error(`Expected a ${this.name} to be thrown but none was thrown`);
|
|
219
172
|
}
|
|
220
173
|
};
|
|
221
174
|
//#endregion
|
|
222
|
-
//#region src/
|
|
175
|
+
//#region src/v6/DataError.ts
|
|
223
176
|
/**
|
|
224
|
-
* Represents
|
|
177
|
+
* Represents errors you may get that may've been caused by a specific piece of data.
|
|
225
178
|
*
|
|
226
179
|
* @category Types
|
|
180
|
+
*
|
|
181
|
+
* @template DataType - The type of the data that caused the error.
|
|
227
182
|
*/
|
|
228
|
-
var
|
|
229
|
-
|
|
230
|
-
/** The major number. Increments when a feature is removed or changed in a way that is not backwards-compatible with the previous release. */
|
|
231
|
-
major = 0;
|
|
232
|
-
/** The minor number. Increments when a new feature is added/deprecated and is expected to be backwards-compatible with the previous release. */
|
|
233
|
-
minor = 0;
|
|
234
|
-
/** The patch number. Increments when the next release is fixing a bug or doing a small refactor that should not be noticeable in practice. */
|
|
235
|
-
patch = 0;
|
|
236
|
-
/**
|
|
237
|
-
* @param input - The input to create a new instance of `VersionNumber` from.
|
|
238
|
-
*/
|
|
239
|
-
constructor(input) {
|
|
240
|
-
if (input instanceof VersionNumber) {
|
|
241
|
-
this.major = input.major;
|
|
242
|
-
this.minor = input.minor;
|
|
243
|
-
this.patch = input.patch;
|
|
244
|
-
} else if (typeof input === "string") {
|
|
245
|
-
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.`);
|
|
246
|
-
const [major, minor, patch] = VersionNumber.formatString(input, { omitPrefix: true }).split(".").map((number) => {
|
|
247
|
-
return parseIntStrict(number);
|
|
248
|
-
});
|
|
249
|
-
this.major = major;
|
|
250
|
-
this.minor = minor;
|
|
251
|
-
this.patch = patch;
|
|
252
|
-
} else if (Array.isArray(input)) {
|
|
253
|
-
if (input.length !== 3) throw new DataError({ input }, "INVALID_LENGTH", VersionNumber.NON_NEGATIVE_TUPLE_ERROR);
|
|
254
|
-
const [major, minor, patch] = input.map((number) => {
|
|
255
|
-
const parsedInteger = parseIntStrict(number?.toString());
|
|
256
|
-
if (parsedInteger < 0) throw new DataError({ input }, "NEGATIVE_INPUTS", VersionNumber.NON_NEGATIVE_TUPLE_ERROR);
|
|
257
|
-
return parsedInteger;
|
|
258
|
-
});
|
|
259
|
-
this.major = major;
|
|
260
|
-
this.minor = minor;
|
|
261
|
-
this.patch = patch;
|
|
262
|
-
} else throw new DataError({ input }, "INVALID_INPUT", normaliseIndents`
|
|
263
|
-
The provided input can not be parsed into a valid version number.
|
|
264
|
-
Expected either a string of format X.Y.Z or vX.Y.Z, a tuple of three numbers, or another \`VersionNumber\` instance.
|
|
265
|
-
`);
|
|
266
|
-
}
|
|
183
|
+
var DataError = class DataError extends CodeError {
|
|
184
|
+
data;
|
|
267
185
|
/**
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
* @
|
|
186
|
+
* @param data - The data that caused the error.
|
|
187
|
+
* @param code - A standardised code (e.g. UNEXPECTED_DATA).
|
|
188
|
+
* @param message - A human-readable error message (e.g. The data provided is invalid).
|
|
189
|
+
* @param options - Extra options to pass to super Error constructor.
|
|
271
190
|
*/
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
if (
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
191
|
+
constructor(data, code = "INVALID_DATA", message = "The data provided is invalid", options) {
|
|
192
|
+
super(code, message, options);
|
|
193
|
+
if (Error.captureStackTrace) Error.captureStackTrace(this, new.target);
|
|
194
|
+
this.name = new.target.name;
|
|
195
|
+
this.code = code;
|
|
196
|
+
this.data = data;
|
|
197
|
+
Object.defineProperty(this, "message", { enumerable: true });
|
|
198
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
280
199
|
}
|
|
281
200
|
/**
|
|
282
|
-
* Checks
|
|
201
|
+
* Checks whether the given input may have been caused by a DataError.
|
|
283
202
|
*
|
|
284
|
-
* @param
|
|
285
|
-
* @param secondVersion - The second version number to compare.
|
|
203
|
+
* @param input - The input to check.
|
|
286
204
|
*
|
|
287
|
-
* @returns `true` if the
|
|
205
|
+
* @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`.
|
|
288
206
|
*/
|
|
289
|
-
static
|
|
290
|
-
|
|
207
|
+
static check(input) {
|
|
208
|
+
if (input instanceof DataError) return true;
|
|
209
|
+
return typeof input === "object" && input !== null && "message" in input && typeof input.message === "string" && "code" in input && typeof input.code === "string" && "data" in input;
|
|
291
210
|
}
|
|
292
211
|
/**
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
* @param options - Options to apply to the string formatting.
|
|
212
|
+
* 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.
|
|
296
213
|
*
|
|
297
|
-
* @
|
|
298
|
-
|
|
299
|
-
format(options) {
|
|
300
|
-
let baseOutput = `${this.major}`;
|
|
301
|
-
if (!options?.omitMinor) {
|
|
302
|
-
baseOutput += `.${this.minor}`;
|
|
303
|
-
if (!options?.omitPatch) baseOutput += `.${this.patch}`;
|
|
304
|
-
}
|
|
305
|
-
return VersionNumber.formatString(baseOutput, { omitPrefix: options?.omitPrefix });
|
|
306
|
-
}
|
|
307
|
-
/**
|
|
308
|
-
* Increments the current version number by the given increment type, returning the result as a new reference in memory.
|
|
214
|
+
* @param errorFunction - The function expected to throw the error.
|
|
215
|
+
* @param options - Extra options to apply.
|
|
309
216
|
*
|
|
310
|
-
* @
|
|
311
|
-
*
|
|
312
|
-
* - `"minor"`: Change the minor version `v1.2.3` → `v1.3.0`
|
|
313
|
-
* - `"patch"`: Change the patch version `v1.2.3` → `v1.2.4`
|
|
314
|
-
* @param incrementAmount - The amount to increment by (defaults to 1).
|
|
217
|
+
* @throws {Error} Any other errors thrown by the `errorFunction` that are not a `DataError`.
|
|
218
|
+
* @throws {Error} If no `DataError` was thrown by the `errorFunction`
|
|
315
219
|
*
|
|
316
|
-
* @returns
|
|
220
|
+
* @returns The `DataError` that was thrown by the `errorFunction`
|
|
317
221
|
*/
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
const calculatedRawVersion = {
|
|
321
|
-
major: [
|
|
322
|
-
this.major + incrementBy,
|
|
323
|
-
0,
|
|
324
|
-
0
|
|
325
|
-
],
|
|
326
|
-
minor: [
|
|
327
|
-
this.major,
|
|
328
|
-
this.minor + incrementBy,
|
|
329
|
-
0
|
|
330
|
-
],
|
|
331
|
-
patch: [
|
|
332
|
-
this.major,
|
|
333
|
-
this.minor,
|
|
334
|
-
this.patch + incrementBy
|
|
335
|
-
]
|
|
336
|
-
}[incrementType];
|
|
337
|
-
try {
|
|
338
|
-
return new VersionNumber(calculatedRawVersion);
|
|
339
|
-
} catch (error) {
|
|
340
|
-
if (DataError.check(error) && error.code === "NEGATIVE_INPUTS") throw new DataError({
|
|
341
|
-
currentVersion: this.toString(),
|
|
342
|
-
calculatedRawVersion: `v${calculatedRawVersion.join(".")}`,
|
|
343
|
-
incrementAmount
|
|
344
|
-
}, "NEGATIVE_VERSION", "Cannot apply this increment amount as it would lead to a negative version number.");
|
|
345
|
-
else throw error;
|
|
346
|
-
}
|
|
222
|
+
static expectError(errorFunction, options) {
|
|
223
|
+
return super.expectError(errorFunction, options);
|
|
347
224
|
}
|
|
348
225
|
/**
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
* @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).
|
|
226
|
+
* 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.
|
|
352
227
|
*
|
|
353
|
-
* @
|
|
354
|
-
|
|
355
|
-
[Symbol.toPrimitive](hint) {
|
|
356
|
-
if (hint === "number") throw new DataError({ thisVersion: this.toString() }, "INVALID_COERCION", "VersionNumber cannot be coerced to a number type.");
|
|
357
|
-
return this.toString();
|
|
358
|
-
}
|
|
359
|
-
/**
|
|
360
|
-
* Ensures that the VersionNumber behaves correctly when attempted to be converted to JSON.
|
|
228
|
+
* @param errorFunction - The function expected to throw the error.
|
|
229
|
+
* @param options - Extra options to apply.
|
|
361
230
|
*
|
|
362
|
-
* @
|
|
363
|
-
|
|
364
|
-
toJSON() {
|
|
365
|
-
return this.toString();
|
|
366
|
-
}
|
|
367
|
-
/**
|
|
368
|
-
* Get a string representation of the current version number.
|
|
231
|
+
* @throws {Error} Any other errors thrown by the `errorFunction` that are not a `DataError`.
|
|
232
|
+
* @throws {Error} If no `DataError` was thrown by the `errorFunction`
|
|
369
233
|
*
|
|
370
|
-
* @returns
|
|
234
|
+
* @returns The `DataError` that was thrown by the `errorFunction`
|
|
371
235
|
*/
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
return VersionNumber.formatString(rawString, { omitPrefix: false });
|
|
236
|
+
static async expectErrorAsync(errorFunction, options) {
|
|
237
|
+
return await super.expectErrorAsync(errorFunction, options);
|
|
375
238
|
}
|
|
376
239
|
};
|
|
377
|
-
const zodVersionNumber = zod.default.union([
|
|
378
|
-
zod.default.string(),
|
|
379
|
-
zod.default.tuple([
|
|
380
|
-
zod.default.number(),
|
|
381
|
-
zod.default.number(),
|
|
382
|
-
zod.default.number()
|
|
383
|
-
]),
|
|
384
|
-
zod.default.instanceof(VersionNumber)
|
|
385
|
-
]).transform((rawVersionNumber) => {
|
|
386
|
-
return new VersionNumber(rawVersionNumber);
|
|
387
|
-
});
|
|
388
240
|
//#endregion
|
|
389
241
|
//#region src/root/functions/parsers/parseIntStrict.ts
|
|
390
242
|
/**
|
|
@@ -784,350 +636,116 @@ function isOrdered(array) {
|
|
|
784
636
|
return true;
|
|
785
637
|
}
|
|
786
638
|
//#endregion
|
|
787
|
-
//#region src/root/functions/
|
|
639
|
+
//#region src/root/functions/miscellaneous/sayHello.ts
|
|
788
640
|
/**
|
|
789
|
-
*
|
|
790
|
-
*
|
|
791
|
-
* Note that this will also freeze the input itself as well.
|
|
792
|
-
* If the intent is to create a newly frozen object with a different reference in memory, pass your object through deepCopy first before passing to deepFreeze.
|
|
793
|
-
*
|
|
794
|
-
* @category Recursive
|
|
795
|
-
*
|
|
796
|
-
* @template ObjectType - The type of the input object.
|
|
797
|
-
*
|
|
798
|
-
* @param object - The object to freeze. May also be an array.
|
|
799
|
-
*
|
|
800
|
-
* @returns The input object completely frozen.
|
|
801
|
-
*/
|
|
802
|
-
function deepFreeze(object) {
|
|
803
|
-
for (const value of Object.values(object)) {
|
|
804
|
-
if (typeof value === "function") continue;
|
|
805
|
-
if (value && typeof value === "object") deepFreeze(value);
|
|
806
|
-
}
|
|
807
|
-
return Object.freeze(object);
|
|
808
|
-
}
|
|
809
|
-
//#endregion
|
|
810
|
-
//#region src/root/functions/taggedTemplate/createTemplateStringsArray.ts
|
|
811
|
-
/**
|
|
812
|
-
* Creates a template strings array given a regular array of strings
|
|
813
|
-
*
|
|
814
|
-
* @category Tagged Template
|
|
815
|
-
*
|
|
816
|
-
* @param strings - The array of strings.
|
|
817
|
-
*
|
|
818
|
-
* @returns A template strings array that can be passed as the first argument of any tagged template function.
|
|
819
|
-
*/
|
|
820
|
-
function createTemplateStringsArray(strings) {
|
|
821
|
-
return deepFreeze(Object.assign([...strings], { raw: [...strings] }));
|
|
822
|
-
}
|
|
823
|
-
//#endregion
|
|
824
|
-
//#region src/root/functions/taggedTemplate/getStringsAndInterpolations.ts
|
|
825
|
-
/**
|
|
826
|
-
*
|
|
827
|
-
* Gets the strings and interpolations separately from a template string.
|
|
828
|
-
* You can pass a template string directly by doing:
|
|
829
|
-
*
|
|
830
|
-
* ```typescript
|
|
831
|
-
* getStringsAndInterpolations`Template string here`;
|
|
832
|
-
* ```
|
|
833
|
-
*
|
|
834
|
-
* @category Tagged Template
|
|
835
|
-
*
|
|
836
|
-
* @template InterpolationsType - The type of the interpolations.
|
|
837
|
-
*
|
|
838
|
-
* @param strings - The strings from the template to process.
|
|
839
|
-
* @param interpolations - An array of all interpolations from the template.
|
|
840
|
-
*
|
|
841
|
-
* @returns A tuple where the first item is the strings from the template, and the remaining items are the interpolations.
|
|
842
|
-
*
|
|
843
|
-
* The return of this function may also be spread into any other tagged template function in the following way:
|
|
844
|
-
*
|
|
845
|
-
* ```typescript
|
|
846
|
-
* import { interpolate } from "@alextheman/utility"; // Example function
|
|
847
|
-
*
|
|
848
|
-
* const packageName = "@alextheman/utility";
|
|
849
|
-
* const packageManager = getPackageManager(packageName);
|
|
850
|
-
*
|
|
851
|
-
* interpolate(...getStringsAndInterpolations`The package ${packageName} uses the ${packageManager} package manager.`);
|
|
852
|
-
* ```
|
|
853
|
-
*/
|
|
854
|
-
function getStringsAndInterpolations(strings, ...interpolations) {
|
|
855
|
-
if (strings.length !== interpolations.length + 1) throw new DataError({
|
|
856
|
-
stringsLength: strings.length,
|
|
857
|
-
interpolationsLength: interpolations.length,
|
|
858
|
-
strings,
|
|
859
|
-
interpolations
|
|
860
|
-
}, "INVALID_STRINGS_AND_INTERPOLATIONS_LENGTH", "The length of the strings must be exactly one more than the length of the interpolations.");
|
|
861
|
-
return [createTemplateStringsArray(strings), ...interpolations];
|
|
862
|
-
}
|
|
863
|
-
//#endregion
|
|
864
|
-
//#region src/root/functions/taggedTemplate/interpolate.ts
|
|
865
|
-
/**
|
|
866
|
-
* Returns the result of interpolating a template string when given the strings and interpolations separately.
|
|
867
|
-
*
|
|
868
|
-
* You can pass a template string directly by doing:
|
|
869
|
-
*
|
|
870
|
-
* ```
|
|
871
|
-
* interpolate`Template string here`;
|
|
872
|
-
* ```
|
|
873
|
-
*
|
|
874
|
-
* In this case, it will be functionally the same as if you just wrote the template string by itself.
|
|
875
|
-
*
|
|
876
|
-
* @category Tagged Template
|
|
877
|
-
*
|
|
878
|
-
* @template InterpolationsType - The type of the interpolations.
|
|
879
|
-
*
|
|
880
|
-
* @param strings - The strings from the template to process.
|
|
881
|
-
* @param interpolations - An array of all interpolations from the template.
|
|
882
|
-
*
|
|
883
|
-
* @returns A new string with the strings and interpolations from the template applied.
|
|
884
|
-
*/
|
|
885
|
-
function interpolate(strings, ...interpolations) {
|
|
886
|
-
let result = "";
|
|
887
|
-
for (const [string, interpolation = ""] of paralleliseArrays(strings, interpolations)) result += string + interpolation;
|
|
888
|
-
return result;
|
|
889
|
-
}
|
|
890
|
-
//#endregion
|
|
891
|
-
//#region src/root/functions/taggedTemplate/interpolateObjects.ts
|
|
892
|
-
/**
|
|
893
|
-
* Returns the result of interpolating a template string, also stringifying objects.
|
|
894
|
-
*
|
|
895
|
-
* You can pass a template string directly by doing:
|
|
896
|
-
*
|
|
897
|
-
* ```typescript
|
|
898
|
-
* interpolateObjects`Template string here ${{ my: "object" }}`;
|
|
899
|
-
* ```
|
|
900
|
-
*
|
|
901
|
-
* @category Tagged Template
|
|
902
|
-
*
|
|
903
|
-
* @template InterpolationsType - The type of the interpolations.
|
|
904
|
-
*
|
|
905
|
-
* @param strings - The strings from the template to process.
|
|
906
|
-
* @param interpolations - An array of all interpolations from the template.
|
|
907
|
-
*
|
|
908
|
-
* @returns A new string with the strings and interpolations from the template applied, with objects stringified.
|
|
909
|
-
*/
|
|
910
|
-
function interpolateObjects(strings, ...interpolations) {
|
|
911
|
-
let result = "";
|
|
912
|
-
for (let i = 0; i < strings.length; i++) {
|
|
913
|
-
result += strings[i];
|
|
914
|
-
if (i !== strings.length - 1) result += interpolations[i] && typeof interpolations[i] === "object" ? JSON.stringify(interpolations[i]) : interpolations[i];
|
|
915
|
-
}
|
|
916
|
-
return result;
|
|
917
|
-
}
|
|
918
|
-
//#endregion
|
|
919
|
-
//#region src/root/functions/taggedTemplate/isTemplateStringsArray.ts
|
|
920
|
-
/**
|
|
921
|
-
* Determines whether or not the input is a valid `TemplateStringsArray`.
|
|
922
|
-
*
|
|
923
|
-
* @category Tagged Template
|
|
924
|
-
*
|
|
925
|
-
* @param input - The input to check
|
|
926
|
-
*
|
|
927
|
-
* @returns `true` if the input is a valid `TemplateStringsArray`, and false otherwise. The type of the input will also be narrowed down to `TemplateStringsArray` if `true`.
|
|
928
|
-
*/
|
|
929
|
-
function isTemplateStringsArray(input) {
|
|
930
|
-
return typeof input === "object" && input !== null && "raw" in input;
|
|
931
|
-
}
|
|
932
|
-
//#endregion
|
|
933
|
-
//#region src/root/functions/taggedTemplate/normaliseIndents.ts
|
|
934
|
-
function calculateTabSize(line, whitespaceLength) {
|
|
935
|
-
const potentialWhitespacePart = line.slice(0, whitespaceLength);
|
|
936
|
-
const trimmedString = line.trimStart();
|
|
937
|
-
if (potentialWhitespacePart.trim() !== "") return 0;
|
|
938
|
-
const tabSize = line.length - (trimmedString.length + whitespaceLength);
|
|
939
|
-
return tabSize < 0 ? 0 : tabSize;
|
|
940
|
-
}
|
|
941
|
-
function getWhitespaceLength(lines) {
|
|
942
|
-
const [firstNonEmptyLine] = lines.filter((line) => {
|
|
943
|
-
return line.trim() !== "";
|
|
944
|
-
});
|
|
945
|
-
return firstNonEmptyLine.length - firstNonEmptyLine.trimStart().length;
|
|
946
|
-
}
|
|
947
|
-
function reduceLines(lines, { preserveTabs = true }) {
|
|
948
|
-
const slicedLines = lines.slice(1);
|
|
949
|
-
const isFirstLineEmpty = lines[0].trim() === "";
|
|
950
|
-
const whitespaceLength = getWhitespaceLength(isFirstLineEmpty ? lines : slicedLines);
|
|
951
|
-
return (isFirstLineEmpty ? slicedLines : lines).map((line) => {
|
|
952
|
-
const tabSize = calculateTabSize(line, whitespaceLength);
|
|
953
|
-
return (preserveTabs ? fillArray(() => {
|
|
954
|
-
return " ";
|
|
955
|
-
}, tabSize).join("") : "") + line.trimStart();
|
|
956
|
-
}).join("\n");
|
|
957
|
-
}
|
|
958
|
-
/**
|
|
959
|
-
* Applies any options if provided, then removes any extraneous indents from a multi-line template string.
|
|
960
|
-
*
|
|
961
|
-
* You can pass a template string directly by doing:
|
|
962
|
-
*
|
|
963
|
-
* ```typescript
|
|
964
|
-
* normaliseIndents`Template string here
|
|
965
|
-
* with a new line
|
|
966
|
-
* and another new line`;
|
|
967
|
-
* ```
|
|
968
|
-
*
|
|
969
|
-
* You may also pass the options first, then invoke the resulting function with a template string:
|
|
970
|
-
*
|
|
971
|
-
* ```typescript
|
|
972
|
-
* normaliseIndents({ preserveTabs: false })`Template string here
|
|
973
|
-
* with a new line
|
|
974
|
-
* and another new line`;
|
|
975
|
-
* ```
|
|
976
|
-
*
|
|
977
|
-
* @category Tagged Template
|
|
978
|
-
*
|
|
979
|
-
* @param first - The strings from the template to process, or the options to apply.
|
|
980
|
-
* @param args - An array of all interpolations from the template.
|
|
981
|
-
*
|
|
982
|
-
* @returns An additional function to invoke, or a new string with the strings and interpolations from the template applied, and extraneous indents removed.
|
|
983
|
-
*/
|
|
984
|
-
function normaliseIndents(first, ...args) {
|
|
985
|
-
if (typeof first === "object" && first !== null && !Array.isArray(first)) {
|
|
986
|
-
const options = first;
|
|
987
|
-
return (strings, ...interpolations) => {
|
|
988
|
-
return normaliseIndents(strings, ...interpolations, options);
|
|
989
|
-
};
|
|
990
|
-
}
|
|
991
|
-
const strings = first;
|
|
992
|
-
const options = typeof args[args.length - 1] === "object" && !Array.isArray(args[args.length - 1]) ? args.pop() : {};
|
|
993
|
-
return reduceLines(interpolate(strings, ...[...args]).split("\n"), options);
|
|
994
|
-
}
|
|
995
|
-
/**
|
|
996
|
-
* Applies any options if provided, then removes any extraneous indents from a multi-line template string.
|
|
997
|
-
*
|
|
998
|
-
* You can pass a template string directly by doing:
|
|
999
|
-
*
|
|
1000
|
-
* ```typescript
|
|
1001
|
-
* normalizeIndents`Template string here
|
|
1002
|
-
* with a new line
|
|
1003
|
-
* and another new line`.
|
|
1004
|
-
* ```
|
|
1005
|
-
*
|
|
1006
|
-
* You may also pass the options first, then invoke the resulting function with a template string:
|
|
1007
|
-
*
|
|
1008
|
-
* ```typescript
|
|
1009
|
-
* normalizeIndents({ preserveTabs: false })`Template string here
|
|
1010
|
-
* with a new line
|
|
1011
|
-
* and another new line`.
|
|
1012
|
-
* ```
|
|
1013
|
-
*
|
|
1014
|
-
* @param first - The strings from the template to process, or the options to apply.
|
|
1015
|
-
* @param args - An array of all interpolations from the template.
|
|
1016
|
-
*
|
|
1017
|
-
* @returns An additional function to invoke, or a new string with the strings and interpolations from the template applied, and extraneous indents removed.
|
|
1018
|
-
*/
|
|
1019
|
-
const normalizeIndents = normaliseIndents;
|
|
1020
|
-
//#endregion
|
|
1021
|
-
//#region src/root/functions/miscellaneous/sayHello.ts
|
|
1022
|
-
/**
|
|
1023
|
-
* Returns a string representing the lyrics to the package's theme song, Commit To You
|
|
641
|
+
* Returns a string representing the lyrics to the package's theme song, Commit To You
|
|
1024
642
|
*
|
|
1025
643
|
* [Pls listen!](https://www.youtube.com/watch?v=mH-Sg-8EnxM)
|
|
1026
644
|
*
|
|
1027
645
|
* @returns The lyrics string in markdown format.
|
|
1028
646
|
*/
|
|
1029
647
|
function sayHello() {
|
|
1030
|
-
return
|
|
1031
|
-
|
|
648
|
+
return `
|
|
649
|
+
# Commit To You
|
|
1032
650
|
|
|
1033
|
-
|
|
651
|
+
### Verse 1
|
|
1034
652
|
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
653
|
+
I know you've been checking me out,
|
|
654
|
+
Shall we take it to the next level now?
|
|
655
|
+
'Cause I really wanna be there all for you,
|
|
656
|
+
All for you!
|
|
657
|
+
Come on now, let's make a fresh start!
|
|
658
|
+
Pin my number, then you can take me out!
|
|
659
|
+
Can't you see I really do care about you,
|
|
660
|
+
About you!
|
|
1043
661
|
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
662
|
+
### Pre-chorus 1
|
|
663
|
+
Although our calendars are imperfect, at best,
|
|
664
|
+
I'd like to organise time with you! (with you!).
|
|
665
|
+
Just tell me when and I'll make it clear,
|
|
666
|
+
All clear for you,
|
|
667
|
+
All clear for you!
|
|
668
|
+
(One, two, three, go!)
|
|
1051
669
|
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
670
|
+
### Chorus
|
|
671
|
+
I wanna be of utility, I'll help you on the run!
|
|
672
|
+
I'll be the one here in the back, while you go have some fun!
|
|
673
|
+
Looking out for you tonight, I'll be the one you can rely on!
|
|
674
|
+
Watch you go and watch me pass by,
|
|
675
|
+
I'll be here!
|
|
676
|
+
I'll commit to you!
|
|
1059
677
|
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
678
|
+
### Verse 2
|
|
679
|
+
Though sometimes it won't be easy,
|
|
680
|
+
You'll be here to bring out the best in me,
|
|
681
|
+
And I'll hold myself to high standards for you!
|
|
682
|
+
All for you!
|
|
683
|
+
We'll grow as a pair, you and me,
|
|
684
|
+
We'll build up a healthy dependency,
|
|
685
|
+
You can build with me and I'll develop with you!
|
|
686
|
+
I'm with you!
|
|
1069
687
|
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
688
|
+
### Pre-chorus 2
|
|
689
|
+
I'll be with you when you're up or you're down,
|
|
690
|
+
We'll deal with all our problems together (together!)
|
|
691
|
+
Just tell me what you want, I'll make it clear,
|
|
692
|
+
All clear for you,
|
|
693
|
+
All clear for you!
|
|
694
|
+
(One, three, one, go!)
|
|
1077
695
|
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
696
|
+
### Chorus
|
|
697
|
+
I wanna be of utility, I'll help you on the run!
|
|
698
|
+
(help you on the run!)
|
|
699
|
+
I'll be the one here in the back, while you go have some fun!
|
|
700
|
+
(you go have some fun!)
|
|
701
|
+
Looking out for you tonight, I'll be the one you can rely on!
|
|
702
|
+
Watch you go and watch me pass by,
|
|
703
|
+
I'll be here!
|
|
704
|
+
I'll commit to you!
|
|
1087
705
|
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
706
|
+
### Bridge
|
|
707
|
+
Looking into our stack!
|
|
708
|
+
I'll commit to you!
|
|
709
|
+
We've got a lot to unpack!
|
|
710
|
+
I'll commit to you!
|
|
711
|
+
The environment that we're in!
|
|
712
|
+
I'll commit to you!
|
|
713
|
+
Delicate as a string!
|
|
714
|
+
I'll commit to you!
|
|
1097
715
|
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
716
|
+
But I think you're my type!
|
|
717
|
+
I'll commit to you!
|
|
718
|
+
Oh, this feels all so right!
|
|
719
|
+
I'll commit to you!
|
|
720
|
+
Nothing stopping us now!
|
|
721
|
+
I'll commit to you!
|
|
722
|
+
Let's show them what we're about!
|
|
723
|
+
Two, three, four, go!
|
|
1106
724
|
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
725
|
+
### Final Chorus
|
|
726
|
+
I wanna be of utility, I'll help you on the run!
|
|
727
|
+
(help you on the run!)
|
|
728
|
+
I'll be the one here in the back, while you go have some fun!
|
|
729
|
+
(you go have some fun!)
|
|
730
|
+
Looking out for you tonight, I'll be the one you can rely on!
|
|
731
|
+
Watch you go and watch me pass by,
|
|
732
|
+
I'll be here!
|
|
733
|
+
I'll commit to you!
|
|
1116
734
|
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
735
|
+
I wanna be of utility, I'll help you on the run!
|
|
736
|
+
(I'll commit to you!)
|
|
737
|
+
I'll be the one here in the back, while you go have some fun!
|
|
738
|
+
(I'll commit to you!)
|
|
739
|
+
Looking out for you tonight, I'll be the one you can rely on!
|
|
740
|
+
(I'll commit to you!)
|
|
741
|
+
Watch you go and watch me pass by,
|
|
742
|
+
(I'll commit to you!)
|
|
743
|
+
I'll be here!
|
|
1126
744
|
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
745
|
+
### Outro
|
|
746
|
+
I'll commit to you!
|
|
747
|
+
I'll commit to you!
|
|
748
|
+
I'll commit to you!
|
|
1131
749
|
`;
|
|
1132
750
|
}
|
|
1133
751
|
//#endregion
|
|
@@ -1314,7 +932,7 @@ function _parseZodSchema(parsedResult, input, onError) {
|
|
|
1314
932
|
* @param input - The data to parse.
|
|
1315
933
|
* @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.
|
|
1316
934
|
*
|
|
1317
|
-
* @throws {
|
|
935
|
+
* @throws {DataErrorCode} If the given data cannot be parsed according to the schema.
|
|
1318
936
|
*
|
|
1319
937
|
* @returns The parsed data from the Zod schema.
|
|
1320
938
|
*/
|
|
@@ -1462,6 +1080,29 @@ function deepCopy(object) {
|
|
|
1462
1080
|
return clonedObject;
|
|
1463
1081
|
}
|
|
1464
1082
|
//#endregion
|
|
1083
|
+
//#region src/root/functions/recursive/deepFreeze.ts
|
|
1084
|
+
/**
|
|
1085
|
+
* Deeply freezes an object or array such that all child objects/arrays are also frozen.
|
|
1086
|
+
*
|
|
1087
|
+
* Note that this will also freeze the input itself as well.
|
|
1088
|
+
* If the intent is to create a newly frozen object with a different reference in memory, pass your object through deepCopy first before passing to deepFreeze.
|
|
1089
|
+
*
|
|
1090
|
+
* @category Recursive
|
|
1091
|
+
*
|
|
1092
|
+
* @template ObjectType - The type of the input object.
|
|
1093
|
+
*
|
|
1094
|
+
* @param object - The object to freeze. May also be an array.
|
|
1095
|
+
*
|
|
1096
|
+
* @returns The input object completely frozen.
|
|
1097
|
+
*/
|
|
1098
|
+
function deepFreeze(object) {
|
|
1099
|
+
for (const value of Object.values(object)) {
|
|
1100
|
+
if (typeof value === "function") continue;
|
|
1101
|
+
if (value && typeof value === "object") deepFreeze(value);
|
|
1102
|
+
}
|
|
1103
|
+
return Object.freeze(object);
|
|
1104
|
+
}
|
|
1105
|
+
//#endregion
|
|
1465
1106
|
//#region src/root/functions/stringHelpers/appendSemicolon.ts
|
|
1466
1107
|
/**
|
|
1467
1108
|
* Appends a semicolon to the end of a string, trimming where necessary first.
|
|
@@ -1613,8 +1254,428 @@ function truncate(stringToTruncate, maxLength = 5) {
|
|
|
1613
1254
|
return stringToTruncate.length > maxLength ? `${stringToTruncate.slice(0, maxLength)}...` : stringToTruncate;
|
|
1614
1255
|
}
|
|
1615
1256
|
//#endregion
|
|
1257
|
+
//#region src/root/functions/taggedTemplate/createTemplateStringsArray.ts
|
|
1258
|
+
/**
|
|
1259
|
+
* Creates a template strings array given a regular array of strings
|
|
1260
|
+
*
|
|
1261
|
+
* @category Tagged Template
|
|
1262
|
+
*
|
|
1263
|
+
* @param strings - The array of strings.
|
|
1264
|
+
*
|
|
1265
|
+
* @returns A template strings array that can be passed as the first argument of any tagged template function.
|
|
1266
|
+
*/
|
|
1267
|
+
function createTemplateStringsArray(strings) {
|
|
1268
|
+
return deepFreeze(Object.assign([...strings], { raw: [...strings] }));
|
|
1269
|
+
}
|
|
1270
|
+
//#endregion
|
|
1271
|
+
//#region src/root/functions/taggedTemplate/getStringsAndInterpolations.ts
|
|
1272
|
+
/**
|
|
1273
|
+
*
|
|
1274
|
+
* Gets the strings and interpolations separately from a template string.
|
|
1275
|
+
* You can pass a template string directly by doing:
|
|
1276
|
+
*
|
|
1277
|
+
* ```typescript
|
|
1278
|
+
* getStringsAndInterpolations`Template string here`;
|
|
1279
|
+
* ```
|
|
1280
|
+
*
|
|
1281
|
+
* @category Tagged Template
|
|
1282
|
+
*
|
|
1283
|
+
* @template InterpolationsType - The type of the interpolations.
|
|
1284
|
+
*
|
|
1285
|
+
* @param strings - The strings from the template to process.
|
|
1286
|
+
* @param interpolations - An array of all interpolations from the template.
|
|
1287
|
+
*
|
|
1288
|
+
* @returns A tuple where the first item is the strings from the template, and the remaining items are the interpolations.
|
|
1289
|
+
*
|
|
1290
|
+
* The return of this function may also be spread into any other tagged template function in the following way:
|
|
1291
|
+
*
|
|
1292
|
+
* ```typescript
|
|
1293
|
+
* import { interpolate } from "@alextheman/utility"; // Example function
|
|
1294
|
+
*
|
|
1295
|
+
* const packageName = "@alextheman/utility";
|
|
1296
|
+
* const packageManager = getPackageManager(packageName);
|
|
1297
|
+
*
|
|
1298
|
+
* interpolate(...getStringsAndInterpolations`The package ${packageName} uses the ${packageManager} package manager.`);
|
|
1299
|
+
* ```
|
|
1300
|
+
*/
|
|
1301
|
+
function getStringsAndInterpolations(strings, ...interpolations) {
|
|
1302
|
+
if (strings.length !== interpolations.length + 1) throw new DataError({
|
|
1303
|
+
stringsLength: strings.length,
|
|
1304
|
+
interpolationsLength: interpolations.length,
|
|
1305
|
+
strings,
|
|
1306
|
+
interpolations
|
|
1307
|
+
}, "INVALID_STRINGS_AND_INTERPOLATIONS_LENGTH", "The length of the strings must be exactly one more than the length of the interpolations.");
|
|
1308
|
+
return [createTemplateStringsArray(strings), ...interpolations];
|
|
1309
|
+
}
|
|
1310
|
+
//#endregion
|
|
1311
|
+
//#region src/root/functions/taggedTemplate/interpolate.ts
|
|
1312
|
+
/**
|
|
1313
|
+
* Returns the result of interpolating a template string when given the strings and interpolations separately.
|
|
1314
|
+
*
|
|
1315
|
+
* You can pass a template string directly by doing:
|
|
1316
|
+
*
|
|
1317
|
+
* ```
|
|
1318
|
+
* interpolate`Template string here`;
|
|
1319
|
+
* ```
|
|
1320
|
+
*
|
|
1321
|
+
* In this case, it will be functionally the same as if you just wrote the template string by itself.
|
|
1322
|
+
*
|
|
1323
|
+
* @category Tagged Template
|
|
1324
|
+
*
|
|
1325
|
+
* @template InterpolationsType - The type of the interpolations.
|
|
1326
|
+
*
|
|
1327
|
+
* @param strings - The strings from the template to process.
|
|
1328
|
+
* @param interpolations - An array of all interpolations from the template.
|
|
1329
|
+
*
|
|
1330
|
+
* @returns A new string with the strings and interpolations from the template applied.
|
|
1331
|
+
*/
|
|
1332
|
+
function interpolate(strings, ...interpolations) {
|
|
1333
|
+
let result = "";
|
|
1334
|
+
for (const [string, interpolation = ""] of paralleliseArrays(strings, interpolations)) result += string + interpolation;
|
|
1335
|
+
return result;
|
|
1336
|
+
}
|
|
1337
|
+
//#endregion
|
|
1338
|
+
//#region src/root/functions/taggedTemplate/interpolateObjects.ts
|
|
1339
|
+
/**
|
|
1340
|
+
* Returns the result of interpolating a template string, also stringifying objects.
|
|
1341
|
+
*
|
|
1342
|
+
* You can pass a template string directly by doing:
|
|
1343
|
+
*
|
|
1344
|
+
* ```typescript
|
|
1345
|
+
* interpolateObjects`Template string here ${{ my: "object" }}`;
|
|
1346
|
+
* ```
|
|
1347
|
+
*
|
|
1348
|
+
* @category Tagged Template
|
|
1349
|
+
*
|
|
1350
|
+
* @template InterpolationsType - The type of the interpolations.
|
|
1351
|
+
*
|
|
1352
|
+
* @param strings - The strings from the template to process.
|
|
1353
|
+
* @param interpolations - An array of all interpolations from the template.
|
|
1354
|
+
*
|
|
1355
|
+
* @returns A new string with the strings and interpolations from the template applied, with objects stringified.
|
|
1356
|
+
*/
|
|
1357
|
+
function interpolateObjects(strings, ...interpolations) {
|
|
1358
|
+
let result = "";
|
|
1359
|
+
for (let i = 0; i < strings.length; i++) {
|
|
1360
|
+
result += strings[i];
|
|
1361
|
+
if (i !== strings.length - 1) result += interpolations[i] && typeof interpolations[i] === "object" ? JSON.stringify(interpolations[i]) : interpolations[i];
|
|
1362
|
+
}
|
|
1363
|
+
return result;
|
|
1364
|
+
}
|
|
1365
|
+
//#endregion
|
|
1366
|
+
//#region src/root/functions/taggedTemplate/isTemplateStringsArray.ts
|
|
1367
|
+
/**
|
|
1368
|
+
* Determines whether or not the input is a valid `TemplateStringsArray`.
|
|
1369
|
+
*
|
|
1370
|
+
* @category Tagged Template
|
|
1371
|
+
*
|
|
1372
|
+
* @param input - The input to check
|
|
1373
|
+
*
|
|
1374
|
+
* @returns `true` if the input is a valid `TemplateStringsArray`, and false otherwise. The type of the input will also be narrowed down to `TemplateStringsArray` if `true`.
|
|
1375
|
+
*/
|
|
1376
|
+
function isTemplateStringsArray(input) {
|
|
1377
|
+
return typeof input === "object" && input !== null && "raw" in input;
|
|
1378
|
+
}
|
|
1379
|
+
//#endregion
|
|
1380
|
+
//#region src/root/functions/taggedTemplate/normaliseIndents.ts
|
|
1381
|
+
function calculateTabSize(line, whitespaceLength) {
|
|
1382
|
+
const potentialWhitespacePart = line.slice(0, whitespaceLength);
|
|
1383
|
+
const trimmedString = line.trimStart();
|
|
1384
|
+
if (potentialWhitespacePart.trim() !== "") return 0;
|
|
1385
|
+
const tabSize = line.length - (trimmedString.length + whitespaceLength);
|
|
1386
|
+
return tabSize < 0 ? 0 : tabSize;
|
|
1387
|
+
}
|
|
1388
|
+
function getWhitespaceLength(lines) {
|
|
1389
|
+
const [firstNonEmptyLine] = lines.filter((line) => {
|
|
1390
|
+
return line.trim() !== "";
|
|
1391
|
+
});
|
|
1392
|
+
return firstNonEmptyLine.length - firstNonEmptyLine.trimStart().length;
|
|
1393
|
+
}
|
|
1394
|
+
function reduceLines(lines, { preserveTabs = true }) {
|
|
1395
|
+
const slicedLines = lines.slice(1);
|
|
1396
|
+
const isFirstLineEmpty = lines[0].trim() === "";
|
|
1397
|
+
const whitespaceLength = getWhitespaceLength(isFirstLineEmpty ? lines : slicedLines);
|
|
1398
|
+
return (isFirstLineEmpty ? slicedLines : lines).map((line) => {
|
|
1399
|
+
const tabSize = calculateTabSize(line, whitespaceLength);
|
|
1400
|
+
return (preserveTabs ? fillArray(() => {
|
|
1401
|
+
return " ";
|
|
1402
|
+
}, tabSize).join("") : "") + line.trimStart();
|
|
1403
|
+
}).join("\n");
|
|
1404
|
+
}
|
|
1405
|
+
/**
|
|
1406
|
+
* Applies any options if provided, then removes any extraneous indents from a multi-line template string.
|
|
1407
|
+
*
|
|
1408
|
+
* You can pass a template string directly by doing:
|
|
1409
|
+
*
|
|
1410
|
+
* ```typescript
|
|
1411
|
+
* normaliseIndents`Template string here
|
|
1412
|
+
* with a new line
|
|
1413
|
+
* and another new line`;
|
|
1414
|
+
* ```
|
|
1415
|
+
*
|
|
1416
|
+
* You may also pass the options first, then invoke the resulting function with a template string:
|
|
1417
|
+
*
|
|
1418
|
+
* ```typescript
|
|
1419
|
+
* normaliseIndents({ preserveTabs: false })`Template string here
|
|
1420
|
+
* with a new line
|
|
1421
|
+
* and another new line`;
|
|
1422
|
+
* ```
|
|
1423
|
+
*
|
|
1424
|
+
* @category Tagged Template
|
|
1425
|
+
*
|
|
1426
|
+
* @param first - The strings from the template to process, or the options to apply.
|
|
1427
|
+
* @param args - An array of all interpolations from the template.
|
|
1428
|
+
*
|
|
1429
|
+
* @returns An additional function to invoke, or a new string with the strings and interpolations from the template applied, and extraneous indents removed.
|
|
1430
|
+
*/
|
|
1431
|
+
function normaliseIndents(first, ...args) {
|
|
1432
|
+
if (typeof first === "object" && first !== null && !Array.isArray(first)) {
|
|
1433
|
+
const options = first;
|
|
1434
|
+
return (strings, ...interpolations) => {
|
|
1435
|
+
return normaliseIndents(strings, ...interpolations, options);
|
|
1436
|
+
};
|
|
1437
|
+
}
|
|
1438
|
+
const strings = first;
|
|
1439
|
+
const options = typeof args[args.length - 1] === "object" && !Array.isArray(args[args.length - 1]) ? args.pop() : {};
|
|
1440
|
+
return reduceLines(interpolate(strings, ...[...args]).split("\n"), options);
|
|
1441
|
+
}
|
|
1442
|
+
/**
|
|
1443
|
+
* Applies any options if provided, then removes any extraneous indents from a multi-line template string.
|
|
1444
|
+
*
|
|
1445
|
+
* You can pass a template string directly by doing:
|
|
1446
|
+
*
|
|
1447
|
+
* ```typescript
|
|
1448
|
+
* normalizeIndents`Template string here
|
|
1449
|
+
* with a new line
|
|
1450
|
+
* and another new line`.
|
|
1451
|
+
* ```
|
|
1452
|
+
*
|
|
1453
|
+
* You may also pass the options first, then invoke the resulting function with a template string:
|
|
1454
|
+
*
|
|
1455
|
+
* ```typescript
|
|
1456
|
+
* normalizeIndents({ preserveTabs: false })`Template string here
|
|
1457
|
+
* with a new line
|
|
1458
|
+
* and another new line`.
|
|
1459
|
+
* ```
|
|
1460
|
+
*
|
|
1461
|
+
* @param first - The strings from the template to process, or the options to apply.
|
|
1462
|
+
* @param args - An array of all interpolations from the template.
|
|
1463
|
+
*
|
|
1464
|
+
* @returns An additional function to invoke, or a new string with the strings and interpolations from the template applied, and extraneous indents removed.
|
|
1465
|
+
*/
|
|
1466
|
+
const normalizeIndents = normaliseIndents;
|
|
1467
|
+
//#endregion
|
|
1468
|
+
//#region src/root/types/APIError.ts
|
|
1469
|
+
const httpErrorCodeLookup = {
|
|
1470
|
+
400: "BAD_REQUEST",
|
|
1471
|
+
401: "UNAUTHORISED",
|
|
1472
|
+
403: "FORBIDDEN",
|
|
1473
|
+
404: "NOT_FOUND",
|
|
1474
|
+
418: "I_AM_A_TEAPOT",
|
|
1475
|
+
500: "INTERNAL_SERVER_ERROR"
|
|
1476
|
+
};
|
|
1477
|
+
/**
|
|
1478
|
+
* Represents common errors you may get from a HTTP API request.
|
|
1479
|
+
*
|
|
1480
|
+
* @category Types
|
|
1481
|
+
*/
|
|
1482
|
+
var APIError = class APIError extends Error {
|
|
1483
|
+
status;
|
|
1484
|
+
/**
|
|
1485
|
+
* @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.
|
|
1486
|
+
* @param message - An error message to display alongside the status code.
|
|
1487
|
+
* @param options - Extra options to be passed to super Error constructor.
|
|
1488
|
+
*/
|
|
1489
|
+
constructor(status = 500, message, options) {
|
|
1490
|
+
super(message, options);
|
|
1491
|
+
this.status = status;
|
|
1492
|
+
if (message) this.message = message;
|
|
1493
|
+
else this.message = httpErrorCodeLookup[this.status] ?? "API_ERROR";
|
|
1494
|
+
Object.defineProperty(this, "message", { enumerable: true });
|
|
1495
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
1496
|
+
}
|
|
1497
|
+
/**
|
|
1498
|
+
* Checks whether the given input may have been caused by an APIError.
|
|
1499
|
+
*
|
|
1500
|
+
* @param input - The input to check.
|
|
1501
|
+
*
|
|
1502
|
+
* @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`.
|
|
1503
|
+
*/
|
|
1504
|
+
static check(input) {
|
|
1505
|
+
if (input instanceof APIError) return true;
|
|
1506
|
+
const data = input;
|
|
1507
|
+
return typeof data === "object" && data !== null && typeof data?.status === "number" && typeof data?.message === "string";
|
|
1508
|
+
}
|
|
1509
|
+
};
|
|
1510
|
+
//#endregion
|
|
1511
|
+
//#region src/root/types/VersionNumber.ts
|
|
1512
|
+
/**
|
|
1513
|
+
* Represents a software version number, considered to be made up of a major, minor, and patch part.
|
|
1514
|
+
*
|
|
1515
|
+
* @category Types
|
|
1516
|
+
*/
|
|
1517
|
+
var VersionNumber = class VersionNumber {
|
|
1518
|
+
static NON_NEGATIVE_TUPLE_ERROR = "Input array must be a tuple of three non-negative integers.";
|
|
1519
|
+
/** The major number. Increments when a feature is removed or changed in a way that is not backwards-compatible with the previous release. */
|
|
1520
|
+
major = 0;
|
|
1521
|
+
/** The minor number. Increments when a new feature is added/deprecated and is expected to be backwards-compatible with the previous release. */
|
|
1522
|
+
minor = 0;
|
|
1523
|
+
/** The patch number. Increments when the next release is fixing a bug or doing a small refactor that should not be noticeable in practice. */
|
|
1524
|
+
patch = 0;
|
|
1525
|
+
/**
|
|
1526
|
+
* @param input - The input to create a new instance of `VersionNumber` from.
|
|
1527
|
+
*/
|
|
1528
|
+
constructor(input) {
|
|
1529
|
+
if (input instanceof VersionNumber) {
|
|
1530
|
+
this.major = input.major;
|
|
1531
|
+
this.minor = input.minor;
|
|
1532
|
+
this.patch = input.patch;
|
|
1533
|
+
} else if (typeof input === "string") {
|
|
1534
|
+
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.`);
|
|
1535
|
+
const [major, minor, patch] = VersionNumber.formatString(input, { omitPrefix: true }).split(".").map((number) => {
|
|
1536
|
+
return parseIntStrict(number);
|
|
1537
|
+
});
|
|
1538
|
+
this.major = major;
|
|
1539
|
+
this.minor = minor;
|
|
1540
|
+
this.patch = patch;
|
|
1541
|
+
} else if (Array.isArray(input)) {
|
|
1542
|
+
if (input.length !== 3) throw new DataError({ input }, "INVALID_LENGTH", VersionNumber.NON_NEGATIVE_TUPLE_ERROR);
|
|
1543
|
+
const [major, minor, patch] = input.map((number) => {
|
|
1544
|
+
const parsedInteger = parseIntStrict(number?.toString());
|
|
1545
|
+
if (parsedInteger < 0) throw new DataError({ input }, "NEGATIVE_INPUTS", VersionNumber.NON_NEGATIVE_TUPLE_ERROR);
|
|
1546
|
+
return parsedInteger;
|
|
1547
|
+
});
|
|
1548
|
+
this.major = major;
|
|
1549
|
+
this.minor = minor;
|
|
1550
|
+
this.patch = patch;
|
|
1551
|
+
} else throw new DataError({ input }, "INVALID_INPUT", normaliseIndents`
|
|
1552
|
+
The provided input can not be parsed into a valid version number.
|
|
1553
|
+
Expected either a string of format X.Y.Z or vX.Y.Z, a tuple of three numbers, or another \`VersionNumber\` instance.
|
|
1554
|
+
`);
|
|
1555
|
+
}
|
|
1556
|
+
/**
|
|
1557
|
+
* Gets the current version type of the current instance of `VersionNumber`.
|
|
1558
|
+
*
|
|
1559
|
+
* @returns Either `"major"`, `"minor"`, or `"patch"`, depending on the version type.
|
|
1560
|
+
*/
|
|
1561
|
+
get type() {
|
|
1562
|
+
if (this.minor === 0 && this.patch === 0) return VersionType.MAJOR;
|
|
1563
|
+
if (this.patch === 0) return VersionType.MINOR;
|
|
1564
|
+
return VersionType.PATCH;
|
|
1565
|
+
}
|
|
1566
|
+
static formatString(input, options) {
|
|
1567
|
+
if (options?.omitPrefix) return input.startsWith("v") ? input.slice(1) : input;
|
|
1568
|
+
return input.startsWith("v") ? input : `v${input}`;
|
|
1569
|
+
}
|
|
1570
|
+
/**
|
|
1571
|
+
* Checks if the provided version numbers have the exact same major, minor, and patch numbers.
|
|
1572
|
+
*
|
|
1573
|
+
* @param firstVersion - The first version number to compare.
|
|
1574
|
+
* @param secondVersion - The second version number to compare.
|
|
1575
|
+
*
|
|
1576
|
+
* @returns `true` if the provided version numbers have exactly the same major, minor, and patch numbers, and returns `false` otherwise.
|
|
1577
|
+
*/
|
|
1578
|
+
static isEqual(firstVersion, secondVersion) {
|
|
1579
|
+
return firstVersion.major === secondVersion.major && firstVersion.minor === secondVersion.minor && firstVersion.patch === secondVersion.patch;
|
|
1580
|
+
}
|
|
1581
|
+
/**
|
|
1582
|
+
* Get a formatted string representation of the current version number
|
|
1583
|
+
*
|
|
1584
|
+
* @param options - Options to apply to the string formatting.
|
|
1585
|
+
*
|
|
1586
|
+
* @returns A formatted string representation of the current version number with the options applied.
|
|
1587
|
+
*/
|
|
1588
|
+
format(options) {
|
|
1589
|
+
let baseOutput = `${this.major}`;
|
|
1590
|
+
if (!options?.omitMinor) {
|
|
1591
|
+
baseOutput += `.${this.minor}`;
|
|
1592
|
+
if (!options?.omitPatch) baseOutput += `.${this.patch}`;
|
|
1593
|
+
}
|
|
1594
|
+
return VersionNumber.formatString(baseOutput, { omitPrefix: options?.omitPrefix });
|
|
1595
|
+
}
|
|
1596
|
+
/**
|
|
1597
|
+
* Increments the current version number by the given increment type, returning the result as a new reference in memory.
|
|
1598
|
+
*
|
|
1599
|
+
* @param incrementType - The type of increment. Can be one of the following:
|
|
1600
|
+
* - `"major"`: Change the major version `v1.2.3` → `v2.0.0`
|
|
1601
|
+
* - `"minor"`: Change the minor version `v1.2.3` → `v1.3.0`
|
|
1602
|
+
* - `"patch"`: Change the patch version `v1.2.3` → `v1.2.4`
|
|
1603
|
+
* @param incrementAmount - The amount to increment by (defaults to 1).
|
|
1604
|
+
*
|
|
1605
|
+
* @returns A new instance of `VersionNumber` with the increment applied.
|
|
1606
|
+
*/
|
|
1607
|
+
increment(incrementType, incrementAmount = 1) {
|
|
1608
|
+
const incrementBy = parseIntStrict(String(incrementAmount));
|
|
1609
|
+
const calculatedRawVersion = {
|
|
1610
|
+
major: [
|
|
1611
|
+
this.major + incrementBy,
|
|
1612
|
+
0,
|
|
1613
|
+
0
|
|
1614
|
+
],
|
|
1615
|
+
minor: [
|
|
1616
|
+
this.major,
|
|
1617
|
+
this.minor + incrementBy,
|
|
1618
|
+
0
|
|
1619
|
+
],
|
|
1620
|
+
patch: [
|
|
1621
|
+
this.major,
|
|
1622
|
+
this.minor,
|
|
1623
|
+
this.patch + incrementBy
|
|
1624
|
+
]
|
|
1625
|
+
}[incrementType];
|
|
1626
|
+
try {
|
|
1627
|
+
return new VersionNumber(calculatedRawVersion);
|
|
1628
|
+
} catch (error) {
|
|
1629
|
+
if (DataError.check(error) && error.code === "NEGATIVE_INPUTS") throw new DataError({
|
|
1630
|
+
currentVersion: this.toString(),
|
|
1631
|
+
calculatedRawVersion: `v${calculatedRawVersion.join(".")}`,
|
|
1632
|
+
incrementAmount
|
|
1633
|
+
}, "NEGATIVE_VERSION", "Cannot apply this increment amount as it would lead to a negative version number.");
|
|
1634
|
+
else throw error;
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
/**
|
|
1638
|
+
* Ensures that the VersionNumber behaves correctly when attempted to be coerced to a string.
|
|
1639
|
+
*
|
|
1640
|
+
* @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).
|
|
1641
|
+
*
|
|
1642
|
+
* @returns A stringified representation of the current version number, prefixed with `v`.
|
|
1643
|
+
*/
|
|
1644
|
+
[Symbol.toPrimitive](hint) {
|
|
1645
|
+
if (hint === "number") throw new DataError({ thisVersion: this.toString() }, "INVALID_COERCION", "VersionNumber cannot be coerced to a number type.");
|
|
1646
|
+
return this.toString();
|
|
1647
|
+
}
|
|
1648
|
+
/**
|
|
1649
|
+
* Ensures that the VersionNumber behaves correctly when attempted to be converted to JSON.
|
|
1650
|
+
*
|
|
1651
|
+
* @returns A stringified representation of the current version number, prefixed with `v`.
|
|
1652
|
+
*/
|
|
1653
|
+
toJSON() {
|
|
1654
|
+
return this.toString();
|
|
1655
|
+
}
|
|
1656
|
+
/**
|
|
1657
|
+
* Get a string representation of the current version number.
|
|
1658
|
+
*
|
|
1659
|
+
* @returns A stringified representation of the current version number with the prefix.
|
|
1660
|
+
*/
|
|
1661
|
+
toString() {
|
|
1662
|
+
const rawString = `${this.major}.${this.minor}.${this.patch}`;
|
|
1663
|
+
return VersionNumber.formatString(rawString, { omitPrefix: false });
|
|
1664
|
+
}
|
|
1665
|
+
};
|
|
1666
|
+
const zodVersionNumber = zod.default.union([
|
|
1667
|
+
zod.default.string(),
|
|
1668
|
+
zod.default.tuple([
|
|
1669
|
+
zod.default.number(),
|
|
1670
|
+
zod.default.number(),
|
|
1671
|
+
zod.default.number()
|
|
1672
|
+
]),
|
|
1673
|
+
zod.default.instanceof(VersionNumber)
|
|
1674
|
+
]).transform((rawVersionNumber) => {
|
|
1675
|
+
return new VersionNumber(rawVersionNumber);
|
|
1676
|
+
});
|
|
1677
|
+
//#endregion
|
|
1616
1678
|
exports.APIError = APIError;
|
|
1617
|
-
exports.DataError = DataError;
|
|
1618
1679
|
exports.Env = Env;
|
|
1619
1680
|
exports.FILE_PATH_PATTERN = FILE_PATH_PATTERN;
|
|
1620
1681
|
exports.FILE_PATH_REGEX = FILE_PATH_REGEX;
|