@azure-net/node-kit 0.0.0-stage → 0.1.1
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/CHANGELOG.md +11 -0
- package/LICENSE +21 -0
- package/README.md +301 -2
- package/VALIDATION.md +866 -0
- package/dist/delivery/http/ResponseBuilder.d.ts +44 -0
- package/dist/delivery/http/ResponseBuilder.js +135 -0
- package/dist/delivery/http/index.d.ts +1 -0
- package/dist/delivery/http/index.js +1 -0
- package/dist/delivery/index.d.ts +1 -0
- package/dist/delivery/index.js +1 -0
- package/dist/domain/Ensure.d.ts +2 -0
- package/dist/domain/Ensure.js +7 -0
- package/dist/domain/EnsureError.d.ts +4 -0
- package/dist/domain/EnsureError.js +7 -0
- package/dist/domain/index.d.ts +2 -0
- package/dist/domain/index.js +2 -0
- package/dist/infrastructure/background/BackgroundTask.d.ts +12 -0
- package/dist/infrastructure/background/BackgroundTask.js +41 -0
- package/dist/infrastructure/background/index.d.ts +1 -0
- package/dist/infrastructure/background/index.js +1 -0
- package/dist/infrastructure/environment/Env.d.ts +19 -0
- package/dist/infrastructure/environment/Env.js +33 -0
- package/dist/infrastructure/environment/index.d.ts +1 -0
- package/dist/infrastructure/environment/index.js +1 -0
- package/dist/infrastructure/index.d.ts +2 -0
- package/dist/infrastructure/index.js +2 -0
- package/dist/tools/DateHelper.d.ts +13 -0
- package/dist/tools/DateHelper.js +77 -0
- package/dist/tools/DurationHelper.d.ts +6 -0
- package/dist/tools/DurationHelper.js +31 -0
- package/dist/tools/EnumHelper.d.ts +26 -0
- package/dist/tools/EnumHelper.js +40 -0
- package/dist/tools/ErrorHelper.d.ts +3 -0
- package/dist/tools/ErrorHelper.js +31 -0
- package/dist/tools/NumberHelper.d.ts +19 -0
- package/dist/tools/NumberHelper.js +49 -0
- package/dist/tools/ObjectHelper.d.ts +28 -0
- package/dist/tools/ObjectHelper.js +90 -0
- package/dist/tools/PromiseHelper.d.ts +3 -0
- package/dist/tools/PromiseHelper.js +7 -0
- package/dist/tools/StringHelper.d.ts +75 -0
- package/dist/tools/StringHelper.js +127 -0
- package/dist/tools/UidHelper.d.ts +56 -0
- package/dist/tools/UidHelper.js +132 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.js +9 -0
- package/dist/validation/Engine.d.ts +6 -0
- package/dist/validation/Engine.js +406 -0
- package/dist/validation/Result.d.ts +31 -0
- package/dist/validation/Result.js +264 -0
- package/dist/validation/Symbols.d.ts +1 -0
- package/dist/validation/Symbols.js +1 -0
- package/dist/validation/Types.d.ts +105 -0
- package/dist/validation/Types.js +1 -0
- package/dist/validation/Validator.d.ts +26 -0
- package/dist/validation/Validator.js +95 -0
- package/dist/validation/index.d.ts +5 -0
- package/dist/validation/index.js +5 -0
- package/dist/validation/messages/En.d.ts +3 -0
- package/dist/validation/messages/En.js +118 -0
- package/dist/validation/messages/Ru.d.ts +3 -0
- package/dist/validation/messages/Ru.js +118 -0
- package/dist/validation/messages/index.d.ts +7 -0
- package/dist/validation/messages/index.js +7 -0
- package/dist/validation/rules/CustomRules.d.ts +8 -0
- package/dist/validation/rules/CustomRules.js +64 -0
- package/dist/validation/rules/Factory.d.ts +4 -0
- package/dist/validation/rules/Factory.js +21 -0
- package/dist/validation/rules/StringRules.d.ts +105 -0
- package/dist/validation/rules/StringRules.js +481 -0
- package/dist/validation/rules/StructureRules.d.ts +30 -0
- package/dist/validation/rules/StructureRules.js +145 -0
- package/dist/validation/rules/ValueRules.d.ts +82 -0
- package/dist/validation/rules/ValueRules.js +365 -0
- package/dist/validation/rules/index.d.ts +151 -0
- package/dist/validation/rules/index.js +19 -0
- package/package.json +77 -3
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/** Predicates never coerce strings, booleans or null into numbers. */
|
|
2
|
+
export class NumberHelper {
|
|
3
|
+
static isFinite(value) {
|
|
4
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
5
|
+
}
|
|
6
|
+
static isInteger(value) {
|
|
7
|
+
return typeof value === "number" && Number.isInteger(value);
|
|
8
|
+
}
|
|
9
|
+
static isSafeInteger(value) {
|
|
10
|
+
return typeof value === "number" && Number.isSafeInteger(value);
|
|
11
|
+
}
|
|
12
|
+
static isPositive(value) {
|
|
13
|
+
return this.isFinite(value) && value > 0;
|
|
14
|
+
}
|
|
15
|
+
static isNegative(value) {
|
|
16
|
+
return this.isFinite(value) && value < 0;
|
|
17
|
+
}
|
|
18
|
+
static isNonNegative(value) {
|
|
19
|
+
return this.isFinite(value) && value >= 0;
|
|
20
|
+
}
|
|
21
|
+
static isPositiveInteger(value) {
|
|
22
|
+
return this.isInteger(value) && value > 0;
|
|
23
|
+
}
|
|
24
|
+
static isNonNegativeInteger(value) {
|
|
25
|
+
return this.isInteger(value) && value >= 0;
|
|
26
|
+
}
|
|
27
|
+
static isInRange(value, min, max, inclusive = true) {
|
|
28
|
+
return (this.isFinite(value) &&
|
|
29
|
+
this.isFinite(min) &&
|
|
30
|
+
this.isFinite(max) &&
|
|
31
|
+
min <= max &&
|
|
32
|
+
(inclusive ? value >= min && value <= max : value > min && value < max));
|
|
33
|
+
}
|
|
34
|
+
static isValid(value, options = {}) {
|
|
35
|
+
if (!this.isFinite(value))
|
|
36
|
+
return false;
|
|
37
|
+
if (options.integer && !this.isInteger(value))
|
|
38
|
+
return false;
|
|
39
|
+
if (options.safeInteger && !this.isSafeInteger(value))
|
|
40
|
+
return false;
|
|
41
|
+
if (options.min !== undefined &&
|
|
42
|
+
(!this.isFinite(options.min) || value < options.min))
|
|
43
|
+
return false;
|
|
44
|
+
if (options.max !== undefined &&
|
|
45
|
+
(!this.isFinite(options.max) || value > options.max))
|
|
46
|
+
return false;
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export declare class ObjectHelper {
|
|
2
|
+
static toEntries<T extends object>(object: T): Array<{
|
|
3
|
+
key: string;
|
|
4
|
+
value: T[keyof T];
|
|
5
|
+
}>;
|
|
6
|
+
static toEntries<T extends object, K>(object: T, options: {
|
|
7
|
+
mapKey: (key: string, value: T[keyof T]) => K;
|
|
8
|
+
}): Array<{
|
|
9
|
+
key: K;
|
|
10
|
+
value: T[keyof T];
|
|
11
|
+
}>;
|
|
12
|
+
static isPlainObject(value: unknown): value is Record<PropertyKey, unknown>;
|
|
13
|
+
/** Shallow clone of arrays/plain records; nested references are retained. */
|
|
14
|
+
static clone<T extends object>(value: T): T;
|
|
15
|
+
/** Native structured-clone semantics, not a class-instance/prototype clone. */
|
|
16
|
+
static deepClone<T>(value: T): T;
|
|
17
|
+
static equals(left: unknown, right: unknown): boolean;
|
|
18
|
+
/** JSON representation comparison only; may throw for cycles/BigInt. Prefer equals(). */
|
|
19
|
+
static compareAsString(left: unknown, right: unknown): boolean;
|
|
20
|
+
private static enumerableKeys;
|
|
21
|
+
static countProps(value: unknown): number;
|
|
22
|
+
/** Only actual objects with no own enumerable keys are empty. Collections use size. */
|
|
23
|
+
static isObjectEmpty(value: unknown): boolean;
|
|
24
|
+
/** Nullish leaves and empty containers; cycles are visited once. */
|
|
25
|
+
static isAllKeysEmpty(value: unknown): boolean;
|
|
26
|
+
static pick<T extends object, K extends keyof T>(object: T, keys: readonly K[]): Pick<T, K>;
|
|
27
|
+
static omit<T extends object, K extends keyof T>(object: T, keys: readonly K[]): Omit<T, K>;
|
|
28
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from "node:util";
|
|
2
|
+
export class ObjectHelper {
|
|
3
|
+
static toEntries(object, options) {
|
|
4
|
+
return Object.entries(object).map(([key, value]) => ({
|
|
5
|
+
key: options ? options.mapKey(key, value) : key,
|
|
6
|
+
value: value,
|
|
7
|
+
}));
|
|
8
|
+
}
|
|
9
|
+
static isPlainObject(value) {
|
|
10
|
+
if (value === null || typeof value !== "object")
|
|
11
|
+
return false;
|
|
12
|
+
const prototype = Object.getPrototypeOf(value);
|
|
13
|
+
return prototype === Object.prototype || prototype === null;
|
|
14
|
+
}
|
|
15
|
+
/** Shallow clone of arrays/plain records; nested references are retained. */
|
|
16
|
+
static clone(value) {
|
|
17
|
+
if (Array.isArray(value))
|
|
18
|
+
return Object.defineProperties(new Array(value.length), Object.getOwnPropertyDescriptors(value));
|
|
19
|
+
if (!this.isPlainObject(value))
|
|
20
|
+
throw new TypeError("Shallow clone requires an array or a plain object");
|
|
21
|
+
return Object.defineProperties(Object.create(Object.getPrototypeOf(value)), Object.getOwnPropertyDescriptors(value));
|
|
22
|
+
}
|
|
23
|
+
/** Native structured-clone semantics, not a class-instance/prototype clone. */
|
|
24
|
+
static deepClone(value) {
|
|
25
|
+
return structuredClone(value);
|
|
26
|
+
}
|
|
27
|
+
static equals(left, right) {
|
|
28
|
+
return isDeepStrictEqual(left, right);
|
|
29
|
+
}
|
|
30
|
+
/** JSON representation comparison only; may throw for cycles/BigInt. Prefer equals(). */
|
|
31
|
+
static compareAsString(left, right) {
|
|
32
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
33
|
+
}
|
|
34
|
+
static enumerableKeys(object) {
|
|
35
|
+
return Reflect.ownKeys(object).filter((key) => Object.prototype.propertyIsEnumerable.call(object, key));
|
|
36
|
+
}
|
|
37
|
+
static countProps(value) {
|
|
38
|
+
return value !== null && typeof value === "object"
|
|
39
|
+
? this.enumerableKeys(value).length
|
|
40
|
+
: 0;
|
|
41
|
+
}
|
|
42
|
+
/** Only actual objects with no own enumerable keys are empty. Collections use size. */
|
|
43
|
+
static isObjectEmpty(value) {
|
|
44
|
+
if (value instanceof Map || value instanceof Set)
|
|
45
|
+
return value.size === 0;
|
|
46
|
+
if (!this.isPlainObject(value) && !Array.isArray(value))
|
|
47
|
+
return false;
|
|
48
|
+
return this.countProps(value) === 0;
|
|
49
|
+
}
|
|
50
|
+
/** Nullish leaves and empty containers; cycles are visited once. */
|
|
51
|
+
static isAllKeysEmpty(value) {
|
|
52
|
+
const visited = new WeakSet();
|
|
53
|
+
const pending = [value];
|
|
54
|
+
while (pending.length > 0) {
|
|
55
|
+
const current = pending.pop();
|
|
56
|
+
if (current === null || current === undefined)
|
|
57
|
+
continue;
|
|
58
|
+
if (current instanceof Map || current instanceof Set) {
|
|
59
|
+
if (current.size !== 0)
|
|
60
|
+
return false;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (!this.isPlainObject(current) && !Array.isArray(current))
|
|
64
|
+
return false;
|
|
65
|
+
if (visited.has(current))
|
|
66
|
+
continue;
|
|
67
|
+
visited.add(current);
|
|
68
|
+
for (const key of this.enumerableKeys(current)) {
|
|
69
|
+
const descriptor = Object.getOwnPropertyDescriptor(current, key);
|
|
70
|
+
// Do not invoke arbitrary getters while checking emptiness.
|
|
71
|
+
if (!("value" in descriptor))
|
|
72
|
+
return false;
|
|
73
|
+
pending.push(descriptor.value);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
static pick(object, keys) {
|
|
79
|
+
const entries = keys
|
|
80
|
+
.filter((key) => Object.hasOwn(object, key))
|
|
81
|
+
.map((key) => [key, object[key]]);
|
|
82
|
+
return Object.fromEntries(entries);
|
|
83
|
+
}
|
|
84
|
+
static omit(object, keys) {
|
|
85
|
+
const excluded = new Set(keys.map((key) => (typeof key === "number" ? String(key) : key)));
|
|
86
|
+
return Object.fromEntries(this.enumerableKeys(object)
|
|
87
|
+
.filter((key) => !excluded.has(key))
|
|
88
|
+
.map((key) => [key, object[key]]));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility class for common text operations such as pluralization, truncation, and more.
|
|
3
|
+
*/
|
|
4
|
+
export interface StringValidationOptions {
|
|
5
|
+
minLength?: number;
|
|
6
|
+
maxLength?: number;
|
|
7
|
+
pattern?: RegExp;
|
|
8
|
+
/** Trim before checking length and pattern, without modifying the input. */
|
|
9
|
+
trim?: boolean;
|
|
10
|
+
allowEmpty?: boolean;
|
|
11
|
+
allowNull?: boolean;
|
|
12
|
+
allowUndefined?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare class StringHelper {
|
|
15
|
+
static isNotEmpty(value: unknown): value is string;
|
|
16
|
+
/** Length follows JavaScript string.length (UTF-16 code units). */
|
|
17
|
+
static hasLength(value: unknown, min?: number, max?: number): value is string;
|
|
18
|
+
/** Global/sticky patterns start at zero without changing the caller's lastIndex. */
|
|
19
|
+
static matches(value: unknown, pattern: RegExp): value is string;
|
|
20
|
+
static isValid(value: unknown, options?: StringValidationOptions): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Returns the correct plural form from an array of word forms based on the given number.
|
|
23
|
+
*
|
|
24
|
+
* For languages like Russian, array should have 3 forms:
|
|
25
|
+
* - titles[0]: singular (1)
|
|
26
|
+
* - titles[1]: few (2,3,4)
|
|
27
|
+
* - titles[2]: many (0,5-20)
|
|
28
|
+
*
|
|
29
|
+
* For languages like English, array should have 2 forms:
|
|
30
|
+
* - titles[0]: singular (1)
|
|
31
|
+
* - titles[1]: plural (everything else)
|
|
32
|
+
*
|
|
33
|
+
* @param {number} num - The number to base the pluralization on.
|
|
34
|
+
* @param {string[]} titles - Array of word forms (2 or 3 elements).
|
|
35
|
+
* @returns {string} The correct plural form according to the number.
|
|
36
|
+
* @throws {Error} Throws if titles array length is not 2 or 3.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* StringHelper.pluralize(1, ['apple', 'apples']); // 'apple'
|
|
40
|
+
* StringHelper.pluralize(2, ['apple', 'apples']); // 'apples'
|
|
41
|
+
* StringHelper.pluralize(1, ['яблоко', 'яблока', 'яблок']); // 'яблоко'
|
|
42
|
+
* StringHelper.pluralize(5, ['яблоко', 'яблока', 'яблок']); // 'яблок'
|
|
43
|
+
*/
|
|
44
|
+
static pluralize(num: number, titles: string[]): string;
|
|
45
|
+
/**
|
|
46
|
+
* Truncates a string to a specified maximum length, adding ellipsis if truncated.
|
|
47
|
+
*
|
|
48
|
+
* @param text - The input string to truncate.
|
|
49
|
+
* @param maxLength - Maximum number of source characters kept before the ellipsis.
|
|
50
|
+
* @param ellipsis - Optional ellipsis string to append (default: '...').
|
|
51
|
+
* @returns Truncated string with ellipsis if needed. Its final length can be `maxLength + ellipsis.length`.
|
|
52
|
+
*/
|
|
53
|
+
static truncate(text: string, maxLength: number, ellipsis?: string): string;
|
|
54
|
+
/**
|
|
55
|
+
* Capitalizes the first character of the given string.
|
|
56
|
+
*
|
|
57
|
+
* @param text - The input string.
|
|
58
|
+
* @returns The string with the first character capitalized.
|
|
59
|
+
*/
|
|
60
|
+
static capitalize(text: string): string;
|
|
61
|
+
/**
|
|
62
|
+
* Converts the first character of the given string to lowercase.
|
|
63
|
+
*
|
|
64
|
+
* @param text - The input string.
|
|
65
|
+
* @returns The string with the first character in lowercase.
|
|
66
|
+
*/
|
|
67
|
+
static decapitalize(text: string): string;
|
|
68
|
+
/**
|
|
69
|
+
* Checks if a string is empty or consists only of whitespace characters.
|
|
70
|
+
*
|
|
71
|
+
* @param text - The input string.
|
|
72
|
+
* @returns True if string is empty or whitespace only, false otherwise.
|
|
73
|
+
*/
|
|
74
|
+
static isEmptyOrWhitespace(text: unknown): boolean;
|
|
75
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
export class StringHelper {
|
|
2
|
+
static isNotEmpty(value) {
|
|
3
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
4
|
+
}
|
|
5
|
+
/** Length follows JavaScript string.length (UTF-16 code units). */
|
|
6
|
+
static hasLength(value, min = 0, max) {
|
|
7
|
+
return (typeof value === "string" &&
|
|
8
|
+
Number.isSafeInteger(min) &&
|
|
9
|
+
min >= 0 &&
|
|
10
|
+
value.length >= min &&
|
|
11
|
+
(max === undefined ||
|
|
12
|
+
(Number.isSafeInteger(max) && max >= min && value.length <= max)));
|
|
13
|
+
}
|
|
14
|
+
/** Global/sticky patterns start at zero without changing the caller's lastIndex. */
|
|
15
|
+
static matches(value, pattern) {
|
|
16
|
+
if (typeof value !== "string" || !(pattern instanceof RegExp))
|
|
17
|
+
return false;
|
|
18
|
+
return (pattern.global || pattern.sticky
|
|
19
|
+
? new RegExp(pattern.source, pattern.flags)
|
|
20
|
+
: pattern).test(value);
|
|
21
|
+
}
|
|
22
|
+
static isValid(value, options = {}) {
|
|
23
|
+
if (value === null)
|
|
24
|
+
return options.allowNull === true;
|
|
25
|
+
if (value === undefined)
|
|
26
|
+
return options.allowUndefined === true;
|
|
27
|
+
if (typeof value !== "string")
|
|
28
|
+
return false;
|
|
29
|
+
const candidate = options.trim ? value.trim() : value;
|
|
30
|
+
if (!options.allowEmpty && !this.isNotEmpty(candidate))
|
|
31
|
+
return false;
|
|
32
|
+
if (!this.hasLength(candidate, options.minLength ?? 0, options.maxLength))
|
|
33
|
+
return false;
|
|
34
|
+
return (options.pattern === undefined || this.matches(candidate, options.pattern));
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Returns the correct plural form from an array of word forms based on the given number.
|
|
38
|
+
*
|
|
39
|
+
* For languages like Russian, array should have 3 forms:
|
|
40
|
+
* - titles[0]: singular (1)
|
|
41
|
+
* - titles[1]: few (2,3,4)
|
|
42
|
+
* - titles[2]: many (0,5-20)
|
|
43
|
+
*
|
|
44
|
+
* For languages like English, array should have 2 forms:
|
|
45
|
+
* - titles[0]: singular (1)
|
|
46
|
+
* - titles[1]: plural (everything else)
|
|
47
|
+
*
|
|
48
|
+
* @param {number} num - The number to base the pluralization on.
|
|
49
|
+
* @param {string[]} titles - Array of word forms (2 or 3 elements).
|
|
50
|
+
* @returns {string} The correct plural form according to the number.
|
|
51
|
+
* @throws {Error} Throws if titles array length is not 2 or 3.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* StringHelper.pluralize(1, ['apple', 'apples']); // 'apple'
|
|
55
|
+
* StringHelper.pluralize(2, ['apple', 'apples']); // 'apples'
|
|
56
|
+
* StringHelper.pluralize(1, ['яблоко', 'яблока', 'яблок']); // 'яблоко'
|
|
57
|
+
* StringHelper.pluralize(5, ['яблоко', 'яблока', 'яблок']); // 'яблок'
|
|
58
|
+
*/
|
|
59
|
+
static pluralize(num, titles) {
|
|
60
|
+
if (titles.length !== 2 && titles.length !== 3) {
|
|
61
|
+
throw new Error("Titles array must have 2 or 3 elements");
|
|
62
|
+
}
|
|
63
|
+
const number = Math.abs(num);
|
|
64
|
+
if (!Number.isFinite(number))
|
|
65
|
+
return titles[titles.length - 1];
|
|
66
|
+
if (titles.length === 2) {
|
|
67
|
+
// English-like pluralization (singular/plural)
|
|
68
|
+
return number === 1 ? titles[0] : titles[1];
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
if (!Number.isInteger(number))
|
|
72
|
+
return titles[1];
|
|
73
|
+
// Russian-like pluralization with 3 forms
|
|
74
|
+
const cases = [2, 0, 1, 1, 1, 2];
|
|
75
|
+
if (number % 100 > 4 && number % 100 < 20) {
|
|
76
|
+
return titles[2];
|
|
77
|
+
}
|
|
78
|
+
return titles[cases[number % 10 < 5 ? number % 10 : 5]];
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Truncates a string to a specified maximum length, adding ellipsis if truncated.
|
|
83
|
+
*
|
|
84
|
+
* @param text - The input string to truncate.
|
|
85
|
+
* @param maxLength - Maximum number of source characters kept before the ellipsis.
|
|
86
|
+
* @param ellipsis - Optional ellipsis string to append (default: '...').
|
|
87
|
+
* @returns Truncated string with ellipsis if needed. Its final length can be `maxLength + ellipsis.length`.
|
|
88
|
+
*/
|
|
89
|
+
static truncate(text, maxLength, ellipsis = "...") {
|
|
90
|
+
if (text.length <= maxLength)
|
|
91
|
+
return text;
|
|
92
|
+
return text.slice(0, maxLength) + ellipsis;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Capitalizes the first character of the given string.
|
|
96
|
+
*
|
|
97
|
+
* @param text - The input string.
|
|
98
|
+
* @returns The string with the first character capitalized.
|
|
99
|
+
*/
|
|
100
|
+
static capitalize(text) {
|
|
101
|
+
if (!text)
|
|
102
|
+
return "";
|
|
103
|
+
return text.charAt(0).toUpperCase() + text.slice(1);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Converts the first character of the given string to lowercase.
|
|
107
|
+
*
|
|
108
|
+
* @param text - The input string.
|
|
109
|
+
* @returns The string with the first character in lowercase.
|
|
110
|
+
*/
|
|
111
|
+
static decapitalize(text) {
|
|
112
|
+
if (!text)
|
|
113
|
+
return "";
|
|
114
|
+
return text.charAt(0).toLowerCase() + text.slice(1);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Checks if a string is empty or consists only of whitespace characters.
|
|
118
|
+
*
|
|
119
|
+
* @param text - The input string.
|
|
120
|
+
* @returns True if string is empty or whitespace only, false otherwise.
|
|
121
|
+
*/
|
|
122
|
+
static isEmptyOrWhitespace(text) {
|
|
123
|
+
return (text === null ||
|
|
124
|
+
text === undefined ||
|
|
125
|
+
(typeof text === "string" && text.trim().length === 0));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cryptographically random IDs. Randomness does not guarantee collision-free IDs.
|
|
3
|
+
*/
|
|
4
|
+
export declare class UidHelper {
|
|
5
|
+
private static readonly ALPHANUMERIC;
|
|
6
|
+
private static readonly URL_SAFE;
|
|
7
|
+
private static readonly HEX;
|
|
8
|
+
private static counter;
|
|
9
|
+
private static readonly MAX_RANDOM_BYTES;
|
|
10
|
+
private static readonly RANDOM_NUMBER_SPACE;
|
|
11
|
+
private static fillRandomBytes;
|
|
12
|
+
/**
|
|
13
|
+
* Generates unique string
|
|
14
|
+
* @param length - string length (16 by default)
|
|
15
|
+
* @param alphabet - symbols alphabet
|
|
16
|
+
* @returns unique string
|
|
17
|
+
*/
|
|
18
|
+
static generateUniqueString(length?: number, alphabet?: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* Generates a random integer in the inclusive range. Values can repeat.
|
|
21
|
+
* @param min - min value (0 by default)
|
|
22
|
+
* @param max - max value (Number.MAX_SAFE_INTEGER by default)
|
|
23
|
+
* @returns unique number
|
|
24
|
+
*/
|
|
25
|
+
static generateUniqueNumber(min?: number, max?: number): number;
|
|
26
|
+
/**
|
|
27
|
+
* Generates UUID v4
|
|
28
|
+
* @returns UUID string. Format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
|
|
29
|
+
*/
|
|
30
|
+
static generateUuid(): string;
|
|
31
|
+
/**
|
|
32
|
+
* Generates url-safe ID (like NanoID)
|
|
33
|
+
* @param length - length (21 by default)
|
|
34
|
+
* @returns URL-safe id
|
|
35
|
+
*/
|
|
36
|
+
static generateNanoId(length?: number): string;
|
|
37
|
+
/**
|
|
38
|
+
* Generates id by current timestamp
|
|
39
|
+
* @param prefix - prefix
|
|
40
|
+
* @param includeRandom - includes random part (for unique safety)
|
|
41
|
+
* @returns ID with timestamp
|
|
42
|
+
*/
|
|
43
|
+
static generateTimestampId(prefix?: string, includeRandom?: boolean): string;
|
|
44
|
+
/**
|
|
45
|
+
* Generates a random hexadecimal ID, not a hash of data.
|
|
46
|
+
* @param length - length (32 by default)
|
|
47
|
+
* @returns hash-like string
|
|
48
|
+
*/
|
|
49
|
+
static generateHashId(length?: number): string;
|
|
50
|
+
/**
|
|
51
|
+
* Checks that a value is a valid UUID v4 (same version as generateUuid).
|
|
52
|
+
* @param uuid - uuid string
|
|
53
|
+
* @returns boolean - is uuid valid or not
|
|
54
|
+
*/
|
|
55
|
+
static isValidUuid(uuid: unknown): boolean;
|
|
56
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { randomFillSync, randomUUID } from "node:crypto";
|
|
2
|
+
/**
|
|
3
|
+
* Cryptographically random IDs. Randomness does not guarantee collision-free IDs.
|
|
4
|
+
*/
|
|
5
|
+
export class UidHelper {
|
|
6
|
+
static ALPHANUMERIC = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
7
|
+
static URL_SAFE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
8
|
+
static HEX = "0123456789abcdef";
|
|
9
|
+
static counter = 0;
|
|
10
|
+
static MAX_RANDOM_BYTES = 65_536;
|
|
11
|
+
static RANDOM_NUMBER_SPACE = 2 ** 53;
|
|
12
|
+
static fillRandomBytes(bytes) {
|
|
13
|
+
randomFillSync(bytes);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Generates unique string
|
|
17
|
+
* @param length - string length (16 by default)
|
|
18
|
+
* @param alphabet - symbols alphabet
|
|
19
|
+
* @returns unique string
|
|
20
|
+
*/
|
|
21
|
+
static generateUniqueString(length = 16, alphabet = UidHelper.ALPHANUMERIC) {
|
|
22
|
+
if (!Number.isSafeInteger(length) || length < 0) {
|
|
23
|
+
throw new RangeError("Length must be a non-negative safe integer");
|
|
24
|
+
}
|
|
25
|
+
const symbols = Array.from(alphabet);
|
|
26
|
+
if (symbols.length === 0 ||
|
|
27
|
+
symbols.length > 256 ||
|
|
28
|
+
new Set(symbols).size !== symbols.length) {
|
|
29
|
+
throw new RangeError("Alphabet must contain between 1 and 256 distinct symbols");
|
|
30
|
+
}
|
|
31
|
+
if (length === 0)
|
|
32
|
+
return "";
|
|
33
|
+
const chars = [];
|
|
34
|
+
const acceptanceLimit = 256 - (256 % symbols.length);
|
|
35
|
+
while (chars.length < length) {
|
|
36
|
+
const remaining = length - chars.length;
|
|
37
|
+
const batchSize = Math.min(this.MAX_RANDOM_BYTES, Math.ceil((remaining * 256) / acceptanceLimit));
|
|
38
|
+
const randomValues = new Uint8Array(batchSize);
|
|
39
|
+
this.fillRandomBytes(randomValues);
|
|
40
|
+
for (const randomValue of randomValues) {
|
|
41
|
+
if (randomValue >= acceptanceLimit)
|
|
42
|
+
continue;
|
|
43
|
+
chars.push(symbols[randomValue % symbols.length]);
|
|
44
|
+
if (chars.length === length)
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return chars.join("");
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Generates a random integer in the inclusive range. Values can repeat.
|
|
52
|
+
* @param min - min value (0 by default)
|
|
53
|
+
* @param max - max value (Number.MAX_SAFE_INTEGER by default)
|
|
54
|
+
* @returns unique number
|
|
55
|
+
*/
|
|
56
|
+
static generateUniqueNumber(min = 0, max = Number.MAX_SAFE_INTEGER) {
|
|
57
|
+
if (!Number.isSafeInteger(min) || !Number.isSafeInteger(max)) {
|
|
58
|
+
throw new RangeError("Min and max must be safe integers");
|
|
59
|
+
}
|
|
60
|
+
if (max < min)
|
|
61
|
+
throw new RangeError("Max must be greater than or equal to min");
|
|
62
|
+
if (min === max)
|
|
63
|
+
return min;
|
|
64
|
+
const rangeSize = max - min + 1;
|
|
65
|
+
if (!Number.isSafeInteger(rangeSize) &&
|
|
66
|
+
rangeSize !== this.RANDOM_NUMBER_SPACE) {
|
|
67
|
+
throw new RangeError("Requested range is too large");
|
|
68
|
+
}
|
|
69
|
+
const acceptanceLimit = Math.floor(this.RANDOM_NUMBER_SPACE / rangeSize) * rangeSize;
|
|
70
|
+
const bytes = new Uint8Array(7);
|
|
71
|
+
let randomNumber;
|
|
72
|
+
do {
|
|
73
|
+
this.fillRandomBytes(bytes);
|
|
74
|
+
randomNumber = bytes[0] & 0x1f;
|
|
75
|
+
for (let index = 1; index < bytes.length; index += 1) {
|
|
76
|
+
randomNumber = randomNumber * 256 + bytes[index];
|
|
77
|
+
}
|
|
78
|
+
} while (randomNumber >= acceptanceLimit);
|
|
79
|
+
return min + (randomNumber % rangeSize);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Generates UUID v4
|
|
83
|
+
* @returns UUID string. Format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
|
|
84
|
+
*/
|
|
85
|
+
static generateUuid() {
|
|
86
|
+
return randomUUID();
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Generates url-safe ID (like NanoID)
|
|
90
|
+
* @param length - length (21 by default)
|
|
91
|
+
* @returns URL-safe id
|
|
92
|
+
*/
|
|
93
|
+
static generateNanoId(length = 21) {
|
|
94
|
+
return UidHelper.generateUniqueString(length, UidHelper.URL_SAFE);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Generates id by current timestamp
|
|
98
|
+
* @param prefix - prefix
|
|
99
|
+
* @param includeRandom - includes random part (for unique safety)
|
|
100
|
+
* @returns ID with timestamp
|
|
101
|
+
*/
|
|
102
|
+
static generateTimestampId(prefix = "", includeRandom = true) {
|
|
103
|
+
const timestamp = Date.now().toString(36);
|
|
104
|
+
UidHelper.counter =
|
|
105
|
+
UidHelper.counter >= Number.MAX_SAFE_INTEGER ? 1 : UidHelper.counter + 1;
|
|
106
|
+
const counter = UidHelper.counter.toString(36);
|
|
107
|
+
let id = prefix ? `${prefix}_${timestamp}` : timestamp;
|
|
108
|
+
id += `_${counter}`;
|
|
109
|
+
if (includeRandom) {
|
|
110
|
+
const random = UidHelper.generateUniqueString(6, UidHelper.ALPHANUMERIC);
|
|
111
|
+
id += `_${random}`;
|
|
112
|
+
}
|
|
113
|
+
return id;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Generates a random hexadecimal ID, not a hash of data.
|
|
117
|
+
* @param length - length (32 by default)
|
|
118
|
+
* @returns hash-like string
|
|
119
|
+
*/
|
|
120
|
+
static generateHashId(length = 32) {
|
|
121
|
+
return UidHelper.generateUniqueString(length, UidHelper.HEX);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Checks that a value is a valid UUID v4 (same version as generateUuid).
|
|
125
|
+
* @param uuid - uuid string
|
|
126
|
+
* @returns boolean - is uuid valid or not
|
|
127
|
+
*/
|
|
128
|
+
static isValidUuid(uuid) {
|
|
129
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
130
|
+
return typeof uuid === "string" && uuidRegex.test(uuid);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from "./EnumHelper.js";
|
|
2
|
+
export * from "./UidHelper.js";
|
|
3
|
+
export * from "./StringHelper.js";
|
|
4
|
+
export * from "./PromiseHelper.js";
|
|
5
|
+
export * from "./ErrorHelper.js";
|
|
6
|
+
export * from "./ObjectHelper.js";
|
|
7
|
+
export * from "./DateHelper.js";
|
|
8
|
+
export * from "./NumberHelper.js";
|
|
9
|
+
export * from "./DurationHelper.js";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from "./EnumHelper.js";
|
|
2
|
+
export * from "./UidHelper.js";
|
|
3
|
+
export * from "./StringHelper.js";
|
|
4
|
+
export * from "./PromiseHelper.js";
|
|
5
|
+
export * from "./ErrorHelper.js";
|
|
6
|
+
export * from "./ObjectHelper.js";
|
|
7
|
+
export * from "./DateHelper.js";
|
|
8
|
+
export * from "./NumberHelper.js";
|
|
9
|
+
export * from "./DurationHelper.js";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { AnyRule, ValidationOptions, ValidatorOptions } from "./Types.js";
|
|
2
|
+
import { ValidationResult } from "./Result.js";
|
|
3
|
+
type Shape = Readonly<Record<string, readonly AnyRule[]>>;
|
|
4
|
+
export declare function validateSync<T>(input: unknown, shape: Shape, config: ValidatorOptions, options: ValidationOptions): ValidationResult<T>;
|
|
5
|
+
export declare function validateAsync<T>(input: unknown, shape: Shape, config: ValidatorOptions, options: ValidationOptions): Promise<ValidationResult<T>>;
|
|
6
|
+
export {};
|