@koloseum/utils 0.2.28 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.ts +125 -0
- package/dist/client.js +680 -0
- package/dist/formatting.d.ts +125 -0
- package/dist/formatting.js +369 -0
- package/dist/general.d.ts +54 -0
- package/dist/general.js +88 -0
- package/dist/platform.d.ts +66 -0
- package/dist/platform.js +764 -0
- package/dist/server.d.ts +77 -0
- package/dist/server.js +300 -0
- package/package.json +25 -3
- package/dist/utils.d.ts +0 -394
- package/dist/utils.js +0 -2019
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { IdentityType, PronounsItem, Sex, SocialMediaPlatform, SocialMediaPlatformObject } from "@koloseum/types/public-auth";
|
|
2
|
+
import type { MobilePhoneLocale } from "validator";
|
|
3
|
+
export declare const Transform: {
|
|
4
|
+
/**
|
|
5
|
+
* Capitalises a given word.
|
|
6
|
+
* @param {string} word - The word to be capitalised
|
|
7
|
+
* @returns The capitalised word
|
|
8
|
+
*/
|
|
9
|
+
capitalise: (word: string) => string;
|
|
10
|
+
/**
|
|
11
|
+
* Cleans a URL by removing the protocol and trailing slashes.
|
|
12
|
+
* @param {string} url - The URL to clean
|
|
13
|
+
* @returns {string} The cleaned URL
|
|
14
|
+
*/
|
|
15
|
+
cleanUrl: (url: string) => string;
|
|
16
|
+
/**
|
|
17
|
+
* Formats a date in the format DD/MM/YYYY (by default) or YYYY-MM-DD (for client).
|
|
18
|
+
* @param {string} date - Date to be formatted
|
|
19
|
+
* @param {boolean} forClient - Specify whether the data is for displaying on the client; defaults to `false`
|
|
20
|
+
* @returns The formatted date string, or `null` if invalid input or in case of an error
|
|
21
|
+
*/
|
|
22
|
+
formatDate: (date: string, forClient?: boolean) => string | null;
|
|
23
|
+
/**
|
|
24
|
+
* Formats a social media platform for display.
|
|
25
|
+
* @param {SocialMediaPlatform} platform - The social media platform to be formatted
|
|
26
|
+
* @returns The formatted social media platform string
|
|
27
|
+
*/
|
|
28
|
+
formatSocialMediaPlatform: (platform: SocialMediaPlatform) => string;
|
|
29
|
+
/**
|
|
30
|
+
* Returns an age in years given a birth date.
|
|
31
|
+
* @param {Date | string | number | null} dateOfBirth - The date of birth
|
|
32
|
+
* @returns The age in years, or `NaN` if the input is invalid
|
|
33
|
+
*/
|
|
34
|
+
getAge: (dateOfBirth: Date | string | number | null) => number;
|
|
35
|
+
/**
|
|
36
|
+
* Formats a date as a locale-specific string.
|
|
37
|
+
* @param {Date | string | number | null} date - Date to be formatted
|
|
38
|
+
* @param {boolean} withTime - Specify whether the date should include time; defaults to `false`
|
|
39
|
+
* @param {string} timeZone - The time zone to use for formatting; defaults to `Africa/Nairobi`
|
|
40
|
+
* @returns The formatted date string, or an empty string if invalid input
|
|
41
|
+
*/
|
|
42
|
+
getDateString: (date: Date | string | number | null, withTime?: boolean, timeZone?: string) => string;
|
|
43
|
+
/**
|
|
44
|
+
* Formats an identity type for display.
|
|
45
|
+
* @param {IdentityType} identityType - The identity type to be formatted
|
|
46
|
+
* @returns The formatted identity type string
|
|
47
|
+
*/
|
|
48
|
+
getIdentityTypeString: (identityType: IdentityType) => string;
|
|
49
|
+
/**
|
|
50
|
+
* Formats a date to an ISO string in Kenyan time, i.e. `Africa/Nairobi` (UTC+3).
|
|
51
|
+
* @param {Date | string | number | null} date - The date to format
|
|
52
|
+
* @returns {string} The formatted date in ISO string format, or an empty string if invalid input
|
|
53
|
+
*/
|
|
54
|
+
getKenyanISOString: (date: Date | string | number | null) => string;
|
|
55
|
+
/**
|
|
56
|
+
* Returns a pronoun given pronouns and a type.
|
|
57
|
+
* @param {PronounsItem | null} pronouns - The pronouns to be formatted
|
|
58
|
+
* @param {"subject" | "object" | "possessive" | "reflexive"} type - The type of pronoun to be returned
|
|
59
|
+
* @param {Sex} sex - The user's sex; defaults to `undefined`
|
|
60
|
+
* @returns The formatted pronoun, or `null` if the input is invalid
|
|
61
|
+
*/
|
|
62
|
+
getPronoun: (pronouns: PronounsItem | null, type: "subject" | "object" | "possessive" | "reflexive", sex?: Sex) => string | null;
|
|
63
|
+
/**
|
|
64
|
+
* Paginates an array.
|
|
65
|
+
* @template T - The type of array items; defaults to `any`
|
|
66
|
+
* @param {T[]} array - The array to paginate
|
|
67
|
+
* @param {number} page - The page number
|
|
68
|
+
* @param {number} pageSize - The number of items per page; defaults to 10
|
|
69
|
+
* @returns {Object} The paginated array and total number of pages
|
|
70
|
+
*/
|
|
71
|
+
paginateArray: <T = any>(array: T[], page: number, pageSize?: number) => {
|
|
72
|
+
paginatedItems: T[];
|
|
73
|
+
totalPages: number;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Sanitises any potential HTML injected into user input.
|
|
77
|
+
* @param {string} input - The input to be sanitised
|
|
78
|
+
* @returns A sanitised string, or an empty string if the input is invalid
|
|
79
|
+
*/
|
|
80
|
+
sanitiseHtml: (input: string) => string;
|
|
81
|
+
};
|
|
82
|
+
export declare const Validate: {
|
|
83
|
+
/**
|
|
84
|
+
* A regular expression for E.164 phone numbers.
|
|
85
|
+
*
|
|
86
|
+
* - Source: https://www.twilio.com/docs/glossary/what-e164
|
|
87
|
+
*/
|
|
88
|
+
e164Regex: RegExp;
|
|
89
|
+
/**
|
|
90
|
+
* The minimum birth date (from today's date) for a user who is a minor, i.e. `YYYY-MM-DD`
|
|
91
|
+
*/
|
|
92
|
+
minimumMinorBirthDate: string;
|
|
93
|
+
/**
|
|
94
|
+
* A regular expression for user passwords to match during authentication. It covers the following rules:
|
|
95
|
+
* - at least 8 characters long
|
|
96
|
+
* - at least one digit
|
|
97
|
+
* - at least one lowercase letter
|
|
98
|
+
* - at least one uppercase letter
|
|
99
|
+
* - at least one symbol
|
|
100
|
+
*/
|
|
101
|
+
passwordRegex: RegExp;
|
|
102
|
+
/**
|
|
103
|
+
* A regular expression for social media handles, without a leading slash or @ character.
|
|
104
|
+
*
|
|
105
|
+
* - Source: https://stackoverflow.com/a/74579651
|
|
106
|
+
*/
|
|
107
|
+
socialMediaHandleRegex: RegExp;
|
|
108
|
+
/**
|
|
109
|
+
* An array of values representing social media platforms recognised in the database.
|
|
110
|
+
*/
|
|
111
|
+
socialMediaPlatforms: SocialMediaPlatformObject[];
|
|
112
|
+
/**
|
|
113
|
+
* Validates an E.164 phone number.
|
|
114
|
+
* @param {string} phoneNumber - The phone number to be validated
|
|
115
|
+
* @param {"any" | MobilePhoneLocale | MobilePhoneLocale[]} [locale="any"] - The locale(s) to validate the phone number against
|
|
116
|
+
* @returns `true` if the phone number is valid; `false` otherwise
|
|
117
|
+
*/
|
|
118
|
+
validateE164: (phoneNumber: string, locale?: "any" | MobilePhoneLocale | MobilePhoneLocale[]) => boolean;
|
|
119
|
+
/**
|
|
120
|
+
* Validates a social media handle.
|
|
121
|
+
* @param {string} handle - The social media handle to be validated
|
|
122
|
+
* @returns `true` if the handle is valid; `false` otherwise
|
|
123
|
+
*/
|
|
124
|
+
validateSocialMediaHandle: (handle: string) => boolean;
|
|
125
|
+
};
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import validator from "validator";
|
|
2
|
+
/* HELPERS */
|
|
3
|
+
const sanitize = (await import("sanitize-html")).default;
|
|
4
|
+
const { isMobilePhone, isURL } = validator;
|
|
5
|
+
/* DATA TRANSFORMATION HELPERS */
|
|
6
|
+
export const Transform = {
|
|
7
|
+
/**
|
|
8
|
+
* Capitalises a given word.
|
|
9
|
+
* @param {string} word - The word to be capitalised
|
|
10
|
+
* @returns The capitalised word
|
|
11
|
+
*/
|
|
12
|
+
capitalise: (word) => word.charAt(0).toUpperCase() +
|
|
13
|
+
word
|
|
14
|
+
.slice(1)
|
|
15
|
+
.split("")
|
|
16
|
+
.map((c) => c.toLowerCase())
|
|
17
|
+
.join(""),
|
|
18
|
+
/**
|
|
19
|
+
* Cleans a URL by removing the protocol and trailing slashes.
|
|
20
|
+
* @param {string} url - The URL to clean
|
|
21
|
+
* @returns {string} The cleaned URL
|
|
22
|
+
*/
|
|
23
|
+
cleanUrl: (url) => url.replace(/^https?:\/\//, "").replace(/\/+$/, ""),
|
|
24
|
+
/**
|
|
25
|
+
* Formats a date in the format DD/MM/YYYY (by default) or YYYY-MM-DD (for client).
|
|
26
|
+
* @param {string} date - Date to be formatted
|
|
27
|
+
* @param {boolean} forClient - Specify whether the data is for displaying on the client; defaults to `false`
|
|
28
|
+
* @returns The formatted date string, or `null` if invalid input or in case of an error
|
|
29
|
+
*/
|
|
30
|
+
formatDate: (date, forClient = false) => {
|
|
31
|
+
// Return null if no date is provided
|
|
32
|
+
if (date === "" || typeof date !== "string")
|
|
33
|
+
return null;
|
|
34
|
+
// Handle input
|
|
35
|
+
try {
|
|
36
|
+
// Parse date string
|
|
37
|
+
const parsedDate = Date.parse(date);
|
|
38
|
+
// Return null if date string is not parseable
|
|
39
|
+
if (isNaN(parsedDate))
|
|
40
|
+
return null;
|
|
41
|
+
// Use the Intl.DateTimeFormat with explicit options to ensure correct formatting
|
|
42
|
+
const formattedDate = new Intl.DateTimeFormat(forClient ? "fr-CA" : "en-KE", {
|
|
43
|
+
day: "2-digit",
|
|
44
|
+
month: "2-digit",
|
|
45
|
+
year: "numeric",
|
|
46
|
+
timeZone: "Africa/Nairobi"
|
|
47
|
+
}).format(parsedDate);
|
|
48
|
+
// Return formatted date string
|
|
49
|
+
return formattedDate;
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
console.error(error);
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
/**
|
|
57
|
+
* Formats a social media platform for display.
|
|
58
|
+
* @param {SocialMediaPlatform} platform - The social media platform to be formatted
|
|
59
|
+
* @returns The formatted social media platform string
|
|
60
|
+
*/
|
|
61
|
+
formatSocialMediaPlatform: (platform) => {
|
|
62
|
+
const platforms = Validate.socialMediaPlatforms.map(({ value }) => value);
|
|
63
|
+
if (!platforms.includes(platform))
|
|
64
|
+
return "";
|
|
65
|
+
if (platform === "tiktok")
|
|
66
|
+
return "TikTok";
|
|
67
|
+
if (platform === "twitter")
|
|
68
|
+
return "X (formerly Twitter)";
|
|
69
|
+
if (platform === "youtube")
|
|
70
|
+
return "YouTube";
|
|
71
|
+
return Transform.capitalise(platform);
|
|
72
|
+
},
|
|
73
|
+
/**
|
|
74
|
+
* Returns an age in years given a birth date.
|
|
75
|
+
* @param {Date | string | number | null} dateOfBirth - The date of birth
|
|
76
|
+
* @returns The age in years, or `NaN` if the input is invalid
|
|
77
|
+
*/
|
|
78
|
+
getAge: (dateOfBirth) => {
|
|
79
|
+
// Return NaN if no date is provided
|
|
80
|
+
if (!dateOfBirth)
|
|
81
|
+
return NaN;
|
|
82
|
+
// Validate and process date of birth
|
|
83
|
+
let birthDate;
|
|
84
|
+
if (dateOfBirth instanceof Date) {
|
|
85
|
+
if (isNaN(dateOfBirth.getTime()))
|
|
86
|
+
return NaN;
|
|
87
|
+
birthDate = dateOfBirth;
|
|
88
|
+
}
|
|
89
|
+
else if (typeof dateOfBirth === "string") {
|
|
90
|
+
if (dateOfBirth === "" || isNaN(Date.parse(dateOfBirth)))
|
|
91
|
+
return NaN;
|
|
92
|
+
// Format date of birth if it's a string
|
|
93
|
+
const formattedDate = Transform.formatDate(dateOfBirth, true);
|
|
94
|
+
if (!formattedDate)
|
|
95
|
+
return NaN;
|
|
96
|
+
birthDate = new Date(formattedDate);
|
|
97
|
+
}
|
|
98
|
+
else if (typeof dateOfBirth === "number") {
|
|
99
|
+
if (isNaN(dateOfBirth))
|
|
100
|
+
return NaN;
|
|
101
|
+
birthDate = new Date(dateOfBirth);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
return NaN;
|
|
105
|
+
}
|
|
106
|
+
// Validate the final date object
|
|
107
|
+
if (isNaN(birthDate.getTime()))
|
|
108
|
+
return NaN;
|
|
109
|
+
// Calculate age
|
|
110
|
+
const currentDate = new Date();
|
|
111
|
+
const monthDiff = currentDate.getMonth() - birthDate.getMonth();
|
|
112
|
+
let age = currentDate.getFullYear() - birthDate.getFullYear();
|
|
113
|
+
// Return age
|
|
114
|
+
return monthDiff < 0 || (monthDiff === 0 && currentDate.getDate() < birthDate.getDate()) ? --age : age;
|
|
115
|
+
},
|
|
116
|
+
/**
|
|
117
|
+
* Formats a date as a locale-specific string.
|
|
118
|
+
* @param {Date | string | number | null} date - Date to be formatted
|
|
119
|
+
* @param {boolean} withTime - Specify whether the date should include time; defaults to `false`
|
|
120
|
+
* @param {string} timeZone - The time zone to use for formatting; defaults to `Africa/Nairobi`
|
|
121
|
+
* @returns The formatted date string, or an empty string if invalid input
|
|
122
|
+
*/
|
|
123
|
+
getDateString: (date, withTime = false, timeZone = "Africa/Nairobi") => {
|
|
124
|
+
// Return empty string if no date is provided
|
|
125
|
+
if (!date)
|
|
126
|
+
return "";
|
|
127
|
+
// Validate date to format
|
|
128
|
+
let dateToFormat;
|
|
129
|
+
if (date instanceof Date) {
|
|
130
|
+
if (isNaN(date.getTime()))
|
|
131
|
+
return "";
|
|
132
|
+
dateToFormat = date;
|
|
133
|
+
}
|
|
134
|
+
else if (typeof date === "string" || typeof date === "number") {
|
|
135
|
+
if (typeof date === "string" && (date === "" || isNaN(Date.parse(date))))
|
|
136
|
+
return "";
|
|
137
|
+
if (typeof date === "number" && isNaN(date))
|
|
138
|
+
return "";
|
|
139
|
+
dateToFormat = new Date(date);
|
|
140
|
+
}
|
|
141
|
+
else
|
|
142
|
+
return "";
|
|
143
|
+
// Validate the final date object
|
|
144
|
+
if (isNaN(dateToFormat.getTime()))
|
|
145
|
+
return "";
|
|
146
|
+
// Format date
|
|
147
|
+
return dateToFormat.toLocaleString("en-KE", {
|
|
148
|
+
timeZone,
|
|
149
|
+
year: "numeric",
|
|
150
|
+
month: "long",
|
|
151
|
+
day: "numeric",
|
|
152
|
+
hour: withTime ? "numeric" : undefined,
|
|
153
|
+
minute: withTime ? "2-digit" : undefined,
|
|
154
|
+
hour12: withTime ? true : undefined
|
|
155
|
+
});
|
|
156
|
+
},
|
|
157
|
+
/**
|
|
158
|
+
* Formats an identity type for display.
|
|
159
|
+
* @param {IdentityType} identityType - The identity type to be formatted
|
|
160
|
+
* @returns The formatted identity type string
|
|
161
|
+
*/
|
|
162
|
+
getIdentityTypeString: (identityType) => {
|
|
163
|
+
if (identityType === "national")
|
|
164
|
+
return "National ID";
|
|
165
|
+
if (identityType === "alien")
|
|
166
|
+
return "Alien ID";
|
|
167
|
+
if (identityType === "passport")
|
|
168
|
+
return "Passport";
|
|
169
|
+
if (identityType === "driver_licence")
|
|
170
|
+
return "Driver's licence";
|
|
171
|
+
return "";
|
|
172
|
+
},
|
|
173
|
+
/**
|
|
174
|
+
* Formats a date to an ISO string in Kenyan time, i.e. `Africa/Nairobi` (UTC+3).
|
|
175
|
+
* @param {Date | string | number | null} date - The date to format
|
|
176
|
+
* @returns {string} The formatted date in ISO string format, or an empty string if invalid input
|
|
177
|
+
*/
|
|
178
|
+
getKenyanISOString: (date) => {
|
|
179
|
+
// Return empty string if no date is provided or input is invalid
|
|
180
|
+
if (!date || (typeof date !== "string" && typeof date !== "number" && !(date instanceof Date)))
|
|
181
|
+
return "";
|
|
182
|
+
// Get locale string to format
|
|
183
|
+
const dateObj = date instanceof Date ? date : new Date(date);
|
|
184
|
+
const localeString = dateObj.toLocaleString("sv-SE", {
|
|
185
|
+
timeZone: "Africa/Nairobi"
|
|
186
|
+
});
|
|
187
|
+
// Return formatted string
|
|
188
|
+
return localeString.replace(" ", "T") + "+03:00";
|
|
189
|
+
},
|
|
190
|
+
/**
|
|
191
|
+
* Returns a pronoun given pronouns and a type.
|
|
192
|
+
* @param {PronounsItem | null} pronouns - The pronouns to be formatted
|
|
193
|
+
* @param {"subject" | "object" | "possessive" | "reflexive"} type - The type of pronoun to be returned
|
|
194
|
+
* @param {Sex} sex - The user's sex; defaults to `undefined`
|
|
195
|
+
* @returns The formatted pronoun, or `null` if the input is invalid
|
|
196
|
+
*/
|
|
197
|
+
getPronoun: (pronouns, type, sex) => {
|
|
198
|
+
// Get pronoun from pronouns item
|
|
199
|
+
if (pronouns) {
|
|
200
|
+
// Return subject pronoun
|
|
201
|
+
if (type === "subject") {
|
|
202
|
+
if (pronouns === "he/him/his/himself")
|
|
203
|
+
return "he";
|
|
204
|
+
if (pronouns === "she/her/hers/herself")
|
|
205
|
+
return "she";
|
|
206
|
+
if (pronouns === "they/them/their/themself")
|
|
207
|
+
return "they";
|
|
208
|
+
if (pronouns === "name_only")
|
|
209
|
+
return "";
|
|
210
|
+
}
|
|
211
|
+
// Return object pronoun
|
|
212
|
+
else if (type === "object") {
|
|
213
|
+
if (pronouns === "he/him/his/himself")
|
|
214
|
+
return "him";
|
|
215
|
+
if (pronouns === "she/her/hers/herself")
|
|
216
|
+
return "her";
|
|
217
|
+
if (pronouns === "they/them/their/themself")
|
|
218
|
+
return "them";
|
|
219
|
+
if (pronouns === "name_only")
|
|
220
|
+
return "";
|
|
221
|
+
}
|
|
222
|
+
// Return possessive pronoun
|
|
223
|
+
else if (type === "possessive") {
|
|
224
|
+
if (pronouns === "he/him/his/himself")
|
|
225
|
+
return "his";
|
|
226
|
+
if (pronouns === "she/her/hers/herself")
|
|
227
|
+
return "her";
|
|
228
|
+
if (pronouns === "they/them/their/themself")
|
|
229
|
+
return "their";
|
|
230
|
+
if (pronouns === "name_only")
|
|
231
|
+
return "";
|
|
232
|
+
}
|
|
233
|
+
// Return reflexive pronoun
|
|
234
|
+
else if (type === "reflexive") {
|
|
235
|
+
if (pronouns === "he/him/his/himself")
|
|
236
|
+
return "himself";
|
|
237
|
+
if (pronouns === "she/her/hers/herself")
|
|
238
|
+
return "herself";
|
|
239
|
+
if (pronouns === "they/them/their/themself")
|
|
240
|
+
return "themself";
|
|
241
|
+
if (pronouns === "name_only")
|
|
242
|
+
return "";
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
// Assume pronoun for sex if no pronouns are provided
|
|
246
|
+
else if (sex) {
|
|
247
|
+
// Return subject pronoun
|
|
248
|
+
if (type === "subject") {
|
|
249
|
+
if (sex === "male" || sex === "intersex_man")
|
|
250
|
+
return "he";
|
|
251
|
+
if (sex === "female" || sex === "intersex_woman")
|
|
252
|
+
return "she";
|
|
253
|
+
return "they";
|
|
254
|
+
}
|
|
255
|
+
// Return object pronoun
|
|
256
|
+
else if (type === "object") {
|
|
257
|
+
if (sex === "male" || sex === "intersex_man")
|
|
258
|
+
return "him";
|
|
259
|
+
if (sex === "female" || sex === "intersex_woman")
|
|
260
|
+
return "her";
|
|
261
|
+
return "them";
|
|
262
|
+
}
|
|
263
|
+
// Return possessive pronoun
|
|
264
|
+
else if (type === "possessive") {
|
|
265
|
+
if (sex === "male" || sex === "intersex_man")
|
|
266
|
+
return "his";
|
|
267
|
+
if (sex === "female" || sex === "intersex_woman")
|
|
268
|
+
return "her";
|
|
269
|
+
return "their";
|
|
270
|
+
}
|
|
271
|
+
// Return reflexive pronoun
|
|
272
|
+
else if (type === "reflexive") {
|
|
273
|
+
if (sex === "male" || sex === "intersex_man")
|
|
274
|
+
return "himself";
|
|
275
|
+
if (sex === "female" || sex === "intersex_woman")
|
|
276
|
+
return "herself";
|
|
277
|
+
return "themself";
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
// Return null
|
|
281
|
+
return null;
|
|
282
|
+
},
|
|
283
|
+
/**
|
|
284
|
+
* Paginates an array.
|
|
285
|
+
* @template T - The type of array items; defaults to `any`
|
|
286
|
+
* @param {T[]} array - The array to paginate
|
|
287
|
+
* @param {number} page - The page number
|
|
288
|
+
* @param {number} pageSize - The number of items per page; defaults to 10
|
|
289
|
+
* @returns {Object} The paginated array and total number of pages
|
|
290
|
+
*/
|
|
291
|
+
paginateArray: (array, page, pageSize = 10) => {
|
|
292
|
+
// Define variables
|
|
293
|
+
const startIndex = (page - 1) * pageSize;
|
|
294
|
+
const endIndex = startIndex + pageSize;
|
|
295
|
+
const totalPages = Math.ceil(array.length / pageSize);
|
|
296
|
+
// Paginate items
|
|
297
|
+
const paginatedItems = array.slice(startIndex, endIndex);
|
|
298
|
+
// Return data
|
|
299
|
+
return { paginatedItems, totalPages };
|
|
300
|
+
},
|
|
301
|
+
/**
|
|
302
|
+
* Sanitises any potential HTML injected into user input.
|
|
303
|
+
* @param {string} input - The input to be sanitised
|
|
304
|
+
* @returns A sanitised string, or an empty string if the input is invalid
|
|
305
|
+
*/
|
|
306
|
+
sanitiseHtml: (input) => typeof input !== "string" ? "" : sanitize(input, { allowedTags: [], allowedAttributes: {} })
|
|
307
|
+
};
|
|
308
|
+
/* DATA VALIDATION HELPERS */
|
|
309
|
+
export const Validate = {
|
|
310
|
+
/**
|
|
311
|
+
* A regular expression for E.164 phone numbers.
|
|
312
|
+
*
|
|
313
|
+
* - Source: https://www.twilio.com/docs/glossary/what-e164
|
|
314
|
+
*/
|
|
315
|
+
e164Regex: /^\+?[1-9]\d{1,14}$/,
|
|
316
|
+
/**
|
|
317
|
+
* The minimum birth date (from today's date) for a user who is a minor, i.e. `YYYY-MM-DD`
|
|
318
|
+
*/
|
|
319
|
+
minimumMinorBirthDate: (() => {
|
|
320
|
+
const today = new Date();
|
|
321
|
+
const tomorrow = new Date();
|
|
322
|
+
tomorrow.setUTCDate(today.getDate() + 1);
|
|
323
|
+
const tomorrow18YearsAgo = tomorrow.getFullYear() - 18;
|
|
324
|
+
tomorrow.setFullYear(tomorrow18YearsAgo);
|
|
325
|
+
return tomorrow.toISOString().split("T")[0];
|
|
326
|
+
})(),
|
|
327
|
+
/**
|
|
328
|
+
* A regular expression for user passwords to match during authentication. It covers the following rules:
|
|
329
|
+
* - at least 8 characters long
|
|
330
|
+
* - at least one digit
|
|
331
|
+
* - at least one lowercase letter
|
|
332
|
+
* - at least one uppercase letter
|
|
333
|
+
* - at least one symbol
|
|
334
|
+
*/
|
|
335
|
+
passwordRegex: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()\-_+=|{}\[\]:;'<>,.?/~]).{8,}$/,
|
|
336
|
+
/**
|
|
337
|
+
* A regular expression for social media handles, without a leading slash or @ character.
|
|
338
|
+
*
|
|
339
|
+
* - Source: https://stackoverflow.com/a/74579651
|
|
340
|
+
*/
|
|
341
|
+
socialMediaHandleRegex: /^([A-Za-z0-9_.]{3,25})$/gm,
|
|
342
|
+
/**
|
|
343
|
+
* An array of values representing social media platforms recognised in the database.
|
|
344
|
+
*/
|
|
345
|
+
socialMediaPlatforms: [
|
|
346
|
+
{ value: "facebook", linkPrefix: "https://www.facebook.com/" },
|
|
347
|
+
{ value: "instagram", linkPrefix: "https://www.instagram.com/" },
|
|
348
|
+
{ value: "kick", linkPrefix: "https://kick.com/" },
|
|
349
|
+
{ value: "tiktok", linkPrefix: "https://www.tiktok.com/@" },
|
|
350
|
+
{ value: "twitch", linkPrefix: "https://www.twitch.tv/" },
|
|
351
|
+
{ value: "twitter", linkPrefix: "https://x.com/" },
|
|
352
|
+
{ value: "youtube", linkPrefix: "https://www.youtube.com/@" }
|
|
353
|
+
],
|
|
354
|
+
/**
|
|
355
|
+
* Validates an E.164 phone number.
|
|
356
|
+
* @param {string} phoneNumber - The phone number to be validated
|
|
357
|
+
* @param {"any" | MobilePhoneLocale | MobilePhoneLocale[]} [locale="any"] - The locale(s) to validate the phone number against
|
|
358
|
+
* @returns `true` if the phone number is valid; `false` otherwise
|
|
359
|
+
*/
|
|
360
|
+
validateE164: (phoneNumber, locale = "en-KE") => isMobilePhone(phoneNumber, locale) && Boolean(phoneNumber.match(Validate.e164Regex)),
|
|
361
|
+
/**
|
|
362
|
+
* Validates a social media handle.
|
|
363
|
+
* @param {string} handle - The social media handle to be validated
|
|
364
|
+
* @returns `true` if the handle is valid; `false` otherwise
|
|
365
|
+
*/
|
|
366
|
+
validateSocialMediaHandle: (handle) => !isURL(handle, { require_protocol: false }) &&
|
|
367
|
+
!handle.startsWith("@") &&
|
|
368
|
+
Boolean(handle.match(Validate.socialMediaHandleRegex))
|
|
369
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export declare const Status: {
|
|
2
|
+
/**
|
|
3
|
+
* A generic error message.
|
|
4
|
+
*/
|
|
5
|
+
ERROR: string;
|
|
6
|
+
/**
|
|
7
|
+
* A generic loading message.
|
|
8
|
+
*/
|
|
9
|
+
LOADING: string;
|
|
10
|
+
/**
|
|
11
|
+
* A generic password reset request message.
|
|
12
|
+
*/
|
|
13
|
+
PASSWORD_RESET_REQUESTED: string;
|
|
14
|
+
};
|
|
15
|
+
export declare const Cache: {
|
|
16
|
+
/**
|
|
17
|
+
* Delete cached data from Valkey.
|
|
18
|
+
* @param valkey - The Valkey client instance
|
|
19
|
+
* @param cachePrefix - The cache prefix
|
|
20
|
+
* @param key - The cache key
|
|
21
|
+
*/
|
|
22
|
+
deleteData: (valkey: any, cachePrefix: string, key: string) => Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Generate cache key with consistent formatting
|
|
25
|
+
* @param prefix - The key prefix
|
|
26
|
+
* @param params - Additional parameters to include in the key
|
|
27
|
+
* @returns The formatted cache key
|
|
28
|
+
*/
|
|
29
|
+
generateDataKey: (prefix: string, ...params: (string | number)[]) => string;
|
|
30
|
+
/**
|
|
31
|
+
* Get cached data from Valkey.
|
|
32
|
+
* @param valkey - The Valkey client instance
|
|
33
|
+
* @param cachePrefix - The cache prefix
|
|
34
|
+
* @param key - The cache key
|
|
35
|
+
* @returns The cached data, or `null` if not found
|
|
36
|
+
*/
|
|
37
|
+
getData: <T>(valkey: any, cachePrefix: string, key: string) => Promise<T | null>;
|
|
38
|
+
/**
|
|
39
|
+
* Invalidate cache by pattern
|
|
40
|
+
* @param valkey - The Valkey client instance
|
|
41
|
+
* @param cachePrefix - The cache prefix
|
|
42
|
+
* @param pattern - The pattern to match keys against (e.g., "app-config*")
|
|
43
|
+
*/
|
|
44
|
+
invalidateDataByPattern: (valkey: any, cachePrefix: string, pattern: string) => Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* Set data in Valkey cache.
|
|
47
|
+
* @param valkey - The Valkey client instance
|
|
48
|
+
* @param cachePrefix - The cache prefix
|
|
49
|
+
* @param key - The cache key
|
|
50
|
+
* @param data - The data to cache
|
|
51
|
+
* @param ttl - The time to live in seconds; defaults to 1 hour
|
|
52
|
+
*/
|
|
53
|
+
setData: <T>(valkey: any, cachePrefix: string, key: string, data: T, ttl?: number) => Promise<void>;
|
|
54
|
+
};
|
package/dist/general.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/* STATUS MESSAGES */
|
|
2
|
+
export const Status = {
|
|
3
|
+
/**
|
|
4
|
+
* A generic error message.
|
|
5
|
+
*/
|
|
6
|
+
ERROR: "An unexpected error occurred. Kindly try again.",
|
|
7
|
+
/**
|
|
8
|
+
* A generic loading message.
|
|
9
|
+
*/
|
|
10
|
+
LOADING: "Please wait…",
|
|
11
|
+
/**
|
|
12
|
+
* A generic password reset request message.
|
|
13
|
+
*/
|
|
14
|
+
PASSWORD_RESET_REQUESTED: "If the provided email address is registered, you will receive a password reset link shortly."
|
|
15
|
+
};
|
|
16
|
+
/* CACHE HELPERS */
|
|
17
|
+
export const Cache = {
|
|
18
|
+
/**
|
|
19
|
+
* Delete cached data from Valkey.
|
|
20
|
+
* @param valkey - The Valkey client instance
|
|
21
|
+
* @param cachePrefix - The cache prefix
|
|
22
|
+
* @param key - The cache key
|
|
23
|
+
*/
|
|
24
|
+
deleteData: async (valkey, cachePrefix, key) => {
|
|
25
|
+
try {
|
|
26
|
+
await valkey.del(`${cachePrefix}${key}`);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
console.error("Cache delete error:", error);
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
/**
|
|
33
|
+
* Generate cache key with consistent formatting
|
|
34
|
+
* @param prefix - The key prefix
|
|
35
|
+
* @param params - Additional parameters to include in the key
|
|
36
|
+
* @returns The formatted cache key
|
|
37
|
+
*/
|
|
38
|
+
generateDataKey: (prefix, ...params) => `${prefix}:${params.join(":")}`,
|
|
39
|
+
/**
|
|
40
|
+
* Get cached data from Valkey.
|
|
41
|
+
* @param valkey - The Valkey client instance
|
|
42
|
+
* @param cachePrefix - The cache prefix
|
|
43
|
+
* @param key - The cache key
|
|
44
|
+
* @returns The cached data, or `null` if not found
|
|
45
|
+
*/
|
|
46
|
+
getData: async (valkey, cachePrefix, key) => {
|
|
47
|
+
try {
|
|
48
|
+
const cached = await valkey.get(`${cachePrefix}${key}`);
|
|
49
|
+
return cached ? JSON.parse(cached) : null;
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
console.error("Cache get error:", error);
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
/**
|
|
57
|
+
* Invalidate cache by pattern
|
|
58
|
+
* @param valkey - The Valkey client instance
|
|
59
|
+
* @param cachePrefix - The cache prefix
|
|
60
|
+
* @param pattern - The pattern to match keys against (e.g., "app-config*")
|
|
61
|
+
*/
|
|
62
|
+
invalidateDataByPattern: async (valkey, cachePrefix, pattern) => {
|
|
63
|
+
try {
|
|
64
|
+
const keys = await valkey.keys(`${cachePrefix}${pattern}`);
|
|
65
|
+
if (keys.length > 0)
|
|
66
|
+
await valkey.del(...keys);
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
console.error("Cache pattern invalidation error:", error);
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
/**
|
|
73
|
+
* Set data in Valkey cache.
|
|
74
|
+
* @param valkey - The Valkey client instance
|
|
75
|
+
* @param cachePrefix - The cache prefix
|
|
76
|
+
* @param key - The cache key
|
|
77
|
+
* @param data - The data to cache
|
|
78
|
+
* @param ttl - The time to live in seconds; defaults to 1 hour
|
|
79
|
+
*/
|
|
80
|
+
setData: async (valkey, cachePrefix, key, data, ttl = 3600) => {
|
|
81
|
+
try {
|
|
82
|
+
await valkey.setex(`${cachePrefix}${key}`, ttl, JSON.stringify(data));
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
console.error("Cache set error:", error);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
};
|