@alextheman/utility 2.13.1 → 2.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,145 @@
1
+ # @alextheman/utility
2
+
3
+ This is my personal utility package. It provides custom utility functions that can be used in more or less any TypeScript or JavaScript project, using either the browser or Node environment.
4
+
5
+ ## Installation
6
+
7
+ To install this into your project, you can do so with the following command:
8
+
9
+ ```bash
10
+ npm install @alextheman/utility
11
+ ```
12
+
13
+ From there, you may import any of the package's functions.
14
+
15
+ ## Quick start
16
+
17
+ You can import and use any function or type from the package in the following way:
18
+
19
+ ```typescript
20
+ import type { NonUndefined } from "@alextheman/utility";
21
+
22
+ import { formatDateAndTime } from "@alextheman/utility";
23
+
24
+ const myVariable: NonUndefined<string> = formatDateAndTime(new Date());
25
+ // ...
26
+ ```
27
+
28
+ ## Contributing
29
+
30
+ ### Creating a function
31
+ #### Setup
32
+ To create a new function in the package, you must first checkout a new branch. Do **not** commit directly on main.
33
+
34
+ From there, create the skeleton of the function. This is just the function declaration itself, along with the arguments and their types, along with the return type.
35
+
36
+ ```typescript
37
+ function myFunction(firstArg: ArgType): ReturnType {
38
+ // Implementation goes here
39
+ }
40
+
41
+ export default myFunction
42
+ ```
43
+
44
+ Note that every function that gets exported from this package **must** have a type annotation. This is also enforced by ESLint. This helps to make the return types extra explicit and more intentional.
45
+
46
+ Once you have this, export the function from `src/functions/index.ts` so that consumers can import your function. Also export any types you may want to export near the bottom of the file:
47
+
48
+ ```typescript
49
+ export { default as addDaysToDate } from "src/functions/addDaysToDate";
50
+ export { default as appendSemicolon } from "src/functions/appendSemicolon";
51
+ export { default as camelToKebab } from "src/functions/camelToKebab";
52
+ export { default as convertFileToBase64 } from "src/functions/convertFileToBase64";
53
+ export { default as createFormData } from "src/functions/createFormData";
54
+ export { default as fillArray } from "src/functions/fillArray";
55
+ export { default as formatDateAndTime } from "src/functions/formatDateAndTime";
56
+ // ...
57
+ export { default as myFunction } from "src/functions/myFunction";
58
+ // ...
59
+
60
+ export type { MyFunctionOptions } from "src/functions/myFunction";
61
+ ```
62
+
63
+ #### Testing
64
+
65
+ Every function must also be tested. Tests are located in the `tests` folder and follows roughly the same structure as `src`. We use Vitest for our tests, and instead of relying on the global test functions like you might do with Jest, we instead import each test function directly.
66
+
67
+ Tests are generally structured in the following way:
68
+
69
+ ```typescript
70
+ import { describe, expect, test } from "vitest";
71
+
72
+ import myFunction from "src/functions/myFunction";
73
+
74
+ describe("myFunction", () => {
75
+ test("Does a thing", () => {
76
+ expect(myFunction("hello")).toBe("world");
77
+ });
78
+ test("Does another thing", () => {
79
+ expect(myFunction("one")).toBe("two");
80
+ });
81
+ // ...
82
+ });
83
+ ```
84
+
85
+ That is, we wrap all tests around a describe block grouping all related tests to that function together, then each test block tests a specific behaviour.
86
+
87
+ Every function must at least have one test for the core behaviour of it. Of course, the more tests, the better, but at the very least, test the core behaviour of the function. From there, if you feel like there may be a few edge cases you want to verify the behaviour of, please add tests in for that as well.
88
+
89
+ Some functions that we create may often end up taking in an array or object. Due to the nature of how JavaScript passes these, we must have non-mutation tests in place to prevent unintended mutation of the input array/object. The best way to do this is to use `Object.freeze()`, as that will ensure that we get an error if the function ever even tries to mutate the input.
90
+
91
+ ```typescript
92
+ describe("removeDuplicates", () => {
93
+ describe("Mutation checks", () => {
94
+ test("Does not mutate the original array", () => {
95
+ const inputArray = Object.freeze([1, 1, 2, 3, 4]);
96
+ // since the array has been frozen, this will give a TypeError if it tries to mutate the inputArray.
97
+ removeDuplicates(inputArray);
98
+ expect(inputArray).toEqual([1, 1, 2, 3, 4]);
99
+ });
100
+ });
101
+ });
102
+ ```
103
+
104
+ Also, if the return type of the function is the same as one or more of the inputs, it's also often helpful to have a test that ensures the output returns a new reference in memory:
105
+
106
+ ```typescript
107
+ describe("removeDuplicates", () => {
108
+ describe("Mutation checks", () => {
109
+ test("Returns an array with a new reference in memory", () => {
110
+ const inputArray = [1, 1, 2, 3, 4];
111
+ const outputArray = removeDuplicates(inputArray);
112
+ expect(outputArray).not.toBe(inputArray);
113
+ });
114
+ });
115
+ });
116
+ ```
117
+
118
+ Note the use of `.toBe()` over `.toEqual()` here. This is because in this case, we are comparing the variable's memory reference to the output's memory reference rather than the actual contents of the array.
119
+
120
+ ### Creating a type
121
+
122
+ Creating a type works in a similar way to creating a function, in the sense that you create a new file for it, add it to `src/types/index.ts`, and test it. Yes, test it. It is possible to test type declarations using `expectTypeOf`. As an example:
123
+
124
+ ```typescript
125
+ import type { OptionalOnCondition } from "src/types";
126
+
127
+ import { describe, expectTypeOf, test } from "vitest";
128
+
129
+ describe("OptionalOnCondition", () => {
130
+ test("Resolves to the type of the second type argument if first type argument is true", () => {
131
+ expectTypeOf<OptionalOnCondition<true, string>>().toEqualTypeOf<string>();
132
+ });
133
+ test("Resolves to a union of the second type and undefined if first type argument is false", () => {
134
+ expectTypeOf<OptionalOnCondition<false, string>>().toEqualTypeOf<string | undefined>();
135
+ });
136
+ });
137
+ ```
138
+
139
+ Note that when you actually run the tests using `npm test`, the tests will always pass even if the logic is incorrect. This is because type tests run with the TypeScript compiler so it will error in your editor and in linting, but it will never fail in runtime. As such, just remember to be extra careful with these type tests and remember that the actual test results for these show in the editor and not in runtime.
140
+
141
+ ### Publishing
142
+
143
+ Once you are happy with your changes, commit the changes to your branch, then change the version number. You can do this by running one of `npm run change-major`, `npm run change-minor`, or `npm run change-patch`. Major changes generally correspond to breaking changes (e.g. removing features, changing the build process substantially), minor changes correspond to non-breaking additions/deprecations, and patch changes are for the small things that aren't as noticeable. Note that your change will fail CI if you change the source code but forget to change the version number. From there, after your changes have been approved and merged, the publish script will automatically run, and if you remembered to change the version number, it will pass the publishing action.
144
+
145
+ Version number changes must also **not** be part of your main commit(s) - they must be their own standalone commit. This helps to make it easier to revert changes if needed - if the version change is part of the main commit, that means that if we need to revert it for whatever reason, the version change also gets reverted, which is not really ideal as it makes re-publishing a bit harder.
package/dist/index.d.cts CHANGED
@@ -36,6 +36,8 @@ declare function parseUUID(UUID: unknown): UUID;
36
36
 
37
37
  type DisallowUndefined<T> = undefined extends T ? ["Error: Generic type cannot include undefined"] : T;
38
38
 
39
+ type IgnoreCase<T extends string> = string extends T ? string : T extends `${infer F1}${infer F2}${infer R}` ? `${Uppercase<F1> | Lowercase<F1>}${Uppercase<F2> | Lowercase<F2>}${IgnoreCase<R>}` : T extends `${infer F}${infer R}` ? `${Uppercase<F> | Lowercase<F>}${IgnoreCase<R>}` : "";
40
+
39
41
  type NonUndefined<T> = T extends undefined ? never : T;
40
42
 
41
43
  type OptionalOnCondition<Condition extends boolean, T> = Condition extends true ? T : T | undefined;
@@ -101,4 +103,4 @@ declare function wait(seconds: number): Promise<void>;
101
103
 
102
104
  declare function interpolateObjects(strings: TemplateStringsArray, ...values: unknown[]): string;
103
105
 
104
- export { APIError, type CreateFormDataOptions, type CreateFormDataOptionsNullableResolution, type CreateFormDataOptionsUndefinedOrNullResolution, type DisallowUndefined, type Email, type Env, type FormDataNullableResolutionStrategy as FormDataResolutionStrategy, type HTTPErrorCode, type HTTPErrorCodes, type NonUndefined, type OptionalOnCondition, type RecordKey, type StringListToArrayOptions, type UUID, addDaysToDate, appendSemicolon, camelToKebab, convertFileToBase64, createFormData, fillArray, formatDateAndTime, getRandomNumber, getRecordKeys, httpErrorCodeLookup, interpolateObjects, isLeapYear, isMonthlyMultiple, isOrdered, isSameDate, omitProperties, parseEmail, parseEnv, parseIntStrict, parseUUID, randomiseArray, range, removeDuplicates, stringListToArray, stringToBoolean, truncate, wait };
106
+ export { APIError, type CreateFormDataOptions, type CreateFormDataOptionsNullableResolution, type CreateFormDataOptionsUndefinedOrNullResolution, type DisallowUndefined, type Email, type Env, type FormDataNullableResolutionStrategy as FormDataResolutionStrategy, type HTTPErrorCode, type HTTPErrorCodes, type IgnoreCase, type NonUndefined, type OptionalOnCondition, type RecordKey, type StringListToArrayOptions, type UUID, addDaysToDate, appendSemicolon, camelToKebab, convertFileToBase64, createFormData, fillArray, formatDateAndTime, getRandomNumber, getRecordKeys, httpErrorCodeLookup, interpolateObjects, isLeapYear, isMonthlyMultiple, isOrdered, isSameDate, omitProperties, parseEmail, parseEnv, parseIntStrict, parseUUID, randomiseArray, range, removeDuplicates, stringListToArray, stringToBoolean, truncate, wait };
package/dist/index.d.ts CHANGED
@@ -36,6 +36,8 @@ declare function parseUUID(UUID: unknown): UUID;
36
36
 
37
37
  type DisallowUndefined<T> = undefined extends T ? ["Error: Generic type cannot include undefined"] : T;
38
38
 
39
+ type IgnoreCase<T extends string> = string extends T ? string : T extends `${infer F1}${infer F2}${infer R}` ? `${Uppercase<F1> | Lowercase<F1>}${Uppercase<F2> | Lowercase<F2>}${IgnoreCase<R>}` : T extends `${infer F}${infer R}` ? `${Uppercase<F> | Lowercase<F>}${IgnoreCase<R>}` : "";
40
+
39
41
  type NonUndefined<T> = T extends undefined ? never : T;
40
42
 
41
43
  type OptionalOnCondition<Condition extends boolean, T> = Condition extends true ? T : T | undefined;
@@ -101,4 +103,4 @@ declare function wait(seconds: number): Promise<void>;
101
103
 
102
104
  declare function interpolateObjects(strings: TemplateStringsArray, ...values: unknown[]): string;
103
105
 
104
- export { APIError, type CreateFormDataOptions, type CreateFormDataOptionsNullableResolution, type CreateFormDataOptionsUndefinedOrNullResolution, type DisallowUndefined, type Email, type Env, type FormDataNullableResolutionStrategy as FormDataResolutionStrategy, type HTTPErrorCode, type HTTPErrorCodes, type NonUndefined, type OptionalOnCondition, type RecordKey, type StringListToArrayOptions, type UUID, addDaysToDate, appendSemicolon, camelToKebab, convertFileToBase64, createFormData, fillArray, formatDateAndTime, getRandomNumber, getRecordKeys, httpErrorCodeLookup, interpolateObjects, isLeapYear, isMonthlyMultiple, isOrdered, isSameDate, omitProperties, parseEmail, parseEnv, parseIntStrict, parseUUID, randomiseArray, range, removeDuplicates, stringListToArray, stringToBoolean, truncate, wait };
106
+ export { APIError, type CreateFormDataOptions, type CreateFormDataOptionsNullableResolution, type CreateFormDataOptionsUndefinedOrNullResolution, type DisallowUndefined, type Email, type Env, type FormDataNullableResolutionStrategy as FormDataResolutionStrategy, type HTTPErrorCode, type HTTPErrorCodes, type IgnoreCase, type NonUndefined, type OptionalOnCondition, type RecordKey, type StringListToArrayOptions, type UUID, addDaysToDate, appendSemicolon, camelToKebab, convertFileToBase64, createFormData, fillArray, formatDateAndTime, getRandomNumber, getRecordKeys, httpErrorCodeLookup, interpolateObjects, isLeapYear, isMonthlyMultiple, isOrdered, isSameDate, omitProperties, parseEmail, parseEnv, parseIntStrict, parseUUID, randomiseArray, range, removeDuplicates, stringListToArray, stringToBoolean, truncate, wait };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alextheman/utility",
3
- "version": "2.13.1",
3
+ "version": "2.14.0",
4
4
  "description": "Helpful utility functions",
5
5
  "license": "ISC",
6
6
  "author": "alextheman",