@zelgadis87/utils-core 4.0.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/Lazy.d.ts +21 -0
- package/dist/LazyAsync.d.ts +23 -0
- package/dist/Logger.d.ts +21 -0
- package/dist/Optional.d.ts +70 -0
- package/dist/async/CancelableDeferred.d.ts +17 -0
- package/dist/async/Deferred.d.ts +27 -0
- package/dist/async/RateThrottler.d.ts +12 -0
- package/dist/async/Semaphore.d.ts +10 -0
- package/dist/async/index.d.ts +4 -0
- package/dist/index.d.ts +11 -0
- package/dist/random/index.d.ts +2 -0
- package/dist/random/randomInterval.d.ts +1 -0
- package/dist/random/randomPercentage.d.ts +1 -0
- package/dist/sorting/ComparisonChain.d.ts +57 -0
- package/dist/sorting/Sorter.d.ts +100 -0
- package/dist/sorting/index.d.ts +4 -0
- package/dist/sorting/types.d.ts +5 -0
- package/dist/time/RandomTimeDuration.d.ts +9 -0
- package/dist/time/TimeBase.d.ts +38 -0
- package/dist/time/TimeDuration.d.ts +81 -0
- package/dist/time/TimeFrequency.d.ts +10 -0
- package/dist/time/TimeInstant.d.ts +137 -0
- package/dist/time/TimeInstantBuilder.d.ts +77 -0
- package/dist/time/TimeUnit.d.ts +17 -0
- package/dist/time/index.d.ts +6 -0
- package/dist/time/types.d.ts +26 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types/arrays.d.ts +33 -0
- package/dist/types/booleans.d.ts +2 -0
- package/dist/types/functions.d.ts +20 -0
- package/dist/types/index.d.ts +12 -0
- package/dist/types/json.d.ts +6 -0
- package/dist/types/nulls.d.ts +8 -0
- package/dist/types/numbers.d.ts +31 -0
- package/dist/types/promises.d.ts +6 -0
- package/dist/types/records.d.ts +14 -0
- package/dist/types/strings.d.ts +40 -0
- package/dist/upgrade/DataUpgrader.d.ts +22 -0
- package/dist/upgrade/errors.d.ts +12 -0
- package/dist/upgrade/getTransitionsPath.d.ts +3 -0
- package/dist/upgrade/index.d.ts +2 -0
- package/dist/upgrade/types.d.ts +31 -0
- package/dist/utils/asError.d.ts +1 -0
- package/dist/utils/bindThis.d.ts +1 -0
- package/dist/utils/constant.d.ts +8 -0
- package/dist/utils/entries.d.ts +4 -0
- package/dist/utils/groupBy.d.ts +7 -0
- package/dist/utils/iff.d.ts +8 -0
- package/dist/utils/index.d.ts +22 -0
- package/dist/utils/indexBy.d.ts +7 -0
- package/dist/utils/jsonCloneDeep.d.ts +2 -0
- package/dist/utils/math.d.ts +9 -0
- package/dist/utils/noop.d.ts +1 -0
- package/dist/utils/omit.d.ts +2 -0
- package/dist/utils/pad.d.ts +3 -0
- package/dist/utils/pluralize.d.ts +1 -0
- package/dist/utils/round.d.ts +8 -0
- package/dist/utils/sortBy.d.ts +7 -0
- package/dist/utils/throttle.d.ts +3 -0
- package/dist/utils/uniq.d.ts +1 -0
- package/dist/utils/uniqBy.d.ts +2 -0
- package/dist/utils/uniqByKey.d.ts +1 -0
- package/dist/utils/upsert.d.ts +0 -0
- package/dist/utils/withTryCatch.d.ts +2 -0
- package/dist/utils/withTryCatchAsync.d.ts +2 -0
- package/dist/utils/wrap.d.ts +1 -0
- package/esbuild/index.cjs +2670 -0
- package/esbuild/index.mjs +2518 -0
- package/package.json +159 -0
- package/src/Lazy.ts +77 -0
- package/src/LazyAsync.ts +100 -0
- package/src/Logger.ts +44 -0
- package/src/Optional.ts +172 -0
- package/src/async/CancelableDeferred.ts +36 -0
- package/src/async/Deferred.ts +84 -0
- package/src/async/RateThrottler.ts +46 -0
- package/src/async/Semaphore.ts +45 -0
- package/src/async/index.ts +6 -0
- package/src/index.ts +13 -0
- package/src/random/index.ts +3 -0
- package/src/random/randomInterval.ts +4 -0
- package/src/random/randomPercentage.ts +6 -0
- package/src/sorting/ComparisonChain.ts +209 -0
- package/src/sorting/Sorter.ts +357 -0
- package/src/sorting/index.ts +5 -0
- package/src/sorting/types.ts +7 -0
- package/src/time/RandomTimeDuration.ts +21 -0
- package/src/time/TimeBase.ts +113 -0
- package/src/time/TimeDuration.ts +296 -0
- package/src/time/TimeFrequency.ts +28 -0
- package/src/time/TimeInstant.ts +488 -0
- package/src/time/TimeInstantBuilder.ts +126 -0
- package/src/time/TimeUnit.ts +43 -0
- package/src/time/index.ts +8 -0
- package/src/time/types.ts +56 -0
- package/src/types/arrays.ts +89 -0
- package/src/types/booleans.ts +8 -0
- package/src/types/functions.ts +27 -0
- package/src/types/index.ts +18 -0
- package/src/types/json.ts +5 -0
- package/src/types/nulls.ts +33 -0
- package/src/types/numbers.ts +80 -0
- package/src/types/promises.ts +23 -0
- package/src/types/records.ts +21 -0
- package/src/types/strings.ts +143 -0
- package/src/upgrade/DataUpgrader.ts +100 -0
- package/src/upgrade/errors.ts +25 -0
- package/src/upgrade/getTransitionsPath.ts +89 -0
- package/src/upgrade/index.ts +4 -0
- package/src/upgrade/types.ts +36 -0
- package/src/utils/asError.ts +12 -0
- package/src/utils/bindThis.ts +4 -0
- package/src/utils/constant.ts +9 -0
- package/src/utils/entries.ts +13 -0
- package/src/utils/groupBy.ts +39 -0
- package/src/utils/iff.ts +26 -0
- package/src/utils/index.ts +24 -0
- package/src/utils/indexBy.ts +36 -0
- package/src/utils/jsonCloneDeep.ts +31 -0
- package/src/utils/math.ts +44 -0
- package/src/utils/noop.ts +2 -0
- package/src/utils/omit.ts +8 -0
- package/src/utils/pad.ts +20 -0
- package/src/utils/pluralize.ts +20 -0
- package/src/utils/round.ts +24 -0
- package/src/utils/sortBy.ts +27 -0
- package/src/utils/throttle.ts +10 -0
- package/src/utils/uniq.ts +6 -0
- package/src/utils/uniqBy.ts +15 -0
- package/src/utils/uniqByKey.ts +5 -0
- package/src/utils/upsert.ts +2 -0
- package/src/utils/withTryCatch.ts +10 -0
- package/src/utils/withTryCatchAsync.ts +6 -0
- package/src/utils/wrap.ts +4 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { TDigit, TDigit1_9, TNumber0_1000, TParseableInt } from "../types";
|
|
2
|
+
|
|
3
|
+
export type TOneDigit = `${TDigit}`;
|
|
4
|
+
export type TTwoDigits = `${TDigit}${TDigit}`;
|
|
5
|
+
export type TThreeDigits = `${TDigit}${TDigit}${TDigit}`;
|
|
6
|
+
export type TFourDigits = `${TDigit}${TDigit}${TDigit}${TDigit}`;
|
|
7
|
+
|
|
8
|
+
export type TUpToTwoDigits = TOneDigit | `${TDigit1_9}${TDigit}`;
|
|
9
|
+
export type TUpToThreeDigits = TUpToTwoDigits | `${TDigit1_9}${TDigit}${TDigit}`;
|
|
10
|
+
export type TUpToFourDigits = TUpToThreeDigits | `${TDigit1_9}${TDigit}${TDigit}${TDigit}`;
|
|
11
|
+
|
|
12
|
+
export type TTwoDigitsMonth = TTwoDigits & TParseableInt<`0${TDigit1_9}` | '10' | '11' | '12'>;
|
|
13
|
+
export type TTwoDigitsDate = TTwoDigits & TParseableInt<`0${TDigit1_9}` | `1${TDigit}` | `2${TDigit}` | '30' | '31'>;
|
|
14
|
+
export type TTwoDigitsHour = TTwoDigits & TParseableInt<`0${TDigit1_9}` | `1${TDigit}` | '20' | '21' | '22' | '23' | '24'>;
|
|
15
|
+
export type TTwoDigitsMinute = TTwoDigits & TParseableInt<`0${TDigit1_9}` | `1${TDigit}` | `2${TDigit}` | `3${TDigit}` | `4${TDigit}` | `5${TDigit}`>;
|
|
16
|
+
export type TTwoDigitsSecond = TTwoDigits & TParseableInt<`0${TDigit1_9}` | `1${TDigit}` | `2${TDigit}` | `3${TDigit}` | `4${TDigit}` | `5${TDigit}`>;
|
|
17
|
+
export type TThreeDigitsMillisecond = TThreeDigits;
|
|
18
|
+
export type TFourDigitsYear = TFourDigits;
|
|
19
|
+
export type TFourDigitsMillisecond = `${'+' | '-'}${TThreeDigits}`;
|
|
20
|
+
|
|
21
|
+
export type TMonth = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
|
|
22
|
+
|
|
23
|
+
export type TDayOfMonth = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
|
|
24
|
+
| 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19
|
|
25
|
+
| 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29
|
|
26
|
+
| 30 | 31;
|
|
27
|
+
|
|
28
|
+
export type TWeekNumber = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
|
|
29
|
+
| 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19
|
|
30
|
+
| 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29
|
|
31
|
+
| 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39
|
|
32
|
+
| 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49
|
|
33
|
+
| 50 | 51 | 52 | 53;
|
|
34
|
+
|
|
35
|
+
export type TDayOfWeek = 1 | 2 | 3 | 4 | 5 | 6 | 7;
|
|
36
|
+
export type THourOfDay = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
|
|
37
|
+
| 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19
|
|
38
|
+
| 20 | 21 | 22 | 23;
|
|
39
|
+
export type TMinuteOfHour = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
|
|
40
|
+
| 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19
|
|
41
|
+
| 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29
|
|
42
|
+
| 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39
|
|
43
|
+
| 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49
|
|
44
|
+
| 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59;
|
|
45
|
+
export type TSecondOfMinute = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
|
|
46
|
+
| 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19
|
|
47
|
+
| 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29
|
|
48
|
+
| 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39
|
|
49
|
+
| 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49
|
|
50
|
+
| 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59;
|
|
51
|
+
export type TMillisecondOfSecond = TNumber0_1000;
|
|
52
|
+
|
|
53
|
+
// Need to use strings here, as digit types are too complex to represent.
|
|
54
|
+
export type TIso8601DateString = `${string}-${string}-${string}T${string}:${string}:${string}.${string}${string}`;
|
|
55
|
+
export type TIso8601DateUtcString = `${string}-${string}-${string}T${string}:${string}:${string}.${string}Z`;
|
|
56
|
+
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { TComparisonFunction } from "../sorting/index.js";
|
|
2
|
+
import { TBiPredicate } from "./functions.js";
|
|
3
|
+
import { TMaybe } from "./nulls.js";
|
|
4
|
+
|
|
5
|
+
export type TReadableArray<T> = Array<T> | ReadonlyArray<T>;
|
|
6
|
+
export type TArrayable<T> = T | T[];
|
|
7
|
+
export type TEmptyArray = [];
|
|
8
|
+
|
|
9
|
+
export function ensureArray<const T>( t: T | Array<T> ): Array<T> {
|
|
10
|
+
return t instanceof Array ? t : [ t ];
|
|
11
|
+
}
|
|
12
|
+
export function ensureReadableArray<const T>( t: T | Array<T> | ReadonlyArray<T> ): TReadableArray<T> {
|
|
13
|
+
return t instanceof Array ? t : [ t ];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function isArray<const T>( t: unknown ): t is Array<T> {
|
|
17
|
+
return t instanceof Array;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Generate a new copy of an array with:
|
|
22
|
+
* - the first instance of an item matching the predicate being replaced with the given argument
|
|
23
|
+
* - the given argument added as last item of the array if the predicate did not produce a match
|
|
24
|
+
* @param arr - the original array of items
|
|
25
|
+
* @param item - the item to insert or update
|
|
26
|
+
* @param predicate - a function that returns true iff an item is equal to the given argument
|
|
27
|
+
* @returns a new copy of the array with the item updated or added in last place
|
|
28
|
+
*/
|
|
29
|
+
export function upsert<T>( arr: ReadonlyArray<T>, item: T, isEqual: TBiPredicate<T> ): Array<T> {
|
|
30
|
+
const index = arr.findIndex( a => isEqual( a, item ) );
|
|
31
|
+
if ( index === -1 ) {
|
|
32
|
+
return [ ...arr, item ];
|
|
33
|
+
} else {
|
|
34
|
+
return [
|
|
35
|
+
...arr.slice( 0, index ),
|
|
36
|
+
item,
|
|
37
|
+
...arr.slice( index + 1 )
|
|
38
|
+
];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function range( start: number, end: number ): Array<number> {
|
|
43
|
+
if ( end < start ) throw new Error();
|
|
44
|
+
let length = ( end - start ) + 1;
|
|
45
|
+
return new Array( length ).fill( 1 ).map( ( _, i ) => start + i );
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function fill<T>( length: number, value: T ): Array<T> {
|
|
49
|
+
return new Array<T>( length ).fill( value );
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function extendArray<T extends Record<string | number | symbol, unknown>, X extends Record<string | number | symbol, unknown>>( arr: TReadableArray<T>, props: X ): Array<( T & X )> {
|
|
53
|
+
return arr.map( ( t: T ) => ( {
|
|
54
|
+
...t,
|
|
55
|
+
...props
|
|
56
|
+
} ) );
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function extendArrayWith<T extends Record<string | number | symbol, unknown>, X extends Record<string | number | symbol, unknown>>( arr: TReadableArray<T>, propsFn: ( t: T ) => X ): Array<( T & X )> {
|
|
60
|
+
return arr.map( ( t: T ) => ( {
|
|
61
|
+
...t,
|
|
62
|
+
...propsFn( t )
|
|
63
|
+
} ) );
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function reverse<T>( arr: TReadableArray<T> ): Array<T> {
|
|
67
|
+
return [ ...arr ].reverse();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function head<T>( arr: TReadableArray<T>, defaultValue: TMaybe<T> = null ): TMaybe<T> {
|
|
71
|
+
return ( arr.length ? arr[ 0 ] : defaultValue );
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function last<T>( arr: TReadableArray<T>, defaultValue: TMaybe<T> = null ): TMaybe<T> {
|
|
75
|
+
return head( reverse( arr ), defaultValue );
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function sortedArray<T>( arr: Array<T>, sortFn?: TComparisonFunction<T> | undefined ): Array<T>;
|
|
79
|
+
export function sortedArray<T>( arr: ReadonlyArray<T>, sortFn?: TComparisonFunction<T> | undefined ): ReadonlyArray<T>;
|
|
80
|
+
export function sortedArray<T>( arr: TReadableArray<T>, sortFn?: TComparisonFunction<T> | undefined ) {
|
|
81
|
+
return [ ...arr ].sort( sortFn );
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Custom implementation of includes which allows checking for arbitrary items inside the array.
|
|
86
|
+
*/
|
|
87
|
+
export function includes<T>( arr: TReadableArray<T>, item: unknown, fromIndex?: number ) {
|
|
88
|
+
return arr.includes( item as any, fromIndex );
|
|
89
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
|
|
2
|
+
export type TFunction<A, R> = ( a: A ) => R;
|
|
3
|
+
export type TVoidFunction = TFunction<void, void>
|
|
4
|
+
export type TProducer<R> = TFunction<void, R>;
|
|
5
|
+
export type TConsumer<T> = TFunction<T, void>;
|
|
6
|
+
export type TPredicate<T> = TFunction<T, boolean>;
|
|
7
|
+
export type TIdentityFunction<T> = TFunction<T, T>;
|
|
8
|
+
|
|
9
|
+
export type TBiFunction<A, B, R> = ( a: A, b: B ) => R;
|
|
10
|
+
export type TBiConsumer<A, B> = TBiFunction<A, B, void>
|
|
11
|
+
export type TBiPredicate<T> = TBiFunction<T, T, boolean>;
|
|
12
|
+
|
|
13
|
+
export type TAsyncFunction<A, R> = TFunction<A, Promise<R>>;
|
|
14
|
+
export type TAsyncVoidFunction = TAsyncFunction<void, void>
|
|
15
|
+
export type TAsyncProducer<R> = TAsyncFunction<void, R>;
|
|
16
|
+
export type TAsyncConsumer<T> = TAsyncFunction<T, void>;
|
|
17
|
+
export type TAsyncPredicate<T> = TAsyncFunction<T, boolean>;
|
|
18
|
+
export type TAsyncBiFunction<A, B, R> = TBiFunction<A, B, Promise<R>>;
|
|
19
|
+
export type TAsyncBiConsumer<A, B> = TAsyncBiFunction<A, B, void>;
|
|
20
|
+
export type TAsyncBiPredicate<T> = TAsyncBiFunction<T, T, boolean>;
|
|
21
|
+
|
|
22
|
+
export type TAnyFunction<R> = ( ...args: unknown[] ) => R;
|
|
23
|
+
export type TAsyncAnyFunction<R> = ( ...args: unknown[] ) => Promise<R>;
|
|
24
|
+
|
|
25
|
+
export function isFunction( t: unknown ): t is Function { // eslint-disable-line @typescript-eslint/no-unsafe-function-type
|
|
26
|
+
return typeof t === 'function';
|
|
27
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
|
|
2
|
+
// Semantic only
|
|
3
|
+
|
|
4
|
+
export type TTimeoutHandle = ReturnType<typeof setTimeout>;
|
|
5
|
+
export type TIntervalHandle = ReturnType<typeof setInterval>;
|
|
6
|
+
|
|
7
|
+
export type TPrimitive = string | number | boolean | null;
|
|
8
|
+
|
|
9
|
+
export * from './arrays';
|
|
10
|
+
export * from './booleans.ts';
|
|
11
|
+
export * from './functions';
|
|
12
|
+
export * from './json';
|
|
13
|
+
export * from './nulls';
|
|
14
|
+
export * from './numbers';
|
|
15
|
+
export * from './promises';
|
|
16
|
+
export * from './records';
|
|
17
|
+
export * from './strings';
|
|
18
|
+
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
|
|
2
|
+
export type TJsonPrimitive = string | number | boolean | null | undefined;
|
|
3
|
+
export type TJsonArray = Array<TJsonSerializable> | ReadonlyArray<TJsonSerializable>;
|
|
4
|
+
export type TJsonObject = { [ key: string ]: TJsonSerializable }
|
|
5
|
+
export type TJsonSerializable = TJsonPrimitive | TJsonArray | TJsonObject;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { TConsumer, TFunction } from "./functions.js";
|
|
2
|
+
|
|
3
|
+
export type TMaybe<T> = T | null;
|
|
4
|
+
|
|
5
|
+
export function ensureDefined<T>( v: T, name = 'value' ): T {
|
|
6
|
+
if ( isDefined( v ) )
|
|
7
|
+
return v;
|
|
8
|
+
throw new Error( 'Expected ' + name + ' to be defined, got: ' + v );
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function isDefined<T>( x: T | null | undefined ): x is T {
|
|
12
|
+
return x !== null && x !== undefined;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function isNullOrUndefined( x: unknown ): x is null | undefined {
|
|
16
|
+
return x === null || x === undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function ifDefined<R>( source: TMaybe<R>, callback: TConsumer<R> ): void {
|
|
20
|
+
if ( isDefined( source ) )
|
|
21
|
+
callback( source );
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function mapDefined<R, S>( source: TMaybe<R>, mapper: TFunction<R, S> ): TMaybe<S> {
|
|
25
|
+
if ( isDefined( source ) )
|
|
26
|
+
return mapper( source );
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function ifNullOrUndefined( source: TMaybe<unknown>, callback: TConsumer<void> ): void {
|
|
31
|
+
if ( isNullOrUndefined( source ) )
|
|
32
|
+
callback();
|
|
33
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { TFunction } from "./functions.js";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export type TNumericString = `${number}`;
|
|
5
|
+
export type TNumericFloatingPointString = `${number}.${number}`;
|
|
6
|
+
export type TPositiveNumber = number; // Only used for semantics. Cannot do better typechecking at the moment.
|
|
7
|
+
export type TNegativeNumber = number; // Only used for semantics. Cannot do better typechecking at the moment.
|
|
8
|
+
|
|
9
|
+
export type TZero = 0;
|
|
10
|
+
export type TDigit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
|
|
11
|
+
export type TDigit1_9 = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
|
|
12
|
+
export type TNumber0_100 = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100;
|
|
13
|
+
export type TNumber0_1000 = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 |
|
|
14
|
+
100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 |
|
|
15
|
+
200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 |
|
|
16
|
+
300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 |
|
|
17
|
+
400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 |
|
|
18
|
+
500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 |
|
|
19
|
+
600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 |
|
|
20
|
+
700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 | 738 | 739 | 740 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | 774 | 775 | 776 | 777 | 778 | 779 | 780 | 781 | 782 | 783 | 784 | 785 | 786 | 787 | 788 | 789 | 790 | 791 | 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 |
|
|
21
|
+
800 | 801 | 802 | 803 | 804 | 805 | 806 | 807 | 808 | 809 | 810 | 811 | 812 | 813 | 814 | 815 | 816 | 817 | 818 | 819 | 820 | 821 | 822 | 823 | 824 | 825 | 826 | 827 | 828 | 829 | 830 | 831 | 832 | 833 | 834 | 835 | 836 | 837 | 838 | 839 | 840 | 841 | 842 | 843 | 844 | 845 | 846 | 847 | 848 | 849 | 850 | 851 | 852 | 853 | 854 | 855 | 856 | 857 | 858 | 859 | 860 | 861 | 862 | 863 | 864 | 865 | 866 | 867 | 868 | 869 | 870 | 871 | 872 | 873 | 874 | 875 | 876 | 877 | 878 | 879 | 880 | 881 | 882 | 883 | 884 | 885 | 886 | 887 | 888 | 889 | 890 | 891 | 892 | 893 | 894 | 895 | 896 | 897 | 898 | 899 |
|
|
22
|
+
900 | 901 | 902 | 903 | 904 | 905 | 906 | 907 | 908 | 909 | 910 | 911 | 912 | 913 | 914 | 915 | 916 | 917 | 918 | 919 | 920 | 921 | 922 | 923 | 924 | 925 | 926 | 927 | 928 | 929 | 930 | 931 | 932 | 933 | 934 | 935 | 936 | 937 | 938 | 939 | 940 | 941 | 942 | 943 | 944 | 945 | 946 | 947 | 948 | 949 | 950 | 951 | 952 | 953 | 954 | 955 | 956 | 957 | 958 | 959 | 960 | 961 | 962 | 963 | 964 | 965 | 966 | 967 | 968 | 969 | 970 | 971 | 972 | 973 | 974 | 975 | 976 | 977 | 978 | 979 | 980 | 981 | 982 | 983 | 984 | 985 | 986 | 987 | 988 | 989 | 990 | 991 | 992 | 993 | 994 | 995 | 996 | 997 | 998 | 999
|
|
23
|
+
export type TNumber0_5 = 0 | 1 | 2 | 3 | 4 | 5;
|
|
24
|
+
export type TNumber0_10 = TNumber0_5 | 6 | 7 | 8 | 9 | 10;
|
|
25
|
+
export type TNumber0_15 = TNumber0_10 | 11 | 12 | 13 | 14 | 15;
|
|
26
|
+
export type TNumber0_20 = TNumber0_15 | 16 | 17 | 18 | 19 | 20;
|
|
27
|
+
export type TNumber1_10 = Exclude<TNumber0_10, 0>;
|
|
28
|
+
|
|
29
|
+
export type TParseInt<T> = T extends `${infer N extends number}` ? N : never;
|
|
30
|
+
export type TParseableInt<T> = T extends `${number}` ? T : never;
|
|
31
|
+
|
|
32
|
+
export function ensurePositiveNumber( v: number, name = 'value' ): number {
|
|
33
|
+
if ( v !== undefined && v !== null && v > 0 )
|
|
34
|
+
return v;
|
|
35
|
+
throw new Error( `Expected ${name} to be positive, got: ${v}` );
|
|
36
|
+
}
|
|
37
|
+
export function ensureNonNegativeNumber( v: number, name = 'value' ): number {
|
|
38
|
+
if ( v !== undefined && v !== null && v >= 0 )
|
|
39
|
+
return v;
|
|
40
|
+
throw new Error( `Expected ${name} to be non-negative, got: ${v}` );
|
|
41
|
+
}
|
|
42
|
+
export function ensureNonPositiveNumber( v: number, name = 'value' ): number {
|
|
43
|
+
if ( v !== undefined && v !== null && v <= 0 )
|
|
44
|
+
return v;
|
|
45
|
+
throw new Error( `Expected ${name} to be non-positive, got: ${v}` );
|
|
46
|
+
}
|
|
47
|
+
export function ensureNegativeNumber( v: number, name = 'value' ): number {
|
|
48
|
+
if ( v !== undefined && v !== null && v < 0 )
|
|
49
|
+
return v;
|
|
50
|
+
throw new Error( `Expected ${name} to be negative, got: ${v}` );
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function incrementBy( n: number ): TFunction<number, number> {
|
|
54
|
+
return ( v ) => v + n;
|
|
55
|
+
}
|
|
56
|
+
export function multiplyBy( n: number ): TFunction<number, number> {
|
|
57
|
+
return ( v ) => v * n;
|
|
58
|
+
}
|
|
59
|
+
export function divideBy( n: number ): TFunction<number, number> {
|
|
60
|
+
return ( v ) => v / n;
|
|
61
|
+
}
|
|
62
|
+
export const increment = incrementBy( 1 );
|
|
63
|
+
export const decrement = incrementBy( -1 );
|
|
64
|
+
export const decrementBy = ( n: number ) => incrementBy( -n );
|
|
65
|
+
|
|
66
|
+
export function isNumber( v: unknown ): v is number {
|
|
67
|
+
return typeof v === "number";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function isPositiveNumber( v: number ): v is TPositiveNumber {
|
|
71
|
+
return v > 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function isNegativeNumber( v: number ): v is TNegativeNumber {
|
|
75
|
+
return v < 0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function isZero( v: number ): v is TZero {
|
|
79
|
+
return v === 0;
|
|
80
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import TimeDuration from "../time/TimeDuration.js";
|
|
2
|
+
import { TAsyncFunction, TFunction } from "./functions.js";
|
|
3
|
+
|
|
4
|
+
export type TPromisable<T> = T | Promise<T>;
|
|
5
|
+
|
|
6
|
+
export function asPromise<T>( promisable: TPromisable<T> ): Promise<T> {
|
|
7
|
+
return Promise.resolve( promisable );
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function promiseSequence<R>( ...fns: TFunction<void, Promise<R>>[] ): TFunction<void, Promise<R[]>> {
|
|
11
|
+
const fn = async function (): Promise<R[]> {
|
|
12
|
+
const results: R[] = [];
|
|
13
|
+
for ( let i = 0; i < fns.length; i++ ) {
|
|
14
|
+
results[ i ] = await fns[ i ]();
|
|
15
|
+
}
|
|
16
|
+
return results;
|
|
17
|
+
};
|
|
18
|
+
return fn;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function delayPromise<T>( duration: TimeDuration ): TAsyncFunction<T, T> {
|
|
22
|
+
return ( result: T ) => duration.promise().then( () => result );
|
|
23
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
|
|
2
|
+
export type TEmpty = Record<string, never>;
|
|
3
|
+
export type TKeysOfType<T, V> = { [ K in keyof T ]-?: T[ K ] extends V ? K : never }[ keyof T ];
|
|
4
|
+
|
|
5
|
+
export function dictToList<T>( obj: Record<string | number, T> ): T[] {
|
|
6
|
+
return Object.keys( obj ).map( key => obj[ key ] );
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function pick<T extends object, K extends keyof T>( o: T, keys: K[] ): Pick<T, K> {
|
|
10
|
+
return keys.reduce( ( obj, key ) => {
|
|
11
|
+
obj[ key ] = o[ key ];
|
|
12
|
+
return obj;
|
|
13
|
+
}, {} as Pick<T, K> );
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type TOptionsWithoutDefaults<T, R> = Partial<T> & Required<Omit<T, keyof R>>;
|
|
17
|
+
export type TOptionalKeysForType<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? K : never }[keyof T];
|
|
18
|
+
export type TRequiredKeysForType<T> = Exclude<keyof T, TOptionalKeysForType<T>>;
|
|
19
|
+
export type TAllKeysOptional<T> = TRequiredKeysForType<T> extends never ? true : false;
|
|
20
|
+
export type TConditionalParameter<T> = TAllKeysOptional<T> extends true ? T | undefined : T;
|
|
21
|
+
export type TConditionalParameterOptions<T, R> = TConditionalParameter<TOptionsWithoutDefaults<T, R>>;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { range, TReadableArray } from "./arrays.js";
|
|
2
|
+
import { isNullOrUndefined, TMaybe } from "./nulls.js";
|
|
3
|
+
|
|
4
|
+
// Semantic-only
|
|
5
|
+
export type THtmlString = string;
|
|
6
|
+
export type TUrl = string;
|
|
7
|
+
export type TRelativeUrl = string;
|
|
8
|
+
|
|
9
|
+
export function hashCode( str: string ): number {
|
|
10
|
+
let hash = 0
|
|
11
|
+
for ( let i = 0; i < str.length; ++i )
|
|
12
|
+
hash = Math.imul( 31, hash ) + str.charCodeAt( i )
|
|
13
|
+
return hash | 0
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function repeat( char: string, times: number ): string {
|
|
17
|
+
return times > 0 ? range( 0, times - 1 ).map( _ => char ).join( '' ) : '';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function capitalizeWord( word: string ): string {
|
|
21
|
+
return word[ 0 ].toUpperCase() + word.substring( 1 ).toLowerCase();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function splitWords( text: string, regEx: RegExp = /\s+/ ): string[] {
|
|
25
|
+
return text.split( regEx ).filter( t => t.length > 0 );
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function stringToNumber( s: string | null | undefined ): number | null {
|
|
29
|
+
if ( isNullOrUndefined( s ) )
|
|
30
|
+
return null;
|
|
31
|
+
return Number( s );
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class StringParts {
|
|
35
|
+
|
|
36
|
+
private readonly _parts: string[]
|
|
37
|
+
|
|
38
|
+
private constructor( ...parts: string[] ) {
|
|
39
|
+
this._parts = parts;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public toUpperCase() {
|
|
43
|
+
return new StringParts( ...this.parts.map( part => part.toUpperCase() ) );
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public toLowerCase() {
|
|
47
|
+
return new StringParts( ...this.parts.map( part => part.toLowerCase() ) );
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public capitalizeFirst() {
|
|
51
|
+
if ( !this.length ) return this;
|
|
52
|
+
return new StringParts( capitalizeWord( this.first! ), ...this.tail.map( part => part.toLowerCase() ) );
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public capitalizeEach() {
|
|
56
|
+
return new StringParts( ...this.parts.map( part => capitalizeWord( part ) ) );
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public get parts() {
|
|
60
|
+
return this._parts;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public get tail() {
|
|
64
|
+
const [ _first, ...tail ] = this.parts;
|
|
65
|
+
return tail;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public get first(): TMaybe<string> {
|
|
69
|
+
return this._parts[ 0 ] ?? null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
public get last(): TMaybe<string> {
|
|
73
|
+
return this._parts[ this.length - 1 ] ?? null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public get length() {
|
|
77
|
+
return this._parts.length;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public trim() {
|
|
81
|
+
return new StringParts( ...this.parts.map( part => part.trim() ) )
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
public toSnakeCase() {
|
|
85
|
+
return this.toLowerCase().join( '_' );
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
public toCamelCase() {
|
|
89
|
+
if ( !this.length ) return '';
|
|
90
|
+
return [ this.first!.toLowerCase(), ...this.tail.map( capitalizeWord ) ].join( '' );
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public toKebabCase() {
|
|
94
|
+
return this.toLowerCase().join( '-' );
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
public toPascalCase() {
|
|
98
|
+
return this.capitalizeEach().join( '' );
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
public toHumanCase() {
|
|
102
|
+
return this.capitalizeFirst().join( ' ' );
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
public join( separator: string ) {
|
|
106
|
+
return this.parts.join( separator );
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
public mergeWith( { parts: otherParts }: { parts: TReadableArray<string> } ) {
|
|
110
|
+
return new StringParts( ...this.parts, ...otherParts )
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
public slice( start: number, end?: number ) {
|
|
114
|
+
return new StringParts( ...this.parts.slice( start, end ) )
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
public splice( start: number, deleteCount: number, ...items: string[] ) {
|
|
118
|
+
return new StringParts( ...this.parts.splice( start, deleteCount, ...items ) )
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
public push( part: string ) {
|
|
122
|
+
return new StringParts( ...this.parts, part )
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
public shift( part: string ) {
|
|
126
|
+
return new StringParts( part, ...this.parts )
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
public reverse() {
|
|
130
|
+
return new StringParts( ...this.parts.reverse() )
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
public static fromString = ( s: string, separator: string | RegExp = /\s+/g ) => {
|
|
134
|
+
if ( s === null || separator === null )
|
|
135
|
+
throw new Error( 'Invalid arguments' );
|
|
136
|
+
return new StringParts( ...s.split( separator ) );
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
public static fromParts = ( ...parts: string[] ) => {
|
|
140
|
+
return new StringParts( ...parts );
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
|
|
2
|
+
import { isDefined, isNumber, isPositiveNumber, TJsonSerializable } from "../types/index.ts";
|
|
3
|
+
import { jsonCloneDeep } from "../utils";
|
|
4
|
+
import { EmptyUpgradeError, UnavailableUpgradeError } from "./errors";
|
|
5
|
+
import getTransitionsPath from "./getTransitionsPath";
|
|
6
|
+
import { TPossibleFromVersion, TPossibleVersion, TTransitionMatrix, TUpgradable, TUpgradeFunction, TVersionMap } from "./types";
|
|
7
|
+
|
|
8
|
+
const VERSION_FIELD = "$version";
|
|
9
|
+
|
|
10
|
+
export interface IDataUpgrader<XStar extends TUpgradable, XLatest extends XStar> {
|
|
11
|
+
upgrade( data: XStar ): Promise<XLatest>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class DataUpgrader<X extends TUpgradable, XLatest extends X> implements IDataUpgrader<X, XLatest> {
|
|
15
|
+
|
|
16
|
+
private transitions: TTransitionMatrix<X> = {};
|
|
17
|
+
|
|
18
|
+
private constructor(
|
|
19
|
+
private latestVersion: XLatest[ typeof VERSION_FIELD ]
|
|
20
|
+
) { }
|
|
21
|
+
|
|
22
|
+
public addTransition<
|
|
23
|
+
XFrom extends TVersionMap<X>[ XFromNumber ],
|
|
24
|
+
XTo extends TVersionMap<X>[ XToNumber ],
|
|
25
|
+
XFromNumber extends TPossibleFromVersion<X, XLatest>,
|
|
26
|
+
XToNumber extends Exclude<TPossibleVersion<X>, XFromNumber>,
|
|
27
|
+
>(
|
|
28
|
+
from: XFromNumber,
|
|
29
|
+
to: XToNumber,
|
|
30
|
+
apply: TUpgradeFunction<X, XFrom, XTo>
|
|
31
|
+
): this {
|
|
32
|
+
if ( from === undefined || from < 0 )
|
|
33
|
+
throw new Error( `Invalid argument from, non-negative number expected, got: ${from}` );
|
|
34
|
+
if ( to === undefined || to <= 0 )
|
|
35
|
+
throw new Error( `Invalid argument to, positive number expected, got: ${to}` );
|
|
36
|
+
if ( to <= from )
|
|
37
|
+
throw new Error( `Invalid argument to, expected number greater than ${from}, got: ${to}` );
|
|
38
|
+
if ( to > this.latestVersion )
|
|
39
|
+
throw new Error( `Invalid argument to, expected number lower than ${this.latestVersion}, got: ${to}` );
|
|
40
|
+
if ( this.transitions[ to ]?.[ from ] )
|
|
41
|
+
throw new Error( `Invalid transition arguments, duplicated transition found from ${from} to ${to}` );
|
|
42
|
+
const toMatrix: TTransitionMatrix<X>[ XTo[ typeof VERSION_FIELD ] ] = this.transitions[ to ] ?? {};
|
|
43
|
+
toMatrix[ from ] = { from, to, apply };
|
|
44
|
+
this.transitions[ to ] = toMatrix;
|
|
45
|
+
|
|
46
|
+
return this;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public async upgrade( data: X ): Promise<XLatest> {
|
|
50
|
+
if ( !data || typeof data !== "object" )
|
|
51
|
+
throw new Error( `Invalid argument data, object expected, got: ${typeof data}.` );
|
|
52
|
+
if ( data.$version === null || data.$version === undefined || typeof data.$version !== "number" )
|
|
53
|
+
throw new Error( `Invalid argument data, version metadata required, got: ${data.$version as unknown as string}` );
|
|
54
|
+
if ( data.$version > this.latestVersion )
|
|
55
|
+
throw new Error( `Invalid argument data, version metadata is in the future: ${data.$version}, current: ${this.latestVersion}` );
|
|
56
|
+
if ( this.isLatestVersion( data ) )
|
|
57
|
+
return data;
|
|
58
|
+
|
|
59
|
+
const from = data.$version;
|
|
60
|
+
const to = this.latestVersion;
|
|
61
|
+
const path = getTransitionsPath<X, typeof from, XLatest[ typeof VERSION_FIELD ]>( this.transitions, from, to );
|
|
62
|
+
if ( path === null )
|
|
63
|
+
throw new UnavailableUpgradeError( data, from, to );
|
|
64
|
+
|
|
65
|
+
let current: X = jsonCloneDeep( data );
|
|
66
|
+
for ( const transition of path ) {
|
|
67
|
+
const next = await transition.apply( current as any );
|
|
68
|
+
if ( next === null || next === undefined )
|
|
69
|
+
throw new EmptyUpgradeError( current, from, to );
|
|
70
|
+
current = next;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return current as XLatest;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public isLatestVersion( data: X ): data is XLatest {
|
|
77
|
+
return data.$version === this.latestVersion;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public static create<
|
|
81
|
+
X extends TUpgradable,
|
|
82
|
+
XLatestVersion extends X,
|
|
83
|
+
>(
|
|
84
|
+
latest: XLatestVersion[ typeof VERSION_FIELD ]
|
|
85
|
+
) {
|
|
86
|
+
return new DataUpgrader<X, XLatestVersion>( latest );
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
public static Errors = {
|
|
90
|
+
EmptyUpgrade: EmptyUpgradeError,
|
|
91
|
+
UnavailableUpgrade: UnavailableUpgradeError
|
|
92
|
+
} as const;
|
|
93
|
+
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function isUpgradable( obj: TJsonSerializable ): obj is TUpgradable {
|
|
97
|
+
return isDefined( obj ) && typeof obj === "object" && VERSION_FIELD in obj && isNumber( obj.$version ) && isPositiveNumber( obj.$version );
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export default DataUpgrader;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
export class UnavailableUpgradeError<T> extends Error {
|
|
4
|
+
|
|
5
|
+
constructor(
|
|
6
|
+
public readonly data: T,
|
|
7
|
+
public readonly from: number,
|
|
8
|
+
public readonly to: number
|
|
9
|
+
) {
|
|
10
|
+
super( `No transition found to upgrade data from version ${from} to version ${to}.` );
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class EmptyUpgradeError<T> extends Error {
|
|
16
|
+
|
|
17
|
+
constructor(
|
|
18
|
+
public readonly data: T,
|
|
19
|
+
public readonly from: number,
|
|
20
|
+
public readonly to: number,
|
|
21
|
+
) {
|
|
22
|
+
super( `Transition from version ${ from } to version ${ to } resulted in empty data` );
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
}
|